import fcntl from pathlib import Path import subprocess import sys import tempfile import unittest from unittest.mock import patch from web_build_lock import cmake_build_tree, run_build class BuildLockTests(unittest.TestCase): def test_build_tree_detection_resolves_relative_paths(self): self.assertEqual(cmake_build_tree(["/usr/bin/cmake", "--build", "cache", "--target", "Probe"], Path("/tmp")), Path("/tmp/cache")) self.assertIsNone(cmake_build_tree(["node", "test.mjs"], Path("/tmp"))) def test_second_writer_cannot_enter_and_child_inherits_descriptor(self): with tempfile.TemporaryDirectory() as temporary: tree = Path(temporary) command = ["cmake", "--build", str(tree)] def child(_command, **kwargs): self.assertIn("pass_fds", kwargs) with (tree / ".freecad-web-build.lock").open("a") as second: with self.assertRaises(BlockingIOError): fcntl.flock(second.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) code = "import os,sys; os.fstat(int(sys.argv[1]))" process = subprocess.Popen([sys.executable, "-c", code, str(kwargs["pass_fds"][-1])], pass_fds=kwargs["pass_fds"]) self.assertEqual(process.wait(timeout=5), 0) return subprocess.CompletedProcess(command, 0) with patch("web_build_lock.subprocess.run", side_effect=child): self.assertEqual(run_build(command, cwd=tree).returncode, 0) with (tree / ".freecad-web-build.lock").open("a") as lock: fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) def test_busy_tree_times_out_without_starting_a_build(self): with tempfile.TemporaryDirectory() as temporary: tree = Path(temporary) with (tree / ".freecad-web-build.lock").open("a") as lock: fcntl.flock(lock.fileno(), fcntl.LOCK_EX) with patch("web_build_lock.subprocess.run") as run: with self.assertRaises(subprocess.TimeoutExpired): run_build(["cmake", "--build", str(tree)], cwd=tree, timeout=0.03) run.assert_not_called() def test_nonbuild_command_is_unchanged(self): with patch("web_build_lock.subprocess.run") as run: run_build(["node", "case.mjs"], cwd=Path("/tmp"), timeout=2) self.assertNotIn("pass_fds", run.call_args.kwargs) def test_missing_tree_is_not_created(self): with tempfile.TemporaryDirectory() as temporary: missing = Path(temporary) / "missing" with self.assertRaises(FileNotFoundError): run_build(["cmake", "--build", str(missing)], cwd=Path(temporary)) self.assertFalse(missing.exists()) if __name__ == "__main__": unittest.main()