test_codeowners_hint.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. Unit tests for ly_test_tools.cli.codeowners_hint
  6. """
  7. import pathlib
  8. import pytest
  9. import ly_test_tools.cli.codeowners_hint as hint
  10. import unittest.mock as mock
  11. _DUMMY_REPO_PATH = "/home/myuser/github/myrepo"
  12. _DUMMY_CODEOWNERS_PATH = _DUMMY_REPO_PATH + "/.github/CODEOWNERS"
  13. _dummy_target_paths = [
  14. ".",
  15. "relative/path",
  16. "/absolute/path",
  17. "/some/fairly/long/path/which/eventually/ends/in/a/directory/a/s/d/f/g/h/j/k/l",
  18. "/some/fairly/long/path/which/eventually/ends/in/a/file/a/s/d/f/g/h/j/k/l/dummy.txt"
  19. "C:\\github\\o3de\\Tools\\LyTestTools\\tests\\unit\\test_codeowners_hint.py",
  20. "/home/myuser/github/o3de/Tools/LyTestTools/tests/unit/test_codeowners_hint.py",
  21. ]
  22. @mock.patch("os.path.exists")
  23. @pytest.mark.parametrize("target_path", _dummy_target_paths)
  24. def test_FindCodeowners_MockExistsInRepo_Found(mock_exists, target_path):
  25. mock_exists.side_effect = [False, True] # found in parent directory
  26. codeowners_path = hint.find_github_codeowners(target_path)
  27. assert codeowners_path
  28. mock_exists.assert_called()
  29. @mock.patch("os.path.exists")
  30. @pytest.mark.parametrize("target_path", _dummy_target_paths)
  31. def test_FindCodeowners_MockNotInRepo_NotFound(mock_exists, target_path):
  32. mock_exists.return_value = False # never found
  33. codeowners_path = hint.find_github_codeowners(target_path)
  34. assert not codeowners_path
  35. mock_exists.assert_called()
  36. def _assert_owners_from(target_relative_path, mock_ownership_data, expected_path_match, expected_alias_match):
  37. """
  38. :param target_relative_path: string path relative to root of repo, starting with slash
  39. :param mock_ownership_data: string contents representing a CODEOWNERS file
  40. :param expected_path_match: expected line's path-value to match
  41. :param expected_alias_match: expected line's alias-value to match
  42. """
  43. with mock.patch('builtins.open', mock.mock_open(read_data=mock_ownership_data)):
  44. path, alias = hint.get_codeowners_from(pathlib.Path(_DUMMY_REPO_PATH + target_relative_path),
  45. pathlib.Path(_DUMMY_CODEOWNERS_PATH))
  46. assert path == expected_path_match
  47. assert alias == expected_alias_match
  48. @mock.patch("os.path.isfile", mock.MagicMock(return_value=True))
  49. @mock.patch("os.path.getsize", mock.MagicMock(return_value=1))
  50. class TestGetOwnersFrom:
  51. def test_ExactMatch_OneExactMatch_Matched(self):
  52. target = "/some/path/to/my/file"
  53. ownership_data = target + " @alias\n"
  54. _assert_owners_from(target, ownership_data, target, "@alias")
  55. @pytest.mark.parametrize("target_path,codeowners_path", [
  56. (pathlib.PurePosixPath("/some/owned/unix/path"), pathlib.PurePosixPath("/some/.github/CODEOWNERS")),
  57. (pathlib.PurePosixPath("/some/owned/unix/.dot/path"), pathlib.PurePosixPath("/some/.github/CODEOWNERS")),
  58. (pathlib.PureWindowsPath("\\some\\owned\\windows\\path"), pathlib.PureWindowsPath("\\some\\.github\\CODEOWNERS")),
  59. (pathlib.PureWindowsPath("\\some\\owned\\windows\\.dot\\path"), pathlib.PureWindowsPath("\\some\\.github\\CODEOWNERS")),
  60. ])
  61. def test_ExactMatch_VariantSystemPaths_Matched(self, target_path, codeowners_path):
  62. with mock.patch('builtins.open', mock.mock_open(read_data=f"/owned @alias")):
  63. path, alias = hint.get_codeowners_from(target_path, codeowners_path)
  64. assert path, "unexpectedly None or empty"
  65. assert alias, "unexpectedly None or empty"
  66. def test_NoMatches_MultipleMismatched_Negative(self):
  67. target = _DUMMY_REPO_PATH + "/does/not/exist"
  68. ownership_data = \
  69. "/a/b/c/ @alias\n"\
  70. "/a/b/d/ @alias2\n"\
  71. "/a/b/e/ @alias3\n"\
  72. "/a/b/f/ @alias4\n"
  73. # warning: negative assertions can easily pass by accident
  74. _assert_owners_from(target, ownership_data, "", "")
  75. def test_WildcardMatch_GlobalWildcard_Matches(self):
  76. target = _DUMMY_REPO_PATH + "/dummy.py"
  77. ownership_data = "* @alias\n"
  78. _assert_owners_from(target, ownership_data, "*", "@alias")
  79. def test_WildcardMatch_GlobalExtensionWildcard_Matches(self):
  80. target = _DUMMY_REPO_PATH + "/some/dummy.py"
  81. ownership_data = "*.py @alias\n"
  82. _assert_owners_from(target, ownership_data, "*.py", "@alias")
  83. def test_WildcardMismatch_GlobalExtensionWildcard_Negative(self):
  84. target = _DUMMY_REPO_PATH + "/some/dummy.py"
  85. ownership_data = "*.txt @alias\n"
  86. # warning: negative assertions can easily pass by accident
  87. _assert_owners_from(target, ownership_data, "", "")
  88. def test_WildcardMatch_RootedExtensionWildcard_Matches(self):
  89. target = _DUMMY_REPO_PATH + "/some/dummy.py"
  90. ownership_data = "/some/*.py @alias\n"
  91. _assert_owners_from(target, ownership_data, "/some/*.py", "@alias")
  92. def test_WildcardMatch_UnrootedExtensionWildcard_Matches(self):
  93. target = _DUMMY_REPO_PATH + "/some/other/dummy.py"
  94. ownership_data = "other/*.py @alias\n"
  95. _assert_owners_from(target, ownership_data, "other/*.py", "@alias")
  96. def test_WildcardMatch_RootedQuestionWildcard_Matches(self):
  97. target = _DUMMY_REPO_PATH + "/some/dummy.py"
  98. ownership_data = "/some/du?my.py @alias\n"
  99. _assert_owners_from(target, ownership_data, "/some/du?my.py", "@alias")
  100. def test_WildcardMatch_UnrootedQuestionWildcard_Matches(self):
  101. target = _DUMMY_REPO_PATH + "/some/dummy.py"
  102. ownership_data = "some/du?my.py @alias\n"
  103. _assert_owners_from(target, ownership_data, "some/du?my.py", "@alias")
  104. def test_WildcardMatch_ComplexRootedExtension_Matches(self):
  105. target = _DUMMY_REPO_PATH + "/some/path/to/my/dummy.py"
  106. ownership_data = "some/pa?h/to/*/*.py @alias\n"
  107. _assert_owners_from(target, ownership_data, "some/pa?h/to/*/*.py", "@alias")
  108. def test_WildcardMatch_ComplexRelativePattern_Matches(self):
  109. target = _DUMMY_REPO_PATH + "/some/path/to/my/dummy.py"
  110. ownership_data = "path/t?/my/*.py @alias\n"
  111. _assert_owners_from(target, ownership_data, "path/t?/my/*.py", "@alias")
  112. def test_PartialMatch_MultipleMatches_FinalMatch(self):
  113. target = "/some/path/to/my/file"
  114. ownership_data = \
  115. "* @alias\n"\
  116. "/some/path/ @alias2\n"\
  117. "/some/path/to/my/ @alias3\n"\
  118. "/some/other/mismatch/ @alias4\n"
  119. _assert_owners_from(target, ownership_data, "/some/path/to/my/", "@alias3")
  120. def test_PartialMatch_NonFinalExactMatch_FinalMatched(self):
  121. target = "/some/path/to/my/file"
  122. ownership_data = \
  123. "* @alias\n"\
  124. "/some/path/ @alias2\n"\
  125. "/some/path/to/my/file @alias3\n"\
  126. "/some/path/to/my/ @alias4\n" # expect final matching entry, despite the previous exact match
  127. _assert_owners_from(target, ownership_data, "/some/path/to/my/", "@alias4")