test_tiaf_unit_tests.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. from logging import getLogger
  9. from typing import Counter
  10. from test_impact import NativeTestImpact, BaseTestImpact, PythonTestImpact
  11. from tiaf_driver import SUPPORTED_RUNTIMES
  12. from tiaf_driver import main
  13. import pytest
  14. logging = getLogger("tiaf")
  15. def assert_list_content_equal(list1, list2):
  16. assert Counter(list1) == Counter(list2)
  17. class ConcreteBaseTestImpact(BaseTestImpact):
  18. """
  19. Concrete implementation of BaseTestImpact so that we can execute unit tests on the functionality of BaseTestImpact individually.
  20. """
  21. _runtime_type = "native"
  22. _default_sequence_type = "regular"
  23. @property
  24. def default_sequence_type(self):
  25. """
  26. Returns default sequence type, defaulting to "regular" for this example.
  27. """
  28. return self._default_sequence_type
  29. @property
  30. def runtime_type(self):
  31. return self._runtime_type
  32. class TestTiafDriver():
  33. def test_run_Tiaf_mars_index_prefix_is_supplied(self, caplog, main_args, skip_if_test_targets_disabled, mock_runtime, mocker):
  34. # given:
  35. # Default args + mars_index_prefix being provided,
  36. # and transmit_report_to_mars is patched to intercept the call.
  37. main_args['mars_index_prefix'] = "test_prefix"
  38. mock_mars = mocker.patch("mars_utils.transmit_report_to_mars")
  39. # when:
  40. # We run Tiaf through the driver.
  41. with pytest.raises(SystemExit):
  42. main(main_args)
  43. # then:
  44. # Tiaf should call the transmit function.
  45. mock_mars.assert_called()
  46. def test_run_Tiaf_mars_index_prefix_is_not_supplied(self, caplog, main_args, skip_if_test_targets_disabled, mock_runtime, mocker):
  47. # given:
  48. # Default_args - mars index is not supplied.
  49. mock_mars = mocker.patch("mars_utils.transmit_report_to_mars")
  50. # when:
  51. # We run tiaf through the driver.
  52. with pytest.raises(SystemExit):
  53. main(main_args)
  54. # then:
  55. # Tiaf should not call the transmit function.
  56. mock_mars.assert_not_called()
  57. @pytest.mark.parametrize("runtime_type,mock_type", [("native", "test_impact.native_test_impact.NativeTestImpact.__init__"), ("python", "test_impact.python_test_impact.PythonTestImpact.__init__")])
  58. def test_run_Tiaf_driver_runtime_type_selection(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, mocker, runtime_type, mock_type):
  59. # given:
  60. # Default args + runtime_type
  61. tiaf_args['runtime_type'] = runtime_type
  62. runtime_class = SUPPORTED_RUNTIMES[runtime_type]
  63. testMock = mocker.patch(mock_type, side_effect=SystemExit)
  64. # when
  65. # We run tiaf through the driver.
  66. with pytest.raises(SystemExit):
  67. main(tiaf_args)
  68. # then:
  69. # Tiaf should instantiate a TestImpact object of the correct runtime type
  70. testMock.assert_called()
  71. class TestTiafInitialiseStorage():
  72. def skip_if_test_targets_disabled(skip_if_test_targets_disabled):
  73. if not skip_if_test_targets_disabled:
  74. pytest.skip("Test targets are disabled for this runtime, test will be skipped.")
  75. @pytest.fixture
  76. def runtime_type(self):
  77. """
  78. Override the runtime_type fixture so that only native tests run in this example, as we have hardcoded ConcreteBaseTestImpact to act as a NativeTestImpact object in many cases.
  79. """
  80. return "native"
  81. def to_list(self, input):
  82. output = []
  83. for entry in input:
  84. output.append(entry)
  85. return output
  86. def test_create_TestImpact_no_s3_bucket_name(self, caplog, tiaf_args, skip_if_test_targets_disabled, config_data, mocker, storage_config):
  87. # given:
  88. # Default args.
  89. expected_storage_args = config_data, tiaf_args['suites'], tiaf_args[
  90. 'commit'], storage_config['active_workspace'], storage_config['unpacked_coverage_data_file'], storage_config['previous_test_run_data_file'], storage_config['historic_workspace'], storage_config['historic_data_file'], storage_config['temp_workspace']
  91. mock_local = mocker.patch(
  92. "persistent_storage.PersistentStorageLocal.__init__", side_effect=SystemError(), return_value=None)
  93. # when:
  94. # We create a TestImpact object.
  95. tiaf = ConcreteBaseTestImpact(tiaf_args)
  96. # then:
  97. # PersistentStorageLocal should be called with suites, commit and config data as arguments.
  98. assert_list_content_equal(self.to_list(mock_local.call_args[0]).pop(), self.to_list(expected_storage_args).pop())
  99. @pytest.mark.parametrize("bucket_name,top_level_dir,expected_top_level_dir", [("test_bucket", "test_dir", "test_dir/native")])
  100. def test_create_TestImpact_s3_bucket_name_supplied(self, caplog, tiaf_args, skip_if_test_targets_disabled, mocker, bucket_name, top_level_dir, config_data, expected_top_level_dir, storage_config):
  101. # given:
  102. # Default arguments + s3_bucket and s3_top_level_dir being set to the above parameters,
  103. # and we patch PersistentStorageS3 to intercept the constructor call.
  104. tiaf_args['s3_bucket'] = bucket_name
  105. tiaf_args['s3_top_level_dir'] = top_level_dir
  106. mock_storage = mocker.patch(
  107. "persistent_storage.PersistentStorageS3.__init__", side_effect=SystemError())
  108. expected_storage_args = config_data, tiaf_args['suites'], tiaf_args[
  109. 'commit'], bucket_name, expected_top_level_dir, tiaf_args['src_branch'], storage_config['active_workspace'], storage_config['unpacked_coverage_data_file'], storage_config['previous_test_run_data_file'], storage_config['temp_workspace']
  110. # when:
  111. # We create a TestImpact object.
  112. tiaf = ConcreteBaseTestImpact(tiaf_args)
  113. # then:
  114. # PersistentStorageS3 should be called with config data, commit, bucket_name, top_level_dir and src branch as arguments.
  115. mock_storage.assert_called_with(*expected_storage_args)
  116. def test_create_TestImpact_s3_bucket_name_not_supplied(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, mocker, config_data):
  117. # given:
  118. # Default arguments + s3_bucket and s3_top_level_dir arguments set to none.
  119. tiaf_args['s3_bucket'] = None
  120. tiaf_args['s3_top_level_dir'] = None
  121. mock_storage = mocker.patch(
  122. "persistent_storage.PersistentStorageS3.__init__", return_value=None)
  123. # when:
  124. # We create a TestImpact object.
  125. tiaf = ConcreteBaseTestImpact(tiaf_args)
  126. # then:
  127. # PersistentStorageS3 should not be called.
  128. mock_storage.assert_not_called()
  129. def test_create_TestImpact_s3_top_level_dir_bucket_name_not_supplied(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, mocker, config_data):
  130. # given:
  131. # Default arguments + s3_bucket set to none and s3_top_level_dir is defined.
  132. tiaf_args['s3_bucket'] = None
  133. tiaf_args['s3_top_level_dir'] = "test_dir"
  134. mock_storage = mocker.patch(
  135. "persistent_storage.PersistentStorageS3.__init__", return_value=None)
  136. # when:
  137. # We create a TestImpact object.
  138. tiaf = ConcreteBaseTestImpact(tiaf_args)
  139. # then:
  140. # PersistentStorageS3 should not be called.
  141. mock_storage.assert_not_called()
  142. class TestTIAFNativeUnitTests():
  143. def skip_if_test_targets_disabled(skip_if_test_targets_disabled):
  144. if not skip_if_test_targets_disabled:
  145. pytest.skip("Test targets are disabled for this runtime, test will be skipped.")
  146. @pytest.fixture
  147. def runtime_type(self):
  148. """
  149. Override the runtime_type fixture so that only native versions of the tests run in this example, as we are only testing the NativeTestImpact class.
  150. """
  151. return "native"
  152. @pytest.mark.parametrize("safemode, arg_val", [("on", "on")])
  153. def test_create_NativeTestImpact_safe_mode_arguments(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, cpp_default_runtime_args, safemode, arg_val):
  154. # given:
  155. # Default args + safe_mode set.
  156. tiaf_args['safe_mode'] = safemode
  157. cpp_default_runtime_args['safemode'] = "--safemode="+arg_val
  158. # when:
  159. # We create a TestImpact object.
  160. tiaf = NativeTestImpact(tiaf_args)
  161. # then:
  162. # tiaf.runtime_args should equal expected args.
  163. assert_list_content_equal(
  164. tiaf.runtime_args, cpp_default_runtime_args.values())
  165. def test_create_NativeTestImpact_no_safe_mode(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, cpp_default_runtime_args):
  166. # given:
  167. # Default args + safe_mode set.
  168. # when:
  169. # We create a TestImpact object.
  170. tiaf = NativeTestImpact(tiaf_args)
  171. # then:
  172. # tiaf.runtime_args should equal expected args.
  173. assert_list_content_equal(
  174. tiaf.runtime_args, cpp_default_runtime_args.values())
  175. @pytest.mark.parametrize("bucket_name,top_level_dir,expected_top_level_dir", [("test_bucket", "test_dir", "test_dir/native")])
  176. def test_create_NativeTestImpact_correct_s3_dir_runtime_type(self, config_data, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, cpp_default_runtime_args, mocker, bucket_name, storage_config, top_level_dir, expected_top_level_dir):
  177. # given:
  178. # Default args + s3_bucket and s3_top_level_dir set
  179. tiaf_args['s3_bucket'] = bucket_name
  180. tiaf_args['s3_top_level_dir'] = top_level_dir
  181. mock_storage = mocker.patch(
  182. "persistent_storage.PersistentStorageS3.__init__", side_effect=SystemError())
  183. expected_storage_args = config_data, tiaf_args['suites'], tiaf_args[
  184. 'commit'], bucket_name, expected_top_level_dir, tiaf_args['src_branch'], storage_config['active_workspace'], storage_config['unpacked_coverage_data_file'], storage_config['previous_test_run_data_file'], storage_config['temp_workspace']
  185. # when:
  186. # We create a NativeTestImpact object
  187. tiaf = NativeTestImpact(tiaf_args)
  188. # then:
  189. # PersistentStorageS3.__init__ should be called with config data, suites, commit, bucket_name, modified top level dir and src_branch as arguments
  190. mock_storage.assert_called_with(*expected_storage_args)
  191. class TestTIAFPythonUnitTests():
  192. def skip_if_test_targets_disabled(skip_if_test_targets_disabled):
  193. if not skip_if_test_targets_disabled:
  194. pytest.skip("Test targets are disabled for this runtime, test will be skipped.")
  195. @pytest.fixture
  196. def runtime_type(self):
  197. """
  198. Override the runtime_type fixture so that only python versions of the tests run in this example, as we are only testing the PythonTestImpact class.
  199. """
  200. return "python"
  201. #@pytest.mark.skip(reason="To fix before PR")
  202. @pytest.mark.parametrize("bucket_name,top_level_dir,expected_top_level_dir", [("test_bucket", "test_dir", "test_dir/python")])
  203. def test_create_PythonTestImpact_correct_s3_dir_runtime_type(self, config_data, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, mocker, bucket_name, top_level_dir, expected_top_level_dir, storage_config):
  204. # given:
  205. # Default args + s3_bucket and s3_top_level_dir set
  206. tiaf_args['s3_bucket'] = bucket_name
  207. tiaf_args['s3_top_level_dir'] = top_level_dir
  208. mock_storage = mocker.patch(
  209. "persistent_storage.PersistentStorageS3.__init__", side_effect=SystemError())
  210. expected_storage_args = config_data, tiaf_args['suites'], tiaf_args[
  211. 'commit'], bucket_name, expected_top_level_dir, tiaf_args['src_branch'], storage_config['active_workspace'], storage_config['unpacked_coverage_data_file'], storage_config['previous_test_run_data_file'], storage_config['temp_workspace']
  212. # when:
  213. # We create a PythonTestImpact object
  214. tiaf = PythonTestImpact(tiaf_args)
  215. # then:
  216. # PersistentStorageS3.__init__ should be called with config data, suites, commit, bucket_name, modified top level dir and src_branch as arguments
  217. mock_storage.assert_called_with(*expected_storage_args)
  218. class TestTIAFBaseUnitTests():
  219. @pytest.fixture
  220. def runtime_type(self):
  221. """
  222. Override the runtime_type fixture so that only native tests run in this example, as we have hardcoded ConcreteBaseTestImpact to act as a NativeTestImpact object in many cases.
  223. """
  224. return "native"
  225. def test_create_TestImpact_valid_config(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, mocker, default_runtime_args):
  226. """
  227. Given default arguments, when we create a TestImpact object, tiaf.runtime_args should be eqaul
  228. """
  229. # given:
  230. # Default arguments.
  231. # when:
  232. # We create a TestImpact object.
  233. tiaf = ConcreteBaseTestImpact(tiaf_args)
  234. # then:
  235. # tiaf.runtime_args should equal our default_runtime_args values.
  236. assert_list_content_equal(
  237. tiaf.runtime_args, default_runtime_args.values())
  238. def test_create_TestImpact_invalid_config(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, tmp_path_factory, default_runtime_args):
  239. # given:
  240. # Invalid config file at invalid_file,
  241. # and setting tiaf_args.config to that path.
  242. invalid_file = tmp_path_factory.mktemp("test") / "test_file.txt"
  243. with open(invalid_file, 'w+') as f:
  244. f.write("fake json")
  245. tiaf_args['config'] = invalid_file
  246. # when:
  247. # We create a TestImpact object.
  248. # then:
  249. # It should raise a SystemError.
  250. with pytest.raises(SystemError) as exc_info:
  251. tiaf = ConcreteBaseTestImpact(tiaf_args)
  252. @ pytest.mark.parametrize("branch_name", ["development", "not_a_real_branch"])
  253. def test_create_TestImpact_src_branch(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, branch_name):
  254. # given:
  255. # Default args + src_branch set to branch_name.
  256. tiaf_args['src_branch'] = branch_name
  257. # when:
  258. # We create a TestImpact object.
  259. tiaf = ConcreteBaseTestImpact(tiaf_args)
  260. # then:
  261. # tiaf.source_branch shoudl equal our branch name.
  262. assert tiaf.source_branch == branch_name
  263. @ pytest.mark.parametrize("branch_name", ["development", "not_a_real_branch"])
  264. def test_create_TestImpact_dst_branch(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, branch_name):
  265. # given:
  266. # Default args + dst_branch set to branch_name.
  267. tiaf_args['dst_branch'] = branch_name
  268. # when:
  269. # We create a TestImpact object.
  270. tiaf = ConcreteBaseTestImpact(tiaf_args)
  271. # then:
  272. # tiaf.destination_branch should equal our branch name parameter.
  273. assert tiaf.destination_branch == branch_name
  274. @ pytest.mark.parametrize("commit", ["9a15f038807ba8b987c9e689952d9271ef7fd086", "foobar"])
  275. def test_create_TestImpact_commit(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, commit):
  276. # given:
  277. # Default args + commit set to commit.
  278. tiaf_args['commit'] = commit
  279. # when:
  280. # We create a TestImpact object.
  281. tiaf = ConcreteBaseTestImpact(tiaf_args)
  282. # then:
  283. # tiaf.destination_commit should equal our commit parameter.
  284. assert tiaf.destination_commit == commit
  285. def test_create_TestImpact_valid_test_suite_name(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args):
  286. # given:
  287. # Default args
  288. # when:
  289. # We create a TestImpact object.
  290. tiaf = ConcreteBaseTestImpact(tiaf_args)
  291. # then:
  292. # tiaf.runtime_args should equal our default_runtime_ars.
  293. assert_list_content_equal(
  294. tiaf.runtime_args, default_runtime_args.values())
  295. def test_create_TestImpact_invalid_test_suite_name(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args):
  296. # given:
  297. # Default args + suites defined as "foobar" in given args and expected args.
  298. tiaf_args['suites'] = "foobar"
  299. default_runtime_args['suites'] = "--suites=foobar"
  300. # when:
  301. # We create a TestImpact object.
  302. tiaf = ConcreteBaseTestImpact(tiaf_args)
  303. # then:
  304. # tiaf.runtime_args should equal expected args.
  305. assert_list_content_equal(
  306. tiaf.runtime_args, default_runtime_args.values())
  307. @ pytest.mark.parametrize("policy", ["continue", "abort", "ignore"])
  308. def test_create_TestImpact_valid_failure_policy(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args, policy):
  309. # given:
  310. # Default args + test_failure_policy set to policy parameter.
  311. tiaf_args['test_failure_policy'] = policy
  312. default_runtime_args['test_failure_policy'] = "--fpolicy="+policy
  313. # Set src branch to be different than dst branch so that we can get a regular sequence, otherwise TIAF will default to a see sequence and overwrite our failure policy.
  314. tiaf_args['src_branch'] = 122
  315. default_runtime_args['sequence'] = "--sequence=regular"
  316. default_runtime_args['ipolicy'] = "--ipolicy=continue"
  317. # when:
  318. # We create a TestImpact object.
  319. tiaf = ConcreteBaseTestImpact(tiaf_args)
  320. # then:
  321. # tiaf.runtime_args should equal expected args.
  322. assert_list_content_equal(
  323. tiaf.runtime_args, default_runtime_args.values())
  324. def test_create_TestImpact_exclude_file_not_supplied(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args):
  325. # given:
  326. # Default args.
  327. # when:
  328. # We create a TestImpact object.
  329. tiaf = ConcreteBaseTestImpact(tiaf_args)
  330. # then:
  331. # tiaf.runtime_args should equal expected args.
  332. assert_list_content_equal(
  333. tiaf.runtime_args, default_runtime_args.values())
  334. def test_create_TestImpact_exclude_file_supplied(self, caplog, tiaf_args, skip_if_test_targets_disabled, mock_runtime, default_runtime_args):
  335. # given:
  336. # Default args + exclude_file set.
  337. tiaf_args['exclude_file'] = "testpath"
  338. default_runtime_args['exclude'] = "--excluded=testpath"
  339. # when:
  340. # We create a TestImpact object.
  341. tiaf = ConcreteBaseTestImpact(tiaf_args)
  342. # then:
  343. # tiaf.runtime_args should equal expected args.
  344. assert_list_content_equal(
  345. tiaf.runtime_args, default_runtime_args.values())