aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_backends_audiocpp.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests/test_backends_audiocpp.py')
-rw-r--r--app/tests/test_backends_audiocpp.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py
index ab405aa..3e97b82 100644
--- a/app/tests/test_backends_audiocpp.py
+++ b/app/tests/test_backends_audiocpp.py
@@ -1277,6 +1277,236 @@ class BuildAudiocppTests(unittest.TestCase):
self.assertIn("No build script found", notices[0])
+class AudiocppUpdateTests(unittest.TestCase):
+ """update: stop the server, refresh the checkout, rebuild when stale.
+
+ The rebuild fires when the checkout moved OR the on-disk binary is
+ missing/older than HEAD's commit time (an interrupted earlier build).
+ """
+
+ COMMIT_TIME = 1_000_000
+
+ def setUp(self):
+ self._td = tempfile.TemporaryDirectory()
+ self.checkout = Path(self._td.name) / "audio.cpp"
+ self.checkout.mkdir()
+ self.addCleanup(common.drain_post_tui_notices)
+
+ def tearDown(self):
+ self._td.cleanup()
+
+ def _make_binary(self, backend="cuda"):
+ bin_dir = self.checkout / "build" / f"linux-{backend}-release" / "bin"
+ bin_dir.mkdir(parents=True, exist_ok=True)
+ binary = bin_dir / "audiocpp_server"
+ binary.write_bytes(b"x")
+ return binary
+
+ def _patch_decision(self, heads, binary, *, commit_time=COMMIT_TIME):
+ """Patch git state + a built binary for BACKEND ("cuda" default).
+
+ Returns the (mocks) (build, git_update) pair for assertions.
+ BINARY None means no binary on disk (a present binary is stamped
+ newer than COMMIT_TIME — stamp it differently after calling this
+ to simulate staleness); COMMIT_TIME None means the commit-time
+ probe cannot be answered.
+ """
+ if binary is not None and commit_time is not None:
+ os.utime(binary, (commit_time + 100,) * 2)
+ return patch.object(common, "git_head", side_effect=heads), \
+ patch.object(common, "git_commit_time",
+ return_value=commit_time), \
+ patch.object(make_server.build, "load_server_config",
+ return_value={"backend": "cuda"})
+
+ def test_no_checkout_is_a_reported_noop(self):
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=None), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patch.object(common, "git_update") as mk_git:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_git.assert_not_called()
+
+ def test_stops_server_then_skips_rebuild_for_a_fresh_binary(self):
+ binary = self._make_binary()
+ patches = self._patch_decision(["a", "a"], binary)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=1234), \
+ patch.object(make_server.build.servers, "stop") as mk_stop, \
+ patches[0], patches[1], patches[2], \
+ patch.object(common, "git_update",
+ return_value=0) as mk_git, \
+ patch.object(make_server.build, "build_audiocpp") as mk_build:
+ rc = make_server.build.update(emit="EMIT")
+ self.assertEqual(rc, 0)
+ mk_stop.assert_called_once_with("audiocpp")
+ mk_git.assert_called_once_with(self.checkout, emit="EMIT",
+ cancel=None)
+ # HEAD did not move and the binary is newer than HEAD's commit:
+ # the binary still matches the sources.
+ mk_build.assert_not_called()
+
+ def test_moved_head_rebuilds_even_with_a_fresh_binary(self):
+ binary = self._make_binary()
+ patches = self._patch_decision(["a", "b"], binary)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patches[0], patches[1], patches[2], \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=0) as mk_build:
+ rc = make_server.build.update(emit="EMIT")
+ self.assertEqual(rc, 0)
+ mk_build.assert_called_once_with(self.checkout, "cuda",
+ emit="EMIT", cancel=None)
+
+ def test_moved_head_falls_back_to_the_detected_backend(self):
+ patches = self._patch_decision(["a", "b"], None)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patches[0], patches[1], \
+ patch.object(make_server.build, "load_server_config",
+ return_value={}), \
+ patch.object(make_server.build, "detect_backend",
+ return_value="vulkan"), \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=0) as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_build.assert_called_once_with(self.checkout, "vulkan",
+ emit=None, cancel=None)
+
+ def test_no_known_backend_skips_the_rebuild(self):
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patch.object(common, "git_head", side_effect=["a", "b"]), \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "load_server_config",
+ return_value={}), \
+ patch.object(make_server.build, "detect_backend",
+ return_value=None), \
+ patch.object(make_server.build, "build_audiocpp") as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_build.assert_not_called()
+
+ def test_stale_binary_rebuilds_without_head_movement(self):
+ # The cancelled-rebuild scenario: sources already at HEAD, the old
+ # binary predates the new commit → the next update rebuilds.
+ binary = self._make_binary()
+ patches = self._patch_decision(["a", "a"], binary)
+ os.utime(binary, (self.COMMIT_TIME - 100,) * 2)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patches[0], patches[1], patches[2], \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=0) as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_build.assert_called_once_with(self.checkout, "cuda",
+ emit=None, cancel=None)
+
+ def test_missing_binary_rebuilds_without_head_movement(self):
+ patches = self._patch_decision(["a", "a"], None)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patches[0], patches[1], patches[2], \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=0) as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_build.assert_called_once_with(self.checkout, "cuda",
+ emit=None, cancel=None)
+
+ def test_unknown_commit_time_rebuilds_without_head_movement(self):
+ binary = self._make_binary()
+ patches = self._patch_decision(["a", "a"], binary, commit_time=None)
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patches[0], patches[1], patches[2], \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=0) as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 0)
+ mk_build.assert_called_once_with(self.checkout, "cuda",
+ emit=None, cancel=None)
+
+ def test_checkout_failure_skips_the_rebuild(self):
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patch.object(common, "git_update",
+ return_value=128) as mk_git, \
+ patch.object(make_server.build, "build_audiocpp") as mk_build:
+ rc = make_server.build.update()
+ self.assertEqual(rc, 128)
+ mk_git.assert_called_once()
+ mk_build.assert_not_called()
+
+ def test_rebuild_failure_propagates_the_exit_code(self):
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patch.object(common, "git_head", side_effect=["a", "b"]), \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "load_server_config",
+ return_value={"backend": "cuda"}), \
+ patch.object(make_server.build, "build_audiocpp",
+ return_value=2):
+ rc = make_server.build.update()
+ self.assertEqual(rc, 2)
+
+ def test_cancel_before_the_update_skips_everything_after_stopping(self):
+ cancel = threading.Event()
+ cancel.set()
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=1234), \
+ patch.object(make_server.build.servers, "stop") as mk_stop, \
+ patch.object(common, "git_update") as mk_git:
+ rc = make_server.build.update(cancel=cancel)
+ self.assertEqual(rc, 130)
+ mk_stop.assert_called_once_with("audiocpp")
+ mk_git.assert_not_called()
+
+ def test_cancel_after_the_checkout_skips_the_rebuild(self):
+ cancel = threading.Event()
+ cancel.set()
+ with patch.object(make_server.build, "find_local_checkout",
+ return_value=self.checkout), \
+ patch.object(make_server.build.servers, "pid_for",
+ return_value=None), \
+ patch.object(common, "git_head", side_effect=["a", "b"]), \
+ patch.object(common, "git_update", return_value=0), \
+ patch.object(make_server.build, "build_audiocpp") as mk_build:
+ rc = make_server.build.update(cancel=cancel)
+ self.assertEqual(rc, 130)
+ mk_build.assert_not_called()
+
+
class AudiocppDetectTests(unittest.TestCase):
"""backends.audiocpp.detect() status reporting."""