async def test_get_schedule(self): """Schedule gets retrieved :assert: Schedule is retrieved by id """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # Declare schedule interval_schedule = IntervalSchedule() interval_schedule.name = 'get_schedule_test' interval_schedule.process_name = "sleep30" interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) # Get schedule schedules = await scheduler.get_schedules() assert len(schedules) == 1 # Assert the number of schedules await scheduler.get_schedule( interval_schedule.schedule_id ) # Get the schedule by schedule process ID # Assert that schedule is retrieved by ID try: await scheduler.get_schedule(uuid.uuid4()) assert False except ScheduleNotFoundError: pass await self.stop_scheduler(scheduler)
async def test_get_task(self): """Test tasks exists :assert: there exists a task """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() interval_schedule = IntervalSchedule() interval_schedule.name = 'get_task' interval_schedule.process_name = "sleep30" interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) await asyncio.sleep(1) tasks = await scheduler.get_running_tasks( ) # retrieve list running tasks assert len(tasks) task = await scheduler.get_task(str(tasks[0].task_id)) assert task # assert there exists a task await self.stop_scheduler(scheduler)
async def test_delete(self): """Test that a scheduled process gets removed :assert: scheduled task gets removed """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # Set schedule to be interval based interval_schedule = IntervalSchedule() interval_schedule.name = 'deletetest' interval_schedule.process_name = "sleep1" interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) await asyncio.sleep(5) # Delete a scheduled task await scheduler.delete_schedule(interval_schedule.schedule_id) # Assert that process was deleted try: await scheduler.delete_schedule(interval_schedule.schedule_id) assert False except ScheduleNotFoundError: pass await self.stop_scheduler(scheduler)
async def test_modify_schedule_type(self): """Test modifying the type of a schedule """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() interval_schedule = IntervalSchedule() interval_schedule.name = 'sleep10' interval_schedule.process_name = 'sleep10' interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) manual_schedule = ManualSchedule() manual_schedule.schedule_id = interval_schedule.schedule_id manual_schedule.name = 'manual' manual_schedule.process_name = 'sleep10' manual_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(manual_schedule) # Assert: only 1 task is running schedule = await scheduler.get_schedule(manual_schedule.schedule_id) assert isinstance(schedule, ManualSchedule) await self.stop_scheduler(scheduler)
async def test_interval_none_repeat(self): """Tests an interval schedule where repeat is None :assert: A task starts immediately and doesn't repeat """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # assert that the schedule type is interval interval_schedule = IntervalSchedule() assert interval_schedule.schedule_type == Schedule.Type.INTERVAL interval_schedule.name = 'sleep10' interval_schedule.process_name = "sleep10" interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) await asyncio.sleep(1) # Assert only 1 task is running tasks = await scheduler.get_running_tasks() assert len(tasks) == 1 await asyncio.sleep(12) # Assert only 1 task is running tasks = await scheduler.get_running_tasks() assert len(tasks) == 1 await self.stop_scheduler(scheduler)
async def test_purge_tasks(self): await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() interval_schedule = IntervalSchedule() interval_schedule.name = 'purge_task' interval_schedule.process_name = "sleep5" interval_schedule.repeat = datetime.timedelta(seconds=0) # interval_schedule.repeat = datetime.timedelta(seconds=30) await scheduler.save_schedule(interval_schedule) await asyncio.sleep(1) tasks = await scheduler.get_tasks(5) assert tasks scheduler.max_running_tasks = 0 await asyncio.sleep(7) scheduler.max_completed_task_age = datetime.timedelta(seconds=1) await scheduler.purge_tasks() tasks = await scheduler.get_tasks(5) assert not tasks await self.stop_scheduler(scheduler)
async def test_update(self): """Test update of a running task :assert: the number of tasks running information regarding the process running """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() interval_schedule = IntervalSchedule() interval_schedule.name = 'sleep10' interval_schedule.process_name = "sleep10" interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule ) # Save update on _scheduler await asyncio.sleep(1) # Assert only 1 task is running tasks = await scheduler.get_running_tasks() assert len(tasks) == 1 # Update 'updated' schedule interval interval_schedule.name = 'updated' interval_schedule.process_name = "sleep1" interval_schedule.repeat = datetime.timedelta( seconds=5) # Set time interval to 5 sec await scheduler.save_schedule(interval_schedule ) # Save update on _scheduler await asyncio.sleep(6) # Assert: only 1 task is running tasks = await scheduler.get_running_tasks( ) # list of current running tasks assert len(tasks) == 1 interval_schedule.exclusive = False await scheduler.save_schedule(interval_schedule) # Check able to get same schedule after restart # Check fields have been modified await self.stop_scheduler(scheduler) scheduler = Scheduler(_address, _m_port) await scheduler.start() schedule = await scheduler.get_schedule(interval_schedule.schedule_id) # Make sure that the values used by schedule are as expected assert schedule.process_name == 'sleep1' assert schedule.name == 'updated' assert schedule.repeat.seconds == 5 assert not schedule.exclusive await self.stop_scheduler(scheduler)
async def mock_schedule(_type): if _type == 1: schedule = StartUpSchedule() schedule.repeat = None schedule.time = None schedule.day = None elif _type == 2: schedule = TimedSchedule() schedule.repeat = None schedule.time = datetime(1, 1, 1, 0, 0, 10) schedule.day = 1 elif _type == 3: schedule = IntervalSchedule() schedule.repeat = timedelta(seconds=15) schedule.time = None schedule.day = None else: schedule = ManualSchedule() schedule.repeat = None schedule.time = None schedule.day = None schedule.schedule_id = self._random_uuid schedule.exclusive = True schedule.enabled = True schedule.name = "foo" schedule.process_name = "bar" return schedule
def _schedule_row_to_schedule(cls, schedule_id: uuid.UUID, schedule_row: _ScheduleRow) -> Schedule: schedule_type = schedule_row.type if schedule_type == Schedule.Type.STARTUP: schedule = StartUpSchedule() elif schedule_type == Schedule.Type.TIMED: schedule = TimedSchedule() elif schedule_type == Schedule.Type.INTERVAL: schedule = IntervalSchedule() elif schedule_type == Schedule.Type.MANUAL: schedule = ManualSchedule() else: raise ValueError("Unknown schedule type {}", schedule_type) schedule.schedule_id = schedule_id schedule.exclusive = schedule_row.exclusive schedule.name = schedule_row.name schedule.process_name = schedule_row.process_name schedule.repeat = schedule_row.repeat if schedule_type == Schedule.Type.TIMED: schedule.day = schedule_row.day schedule.time = schedule_row.time else: schedule.day = None schedule.time = None return schedule
async def test_cancel(self): """Cancel a running process""" await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() interval_schedule = IntervalSchedule() interval_schedule.name = 'cancel_test' interval_schedule.process_name = 'sleep30' interval_schedule.repeat = datetime.timedelta(seconds=0) await scheduler.save_schedule(interval_schedule) await asyncio.sleep(5) tasks = await scheduler.get_running_tasks() await scheduler.cancel_task(tasks[0].task_id) # Cancel a running task await self.stop_scheduler(scheduler)
async def test_create_interval(self): """Test the creation of a new schedule interval :assert: The interval type of the schedule """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # assert that the schedule type is interval interval_schedule = IntervalSchedule() assert interval_schedule.schedule_type == Schedule.Type.INTERVAL interval_schedule.name = 'sleep10' interval_schedule.process_name = "sleep10" interval_schedule.repeat = datetime.timedelta(seconds=1) await scheduler.save_schedule(interval_schedule) await self.stop_scheduler(scheduler)
async def test_max_processes(self): """Test the maximum number of running processes :assert: the number of running processes """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # 2 maximum tasks # 1 runs at 1 second # 2 runs at 2 seconds # 3 runs at 11 seconds # 4 runs at 12 seconds # 5 runs at 21 seconds # 6 runs at 22 seconds # 7 runs at 31 seconds # 8 runs at 32 seconds # Total: 6 scheduler.max_running_tasks = 2 # set the maximum number of running tasks in parallel # Set interval schedule configuration interval_schedule = IntervalSchedule() interval_schedule.repeat = datetime.timedelta(seconds=1) interval_schedule.name = 'max active' interval_schedule.exclusive = False interval_schedule.process_name = 'sleep10' interval_schedule.enabled = True await scheduler.save_schedule(interval_schedule) await asyncio.sleep(30.3) scheduler.max_running_tasks = 0 # set the maximum number of running tasks in parallel tasks = await scheduler.get_tasks(10) assert len(tasks) == 6 tasks = await scheduler.get_running_tasks() assert len(tasks) == 2 # They end... await asyncio.sleep(20) scheduler.max_running_tasks = 10 await asyncio.sleep(11) tasks = await scheduler.get_running_tasks() assert len(tasks) == 10 await self.stop_scheduler(scheduler)
async def _execute_add_update_schedule(data, curr_value=None): """ Private method common to create a new schedule and update an existing schedule Args: data: Returns: schedule_id (new for created, existing for updated) """ _schedule = _extract_args(data, curr_value) # Create schedule object as Scheduler.save_schedule requires an object if _schedule.get('schedule_type') == Schedule.Type.STARTUP: schedule = StartUpSchedule() elif _schedule.get('schedule_type') == Schedule.Type.TIMED: schedule = TimedSchedule() schedule.day = _schedule.get('schedule_day') m, s = divmod(_schedule.get('schedule_time'), 60) h, m = divmod(m, 60) schedule.time = datetime.time().replace(hour=h, minute=m, second=s) elif _schedule.get('schedule_type') == Schedule.Type.INTERVAL: schedule = IntervalSchedule() elif _schedule.get('schedule_type') == Schedule.Type.MANUAL: schedule = ManualSchedule() # Populate scheduler object schedule.schedule_id = _schedule.get('schedule_id') schedule.name = _schedule.get('schedule_name') schedule.process_name = _schedule.get('schedule_process_name') schedule.repeat = datetime.timedelta(seconds=_schedule['schedule_repeat']) schedule.exclusive = True if _schedule.get( 'schedule_exclusive') == 'True' else False schedule.enabled = True if _schedule.get( 'schedule_enabled') == 'True' else False # Save schedule await server.Server.scheduler.save_schedule( schedule, _schedule['is_enabled_modified']) updated_schedule_id = schedule.schedule_id return updated_schedule_id
async def test_stop(self): """Test that stop_scheduler actually works""" await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # Set schedule interval interval_schedule = IntervalSchedule() interval_schedule.exclusive = False interval_schedule.enabled = True interval_schedule.name = 'sleep1' interval_schedule.process_name = "sleep1" interval_schedule.repeat = datetime.timedelta( seconds=1) # Set frequency of await scheduler.save_schedule(interval_schedule ) # Save schedule updates await asyncio.sleep(10) await self.stop_scheduler(scheduler)
async def test_disable_schedule(self): await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # Declare schedule interval_schedule = IntervalSchedule() interval_schedule.name = 'disable_schedule_test' interval_schedule.process_name = "sleep5" interval_schedule.repeat = datetime.timedelta(seconds=0) interval_schedule.enabled = True await scheduler.save_schedule(interval_schedule) # Get schedule schedules = await scheduler.get_schedules() assert len(schedules) == 1 # Assert the number of schedules assert schedules[0].enabled is True await asyncio.sleep(5) # assert there exists a task tasks = await scheduler.get_running_tasks( ) # retrieve list running tasks assert len(tasks) task = await scheduler.get_task(tasks[0].task_id) assert task # Disable Schedule retval, reason = await scheduler.disable_schedule( interval_schedule.schedule_id) assert retval # Confirm enabled changed schedules = await scheduler.get_schedules() assert len(schedules) == 1 # Assert the number of schedules assert schedules[0].enabled is False await self.stop_scheduler(scheduler)
async def add_task(request): """ Create a new task to run a specific plugin :Example: curl -X POST http://localhost:8081/foglamp/scheduled/task -d '{ "name": "North Readings to PI", "plugin": "pi_server", "type": "north", "schedule_type": 3, "schedule_day": 0, "schedule_time": 0, "schedule_repeat": 30, "schedule_enabled": true }' curl -sX POST http://localhost:8081/foglamp/scheduled/task -d '{"name": "PI-2", "plugin": "pi_server", "type": "north", "schedule_type": 3, "schedule_day": 0, "schedule_time": 0, "schedule_repeat": 30, "schedule_enabled": true, "config": { "producerToken": {"value": "uid=180905062754237&sig=kx5l+"}, "URL": {"value": "https://10.2.5.22:5460/ingress/messages"}}}' """ try: data = await request.json() if not isinstance(data, dict): raise ValueError('Data payload must be a valid JSON') name = data.get('name', None) plugin = data.get('plugin', None) task_type = data.get('type', None) schedule_type = data.get('schedule_type', None) schedule_day = data.get('schedule_day', None) schedule_time = data.get('schedule_time', None) schedule_repeat = data.get('schedule_repeat', None) enabled = data.get('schedule_enabled', None) config = data.get('config', None) if name is None: raise web.HTTPBadRequest( reason='Missing name property in payload.') if plugin is None: raise web.HTTPBadRequest( reason='Missing plugin property in payload.') if task_type is None: raise web.HTTPBadRequest( reason='Missing type property in payload.') if utils.check_reserved(name) is False: raise web.HTTPBadRequest( reason='Invalid name property in payload.') if utils.check_reserved(plugin) is False: raise web.HTTPBadRequest( reason='Invalid plugin property in payload.') if task_type not in ['north']: raise web.HTTPBadRequest(reason='Only north type is supported.') if schedule_type is None: raise web.HTTPBadRequest(reason='schedule_type is mandatory') if not isinstance(schedule_type, int) and not schedule_type.isdigit(): raise web.HTTPBadRequest( reason='Error in schedule_type: {}'.format(schedule_type)) if int(schedule_type) not in list(Schedule.Type): raise web.HTTPBadRequest( reason='schedule_type error: {}'.format(schedule_type)) if int(schedule_type) == Schedule.Type.STARTUP: raise web.HTTPBadRequest( reason='schedule_type cannot be STARTUP: {}'.format( schedule_type)) schedule_type = int(schedule_type) if schedule_day is not None: if isinstance(schedule_day, float) or ( isinstance(schedule_day, str) and (schedule_day.strip() != "" and not schedule_day.isdigit())): raise web.HTTPBadRequest( reason='Error in schedule_day: {}'.format(schedule_day)) else: schedule_day = int( schedule_day) if schedule_day is not None else None if schedule_time is not None and (not isinstance(schedule_time, int) and not schedule_time.isdigit()): raise web.HTTPBadRequest( reason='Error in schedule_time: {}'.format(schedule_time)) else: schedule_time = int( schedule_time) if schedule_time is not None else None if schedule_repeat is not None and (not isinstance( schedule_repeat, int) and not schedule_repeat.isdigit()): raise web.HTTPBadRequest( reason='Error in schedule_repeat: {}'.format(schedule_repeat)) else: schedule_repeat = int( schedule_repeat) if schedule_repeat is not None else None if schedule_type == Schedule.Type.TIMED: if not schedule_time: raise web.HTTPBadRequest( reason= 'schedule_time cannot be empty/None for TIMED schedule.') if schedule_day is not None and (schedule_day < 1 or schedule_day > 7): raise web.HTTPBadRequest( reason= 'schedule_day {} must either be None or must be an integer, 1(Monday) ' 'to 7(Sunday).'.format(schedule_day)) if schedule_time < 0 or schedule_time > 86399: raise web.HTTPBadRequest( reason= 'schedule_time {} must be an integer and in range 0-86399.' .format(schedule_time)) if schedule_type == Schedule.Type.INTERVAL: if schedule_repeat is None: raise web.HTTPBadRequest( reason= 'schedule_repeat {} is required for INTERVAL schedule_type.' .format(schedule_repeat)) elif not isinstance(schedule_repeat, int): raise web.HTTPBadRequest( reason='schedule_repeat {} must be an integer.'.format( schedule_repeat)) if enabled is not None: if enabled not in ['true', 'false', True, False]: raise web.HTTPBadRequest( reason= 'Only "true", "false", true, false are allowed for value of enabled.' ) is_enabled = True if ( (type(enabled) is str and enabled.lower() in ['true']) or ((type(enabled) is bool and enabled is True))) else False # Check if a valid plugin has been provided try: # "plugin_module_path" is fixed by design. It is MANDATORY to keep the plugin in the exactly similar named # folder, within the plugin_module_path. # if multiple plugin with same name are found, then python plugin import will be tried first plugin_module_path = "foglamp.plugins.{}".format(task_type) import_file_name = "{path}.{dir}.{file}".format( path=plugin_module_path, dir=plugin, file=plugin) _plugin = __import__(import_file_name, fromlist=['']) script = '["tasks/north"]' # Fetch configuration from the configuration defined in the plugin plugin_info = _plugin.plugin_info() if plugin_info['type'] != task_type: msg = "Plugin of {} type is not supported".format( plugin_info['type']) _logger.exception(msg) return web.HTTPBadRequest(reason=msg) plugin_config = plugin_info['config'] process_name = 'north' except ImportError as ex: # Checking for C-type plugins script = '["tasks/north_c"]' plugin_info = apiutils.get_plugin_info(plugin, dir=task_type) if plugin_info['type'] != task_type: msg = "Plugin of {} type is not supported".format( plugin_info['type']) _logger.exception(msg) return web.HTTPBadRequest(reason=msg) plugin_config = plugin_info['config'] process_name = 'north_c' if not plugin_config: _logger.exception("Plugin %s import problem from path %s. %s", plugin, plugin_module_path, str(ex)) raise web.HTTPNotFound( reason='Plugin "{}" import problem from path "{}"'.format( plugin, plugin_module_path)) except Exception as ex: _logger.exception("Failed to fetch plugin configuration. %s", str(ex)) raise web.HTTPInternalServerError( reason='Failed to fetch plugin configuration.') storage = connect.get_storage_async() config_mgr = ConfigurationManager(storage) # Check whether category name already exists category_info = await config_mgr.get_category_all_items( category_name=name) if category_info is not None: raise web.HTTPBadRequest( reason="The '{}' category already exists".format(name)) # Check that the schedule name is not already registered count = await check_schedules(storage, name) if count != 0: raise web.HTTPBadRequest( reason='A north instance with this name already exists') # Check that the process name is not already registered count = await check_scheduled_processes(storage, process_name) if count == 0: # Create the scheduled process entry for the new task payload = PayloadBuilder().INSERT(name=process_name, script=script).payload() try: res = await storage.insert_into_tbl("scheduled_processes", payload) except StorageServerError as ex: _logger.exception("Failed to create scheduled process. %s", ex.error) raise web.HTTPInternalServerError( reason='Failed to create north instance.') except Exception as ex: _logger.exception("Failed to create scheduled process. %s", ex) raise web.HTTPInternalServerError( reason='Failed to create north instance.') # If successful then create a configuration entry from plugin configuration try: # Create a configuration category from the configuration defined in the plugin category_desc = plugin_config['plugin']['description'] await config_mgr.create_category( category_name=name, category_description=category_desc, category_value=plugin_config, keep_original_items=True) # Create the parent category for all North tasks await config_mgr.create_category("North", {}, 'North tasks', True) await config_mgr.create_child_category("North", [name]) # If config is in POST data, then update the value for each config item if config is not None: if not isinstance(config, dict): raise ValueError('Config must be a JSON object') for k, v in config.items(): await config_mgr.set_category_item_value_entry( name, k, v['value']) except Exception as ex: await config_mgr.delete_category_and_children_recursively(name) _logger.exception("Failed to create plugin configuration. %s", str(ex)) raise web.HTTPInternalServerError( reason='Failed to create plugin configuration.') # If all successful then lastly add a schedule to run the new task at startup try: schedule = TimedSchedule() if schedule_type == Schedule.Type.TIMED else \ IntervalSchedule() if schedule_type == Schedule.Type.INTERVAL else \ ManualSchedule() schedule.name = name schedule.process_name = process_name schedule.day = schedule_day m, s = divmod(schedule_time if schedule_time is not None else 0, 60) h, m = divmod(m, 60) schedule.time = datetime.time().replace(hour=h, minute=m, second=s) schedule.repeat = datetime.timedelta( seconds=schedule_repeat if schedule_repeat is not None else 0) schedule.exclusive = True schedule.enabled = False # if "enabled" is supplied, it gets activated in save_schedule() via is_enabled flag # Save schedule await server.Server.scheduler.save_schedule(schedule, is_enabled) schedule = await server.Server.scheduler.get_schedule_by_name(name) except StorageServerError as ex: await config_mgr.delete_category_and_children_recursively(name) _logger.exception("Failed to create schedule. %s", ex.error) raise web.HTTPInternalServerError( reason='Failed to create north instance.') except Exception as ex: await config_mgr.delete_category_and_children_recursively(name) _logger.exception("Failed to create schedule. %s", str(ex)) raise web.HTTPInternalServerError( reason='Failed to create north instance.') except ValueError as e: raise web.HTTPBadRequest(reason=str(e)) else: return web.json_response({ 'name': name, 'id': str(schedule.schedule_id) })
async def test_get_tasks(self): """Get list of tasks :assert: Number of running tasks The state of tasks the start time of a given task """ await self.populate_test_data( ) # Populate data in foglamp.scheduled_processes scheduler = Scheduler(_address, _m_port) await scheduler.start() # declare _scheduler task interval_schedule = IntervalSchedule() interval_schedule.name = 'get_tasks' interval_schedule.process_name = "sleep5" interval_schedule.repeat = datetime.timedelta(seconds=1) interval_schedule.exclusive = False await scheduler.save_schedule(interval_schedule) await asyncio.sleep(15) # Assert running tasks tasks = await scheduler.get_tasks( where=["state", "=", int(Task.State.INTERRUPTED)]) assert not tasks tasks = await scheduler.get_tasks(where=["end_time", "=", 'NULL']) assert tasks tasks = await scheduler.get_tasks(limit=50) states = [int(task.state) for task in tasks] assert len(tasks) > 1 assert int(Task.State.RUNNING) in states assert int(Task.State.COMPLETE) in states tasks = await scheduler.get_tasks(1) assert len(tasks) == 1 tasks = await scheduler.get_tasks( where=["state", "=", int(Task.State.RUNNING)], sort=[["state", "desc"]], offset=50) assert not tasks tasks = await scheduler.get_tasks( where=["state", "=", int(Task.State.RUNNING)], sort=[["state", "desc"], ["start_time", "asc"]]) assert tasks tasks = await scheduler.get_tasks(or_where_list=[["state", "=", int(Task.State.RUNNING)], \ ["state", "=", int(Task.State.RUNNING)]]) assert tasks tasks = await scheduler.get_tasks(and_where_list=[["state", "=", int(Task.State.RUNNING)], \ ["state", "=", int(Task.State.RUNNING)]]) assert tasks await self.stop_scheduler(scheduler)