From 5e7efdd79633668949bd63c18fea8bccab7a1671 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Tue, 21 Feb 2023 12:13:35 -0800 Subject: [PATCH] Script to regenerate test fixtures Upgrading Pyre requires updating test fixtures with any upstream changes to Pyre's query results for the `simple_class.py` fixture. This adds a new `scripts/` directory to the repo, with a script to regenerate test fixtures. The script regenerates the cache data fixture, and updates the `TypeInferenceProvider` tests to use `assertDictEqual` and helpful error messages for better behavior in future mismatches. This also includes a slight bump to Pyre 0.9.10 to fix install issues on Apple Silicon M1 Macs, and regenerated fixtures using the script above. --- .github/workflows/ci.yml | 2 +- libcst/matchers/_visitors.py | 8 - .../tests/test_type_inference_provider.py | 10 +- libcst/tests/pyre/simple_class.json | 468 +++++++++--------- libcst/tests/test_pyre_integration.py | 30 +- requirements-dev.txt | 2 +- scripts/regenerate-fixtures.py | 42 ++ 7 files changed, 291 insertions(+), 271 deletions(-) create mode 100644 scripts/regenerate-fixtures.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 929d8d900..b1a0b2ddb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: run: pip install -e . - run: pyre --version - run: pyre -n check - - run: python libcst/tests/test_pyre_integration.py + - run: python scripts/regenerate-fixtures.py - run: git diff --exit-code # Upload test coverage diff --git a/libcst/matchers/_visitors.py b/libcst/matchers/_visitors.py index a314fc4d4..a491ffd10 100644 --- a/libcst/matchers/_visitors.py +++ b/libcst/matchers/_visitors.py @@ -323,10 +323,6 @@ def _gather_constructed_visit_funcs( _assert_not_concrete("visit", func) for matcher in matchers: casted_matcher = cast(BaseMatcherNode, matcher) - # pyre-fixme[6]: Expected - # `Sequence[typing.Callable[[cst._nodes.base.CSTNode], None]]` for 2nd - # param but got `Tuple[*Tuple[(CSTNode) -> None, ...], (CSTNode) -> - # None]`. constructed_visitors[casted_matcher] = ( *constructed_visitors.get(casted_matcher, ()), func, @@ -362,10 +358,6 @@ def _gather_constructed_leave_funcs( _assert_not_concrete("leave", func) for matcher in matchers: casted_matcher = cast(BaseMatcherNode, matcher) - # pyre-fixme[6]: Expected - # `Sequence[typing.Callable[[cst._nodes.base.CSTNode], None]]` for 2nd - # param but got `Tuple[*Tuple[(CSTNode) -> None, ...], (CSTNode) -> - # None]`. constructed_visitors[casted_matcher] = ( *constructed_visitors.get(casted_matcher, ()), func, diff --git a/libcst/metadata/tests/test_type_inference_provider.py b/libcst/metadata/tests/test_type_inference_provider.py index c52a7c8ee..50ca34584 100644 --- a/libcst/metadata/tests/test_type_inference_provider.py +++ b/libcst/metadata/tests/test_type_inference_provider.py @@ -9,6 +9,7 @@ import subprocess import sys from pathlib import Path +from typing import cast, Mapping, Optional from unittest import skipIf import libcst as cst @@ -57,6 +58,8 @@ def _test_simple_class_helper(test: UnitTest, wrapper: MetadataWrapper) -> None: ) @skipIf(sys.platform == "win32", "TypeInferenceProvider doesn't support windows") class TypeInferenceProviderTest(UnitTest): + maxDiff: Optional[int] = None + @classmethod def setUpClass(cls) -> None: os.chdir(TEST_SUITE_PATH) @@ -79,8 +82,13 @@ def test_gen_cache(self, source_path: Path, data_path: Path) -> None: cache = TypeInferenceProvider.gen_cache( root_path=source_path.parent, paths=[source_path.name], timeout=None ) + result = cast(Mapping[str, object], cache[source_path.name]) data: PyreData = json.loads(data_path.read_text()) - self.assertEqual(data, cache[source_path.name]) + self.assertDictEqual( + data, + result, + "Pyre query result mismatch, try running `scripts/regenerate-fixtures.py`?", + ) @data_provider( ((TEST_SUITE_PATH / "simple_class.py", TEST_SUITE_PATH / "simple_class.json"),) diff --git a/libcst/tests/pyre/simple_class.json b/libcst/tests/pyre/simple_class.json index 288bb5673..878ed5ebb 100644 --- a/libcst/tests/pyre/simple_class.json +++ b/libcst/tests/pyre/simple_class.json @@ -1,511 +1,511 @@ { "types": [ { + "annotation": "typing.Type[typing.Sequence]", "location": { "start": { - "line": 7, - "column": 19 + "column": 19, + "line": 7 }, "stop": { - "line": 7, - "column": 27 + "column": 27, + "line": 7 } - }, - "annotation": "typing.Type[typing.Sequence]" + } }, { + "annotation": "typing.Type[simple_class.Item]", "location": { "start": { - "line": 10, - "column": 6 + "column": 6, + "line": 10 }, "stop": { - "line": 10, - "column": 10 + "column": 10, + "line": 10 } - }, - "annotation": "typing.Type[simple_class.Item]" + } }, { + "annotation": "typing.Callable(simple_class.Item.__init__)[[Named(self, simple_class.Item), Named(n, int)], None]", "location": { "start": { - "line": 11, - "column": 8 + "column": 8, + "line": 11 }, "stop": { - "line": 11, - "column": 16 + "column": 16, + "line": 11 } - }, - "annotation": "typing.Callable(simple_class.Item.__init__)[[Named(self, simple_class.Item), Named(n, int)], None]" + } }, { + "annotation": "simple_class.Item", "location": { "start": { - "line": 11, - "column": 17 + "column": 17, + "line": 11 }, "stop": { - "line": 11, - "column": 21 + "column": 21, + "line": 11 } - }, - "annotation": "simple_class.Item" + } }, { + "annotation": "int", "location": { "start": { - "line": 11, - "column": 23 + "column": 23, + "line": 11 }, "stop": { - "line": 11, - "column": 29 + "column": 24, + "line": 11 } - }, - "annotation": "int" + } }, { + "annotation": "typing.Type[int]", "location": { "start": { - "line": 11, - "column": 26 + "column": 26, + "line": 11 }, "stop": { - "line": 11, - "column": 29 + "column": 29, + "line": 11 } - }, - "annotation": "typing.Type[int]" + } }, { + "annotation": "None", "location": { "start": { - "line": 11, - "column": 34 + "column": 34, + "line": 11 }, "stop": { - "line": 11, - "column": 38 + "column": 38, + "line": 11 } - }, - "annotation": "None" + } }, { + "annotation": "simple_class.Item", "location": { "start": { - "line": 12, - "column": 8 + "column": 8, + "line": 12 }, "stop": { - "line": 12, - "column": 12 + "column": 12, + "line": 12 } - }, - "annotation": "simple_class.Item" + } }, { + "annotation": "int", "location": { "start": { - "line": 12, - "column": 8 + "column": 8, + "line": 12 }, "stop": { - "line": 12, - "column": 19 + "column": 19, + "line": 12 } - }, - "annotation": "int" + } }, { + "annotation": "typing.Type[int]", "location": { "start": { - "line": 12, - "column": 21 + "column": 21, + "line": 12 }, "stop": { - "line": 12, - "column": 24 + "column": 24, + "line": 12 } - }, - "annotation": "typing.Type[int]" + } }, { + "annotation": "int", "location": { "start": { - "line": 12, - "column": 27 + "column": 27, + "line": 12 }, "stop": { - "line": 12, - "column": 28 + "column": 28, + "line": 12 } - }, - "annotation": "int" + } }, { + "annotation": "typing.Type[simple_class.ItemCollector]", "location": { "start": { - "line": 15, - "column": 6 + "column": 6, + "line": 15 }, "stop": { - "line": 15, - "column": 19 + "column": 19, + "line": 15 } - }, - "annotation": "typing.Type[simple_class.ItemCollector]" + } }, { + "annotation": "typing.Callable(simple_class.ItemCollector.get_items)[[Named(self, simple_class.ItemCollector), Named(n, int)], typing.Sequence[simple_class.Item]]", "location": { "start": { - "line": 16, - "column": 8 + "column": 8, + "line": 16 }, "stop": { - "line": 16, - "column": 17 + "column": 17, + "line": 16 } - }, - "annotation": "typing.Callable(simple_class.ItemCollector.get_items)[[Named(self, simple_class.ItemCollector), Named(n, int)], typing.Sequence[simple_class.Item]]" + } }, { + "annotation": "simple_class.ItemCollector", "location": { "start": { - "line": 16, - "column": 18 + "column": 18, + "line": 16 }, "stop": { - "line": 16, - "column": 22 + "column": 22, + "line": 16 } - }, - "annotation": "simple_class.ItemCollector" + } }, { + "annotation": "int", "location": { "start": { - "line": 16, - "column": 24 + "column": 24, + "line": 16 }, "stop": { - "line": 16, - "column": 30 + "column": 25, + "line": 16 } - }, - "annotation": "int" + } }, { + "annotation": "typing.Type[int]", "location": { "start": { - "line": 16, - "column": 27 + "column": 27, + "line": 16 }, "stop": { - "line": 16, - "column": 30 + "column": 30, + "line": 16 } - }, - "annotation": "typing.Type[int]" + } }, { + "annotation": "BoundMethod[typing.Callable(typing.GenericMeta.__getitem__)[[Named(self, unknown), typing.Type[Variable[typing._T_co](covariant)]], typing.Type[typing.Sequence[Variable[typing._T_co](covariant)]]], typing.Type[typing.Sequence]]", "location": { "start": { - "line": 16, - "column": 35 + "column": 35, + "line": 16 }, "stop": { - "line": 16, - "column": 43 + "column": 43, + "line": 16 } - }, - "annotation": "BoundMethod[typing.Callable(typing.GenericMeta.__getitem__)[[Named(self, unknown), typing.Type[Variable[typing._T_co](covariant)]], typing.Type[typing.Sequence[Variable[typing._T_co](covariant)]]], typing.Type[typing.Sequence]]" + } }, { + "annotation": "typing.Type[typing.Sequence[simple_class.Item]]", "location": { "start": { - "line": 16, - "column": 35 + "column": 35, + "line": 16 }, "stop": { - "line": 16, - "column": 49 + "column": 49, + "line": 16 } - }, - "annotation": "typing.Type[typing.Sequence[simple_class.Item]]" + } }, { + "annotation": "typing.Type[simple_class.Item]", "location": { "start": { - "line": 16, - "column": 44 + "column": 44, + "line": 16 }, "stop": { - "line": 16, - "column": 48 + "column": 48, + "line": 16 } - }, - "annotation": "typing.Type[simple_class.Item]" + } }, { + "annotation": "typing.List[simple_class.Item]", "location": { "start": { - "line": 17, - "column": 15 + "column": 15, + "line": 17 }, "stop": { - "line": 17, - "column": 42 + "column": 42, + "line": 17 } - }, - "annotation": "typing.List[simple_class.Item]" + } }, { + "annotation": "typing.Type[simple_class.Item]", "location": { "start": { - "line": 17, - "column": 16 + "column": 16, + "line": 17 }, "stop": { - "line": 17, - "column": 20 + "column": 20, + "line": 17 } - }, - "annotation": "typing.Type[simple_class.Item]" + } }, { + "annotation": "simple_class.Item", "location": { "start": { - "line": 17, - "column": 16 + "column": 16, + "line": 17 }, "stop": { - "line": 17, - "column": 23 + "column": 23, + "line": 17 } - }, - "annotation": "simple_class.Item" + } }, { + "annotation": "int", "location": { "start": { - "line": 17, - "column": 28 + "column": 28, + "line": 17 }, "stop": { - "line": 17, - "column": 29 + "column": 29, + "line": 17 } - }, - "annotation": "int" + } }, { + "annotation": "typing.Type[range]", "location": { "start": { - "line": 17, - "column": 33 + "column": 33, + "line": 17 }, "stop": { - "line": 17, - "column": 38 + "column": 38, + "line": 17 } - }, - "annotation": "typing.Type[range]" + } }, { + "annotation": "range", "location": { "start": { - "line": 17, - "column": 33 + "column": 33, + "line": 17 }, "stop": { - "line": 17, - "column": 41 + "column": 41, + "line": 17 } - }, - "annotation": "range" + } }, { + "annotation": "int", "location": { "start": { - "line": 17, - "column": 39 + "column": 39, + "line": 17 }, "stop": { - "line": 17, - "column": 40 + "column": 40, + "line": 17 } - }, - "annotation": "int" + } }, { + "annotation": "simple_class.ItemCollector", "location": { "start": { - "line": 20, - "column": 0 + "column": 0, + "line": 20 }, "stop": { - "line": 20, - "column": 9 + "column": 9, + "line": 20 } - }, - "annotation": "simple_class.ItemCollector" + } }, { + "annotation": "typing.Type[simple_class.ItemCollector]", "location": { "start": { - "line": 20, - "column": 12 + "column": 12, + "line": 20 }, "stop": { - "line": 20, - "column": 25 + "column": 25, + "line": 20 } - }, - "annotation": "typing.Type[simple_class.ItemCollector]" + } }, { + "annotation": "simple_class.ItemCollector", "location": { "start": { - "line": 20, - "column": 12 + "column": 12, + "line": 20 }, "stop": { - "line": 20, - "column": 27 + "column": 27, + "line": 20 } - }, - "annotation": "simple_class.ItemCollector" + } }, { + "annotation": "typing.Sequence[simple_class.Item]", "location": { "start": { - "line": 21, - "column": 0 + "column": 0, + "line": 21 }, "stop": { - "line": 21, - "column": 5 + "column": 5, + "line": 21 } - }, - "annotation": "typing.Sequence[simple_class.Item]" + } }, { + "annotation": "typing.Type[typing.Sequence[simple_class.Item]]", "location": { "start": { - "line": 21, - "column": 7 + "column": 7, + "line": 21 }, "stop": { - "line": 21, - "column": 21 + "column": 21, + "line": 21 } - }, - "annotation": "typing.Type[typing.Sequence[simple_class.Item]]" + } }, { + "annotation": "simple_class.ItemCollector", "location": { "start": { - "line": 21, - "column": 24 + "column": 24, + "line": 21 }, "stop": { - "line": 21, - "column": 33 + "column": 33, + "line": 21 } - }, - "annotation": "simple_class.ItemCollector" + } }, { + "annotation": "BoundMethod[typing.Callable(simple_class.ItemCollector.get_items)[[Named(self, simple_class.ItemCollector), Named(n, int)], typing.Sequence[simple_class.Item]], simple_class.ItemCollector]", "location": { "start": { - "line": 21, - "column": 24 + "column": 24, + "line": 21 }, "stop": { - "line": 21, - "column": 43 + "column": 43, + "line": 21 } - }, - "annotation": "BoundMethod[typing.Callable(simple_class.ItemCollector.get_items)[[Named(self, simple_class.ItemCollector), Named(n, int)], typing.Sequence[simple_class.Item]], simple_class.ItemCollector]" + } }, { + "annotation": "typing.Sequence[simple_class.Item]", "location": { "start": { - "line": 21, - "column": 24 + "column": 24, + "line": 21 }, "stop": { - "line": 21, - "column": 46 + "column": 46, + "line": 21 } - }, - "annotation": "typing.Sequence[simple_class.Item]" + } }, { + "annotation": "typing_extensions.Literal[3]", "location": { "start": { - "line": 21, - "column": 44 + "column": 44, + "line": 21 }, "stop": { - "line": 21, - "column": 45 + "column": 45, + "line": 21 } - }, - "annotation": "typing_extensions.Literal[3]" + } }, { + "annotation": "simple_class.Item", "location": { "start": { - "line": 22, - "column": 4 + "column": 4, + "line": 22 }, "stop": { - "line": 22, - "column": 8 + "column": 8, + "line": 22 } - }, - "annotation": "simple_class.Item" + } }, { + "annotation": "typing.Sequence[simple_class.Item]", "location": { "start": { - "line": 22, - "column": 12 + "column": 12, + "line": 22 }, "stop": { - "line": 22, - "column": 17 + "column": 17, + "line": 22 } - }, - "annotation": "typing.Sequence[simple_class.Item]" + } }, { + "annotation": "simple_class.Item", "location": { "start": { - "line": 23, - "column": 4 + "column": 4, + "line": 23 }, "stop": { - "line": 23, - "column": 8 + "column": 8, + "line": 23 } - }, - "annotation": "simple_class.Item" + } }, { + "annotation": "int", "location": { "start": { - "line": 23, - "column": 4 + "column": 4, + "line": 23 }, "stop": { - "line": 23, - "column": 15 + "column": 15, + "line": 23 } - }, - "annotation": "int" + } } ] } \ No newline at end of file diff --git a/libcst/tests/test_pyre_integration.py b/libcst/tests/test_pyre_integration.py index 021385af5..e0a8c4d49 100644 --- a/libcst/tests/test_pyre_integration.py +++ b/libcst/tests/test_pyre_integration.py @@ -117,29 +117,7 @@ def test_type_availability(self, source_path: Path, data_path: Path) -> None: if __name__ == "__main__": - """Run this script directly to generate pyre data for test suite (tests/pyre/*.py)""" - print("start pyre server") - stdout: str - stderr: str - return_code: int - os.chdir(TEST_SUITE_PATH) - stdout, stderr, return_code = run_command(["pyre", "start", "--no-watchman"]) - if return_code != 0: - print(stdout) - print(stderr) - - for path in TEST_SUITE_PATH.glob("*.py"): - # Pull params into it's own arg to avoid the string escaping in subprocess - params = f"path='{path}'" - cmd = ["pyre", "query", f"types({params})"] - print(cmd) - stdout, stderr, return_code = run_command(cmd) - if return_code != 0: - print(stdout) - print(stderr) - data = json.loads(stdout) - data = data["response"][0] - data = _process_pyre_data(data) - output_path = path.with_suffix(".json") - print(f"write output to {output_path}") - output_path.write_text(json.dumps(data, indent=2)) + import sys + + print("run `scripts/regenerate-fixtures.py` instead") + sys.exit(1) diff --git a/requirements-dev.txt b/requirements-dev.txt index d34c666ae..8155a99a6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ jupyter>=1.0.0 maturin>=0.8.3,<0.14 nbsphinx>=0.4.2 prompt-toolkit>=2.0.9 -pyre-check==0.9.9; platform_system != "Windows" +pyre-check==0.9.10; platform_system != "Windows" setuptools_scm>=6.0.1 sphinx-rtd-theme>=0.4.3 ufmt==2.0.1 diff --git a/scripts/regenerate-fixtures.py b/scripts/regenerate-fixtures.py new file mode 100644 index 000000000..2b67b304b --- /dev/null +++ b/scripts/regenerate-fixtures.py @@ -0,0 +1,42 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +Regenerate test fixtures, eg. after upgrading Pyre +""" + +import json +import os +from pathlib import Path +from subprocess import run + +from libcst.metadata import TypeInferenceProvider + + +def main() -> None: + CWD = Path.cwd() + repo_root = Path(__file__).parent.parent + test_root = repo_root / "libcst" / "tests" / "pyre" + + try: + os.chdir(test_root) + run(["pyre", "-n", "start", "--no-watchman"], check=True) + + for file_path in test_root.glob("*.py"): + json_path = file_path.with_suffix(".json") + print(f"generating {file_path} -> {json_path}") + + path_str = file_path.as_posix() + cache = TypeInferenceProvider.gen_cache(test_root, [path_str], timeout=None) + result = cache[path_str] + json_path.write_text(json.dumps(result, sort_keys=True, indent=2)) + + finally: + run(["pyre", "-n", "stop"], check=True) + os.chdir(CWD) + + +if __name__ == "__main__": + main()