Private
Public Access
67 lines
2.8 KiB
Python
67 lines
2.8 KiB
Python
"""Installer ohne Netzwerk; optionaler Smoke-Test mit echten Modellgewichten."""
|
|
from contextlib import redirect_stdout
|
|
import hashlib
|
|
import importlib.util
|
|
from io import BytesIO, StringIO
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from PIL import Image
|
|
|
|
from halloween.detection import NanoDet
|
|
|
|
|
|
class InstallerTest(unittest.TestCase):
|
|
def setUp(self):
|
|
spec = importlib.util.spec_from_file_location('model_installer', Path(__file__).parents[1] / 'tools/install_detection_model.py')
|
|
self.installer = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(self.installer)
|
|
self.folder = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.folder.cleanup)
|
|
self.data = b'test-model'
|
|
self.manifest = {'file': 'model.onnx', 'sha256': hashlib.sha256(self.data).hexdigest(),
|
|
'size': len(self.data), 'url': 'https://example.invalid/model'}
|
|
self.target = Path(self.folder.name) / 'models/model.onnx'
|
|
|
|
def install(self, data):
|
|
with patch.object(self.installer.json, 'loads', return_value=self.manifest), \
|
|
patch.object(self.installer.urllib.request, 'urlopen', return_value=BytesIO(data)) as download, \
|
|
patch('sys.argv', ['installer', '--state-dir', self.folder.name]), redirect_stdout(StringIO()):
|
|
self.installer.main()
|
|
return download
|
|
|
|
def test_verified_download_is_atomic_and_idempotent(self):
|
|
self.assertEqual(self.install(self.data).call_count, 1)
|
|
self.assertEqual(self.target.read_bytes(), self.data)
|
|
self.assertEqual(self.install(self.data).call_count, 0)
|
|
self.assertEqual(list(self.target.parent.iterdir()), [self.target])
|
|
|
|
def test_bad_download_keeps_existing_file(self):
|
|
self.target.parent.mkdir()
|
|
self.target.write_bytes(b'old-corrupt-model')
|
|
with self.assertRaises(ValueError):
|
|
self.install(b'bad-model!')
|
|
self.assertEqual(self.target.read_bytes(), b'old-corrupt-model')
|
|
self.assertEqual(list(self.target.parent.iterdir()), [self.target])
|
|
|
|
def test_oversized_download_is_rejected_and_cleaned(self):
|
|
with self.assertRaises(ValueError):
|
|
self.install(self.data+b'extra')
|
|
self.assertEqual(list(self.target.parent.iterdir()), [])
|
|
|
|
|
|
@unittest.skipUnless(os.environ.get('HALLOWEEN_TEST_MODEL'), 'Optionales Modell nicht angegeben')
|
|
class ModelSmokeTest(unittest.TestCase):
|
|
def test_real_model_outputs_and_empty_frame(self):
|
|
model = NanoDet(Path(os.environ['HALLOWEEN_TEST_MODEL']))
|
|
frame = BytesIO()
|
|
Image.new('RGB', (640,480)).save(frame, format='JPEG')
|
|
self.assertEqual(model.detect(frame.getvalue()), [])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|