def test_RunnableQueue_duplicate_jobs(mocker): """ Verify that duplicate jobs are not added to the queue. """ mock_api_client = mocker.MagicMock() mock_session = mocker.MagicMock() mock_session_maker = mocker.MagicMock(return_value=mock_session) dl_job = FileDownloadJob("mock", "mock", "mock") msg_dl_job = MessageDownloadJob("mock", "mock", "mock") queue = RunnableQueue(mock_api_client, mock_session_maker) debug_logger = mocker.patch("securedrop_client.queue.logger.debug") # Queue begins empty (0 entries). assert len(queue.queue.queue) == 0 queue.add_job(dl_job) assert len(queue.queue.queue) == 1 # Now add the same job again. queue.add_job(dl_job) assert len(queue.queue.queue) == 1 log_msg = "Duplicate job {}, skipping".format(dl_job) debug_logger.call_args[1] == log_msg # Now add a _different_ job with the same arguments (same uuid). queue.add_job(msg_dl_job) assert len(queue.queue.queue) == 2 # Ensure that using _re_add_job in the case of a timeout won't allow duplicate # jobs to be added. with queue.condition_add_or_remove_job: queue._re_add_job(msg_dl_job) assert len(queue.queue.queue) == 2
def test_RunnableQueue_job_timeout(mocker, exception): ''' Add two jobs to the queue. The first times out, and then gets resubmitted for the next pass through the loop. ''' queue = RunnableQueue(mocker.MagicMock(), mocker.MagicMock()) queue.pause = mocker.MagicMock() job_cls = factory.dummy_job_factory(mocker, exception(), remaining_attempts=5) job1 = job_cls() job2 = job_cls() queue.JOB_PRIORITIES = {PauseQueueJob: 0, job_cls: 1} # RequestTimeoutError or ServerConnectionError will cause the queue to pause, # use our fake pause method instead def fake_pause() -> None: queue.add_job(PauseQueueJob()) queue.pause.emit = fake_pause # Add two jobs that timeout during processing to the queue queue.add_job(job1) queue.add_job(job2) # attempt to process job1 knowing that it times out queue.process() assert queue.queue.qsize() == 2 # queue contains: job1, job2 # now process after making it so job1 no longer times out job1.return_value = 'mock' queue.process() assert queue.queue.qsize() == 1 # queue contains: job2 assert queue.queue.get(block=True) == (1, job2)
def test_RunnableQueue_resubmitted_jobs(mocker): ''' Verify that jobs that fail due to timeout are resubmitted without modifying order_number. ''' mock_api_client = mocker.MagicMock() mock_session = mocker.MagicMock() mock_session_maker = mocker.MagicMock(return_value=mock_session) job_cls_high_priority = factory.dummy_job_factory(mocker, 'mock') job_cls_low_priority = factory.dummy_job_factory(mocker, 'mock') queue = RunnableQueue(mock_api_client, mock_session_maker) queue.JOB_PRIORITIES = {job_cls_high_priority: 1, job_cls_low_priority: 2} job1 = job_cls_high_priority() job2 = job_cls_low_priority() job3 = job_cls_high_priority() job4 = job_cls_low_priority() queue.add_job(job1) queue.add_job(job2) queue.add_job(job3) queue.add_job(job4) # Expected order of execution is job1 -> job3 -> job2 -> job4 assert queue.queue.get(block=True) == (1, job1) # Now resubmit job1 via put_nowait. It should execute prior to job2-4. queue.re_add_job(job1) assert queue.queue.get(block=True) == (1, job1) assert queue.queue.get(block=True) == (1, job3) assert queue.queue.get(block=True) == (2, job2) assert queue.queue.get(block=True) == (2, job4)
def test_RunnableQueue_high_priority_jobs_run_first_and_in_fifo_order(mocker): mock_api_client = mocker.MagicMock() mock_session = mocker.MagicMock() mock_session_maker = mocker.MagicMock(return_value=mock_session) return_value = 'wat' job_cls_high_priority = factory.dummy_job_factory(mocker, return_value) job_cls_low_priority = factory.dummy_job_factory(mocker, return_value) queue = RunnableQueue(mock_api_client, mock_session_maker) queue.JOB_PRIORITIES = {job_cls_high_priority: 1, job_cls_low_priority: 2} job1 = job_cls_high_priority() job2 = job_cls_low_priority() job3 = job_cls_high_priority() job4 = job_cls_low_priority() queue.add_job(job1) queue.add_job(job2) queue.add_job(job3) queue.add_job(job4) # Expected order of execution is job1 -> job3 -> job2 -> job4 assert queue.queue.get(block=True) == (1, job1) assert queue.queue.get(block=True) == (1, job3) assert queue.queue.get(block=True) == (2, job2) assert queue.queue.get(block=True) == (2, job4)
def test_RunnableQueue_process_PauseQueueJob(mocker): api_client = mocker.MagicMock() session_maker = mocker.MagicMock(return_value=mocker.MagicMock()) queue = RunnableQueue(api_client, session_maker) queue.JOB_PRIORITIES = {PauseQueueJob: 11} queue.add_job(PauseQueueJob()) queue.process() assert queue.queue.empty()
def test_RunnableQueue_init(mocker): mock_api_client = mocker.MagicMock() mock_session_maker = mocker.MagicMock() mocker.patch('securedrop_client.queue.RunnableQueue.resume', return_value=mocker.MagicMock()) queue = RunnableQueue(mock_api_client, mock_session_maker) assert queue.api_client == mock_api_client assert queue.session_maker == mock_session_maker assert isinstance(queue.queue, Queue) assert queue.queue.empty() queue.resume.connect.assert_called_once_with(queue.process)
def test_RunnableQueue_happy_path(mocker): ''' Add one job to the queue, run it. ''' mock_api_client = mocker.MagicMock() mock_session = mocker.MagicMock() mock_session_maker = mocker.MagicMock(return_value=mock_session) return_value = 'foo' dummy_job_cls = factory.dummy_job_factory(mocker, return_value) queue = RunnableQueue(mock_api_client, mock_session_maker) queue.JOB_PRIORITIES = {dummy_job_cls: 1, PauseQueueJob: 2} queue.add_job(dummy_job_cls()) queue.add_job( PauseQueueJob()) # Pause queue so our test exits the processing loop queue.process() assert queue.queue.empty()
def test_RunnableQueue_job_generic_exception(mocker): ''' Add two jobs to the queue, the first of which will cause a generic exception, which is handled in _do_call_api. Ensure that the queue continues processing jobs after dropping a job that runs into a generic exception. ''' job1_cls = factory.dummy_job_factory(mocker, Exception()) # processing skips job job2_cls = factory.dummy_job_factory(mocker, 'mock') job1 = job1_cls() job2 = job2_cls() queue = RunnableQueue(mocker.MagicMock(), mocker.MagicMock()) queue.JOB_PRIORITIES = {PauseQueueJob: 3, job1_cls: 2, job2_cls: 2} queue.add_job(job1) queue.add_job(job2) queue.add_job( PauseQueueJob()) # Pause queue so our test exits the processing loop queue.process() # check that all jobs are gone assert queue.queue.empty()
def test_RunnableQueue_does_not_run_jobs_when_not_authed(mocker): ''' Check that a job that sees an ApiInaccessibleError does not get resubmitted since it is not authorized and that its api_client is None. ''' queue = RunnableQueue(mocker.MagicMock(), mocker.MagicMock()) job_cls = factory.dummy_job_factory(mocker, ApiInaccessibleError()) queue.JOB_PRIORITIES = {PauseQueueJob: 0, job_cls: 1} # Add a job that results in an ApiInaccessibleError to the queue job = job_cls() queue.add_job(job) # attempt to process job knowing that it errors queue.process() assert queue.queue.qsize( ) == 0 # queue should not contain job since it was not resubmitted assert queue.api_client is None
def test_RunnableQueue_does_not_run_jobs_when_not_authed(mocker): ''' Add a job to the queue, ensure we don't run it when not authenticated. ''' queue = RunnableQueue(mocker.MagicMock(), mocker.MagicMock()) queue.pause = mocker.MagicMock() job_cls = factory.dummy_job_factory(mocker, ApiInaccessibleError()) queue.JOB_PRIORITIES = {PauseQueueJob: 0, job_cls: 1} # ApiInaccessibleError will cause the queue to pause, use our fake pause method instead def fake_pause() -> None: queue.add_job(PauseQueueJob()) queue.pause.emit = fake_pause # Add a job that results in an ApiInaccessibleError to the queue job = job_cls() queue.add_job(job) # attempt to process job1 knowing that it times out queue.process() assert queue.queue.qsize() == 1 # queue contains: job1