def test_take_motion_image(self): plugins = { MOTION_DETECTOR_PLUGIN_NAME: ProcessPack(camera=MotionDetectorCameraPlugin), PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData), ControlledMediaReceiver.plugin_name(): ProcessPack(camera=ControlledMediaReceiver), MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(camera=MediaManagerPlugin) } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera detector = host.plugin_instances[ MOTION_DETECTOR_PLUGIN_NAME].camera media_rcv = host.plugin_instances[ ControlledMediaReceiver.plugin_name()].camera injector.wait_for_completion() detector.take_motion_picture(123) self.retry_until_timeout(lambda: media_rcv.media is not None) self.assertTrue(os.path.isfile(media_rcv.media.path)) self.assertEqual(media_rcv.media.kind, 'jpeg') self.assertEqual(media_rcv.media.info, 123) media_rcv.let_media_go() self.retry_until_timeout( lambda: not os.path.isfile(media_rcv.media.path))
def test_media_dispatch(self): plugins = { ControlledMediaReceiver.plugin_name(): ProcessPack(main=ControlledMediaReceiver), MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(main=RemoteMediaManager) } with ProcessesHost(plugins) as phost: with tempfile.NamedTemporaryFile(delete=False) as media_file: path = media_file.name media_mgr = phost.plugin_instances[MEDIA_MANAGER_PLUGIN_NAME].main media_rcv = phost.plugin_instances[ ControlledMediaReceiver.plugin_name()].main media_mgr.test_deliver_media(path, 'mp4', 45) # Make sure it gets delivered if self.retry_until_timeout(lambda: media_rcv.media is not None): self.assertTrue(os.path.isfile(path)) self.assertEqual(media_rcv.media.path, path) self.assertEqual(media_rcv.media.kind, 'mp4') self.assertEqual(media_rcv.media.info, 45) self.assertEqual(media_rcv.media.owning_process, Process.MAIN) time.sleep(0.5) self.assertTrue(os.path.isfile(path)) media_rcv.let_media_go() self.retry_until_timeout(lambda: not os.path.isfile(path))
def test_runs_with_demo_data(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), BUFFERED_RECORDER_PLUGIN_NAME: ProcessPack(camera=BufferedRecorderPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData) } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera buffered_recorder = host.plugin_instances[ BUFFERED_RECORDER_PLUGIN_NAME].camera buffered_recorder.record(12345) self.assertTrue(buffered_recorder.is_recording) injector.wait_for_completion() self.assertGreater(buffered_recorder.footage_age, 0) buffered_recorder.stop_and_discard() buffered_recorder.record(54321) injector.replay() # Wait until the buffer is cleared self.retry_until_footage_age_changes(buffered_recorder) # Wait until at least one frame is recorded self.retry_until_footage_age_changes(buffered_recorder) self.assertTrue(buffered_recorder.is_recording) self.assertGreater(buffered_recorder.footage_age, 0) buffered_recorder.stop_and_finalize() self.assertTrue(buffered_recorder.is_finalizing) injector.wait_for_completion()
def test_dispatches_media(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), BUFFERED_RECORDER_PLUGIN_NAME: ProcessPack(camera=BufferedRecorderPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData), ControlledMediaReceiver.plugin_name(): ProcessPack(camera=ControlledMediaReceiver), MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(camera=MediaManagerPlugin) } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera buffered_recorder = host.plugin_instances[ BUFFERED_RECORDER_PLUGIN_NAME].camera media_rcv = host.plugin_instances[ ControlledMediaReceiver.plugin_name()].camera buffered_recorder.record(12345) injector.wait_for_completion() buffered_recorder.stop_and_discard() buffered_recorder.record(54321) injector.replay() injector.wait_for_completion() buffered_recorder.stop_and_finalize() self.assertTrue(os.path.isfile(media_rcv.media.path)) self.assertEqual(media_rcv.media.kind, 'mp4') self.assertEqual(media_rcv.media.info, 54321) media_rcv.let_media_go() self.retry_until_timeout( lambda: not os.path.isfile(media_rcv.media.path))
def test_simple(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), STILL_PLUGIN_NAME: ProcessPack(camera=StillPlugin) } with ProcessesHost(plugins): pass
def test_runs_with_demo_data(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), STILL_PLUGIN_NAME: ProcessPack(camera=StillPlugin) } with ProcessesHost(plugins) as host: still = host.plugin_instances[STILL_PLUGIN_NAME].camera still.take_picture()
def test_simple(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), BUFFERED_RECORDER_PLUGIN_NAME: ProcessPack(camera=BufferedRecorderPlugin) } with ProcessesHost(plugins): pass
def test_with_another_plugin(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), 'TestCam': ProcessPack(camera=TestCam) } with ProcessesHost(plugins) as host: picamera_plugin = host.plugin_instances[ PICAMERA_ROOT_PLUGIN_NAME].camera testcam_plugin = host.plugin_instances['TestCam'].camera # Test that they agree on who is who self.assertEqual(picamera_plugin.get_remote_id(), testcam_plugin.get_picamera_root_plugin_id())
def test_simple(self): plugins = { MOTION_DETECTOR_PLUGIN_NAME: ProcessPack(camera=MotionDetectorCameraPlugin, main=MotionDetectorDispatcherPlugin), PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData) } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera injector.wait_for_completion()
def test_with_demo_data(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), 'TestCam': ProcessPack(camera=TestCam), 'InjectDemoData': ProcessPack(camera=InjectDemoData) } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera test_cam_plugin = host.plugin_instances['TestCam'].camera injector.wait_for_completion() self.assertGreater(test_cam_plugin.num_writes, 0) self.assertGreater(test_cam_plugin.num_flushes, 0) self.assertGreater(test_cam_plugin.num_analysis, 0)
def test_rewinds(self): # Identify the max age of a split point max_sps_age = 0 age = 0 for evt in InjectDemoData.DEMO_DATA['events']: # pragma: no cover if evt.frame is None: continue elif evt.frame.frame_type == PiVideoFrameType.sps_header: max_sps_age = max(max_sps_age, age) age = 0 elif evt.frame.frame_type == PiVideoFrameType.key_frame or evt.frame.frame_type == PiVideoFrameType.frame: age += 1 # We will play in a loop max_sps_age = max(age, max_sps_age) self.assertGreater(max_sps_age, 0) del age plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), BUFFERED_RECORDER_PLUGIN_NAME: ProcessPack(camera=BufferedRecorderPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData) } with ProcessesHost(plugins) as host: buffered_recorder = host.plugin_instances[ BUFFERED_RECORDER_PLUGIN_NAME].camera # Patch the recorder to prevent frames to get too old buffered_recorder.sps_header_max_age = max_sps_age / 2 buffered_recorder.buffer_max_age = max_sps_age / 2 injector = host.plugin_instances['InjectDemoData'].camera max_buffer_age = 0 max_footage_age = 0 while not injector.wait_for_completion(0.1): max_buffer_age = max(buffered_recorder.buffer_age, max_buffer_age) max_footage_age = max(buffered_recorder.buffer_age, max_footage_age) injector.replay() while not injector.wait_for_completion(0.1): max_buffer_age = max(buffered_recorder.buffer_age, max_buffer_age) max_footage_age = max(buffered_recorder.buffer_age, max_footage_age) # On average we must not exceed by that much the max allowed buffer length self.assertLessEqual(max_buffer_age, buffered_recorder.total_age) self.assertLessEqual(max_footage_age, buffered_recorder.total_age) self.assertLessEqual(max_buffer_age, max_sps_age) self.assertLessEqual(max_footage_age, max_sps_age)
def __init__(self, plugins): """ :param plugins: dict-like object that maps plugin name in the process instance types. The key type is string, and the value type is a ProcessPack containing three types, subclasses of PluginProcessInstanceBase. Is it ok for the ProcessPack to contain None entries. For example plugins = {'root_plugin': ProcessPack(None, None, MySubclassOfPluginProcessInstanceBase)} """ self._socket_dir = TemporaryDirectory( dir=SETTINGS.get('temp_folder', cast_to_type=str, allow_none=True)) self._plugin_instances = dict({k: None for k in plugins}) self._plugin_process_host_pack = ProcessPack(*[ self.__class__._create_host(self._socket_dir.name, plugins, process) for process in Process ]) self._housekeepers = ProcessPack()
def test_simple(self): with ProcessesHost( {MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(main=RemoteMediaManager)}) as phost: self.assertIn(MEDIA_MANAGER_PLUGIN_NAME, phost.plugin_instances) self.assertIsNotNone( phost.plugin_instances[MEDIA_MANAGER_PLUGIN_NAME].main)
def test_file_deletion(self): with ProcessesHost( {MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(main=RemoteMediaManager)}) as phost: with tempfile.NamedTemporaryFile(delete=False) as media_file: path = media_file.name assert os.path.isfile(path) phost.plugin_instances[ MEDIA_MANAGER_PLUGIN_NAME].main.test_deliver_media(path, None) self.retry_until_timeout(lambda: not os.path.isfile(path))
def test_manual_dispatch(self): plugins = { ControlledMediaReceiver.plugin_name(): ProcessPack(main=ControlledMediaReceiver), MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(main=MediaManagerPlugin) } dummy_media = Media(UUID('4b878c8a-de5a-402b-9f9b-127f5a3a78de'), Process.CAMERA, 'KIND', 'PATH', 'INFO') with ProcessesHost(plugins) as phost: media_mgr = phost.plugin_instances[MEDIA_MANAGER_PLUGIN_NAME].main media_rcv = phost.plugin_instances[ ControlledMediaReceiver.plugin_name()].main media_mgr.dispatch_media(dummy_media) # Make sure it gets delivered if self.retry_until_timeout(lambda: media_rcv.media is not None): self.assertEqual(media_rcv.media.path, 'PATH') self.assertEqual(media_rcv.media.kind, 'KIND') self.assertEqual(media_rcv.media.info, 'INFO') self.assertEqual(media_rcv.media.owning_process, Process.CAMERA) media_rcv.let_media_go()
def test_querying_with_process(self): pack = ProcessPack(*[process.value for process in Process]) for process in Process: self.assertEqual(pack[process], process.value) self.assertEqual(pack[process.value], process.value) self.assertEqual(getattr(pack, process.value), process.value) setattr(pack, process.value, None) pack[process] = None # also None, to test assignment self.assertIsNone(getattr(pack, process.value)) with self.assertRaises(KeyError): _ = pack[dict()] with self.assertRaises(KeyError): pack[dict()] = None
def test_dispatches_media(self): plugins = { PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), STILL_PLUGIN_NAME: ProcessPack(camera=StillPlugin), ControlledMediaReceiver.plugin_name(): ProcessPack(camera=ControlledMediaReceiver), MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(camera=MediaManagerPlugin) } with ProcessesHost(plugins) as host: media_rcv = host.plugin_instances[ ControlledMediaReceiver.plugin_name()].camera still = host.plugin_instances[STILL_PLUGIN_NAME].camera still.take_picture(123) self.retry_until_timeout(lambda: media_rcv.media is not None) self.assertTrue(os.path.isfile(media_rcv.media.path)) self.assertEqual(media_rcv.media.kind, 'jpeg') self.assertEqual(media_rcv.media.info, 123) media_rcv.let_media_go() self.retry_until_timeout( lambda: not os.path.isfile(media_rcv.media.path))
def try_report_motion_on(process): motion_detection_pack = ProcessPack(camera=MotionDetectorCameraPlugin) if process != Process.CAMERA: motion_detection_pack[process] = MotionDetectorDispatcherPlugin responder_pack = ProcessPack() responder_pack[ process] = TestMotionDetectorPlugin.TestMovementResponder plugins = { MOTION_DETECTOR_PLUGIN_NAME: motion_detection_pack, PICAMERA_ROOT_PLUGIN_NAME: ProcessPack(camera=PiCameraRootPlugin), 'InjectDemoData': ProcessPack(camera=InjectDemoData), TestMotionDetectorPlugin.TestMovementResponder.plugin_name(): responder_pack } with ProcessesHost(plugins) as host: injector = host.plugin_instances['InjectDemoData'].camera responder = host.plugin_instances[ TestMotionDetectorPlugin.TestMovementResponder.plugin_name( )][process] injector.wait_for_completion() return responder.num_distinct_movements, responder.num_wrong_changed_events
def test_intra_instance_talk(self): plugins = { 'main': ProcessPack(TestPluginProcess.TestProcess, TestPluginProcess.TestProcess, TestPluginProcess.TestProcess) } with ProcessesHost(plugins) as processes: instance_pack = processes.plugin_instances['main'] pid_sets = list( [instance.get_sibling_pid_set() for instance in instance_pack]) self.assertEqual(len(pid_sets), len(AVAILABLE_PROCESSES)) if len(pid_sets) > 0: for pid_set in pid_sets[1:]: self.assertEqual(pid_set, pid_sets[0])
def test_spurious_consume_calls(self): plugins = { MEDIA_MANAGER_PLUGIN_NAME: ProcessPack(main=MediaManagerPlugin) } dummy_media = Media(UUID('4b878c8a-de5a-402b-9f9b-127f5a3a78de'), Process.MAIN, None, None, None) with ProcessesHost(plugins) as phost: media_mgr = phost.plugin_instances[MEDIA_MANAGER_PLUGIN_NAME].main # Nothing should happen here media_mgr.consume_media(dummy_media, Process.MAIN) media_mgr.consume_media(dummy_media, Process.CAMERA) another_dummy_media = Media(dummy_media.uuid, Process.TELEGRAM, None, None, None) media_mgr.consume_media(another_dummy_media, Process.TELEGRAM) media_mgr.consume_media(another_dummy_media, Process.MAIN) media_mgr.consume_media(another_dummy_media, Process.CAMERA)
def deliver_media(self, path, kind, info=None): media_mgr_pack = find_plugin(self) with self._media_lock: uuid = None while uuid is None or uuid in self._media: uuid = uuid4() media = Media(uuid, active_process(), kind, path, info) self._media[uuid] = media # Assume not necessarily we have a media manager on every single process. This makes easier testing. self._media_in_use[uuid] = ProcessPack( *[entry is not None for entry in media_mgr_pack.values()]) _log.info('Dispatching media %s at path %s.', str(media.uuid), os.path.abspath(media.path)) # Dispatch to all the other media managers. for media_mgr in media_mgr_pack.nonempty_values(): media_mgr.dispatch_media(media) return media
def __enter__(self): # Create temp dir self._socket_dir.__enter__() # Activate all hosts in sequence for host in self._plugin_process_host_pack.values(): host.__enter__() # Collect all plugin instances for plugin_name in self._plugin_instances.keys(): self._plugin_instances[plugin_name] = ProcessPack(*[ self._plugin_process_host_pack[process]. plugin_instances[plugin_name] for process in Process ]) # Change the running process variable in the remote process and publish a list of plugins for process, host in self._plugin_process_host_pack.items(): self._housekeepers[process] = host.singleton_host( ProcessesHost._Housekeeper) self._housekeepers[process].setup(process, self._plugin_instances) # Activate all plugin instances with the information about all plugins self._activate_all_plugin_process_instances() return self
def test_process_host(self): plugins = { TestPluginProcess.TestProcess.PLUGIN_NAME: ProcessPack(TestPluginProcess.TestProcess, TestPluginProcess.TestProcess, TestPluginProcess.TestProcess) } with ProcessesHost(plugins) as processes: self.assertIn('main', processes.plugin_instances) instance_pack = processes.plugin_instances['main'] for process, instance in instance_pack.items(): self.assertIsNotNone(instance) if instance is not None: process_in_instance = instance.get_remote_process() pid_in_instance = instance.get_remote_pid() self.assertIsNotNone(process_in_instance) self.assertIsNotNone(pid_in_instance) self.assertNotEqual(pid_in_instance, os.getpid()) # Need to explicitly convert because the serialization engine may not preserve the Enum self.assertEqual(Process(process_in_instance), process)
def register(plugin_cls, name, process): global _PLUGINS if name not in _PLUGINS: _PLUGINS[name] = ProcessPack(None, None, None) _PLUGINS[name][process] = plugin_cls
def test_none_process_host(self): plugins = {'main': ProcessPack(None, None, None)} with ProcessesHost(plugins) as processes: instance_pack = processes.plugin_instances['main'] for instance in instance_pack.values(): self.assertIsNone(instance)
def test_direct_lookup(self): name = TestPluginLookup.TestPluginTablePluginMain.plugin_name() plugin_main = TestPluginLookup.TestPluginTablePluginMain() plugin_telegram = TestPluginLookup.TestPluginTablePluginTelegram() pack = ProcessPack(main=plugin_main, telegram=plugin_telegram) table = PluginLookupTable({name: pack}, Process.MAIN) self.assertIs(table[name], pack) self.assertIs(table[TestPluginLookup.TestPluginTablePluginTelegram], pack) self.assertIs(table[TestPluginLookup.TestPluginTablePluginMain], pack) self.assertIs(table[plugin_main], pack) self.assertIs(table[plugin_telegram], pack) self.assertIs(table.telegram[name], plugin_telegram) self.assertIs( table.telegram[TestPluginLookup.TestPluginTablePluginTelegram], plugin_telegram) self.assertIs( table.telegram[TestPluginLookup.TestPluginTablePluginMain], plugin_telegram) self.assertIs(table.telegram[plugin_main], plugin_telegram) self.assertIs(table.telegram[plugin_telegram], plugin_telegram) self.assertIs(table.main[name], plugin_main) self.assertIs( table.main[TestPluginLookup.TestPluginTablePluginTelegram], plugin_main) self.assertIs(table.main[TestPluginLookup.TestPluginTablePluginMain], plugin_main) self.assertIs(table.main[plugin_main], plugin_main) self.assertIs(table.main[plugin_telegram], plugin_main) self.assertIs(table[Process.TELEGRAM][name], plugin_telegram) self.assertIs( table[Process.TELEGRAM] [TestPluginLookup.TestPluginTablePluginTelegram], plugin_telegram) self.assertIs( table[Process.TELEGRAM][ TestPluginLookup.TestPluginTablePluginMain], plugin_telegram) self.assertIs(table[Process.TELEGRAM][plugin_main], plugin_telegram) self.assertIs(table[Process.TELEGRAM][plugin_telegram], plugin_telegram) self.assertIs(table[Process.MAIN][name], plugin_main) self.assertIs( table[Process.MAIN][ TestPluginLookup.TestPluginTablePluginTelegram], plugin_main) self.assertIs( table[Process.MAIN][TestPluginLookup.TestPluginTablePluginMain], plugin_main) self.assertIs(table[Process.MAIN][plugin_main], plugin_main) self.assertIs(table[Process.MAIN][plugin_telegram], plugin_main) self.assertIs(getattr(table, name), pack) self.assertIs(getattr(table.telegram, name), plugin_telegram) self.assertIs(table.telegram.TestPluginTable, plugin_telegram) self.assertIs(table.main.TestPluginTable, plugin_main) self.assertIs(table[Process.TELEGRAM].TestPluginTable, plugin_telegram) self.assertIs(table[Process.MAIN].TestPluginTable, plugin_main) self.assertIn(pack, table.values()) self.assertIn((name, pack), table.items()) self.assertIn(name, table.keys()) self.assertIsNone(table[123, 123]) self.assertIsNone(table[123, Process.MAIN]) with self.assertRaises(KeyError): _ = table[1, 2, 3]
def __init__(self, plugins, process): self._plugins = plugins self._replace_local_instances(process) self._plugins_by_process = ProcessPack(*[ PluginLookupDict({k: v[p] for k, v in self._plugins.items()}) for p in Process ])