# mypy: ignore-errors import os import sys from unittest import mock import hypothesis as h import hypothesis.strategies as hs import pytest from .. import manifest, sourcefile, item, utils from typing import Any, Type def SourceFileWithTest(path: str, hash: str, cls: Type[item.ManifestItem], **kwargs: Any) -> sourcefile.SourceFile: rel_path_parts = tuple(path.split(os.path.sep)) s = mock.Mock(rel_path=path, rel_path_parts=rel_path_parts, hash=hash) if cls == item.SupportFile: test = cls("/foobar", path) else: assert issubclass(cls, item.URLManifestItem) test = cls("/foobar", path, "/", utils.from_os_path(path), **kwargs) s.manifest_items = mock.Mock(return_value=(cls.item_type, [test])) return s # type: ignore def SourceFileWithTests(path: str, hash: str, cls: Type[item.URLManifestItem], variants: Any) -> sourcefile.SourceFile: rel_path_parts = tuple(path.split(os.path.sep)) s = mock.Mock(rel_path=path, rel_path_parts=rel_path_parts, hash=hash) tests = [cls("/foobar", path, "/", item[0], **item[1]) for item in variants] s.manifest_items = mock.Mock(return_value=(cls.item_type, tests)) return s # type: ignore def tree_and_sourcefile_mocks(source_files): paths_dict = {} tree = [] for source_file, file_hash, updated in source_files: paths_dict[source_file.rel_path] = source_file tree.append([source_file.rel_path, file_hash, updated]) def MockSourceFile(tests_root, path, url_base, file_hash): return paths_dict[path] return tree, MockSourceFile @hs.composite def sourcefile_strategy(draw): item_classes = [item.TestharnessTest, item.RefTest, item.PrintRefTest, item.ManualTest, item.WebDriverSpecTest, item.ConformanceCheckerTest, item.SupportFile] cls = draw(hs.sampled_from(item_classes)) path = "a" rel_path_parts = tuple(path.split(os.path.sep)) hash = draw(hs.text(alphabet="0123456789abcdef", min_size=40, max_size=40)) s = mock.Mock(rel_path=path, rel_path_parts=rel_path_parts, hash=hash) if cls in (item.RefTest, item.PrintRefTest): ref_path = "b" ref_eq = draw(hs.sampled_from(["==", "!="])) test = cls("/foobar", path, "/", utils.from_os_path(path), references=[(utils.from_os_path(ref_path), ref_eq)]) elif cls is item.SupportFile: test = cls("/foobar", path) else: test = cls("/foobar", path, "/", "foobar") s.manifest_items = mock.Mock(return_value=(cls.item_type, [test])) return s @hs.composite def manifest_tree(draw): names = hs.text(alphabet=hs.characters(blacklist_characters="\0/\\:*\"?<>|"), min_size=1) tree = hs.recursive(sourcefile_strategy(), lambda children: hs.dictionaries(names, children, min_size=1), max_leaves=10) generated_root = draw(tree) h.assume(isinstance(generated_root, dict)) reftest_urls = [] output = [] stack = [((k,), v) for k, v in generated_root.items()] while stack: path, node = stack.pop() if isinstance(node, dict): stack.extend((path + (k,), v) for k, v in node.items()) else: rel_path = os.path.sep.join(path) node.rel_path = rel_path node.rel_path_parts = tuple(path) for test_item in node.manifest_items.return_value[1]: test_item.path = rel_path if isinstance(test_item, item.RefTest): if reftest_urls: possible_urls = hs.sampled_from(reftest_urls) | names else: possible_urls = names reference = hs.tuples(hs.sampled_from(["==", "!="]), possible_urls) references = hs.lists(reference, min_size=1, unique=True) test_item.references = draw(references) reftest_urls.append(test_item.url) output.append(node) return output @pytest.mark.skipif(sys.version_info[:3] in ((3, 10, 10), (3, 11, 2)), reason="https://github.com/python/cpython/issues/102126") @h.given(manifest_tree()) # FIXME: Workaround for https://github.com/web-platform-tests/wpt/issues/22758 @h.settings(suppress_health_check=(h.HealthCheck.too_slow,)) @h.example([SourceFileWithTest("a", "0"*40, item.ConformanceCheckerTest)]) def test_manifest_to_json(s): m = manifest.Manifest("") tree, sourcefile_mock = tree_and_sourcefile_mocks((item, None, True) for item in s) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree) is True json_str = m.to_json() loaded = manifest.Manifest.from_json("/", json_str) assert list(loaded) == list(m) assert loaded.to_json() == json_str @pytest.mark.skipif(sys.version_info[:3] in ((3, 10, 10), (3, 11, 2)), reason="https://github.com/python/cpython/issues/102126") @h.given(manifest_tree()) # FIXME: Workaround for https://github.com/web-platform-tests/wpt/issues/22758 @h.settings(suppress_health_check=(h.HealthCheck.too_slow,)) @h.example([SourceFileWithTest("a", "0"*40, item.TestharnessTest)]) @h.example([SourceFileWithTest("a", "0"*40, item.RefTest, references=[("/aa", "==")])]) def test_manifest_idempotent(s): m = manifest.Manifest("") tree, sourcefile_mock = tree_and_sourcefile_mocks((item, None, True) for item in s) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree) is True m1 = list(m) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree) is False assert list(m) == m1 def test_manifest_to_json_forwardslash(): m = manifest.Manifest("") s = SourceFileWithTest("a" + os.path.sep + "b", "0"*40, item.TestharnessTest) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree) is True assert m.to_json() == { 'version': 9, 'url_base': '/', 'items': { 'testharness': {'a': {'b': [ '0000000000000000000000000000000000000000', (None, {}) ]}}, } } def test_reftest_computation_chain(): m = manifest.Manifest("") s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, references=[("/test2", "==")]) s2 = SourceFileWithTest("test2", "0"*40, item.RefTest, references=[("/test3", "==")]) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, True), (s2, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) test1 = s1.manifest_items()[1][0] test2 = s2.manifest_items()[1][0] assert list(m) == [("reftest", test1.path, {test1}), ("reftest", test2.path, {test2})] def test_iterpath(): m = manifest.Manifest("") sources = [SourceFileWithTest("test1", "0"*40, item.RefTest, references=[("/test1-ref", "==")]), SourceFileWithTests("test2", "1"*40, item.TestharnessTest, [("test2-1.html", {}), ("test2-2.html", {})]), SourceFileWithTest("test3", "0"*40, item.TestharnessTest)] tree, sourcefile_mock = tree_and_sourcefile_mocks((item, None, True) for item in sources) assert len(tree) == len(sources) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) assert {item.url for item in m.iterpath("test2")} == {"/test2-1.html", "/test2-2.html"} assert set(m.iterpath("missing")) == set() def test_no_update(): m = manifest.Manifest("") s1 = SourceFileWithTest("test1", "0"*40, item.TestharnessTest) s2 = SourceFileWithTest("test2", "0"*40, item.TestharnessTest) tree, sourcefile_mock = tree_and_sourcefile_mocks((item, None, True) for item in [s1, s2]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) test1 = s1.manifest_items()[1][0] test2 = s2.manifest_items()[1][0] assert list(m) == [("testharness", test1.path, {test1}), ("testharness", test2.path, {test2})] s1_1 = SourceFileWithTest("test1", "1"*40, item.ManualTest) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1_1, None, True), (s2, None, False)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) test1_1 = s1_1.manifest_items()[1][0] assert list(m) == [("manual", test1_1.path, {test1_1}), ("testharness", test2.path, {test2})] def test_no_update_delete(): m = manifest.Manifest("") s1 = SourceFileWithTest("test1", "0"*40, item.TestharnessTest) s2 = SourceFileWithTest("test2", "0"*40, item.TestharnessTest) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, True), (s2, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) test1 = s1.manifest_items()[1][0] tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, False)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) assert list(m) == [("testharness", test1.path, {test1})] def test_update_from_json(): m = manifest.Manifest("") s1 = SourceFileWithTest("test1", "0"*40, item.TestharnessTest) s2 = SourceFileWithTest("test2", "0"*40, item.TestharnessTest) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, True), (s2, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) json_str = m.to_json() m = manifest.Manifest.from_json("/", json_str) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) test1 = s1.manifest_items()[1][0] assert list(m) == [("testharness", test1.path, {test1})] def test_update_from_json_modified(): # Create the original manifest m = manifest.Manifest("") s1 = SourceFileWithTest("test1", "0"*40, item.TestharnessTest) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s1, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) json_str = m.to_json() # Reload it from JSON m = manifest.Manifest.from_json("/", json_str) testdriver_features = ['feature_1', 'feature_2'] # Update timeout, pac and testdriver_features s2 = SourceFileWithTest("test1", "1" * 40, item.TestharnessTest, timeout="long", pac="proxy.pac", testdriver_features=testdriver_features) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s2, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): m.update(tree) json_str = m.to_json() assert json_str == { 'items': {'testharness': {'test1': [ "1"*40, (None, {'timeout': 'long', 'pac': 'proxy.pac', 'testdriver_features': testdriver_features}) ]}}, 'url_base': '/', 'version': 9 } def test_manifest_spec_to_json(): m = manifest.Manifest("") path = "a" + os.path.sep + "b" hash = "0"*40 rel_path_parts = tuple(path.split(os.path.sep)) s = mock.Mock(rel_path=path, rel_path_parts=rel_path_parts, hash=hash) spec = item.SpecItem("/foobar", path, ["specA"]) s.manifest_spec_items = mock.Mock(return_value=(item.SpecItem.item_type, [spec])) tree, sourcefile_mock = tree_and_sourcefile_mocks([(s, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree, True, manifest.compute_manifest_spec_items) is True assert m.to_json() == { 'version': 9, 'url_base': '/', 'items': { 'spec': {'a': {'b': [ '0000000000000000000000000000000000000000', (None, {'spec_link1': 'specA'}) ]}}, } } @pytest.mark.parametrize("testdriver,expected_extra", [ (True, {"testdriver": True}), # Don't bloat the manifest with the `testdriver=False` default. (False, {}), ]) def test_dump_testdriver(testdriver, expected_extra): m = manifest.Manifest("") source_file = SourceFileWithTest("a" + os.path.sep + "b", "0"*40, item.RefTest, testdriver=testdriver) tree, sourcefile_mock = tree_and_sourcefile_mocks([(source_file, None, True)]) with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock): assert m.update(tree) is True assert m.to_json() == { 'version': 9, 'url_base': '/', 'items': { 'reftest': {'a': {'b': [ '0000000000000000000000000000000000000000', (mock.ANY, [], expected_extra) ]}}, } }