| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import contextlib |
| import fuzzy_matcher |
| import io |
| import json |
| import os |
| import tempfile |
| import unittest |
| |
| |
| class PreserveEnvAndCaptureOutputTestCase(unittest.TestCase): |
| def setUp(self) -> None: |
| self._old_fuchsia_dir = os.getenv("FUCHSIA_DIR") |
| self.stdout = io.StringIO() |
| self._context = contextlib.redirect_stdout(self.stdout) |
| self._context.__enter__() |
| |
| def tearDown(self) -> None: |
| if self._old_fuchsia_dir: |
| os.environ["FUCHSIA_DIR"] = self._old_fuchsia_dir |
| self._context.__exit__(None, None, None) |
| return super().tearDown() |
| |
| |
| class TestSearchLocations(PreserveEnvAndCaptureOutputTestCase): |
| def test_environment_variable_unset(self) -> None: |
| del os.environ["FUCHSIA_DIR"] |
| |
| with self.assertRaises(Exception) as ex: |
| fuzzy_matcher.create_search_locations() |
| |
| self.assertEqual( |
| str(ex.exception), "Environment variable FUCHSIA_DIR must be set" |
| ) |
| |
| def test_not_a_file(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| path = os.path.join(dir, "tmpfile") |
| with open(path, "w"): |
| pass |
| os.environ["FUCHSIA_DIR"] = str(path) |
| |
| with self.assertRaises(Exception) as ex: |
| fuzzy_matcher.create_search_locations() |
| |
| self.assertEqual( |
| str(ex.exception), f"Path {path} should be a directory" |
| ) |
| |
| def test_missing_tests_json(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| with open(os.path.join(dir, ".fx-build-dir"), "w") as f: |
| f.write("out/other") |
| |
| os.makedirs(os.path.join(dir, "out", "other")) |
| |
| os.environ["FUCHSIA_DIR"] = str(dir) |
| |
| with self.assertRaises(Exception) as ex: |
| fuzzy_matcher.create_search_locations() |
| |
| expected = os.path.join(dir, "out", "other", "tests.json") |
| |
| self.assertEqual( |
| str(ex.exception), |
| f"Expected to find a test list file at {expected}", |
| ) |
| |
| def test_success(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| path = os.path.join(dir, ".fx-build-dir") |
| with open(os.path.join(dir, ".fx-build-dir"), "w") as f: |
| f.write("out/other") |
| os.makedirs(os.path.join(dir, "out", "other")) |
| with open( |
| os.path.join(dir, "out", "other", "tests.json"), "w" |
| ) as f: |
| pass |
| |
| os.environ["FUCHSIA_DIR"] = str(dir) |
| |
| locations = fuzzy_matcher.create_search_locations() |
| self.assertEqual(locations.fuchsia_directory, dir) |
| self.assertEqual( |
| locations.tests_json_file, |
| os.path.join(dir, "out", "other", "tests.json"), |
| ) |
| self.assertNotEqual("", str(locations)) |
| |
| |
| class TestTestsFileMatcher(unittest.TestCase): |
| def _write_names(self, dir: str, names: list[str | tuple[str, str]]) -> str: |
| path = os.path.join(dir, "tests.json") |
| with open(path, "w") as f: |
| l = [] |
| if names and isinstance(names[0], str): |
| l = [{"test": {"name": name}} for name in names] |
| elif names and isinstance(names[0], tuple): |
| l = [ |
| {"test": {"name": value[0], "label": value[1]}} |
| for value in names |
| ] |
| json.dump(l, f) |
| return path |
| |
| def test_empty_file(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| path = self._write_names(dir, []) |
| tests_matcher = fuzzy_matcher.TestsFileMatcher(path) |
| matcher = fuzzy_matcher.Matcher(threshold=0.75) |
| self.assertEqual(tests_matcher.find_matches("foo", matcher), []) |
| |
| def test_exact_matches(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| path = self._write_names( |
| dir, |
| [ |
| "fuchsia-pkg://fuchsia.com/my-package#meta/my-component.cm", |
| "host_test/my-host-test", |
| ], |
| ) |
| tests_matcher = fuzzy_matcher.TestsFileMatcher(path) |
| matcher = fuzzy_matcher.Matcher(threshold=1) |
| self.assertEqual( |
| [ |
| val.matched_name |
| for val in tests_matcher.find_matches( |
| "my_component", matcher |
| ) |
| ], |
| ["my-component"], |
| ) |
| self.assertEqual( |
| [ |
| val.matched_name |
| for val in tests_matcher.find_matches("my_package", matcher) |
| ], |
| ["my-package"], |
| ) |
| self.assertEqual( |
| [ |
| val.matched_name |
| for val in tests_matcher.find_matches( |
| "my_host_test", matcher |
| ) |
| ], |
| ["my-host-test"], |
| ) |
| |
| def test_labels(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| path = self._write_names( |
| dir, |
| [ |
| ( |
| "fuchsia-pkg://fuchsia.com/my-package#meta/my-component.cm", |
| "//src/sys:my_component", |
| ), |
| ], |
| ) |
| tests_matcher = fuzzy_matcher.TestsFileMatcher(path) |
| matcher = fuzzy_matcher.Matcher(threshold=0.7) |
| self.assertEqual( |
| [ |
| val.matched_name |
| for val in tests_matcher.find_matches("//src/sys", matcher) |
| ], |
| ["//src/sys:my_component"], |
| ) |
| |
| |
| TEST_PACKAGE = ( |
| lambda x: f""" |
| fuchsia_test_package("{x}") {{ |
| |
| }} |
| """ |
| ) |
| |
| TEST_PACKAGE_CUSTOM_NAME = ( |
| lambda name, x: f""" |
| {name}("{x}") {{ |
| |
| }} |
| """ |
| ) |
| |
| TEST_COMPONENT = ( |
| lambda x: f""" |
| fuchsia_test_component("{x}") {{ |
| |
| }} |
| """ |
| ) |
| |
| TEST_COMPONENT_WITH_COMPONENT_NAME = ( |
| lambda x, name: f""" |
| fuchsia_test_component("{x}") {{ |
| component_name = "{name}" |
| }} |
| """ |
| ) |
| |
| TEST_PACKAGE_WITH_PACKAGE_NAME = ( |
| lambda x, name: f""" |
| fuchsia_test_package("{x}") {{ |
| package_name = "{name}" |
| }} |
| """ |
| ) |
| |
| TEST_PACKAGE_WITH_COMPONENT_NAME = ( |
| lambda x, name: f""" |
| fuchsia_test_package("{x}") {{ |
| component_name = "{name}" |
| }} |
| """ |
| ) |
| |
| TEST_PACKAGE_WITH_TEST_COMPONENTS = ( |
| lambda x, components: f""" |
| fuchsia_test_package("{x}") {{ |
| test_components = [ |
| {",".join([f'"{name}"' for name in components])} |
| ] |
| }} |
| """ |
| ) |
| |
| |
| class TestBuildFileMatcher(unittest.TestCase): |
| def test_simple_packages(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| os.makedirs(os.path.join(dir, "src")) |
| with open(os.path.join(dir, "src", "BUILD.gn"), "w") as f: |
| f.write(TEST_PACKAGE("my-test-package")) |
| f.write( |
| TEST_PACKAGE_WITH_PACKAGE_NAME( |
| "other-package", "other-real-name" |
| ) |
| ) |
| f.write( |
| TEST_PACKAGE_WITH_COMPONENT_NAME( |
| "yet-another-package", "yet-another-real-name" |
| ) |
| ) |
| |
| build_matcher = fuzzy_matcher.BuildFileMatcher(dir) |
| matcher = fuzzy_matcher.Matcher(threshold=1) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "my-test-package", matcher |
| ) |
| ], |
| [("my-test-package", "--with //src:my-test-package")], |
| ) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "other-real-name", matcher |
| ) |
| ], |
| [("other-real-name", "--with //src:other-package")], |
| ) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "yet-another-real-name", matcher |
| ) |
| ], |
| [("yet-another-real-name", "--with //src:yet-another-package")], |
| ) |
| |
| def test_simple_labels(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| os.makedirs(os.path.join(dir, "src")) |
| with open(os.path.join(dir, "src", "BUILD.gn"), "w") as f: |
| f.write(TEST_PACKAGE("my-test-package")) |
| f.write( |
| TEST_PACKAGE_WITH_PACKAGE_NAME( |
| "other-package", "other-real-name" |
| ) |
| ) |
| f.write( |
| TEST_PACKAGE_WITH_COMPONENT_NAME( |
| "yet-another-package", "yet-another-real-name" |
| ) |
| ) |
| |
| build_matcher = fuzzy_matcher.BuildFileMatcher(dir) |
| matcher = fuzzy_matcher.Matcher(threshold=0.4) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches("//src", matcher) |
| ], |
| [ |
| ("my-test-package", "--with //src:my-test-package"), |
| ("other-real-name", "--with //src:other-package"), |
| ( |
| "yet-another-real-name", |
| "--with //src:yet-another-package", |
| ), |
| ], |
| ) |
| |
| def test_packages_with_components(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| os.makedirs(os.path.join(dir, "src", "nested")) |
| with open(os.path.join(dir, "src", "nested", "BUILD.gn"), "w") as f: |
| f.write(TEST_COMPONENT("my-test-component")) |
| f.write( |
| TEST_COMPONENT_WITH_COMPONENT_NAME( |
| "another-component", "component-real-name" |
| ) |
| ) |
| f.write( |
| TEST_PACKAGE_WITH_TEST_COMPONENTS( |
| "test-package", |
| [":my-test-component", ":another-component"], |
| ) |
| ) |
| |
| build_matcher = fuzzy_matcher.BuildFileMatcher(dir) |
| matcher = fuzzy_matcher.Matcher(threshold=1) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "my_test_component", matcher |
| ) |
| ], |
| [("my-test-component", "--with //src/nested:test-package")], |
| ) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "component_real_name", matcher |
| ) |
| ], |
| [("component-real-name", "--with //src/nested:test-package")], |
| ) |
| |
| def test_custom_package_name(self) -> None: |
| with tempfile.TemporaryDirectory() as dir: |
| os.makedirs(os.path.join(dir, "src")) |
| with open(os.path.join(dir, "src", "BUILD.gn"), "w") as f: |
| f.write( |
| TEST_PACKAGE_CUSTOM_NAME( |
| "fuchsia_test_with_expectations_package", |
| "my-test-package", |
| ) |
| ) |
| f.write( |
| TEST_PACKAGE_CUSTOM_NAME( |
| "test_but_does_not_match", "other-package-name" |
| ) |
| ) |
| |
| build_matcher = fuzzy_matcher.BuildFileMatcher(dir) |
| matcher = fuzzy_matcher.Matcher(threshold=1) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "my-test-package", matcher |
| ) |
| ], |
| [("my-test-package", "--with //src:my-test-package")], |
| ) |
| |
| self.assertEqual( |
| [ |
| (val.matched_name, val.full_suggestion) |
| for val in build_matcher.find_matches( |
| "other-package-name", matcher |
| ) |
| ], |
| [], |
| ) |
| |
| |
| class TestTimingTracker(PreserveEnvAndCaptureOutputTestCase): |
| def test_timing(self) -> None: |
| fuzzy_matcher.TimingTracker.reset() |
| |
| with fuzzy_matcher.TimingTracker("Test timings"): |
| pass |
| with fuzzy_matcher.TimingTracker("Test again"): |
| pass |
| |
| fuzzy_matcher.TimingTracker.print_timings() |
| |
| lines = self.stdout.getvalue().strip().split("\n") |
| self.assertEqual(lines[0], "Debug timings:") |
| self.assertRegex(lines[1], r"\s+Test timings\s+\d+\.\d\d\dms$") |
| self.assertRegex(lines[2], r"\s+Test again\s+\d+\.\d\d\dms$") |
| |
| with fuzzy_matcher.TimingTracker("In progress"): |
| # Ensure we omit in progress readings |
| fuzzy_matcher.TimingTracker.print_timings() |
| lines2 = self.stdout.getvalue().strip().split("\n") |
| self.assertListEqual(lines, lines2[len(lines) :]) |
| |
| |
| class TestCommand(PreserveEnvAndCaptureOutputTestCase): |
| def setUp(self) -> None: |
| super().setUp() |
| self.dir = tempfile.TemporaryDirectory() |
| with open(os.path.join(self.dir.name, ".fx-build-dir"), "w") as f: |
| f.write("out/default") |
| os.makedirs(os.path.join(self.dir.name, "out", "default")) |
| os.makedirs(os.path.join(self.dir.name, "src", "nested")) |
| with open( |
| os.path.join(self.dir.name, "out", "default", "tests.json"), "w" |
| ) as f: |
| json.dump( |
| [ |
| { |
| "test": { |
| "name": "fuchsia-pkg://fuchsia.com/foo-tests#meta/foo-test-component.cm" |
| } |
| }, |
| {"test": {"name": "host_x64/local_script_test"}}, |
| ], |
| f, |
| ) |
| with open( |
| os.path.join(self.dir.name, "src", "nested", "BUILD.gn"), "w" |
| ) as f: |
| f.write(TEST_COMPONENT("foo-test-component")) |
| f.write( |
| TEST_PACKAGE_WITH_TEST_COMPONENTS( |
| "foo-tests", [":foo-test-component"] |
| ) |
| ) |
| with open(os.path.join(self.dir.name, "src", "BUILD.gn"), "w") as f: |
| f.write(TEST_PACKAGE_WITH_COMPONENT_NAME("tests", "kernel-tests")) |
| f.write( |
| TEST_PACKAGE_WITH_PACKAGE_NAME( |
| "component-tests", "my-component-tests" |
| ) |
| ) |
| f.write(TEST_PACKAGE("integration-tests")) |
| |
| os.environ["FUCHSIA_DIR"] = str(self.dir.name) |
| |
| def tearDown(self) -> None: |
| self.dir.cleanup() |
| return super().tearDown() |
| |
| def test_bad_arguments(self) -> None: |
| with self.assertRaises(Exception) as ex: |
| fuzzy_matcher.main(["foo", "--threshold", "3"]) |
| self.assertEqual( |
| str(ex.exception), "--threshold must be between 0 and 1" |
| ) |
| |
| def test_without_matches(self) -> None: |
| fuzzy_matcher.main(["afkdjsflkejkgh"]) |
| self.assertTrue( |
| "No matching tests" in self.stdout.getvalue(), |
| "Could not find expected string in " + self.stdout.getvalue(), |
| ) |
| |
| def test_component_match(self) -> None: |
| fuzzy_matcher.main( |
| ["foo-test-component", "--threshold", "1", "--no-color"] |
| ) |
| |
| self.assertEqual( |
| self.stdout.getvalue().strip(), |
| """ |
| foo-test-component (100.00% similar) |
| Build includes: fuchsia-pkg://fuchsia.com/foo-tests#meta/foo-test-component.cm |
| """.strip(), |
| ) |
| |
| def test_package_match(self) -> None: |
| fuzzy_matcher.main(["kernel", "--threshold", ".75", "--no-color"]) |
| |
| self.assertEqual( |
| self.stdout.getvalue().strip(), |
| """ |
| kernel-tests (90.00% similar) |
| --with //src:tests |
| """.strip(), |
| ) |
| |
| def test_multi_match(self) -> None: |
| fuzzy_matcher.main( |
| ["tests", "--threshold", ".2", "--no-color", "--max-results=3"] |
| ) |
| |
| self.assertEqual( |
| self.stdout.getvalue().strip(), |
| """ |
| foo-test-component (67.41% similar) |
| Build includes: fuchsia-pkg://fuchsia.com/foo-tests#meta/foo-test-component.cm |
| kernel-tests (62.42% similar) |
| --with //src:tests |
| integration-tests (59.22% similar) |
| --with //src:integration-tests |
| (3 more matches not shown) |
| """.strip(), |
| ) |
| |
| def test_with_without_tests_json_match(self) -> None: |
| fuzzy_matcher.main( |
| ["foo-test-component", "--threshold", "1", "--no-color"] |
| ) |
| fuzzy_matcher.main( |
| [ |
| "foo-test-component", |
| "--threshold", |
| "1", |
| "--no-color", |
| "--omit-test-file", |
| ] |
| ) |
| |
| self.assertEqual( |
| self.stdout.getvalue().strip(), |
| """ |
| foo-test-component (100.00% similar) |
| Build includes: fuchsia-pkg://fuchsia.com/foo-tests#meta/foo-test-component.cm |
| No matching tests could be found in your Fuchsia checkout. |
| """.strip(), |
| ) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |