Script to regenerate test fixtures (#872)

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.
This commit is contained in:
Amethyst Reese 2023-02-22 12:35:23 -08:00 committed by GitHub
parent 944ff159f6
commit d94687e378
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 291 additions and 276 deletions

View file

@ -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

View file

@ -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,

View file

@ -9,6 +9,7 @@ import os
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 @@ class TypeInferenceProviderTest(UnitTest):
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"),)

View file

@ -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"
}
}
]
}

View file

@ -5,17 +5,12 @@
import json
import os
from pathlib import Path
from typing import Dict, List, Mapping, Optional, Tuple, Union
import libcst as cst
from libcst.metadata import MetadataWrapper, PositionProvider
from libcst.metadata.type_inference_provider import (
_process_pyre_data,
PyreData,
run_command,
)
from libcst.metadata.type_inference_provider import PyreData
from libcst.testing.utils import data_provider, UnitTest
TEST_SUITE_PATH: Path = Path(__file__).parent / "pyre"
@ -117,29 +112,7 @@ class PyreIntegrationTest(UnitTest):
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)
import sys
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))
print("run `scripts/regenerate-fixtures.py` instead")
sys.exit(1)

View file

@ -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

View file

@ -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()