diff --git a/tests/test_bundled_updates.py b/tests/test_bundled_updates.py index d5a5048..ca003cc 100644 --- a/tests/test_bundled_updates.py +++ b/tests/test_bundled_updates.py @@ -202,6 +202,16 @@ class WolfBundledOnlyTests(unittest.TestCase): with patch("util.wolfdawn.bundled_binary_path", return_value=wolf): self.assertEqual(ensure_wolf_binary(), wolf) + def test_ensure_wolf_binary_makes_bundled_copy_executable(self): + with tempfile.TemporaryDirectory() as raw: + wolf = Path(raw) / "wolf" + wolf.write_bytes(b"\x7fELF") + wolf.chmod(0o644) + with patch("util.wolfdawn.bundled_binary_path", return_value=wolf): + with patch("util.wolfdawn._platform_dir", return_value="linux"): + ensure_wolf_binary() + self.assertTrue(wolf.stat().st_mode & 0o111) + def test_ensure_wolf_binary_raises_when_missing(self): missing = Path("/nonexistent/wolf/missing") with patch("util.wolfdawn.bundled_binary_path", return_value=missing): diff --git a/util/wolfdawn/__init__.py b/util/wolfdawn/__init__.py index 3413baf..d53f0fc 100644 --- a/util/wolfdawn/__init__.py +++ b/util/wolfdawn/__init__.py @@ -265,6 +265,18 @@ def download_wolf_binary(platform: Optional[str] = None, log_fn=print) -> Path: return _extract_wolf_from_zip(zip_bytes, dest, log_fn) +def _ensure_executable(path: Path) -> None: + """Ensure a bundled binary is executable (git checkout may drop +x).""" + if _platform_dir() == "windows": + return + try: + if path.stat().st_mode & 0o111: + return + path.chmod(0o755) + except OSError: # pragma: no cover - non-POSIX filesystems + pass + + def ensure_wolf_binary(log_fn=print) -> Path: """Return the bundled ``wolf`` binary path (no upstream fetch at runtime). @@ -274,6 +286,7 @@ def ensure_wolf_binary(log_fn=print) -> Path: """ bundled = bundled_binary_path() if bundled.is_file(): + _ensure_executable(bundled) return bundled raise WolfDawnError( f"No bundled WolfDawn binary for '{_platform_dir()}' at {bundled}. "