77from enum import Enum
88from typing import (TYPE_CHECKING , Callable , Dict , Iterable , List , Optional ,
99 Sequence , Tuple , cast )
10+ from unittest import mock
11+ from unittest .mock import Mock , call , patch
1012
1113import pytest
1214import requests
@@ -244,11 +246,13 @@ def assert_regex(regex: str, string: str) -> None:
244246 assert re .match (regex , string ) is not None , f'`{ string } ` does not match regex { regex } '
245247
246248
249+ @patch .multiple ('time' , sleep = mock .DEFAULT )
247250@requests_mock .Mocker (case_sensitive = True , kw = 'requests_mocker' )
248251def run_test_case (
249252 pytester : pytest .Pytester ,
250- manifest : _api .TestSuiteManifest ,
253+ manifest : Optional [ _api .TestSuiteManifest ] ,
251254 requests_mocker : requests_mock .Mocker ,
255+ sleep : Mock ,
252256 expected_test_file_outcomes : List [
253257 Tuple [str , List [Tuple [Tuple [str , ...], List [_TestAttemptOutcome ]]]]],
254258 expected_test_result_counts : _TestResultCounts ,
@@ -265,14 +269,21 @@ def run_test_case(
265269 env_vars : Optional [Dict [str , str ]] = None ,
266270 expect_progress : bool = True ,
267271 expect_xdist : bool = False ,
272+ failed_manifest_requests : int = 0 ,
273+ failed_upload_requests : int = 0 ,
268274) -> None :
269275 api_key_path = pytester .makefile ('' , expected_api_key ) if use_api_key_path else None
270276 requests_mocker .get (
271277 url = 'https://app.unflakable.com/api/v1/test-suites/MOCK_SUITE_ID/manifest' ,
272278 request_headers = {'Authorization' : f'Bearer { expected_api_key } ' },
273279 complete_qs = True ,
274- status_code = 200 ,
275- json = manifest ,
280+ response_list = [
281+ {'exc' : requests .exceptions .ConnectTimeout }
282+ for _ in range (failed_manifest_requests )
283+ ] + ([{
284+ 'status_code' : 200 ,
285+ 'json' : manifest ,
286+ }] if manifest is not None else [])
276287 )
277288
278289 requests_mocker .post (
@@ -282,8 +293,13 @@ def run_test_case(
282293 'Content-Type' : 'application/json' ,
283294 },
284295 complete_qs = True ,
285- status_code = 201 ,
286- json = mock_create_test_suite_run_response ,
296+ response_list = [
297+ {'exc' : requests .exceptions .ConnectTimeout }
298+ for _ in range (failed_upload_requests )
299+ ] + [{
300+ 'status_code' : 201 ,
301+ 'json' : mock_create_test_suite_run_response ,
302+ }]
287303 )
288304
289305 pytest_args : List [str ] = (
@@ -483,42 +499,82 @@ def run_test_case(
483499 expected_test_result_counts .non_skipped_tests > 0 ) else [])
484500 )
485501
486- assert requests_mocker .call_count == (
487- (
488- 2 if expected_uploaded_test_runs is not None and (
489- expected_test_result_counts .non_skipped_tests > 0 ) else 1
490- ) if plugin_enabled else 0
491- )
502+ if plugin_enabled :
503+ expected_get_test_suite_manifest_attempts = (
504+ failed_manifest_requests + (1 if failed_manifest_requests <
505+ _api .NUM_REQUEST_TRIES and manifest is not None else 0 )
506+ )
507+ for manifest_attempt in range (expected_get_test_suite_manifest_attempts ):
508+ request = requests_mocker .request_history [manifest_attempt ]
492509
493- # Checked expected User-Agent. We do this here instead of using an `additional_matcher` to make
494- # errors easier to diagnose.
495- for request in requests_mocker .request_history :
496- assert_regex (
497- r'^unflakable-pytest-plugin/.* \(PyTest .*; Python .*; Platform .*\)$' ,
498- request .headers .get ('User-Agent' , '' )
510+ assert request .url == (
511+ 'https://app.unflakable.com/api/v1/test-suites/MOCK_SUITE_ID/manifest'
512+ )
513+ assert request .method == 'GET'
514+ assert request .body is None
515+
516+ if manifest_attempt > 0 :
517+ assert (
518+ sleep .call_args_list [manifest_attempt - 1 ] == call (2 ** (manifest_attempt - 1 ))
519+ )
520+
521+ expected_upload_attempts = (
522+ failed_upload_requests + (1 if (
523+ failed_upload_requests < _api .NUM_REQUEST_TRIES
524+ and expected_uploaded_test_runs is not None
525+ and expected_test_result_counts .non_skipped_tests != 0
526+ ) else 0 )
499527 )
500528
501- if plugin_enabled and (
502- expected_uploaded_test_runs is not None and
503- expected_test_result_counts .non_skipped_tests > 0 ):
504- create_test_suite_run_request = requests_mocker .request_history [1 ]
505- assert create_test_suite_run_request .url == (
506- 'https://app.unflakable.com/api/v1/test-suites/MOCK_SUITE_ID/runs' )
507- assert create_test_suite_run_request .method == 'POST'
529+ for upload_attempt in range (expected_upload_attempts ):
530+ create_test_suite_run_request = requests_mocker .request_history [
531+ expected_get_test_suite_manifest_attempts + upload_attempt
532+ ]
533+ assert create_test_suite_run_request .url == (
534+ 'https://app.unflakable.com/api/v1/test-suites/MOCK_SUITE_ID/runs' )
535+ assert create_test_suite_run_request .method == 'POST'
536+
537+ create_test_suite_run_body : _api .CreateTestSuiteRunRequest = (
538+ create_test_suite_run_request .json ()
539+ )
508540
509- create_test_suite_run_body : _api .CreateTestSuiteRunRequest = (
510- create_test_suite_run_request .json ()
541+ if expected_commit is not None :
542+ assert create_test_suite_run_body ['commit' ] == expected_commit
543+ else :
544+ assert 'commit' not in create_test_suite_run_body
545+
546+ if expected_branch is not None :
547+ assert create_test_suite_run_body ['branch' ] == expected_branch
548+ else :
549+ assert 'branch' not in create_test_suite_run_body
550+
551+ if upload_attempt > 0 :
552+ assert (
553+ sleep .call_args_list [
554+ max (expected_get_test_suite_manifest_attempts - 1 , 0 ) +
555+ upload_attempt - 1
556+ ] == call (2 ** (upload_attempt - 1 ))
557+ )
558+
559+ assert requests_mocker .call_count == (
560+ expected_get_test_suite_manifest_attempts + expected_upload_attempts
561+ ), 'Expected %d total API requests, but received %d' % (
562+ expected_get_test_suite_manifest_attempts + expected_upload_attempts ,
563+ requests_mocker .call_count ,
511564 )
512565
513- if expected_commit is not None :
514- assert create_test_suite_run_body [ 'commit' ] == expected_commit
515- else :
516- assert 'commit' not in create_test_suite_run_body
566+ # Checked expected User-Agent. We do this here instead of using an `additional_matcher` to
567+ # make errors easier to diagnose.
568+ for request in requests_mocker . request_history :
569+ assert request . headers . get ( 'Authorization' , '' ) == f'Bearer { expected_api_key } '
517570
518- if expected_branch is not None :
519- assert create_test_suite_run_body ['branch' ] == expected_branch
520- else :
521- assert 'branch' not in create_test_suite_run_body
571+ assert_regex (
572+ r'^unflakable-pytest-plugin/.* \(PyTest .*; Python .*; Platform .*\)$' ,
573+ request .headers .get ('User-Agent' , '' )
574+ )
575+ else :
576+ assert requests_mocker .call_count == 0
577+ assert sleep .call_count == 0
522578
523579 assert result .ret == expected_exit_code , (
524580 f'expected exit code { expected_exit_code } , but got { result .ret } ' )
0 commit comments