From fd2045e3ff6137be1f41a187f47b3accb97e4a78 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:37:00 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Add=20unit=20tests=20for=20ensur?= =?UTF-8?q?e=5Frknn=5Ftoolkit=20and=20update=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: manupawickramasinghe <73810867+manupawickramasinghe@users.noreply.github.com> --- frigate/test/test_rknn_converter.py | 68 +++++++++++++++++++++++++++++ frigate/util/rknn_converter.py | 33 +++++++++++--- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/frigate/test/test_rknn_converter.py b/frigate/test/test_rknn_converter.py index 0ca2063578..d84ea3616c 100644 --- a/frigate/test/test_rknn_converter.py +++ b/frigate/test/test_rknn_converter.py @@ -15,6 +15,74 @@ from frigate.util.rknn_converter import is_rknn_compatible +import subprocess +from frigate.util.rknn_converter import ensure_rknn_toolkit + +class TestEnsureRknnToolkit(unittest.TestCase): + def setUp(self): + # We need to save the original builtins.__import__ to call it for non-mocked modules + self.original_import = __import__ + # Create a dict to track import attempts to test the two-pass logic + self.import_attempts = {"rknn": 0} + + def _mock_import(self, name, *args, **kwargs): + if name == "rknn": + self.import_attempts["rknn"] += 1 + if self.rknn_import_behavior[self.import_attempts["rknn"] - 1] == "fail": + raise ImportError(f"No module named {name}") + return MagicMock() + return self.original_import(name, *args, **kwargs) + + @patch("builtins.__import__") + @patch("frigate.util.rknn_converter.subprocess.check_call") + def test_rknn_already_installed(self, mock_check_call, mock_import): + self.rknn_import_behavior = ["success"] + mock_import.side_effect = self._mock_import + + result = ensure_rknn_toolkit() + + self.assertTrue(result) + mock_check_call.assert_not_called() + self.assertEqual(self.import_attempts["rknn"], 1) + + @patch("builtins.__import__") + @patch("frigate.util.rknn_converter.subprocess.check_call") + def test_rknn_dynamic_install_success(self, mock_check_call, mock_import): + self.rknn_import_behavior = ["fail", "success"] + mock_import.side_effect = self._mock_import + + result = ensure_rknn_toolkit() + + self.assertTrue(result) + mock_check_call.assert_called_once() + self.assertEqual(self.import_attempts["rknn"], 2) + + @patch("builtins.__import__") + @patch("frigate.util.rknn_converter.subprocess.check_call") + def test_rknn_dynamic_install_fail_subprocess(self, mock_check_call, mock_import): + self.rknn_import_behavior = ["fail"] + mock_import.side_effect = self._mock_import + mock_check_call.side_effect = subprocess.CalledProcessError(1, "pip") + + result = ensure_rknn_toolkit() + + self.assertFalse(result) + mock_check_call.assert_called_once() + self.assertEqual(self.import_attempts["rknn"], 1) + + @patch("builtins.__import__") + @patch("frigate.util.rknn_converter.subprocess.check_call") + def test_rknn_dynamic_install_fail_import(self, mock_check_call, mock_import): + self.rknn_import_behavior = ["fail", "fail"] + mock_import.side_effect = self._mock_import + + result = ensure_rknn_toolkit() + + self.assertFalse(result) + mock_check_call.assert_called_once() + self.assertEqual(self.import_attempts["rknn"], 2) + + class TestIsRknnCompatible(unittest.TestCase): @patch("frigate.util.rknn_converter.get_soc_type") def test_no_soc(self, mock_get_soc_type): diff --git a/frigate/util/rknn_converter.py b/frigate/util/rknn_converter.py index 5660c7601d..1c10f8e536 100644 --- a/frigate/util/rknn_converter.py +++ b/frigate/util/rknn_converter.py @@ -128,15 +128,36 @@ def ensure_torch_dependencies() -> bool: def ensure_rknn_toolkit() -> bool: - """Ensure RKNN toolkit is available.""" + """Dynamically install rknn-toolkit2 if not available.""" try: - from rknn.api import RKNN # type: ignore # noqa: F401 + import rknn # type: ignore # noqa: F401 - logger.debug("RKNN toolkit is already available") + logger.debug("RKNN Toolkit is already available") return True - except ImportError as e: - logger.error(f"RKNN toolkit not found. Please ensure it's installed. {e}") - return False + except ImportError: + logger.info("RKNN Toolkit not found, attempting to install...") + + try: + subprocess.check_call( + [ + sys.executable, + "-m", + "pip", + "install", + "--break-system-packages", + "rknn-toolkit2", + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + import rknn # type: ignore # noqa: F401 + + logger.info("RKNN Toolkit installed successfully") + return True + except (subprocess.CalledProcessError, ImportError) as e: + logger.error(f"Failed to install RKNN Toolkit: {e}") + return False def get_soc_type() -> Optional[str]: