diff --git a/gui/butteraugli/windows/.gitkeep b/gui/butteraugli/windows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gui/ssimulacra/windows/.gitkeep b/gui/ssimulacra/windows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gui/utility.py b/gui/utility.py index ede9a95..c70dd5f 100644 --- a/gui/utility.py +++ b/gui/utility.py @@ -1,4 +1,5 @@ import os +import shutil import sys from time import time from math import log10 @@ -401,18 +402,34 @@ def exiftool_exe(): def butter_exe(): if sys.platform.startswith("linux"): - return "butteraugli/linux/butteraugli" - if sys.platform.startswith("win32"): + path = "butteraugli/linux/butteraugli" + elif sys.platform.startswith("win32"): + path = "butteraugli/windows/butteraugli.exe" + else: return None - return None + + if os.path.isfile(path): + return path + + # Fallback: check system PATH + name = "butteraugli.exe" if sys.platform.startswith("win32") else "butteraugli" + return shutil.which(name) def ssimul_exe(): if sys.platform.startswith("linux"): - return "ssimulacra/linux/ssimulacra" - if sys.platform.startswith("win32"): + path = "ssimulacra/linux/ssimulacra" + elif sys.platform.startswith("win32"): + path = "ssimulacra/windows/ssimulacra.exe" + else: return None - return None + + if os.path.isfile(path): + return path + + # Fallback: check system PATH + name = "ssimulacra.exe" if sys.platform.startswith("win32") else "ssimulacra" + return shutil.which(name) class ParamSlider(QWidget): diff --git a/tests/unit/test_utility.py b/tests/unit/test_utility.py index f477d2f..fc985c5 100644 --- a/tests/unit/test_utility.py +++ b/tests/unit/test_utility.py @@ -173,4 +173,122 @@ def test_norm_mat(sample_image): # Test with 3D matrix to BGR norm_3d = norm_mat(matrix_2d, to_bgr=True) assert len(norm_3d.shape) == 3 - assert norm_3d.shape[2] == 3 \ No newline at end of file + assert norm_3d.shape[2] == 3 + + +# ── Tests for external tool exe helpers ────────────────────────────── + +from utility import butter_exe, ssimul_exe, exiftool_exe + + +class TestButterExe: + """Tests for butter_exe() covering bundled binary, PATH fallback, and not-found.""" + + def test_linux_bundled_binary_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=True): + mock_sys.platform = "linux" + result = butter_exe() + assert result == "butteraugli/linux/butteraugli" + + def test_win32_bundled_binary_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=True): + mock_sys.platform = "win32" + result = butter_exe() + assert result == "butteraugli/windows/butteraugli.exe" + + def test_win32_path_fallback(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=False), \ + patch("utility.shutil.which", return_value="C:\\tools\\butteraugli.exe"): + mock_sys.platform = "win32" + result = butter_exe() + assert result == "C:\\tools\\butteraugli.exe" + + def test_linux_path_fallback(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=False), \ + patch("utility.shutil.which", return_value="/usr/bin/butteraugli"): + mock_sys.platform = "linux" + result = butter_exe() + assert result == "/usr/bin/butteraugli" + + def test_not_found_returns_none(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=False), \ + patch("utility.shutil.which", return_value=None): + mock_sys.platform = "win32" + assert butter_exe() is None + + def test_unsupported_platform_returns_none(self): + with patch("utility.sys") as mock_sys: + mock_sys.platform = "darwin" + assert butter_exe() is None + + +class TestSsimulExe: + """Tests for ssimul_exe() covering bundled binary, PATH fallback, and not-found.""" + + def test_linux_bundled_binary_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=True): + mock_sys.platform = "linux" + result = ssimul_exe() + assert result == "ssimulacra/linux/ssimulacra" + + def test_win32_bundled_binary_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=True): + mock_sys.platform = "win32" + result = ssimul_exe() + assert result == "ssimulacra/windows/ssimulacra.exe" + + def test_win32_path_fallback(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=False), \ + patch("utility.shutil.which", return_value="C:\\tools\\ssimulacra.exe"): + mock_sys.platform = "win32" + result = ssimul_exe() + assert result == "C:\\tools\\ssimulacra.exe" + + def test_not_found_returns_none(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.isfile", return_value=False), \ + patch("utility.shutil.which", return_value=None): + mock_sys.platform = "linux" + assert ssimul_exe() is None + + def test_unsupported_platform_returns_none(self): + with patch("utility.sys") as mock_sys: + mock_sys.platform = "darwin" + assert ssimul_exe() is None + + +class TestExiftoolExe: + """Tests for exiftool_exe() to ensure the existing function keeps working.""" + + def test_linux_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.exists", return_value=True): + mock_sys.platform = "linux" + result = exiftool_exe() + assert result == "pyexiftool/exiftool/linux/exiftool" + + def test_win32_found(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.exists", return_value=True): + mock_sys.platform = "win32" + result = exiftool_exe() + assert result == "pyexiftool/exiftool/windows/exiftool(-k).exe" + + def test_not_found_returns_none(self): + with patch("utility.sys") as mock_sys, \ + patch("utility.os.path.exists", return_value=False): + mock_sys.platform = "win32" + assert exiftool_exe() is None + + def test_unsupported_platform(self): + with patch("utility.sys") as mock_sys: + mock_sys.platform = "darwin" + assert exiftool_exe() is None \ No newline at end of file