def validate_patch( reports: Collection[BaseLintRuleReport], test_case: InvalidTestCase ) -> None: expected_replacement = test_case.expected_replacement patched_code = test_case.code # Patches will contain positional changes indexed from the beginning of the test code, so if # there are multiple reports apply them from last to first so positions don't skew reversed_reports = sorted(reports, key=sort_reports, reverse=True) for report in reversed_reports: patch = report.patch if patch is None: if expected_replacement is not None: raise AssertionError( "The rule for this test case has no auto-fix, but expected source was specified." ) return if expected_replacement is None: raise AssertionError( "The rule for this test case has an auto-fix, but no expected source was specified." ) patched_code = patch.apply(_dedent(patched_code)) if expected_replacement: expected_replacement = _dedent(expected_replacement) if patched_code != expected_replacement: raise AssertionError( "Auto-fix did not produce expected result.\n" + f"Expected:\n{expected_replacement}\n" + f"But found:\n{patched_code}" )
def _test_method( self, test_case: Union[ValidTestCase, InvalidTestCase], rule: Type[CstLintRule], fixture_file: Optional[Path] = None, ) -> None: cst_wrapper: Optional[MetadataWrapper] = None if fixture_file is not None: cst_wrapper = gen_type_inference_wrapper(test_case.code, fixture_file) reports = lint_file( Path(test_case.filename), _dedent(test_case.code).encode("utf-8"), config=test_case.config, rules={rule}, cst_wrapper=cst_wrapper, ) if isinstance(test_case, ValidTestCase): self.assertEqual( len(reports), 0, 'Expected zero reports for this "valid" test case. Instead, found:\n' + "\n".join(str(e) for e in reports), ) else: self.assertGreater( len(reports), 0, 'Expected a report for this "invalid" test case but `self.report` was ' + "not called:\n" + test_case.code, ) self.assertLessEqual( len(reports), 1, 'Expected one report from this "invalid" test case. Found multiple:\n' + "\n".join(str(e) for e in reports), ) # pyre-fixme[16]: `Collection` has no attribute `__getitem__`. report = reports[0] if not (test_case.line is None or test_case.line == report.line): raise AssertionError( f"Expected line: {test_case.line} but found line: {report.line}" ) if not (test_case.column is None or test_case.column == report.column): raise AssertionError( f"Expected column: {test_case.column} but found column: {report.column}" ) kind = test_case.kind if test_case.kind is not None else rule.__name__ if kind != report.code: raise AssertionError( f"Expected:\n {test_case.expected_str}\nBut found:\n {report}" ) validate_patch(report, test_case)
def validate_patch(report: BaseLintRuleReport, test_case: InvalidTestCase) -> None: patch: Optional[LintPatch] = report.patch expected_replacement: Optional[str] = test_case.expected_replacement if patch is None: if expected_replacement is not None: raise AssertionError("The rule for this test case has no auto-fix, but expected source was specified.") return if expected_replacement is None: raise AssertionError("The rule for this test case has an auto-fix, but no expected source was specified.") expected_replacement: str = _dedent(expected_replacement) patched_code: str = patch.apply(_dedent(test_case.code)) if patched_code != expected_replacement: raise AssertionError( "Auto-fix did not produce expected result.\n" + f"Expected:\n{expected_replacement}\n" + f"But found:\n{patched_code}" ) logger.debug(str(report))
def gen_type_inference_wrapper(code: str, pyre_fixture_path: Path) -> MetadataWrapper: """ :param str code: :param Path pyre_fixture_path: :return: :rtype: MetadataWrapper """ # Given test case source code and a path to a pyre fixture file, generate a MetadataWrapper for a lint rule test case. module: cst.Module = cst.parse_module(_dedent(code)) provider_type = TypeInferenceProvider try: pyre_json_data: PyreData = json.loads(pyre_fixture_path.read_text()) except FileNotFoundError as e: raise exceptions.FixtureFileNotFoundError( f"Fixture file not found at {e.filename}. " + "Please run `python -m " "fixit.common.generate_pyre_fixtures <rule>` to generate fixtures.") return MetadataWrapper( module=module, cache={cast(Type[BaseMetadataProvider[object]], provider_type): pyre_json_data}, )
def gen_types_for_test_case(source_code: str, dest_path: Path) -> None: rule_fixture_subdir: Path = dest_path.parent if not rule_fixture_subdir.exists(): rule_fixture_subdir.mkdir(parents=True) with tempfile.NamedTemporaryFile("w", dir=rule_fixture_subdir, suffix=".py") as temp: temp.write(_dedent(source_code)) temp.seek(0) cmd = f'''pyre query "types(path='{temp.name}')"''' stdout, stderr, return_code = run_command(cmd) if return_code != 0: raise PyreQueryError(cmd, f"{stdout}\n{stderr}") data = json.loads(stdout) # Check if error is a key in `data` since pyre may report errors this way. if "error" in data: raise PyreQueryError(cmd, data["error"]) data = data["response"][0] data: PyreData = _process_pyre_data(data) print(f"Writing output to {dest_path}") dest_path.write_text(json.dumps({"types": data["types"]}, indent=2))
def _test_method( self, test_case: Union[ValidTestCase, InvalidTestCase], rule: Type[CstLintRule], fixture_file: Optional[Path] = None, ) -> None: cst_wrapper: Optional[MetadataWrapper] = None if fixture_file is not None: cst_wrapper = gen_type_inference_wrapper(test_case.code, fixture_file) reports = lint_file( Path(test_case.filename), _dedent(test_case.code).encode("utf-8"), config=test_case.config, rules={rule}, cst_wrapper=cst_wrapper, ) if isinstance(test_case, ValidTestCase): self.assertEqual( len(reports), 0, 'Expected zero reports for this "valid" test case. Instead, found:\n' + "\n".join(str(e) for e in reports), ) else: self.assertGreater( len(reports), 0, 'Expected a report for this "invalid" test case but `self.report` was ' + "not called:\n" + test_case.code, ) positions = test_case.positions if positions: self.assertEqual( len(reports), len(positions), f'Expected report count to match positions count for this "invalid" test case, Reports : {len(reports)}, Positions: {len(positions)}.\n' + "\n".join(str(e) for e in reports), ) # We assert above that reports and positions are the same length for report, position in zip(reports, positions): if not (position.line is None or position.line == report.line): raise AssertionError( f"Expected line: {position.line} but found line: {report.line}" ) if not ( position.column is None or position.column == report.column ): raise AssertionError( f"Expected column: {position.column} but found column: {report.column}" ) kind = ( test_case.kind if test_case.kind is not None else rule.__name__ ) if kind != report.code: raise AssertionError( f"Expected:\n {test_case.expected_str}\nBut found:\n {report}" ) if ( test_case.expected_message is not None and test_case.expected_message != report.message ): raise AssertionError( f"Expected message:\n {test_case.expected_message}\nBut got:\n {report.message}" ) validate_patch(reports, test_case) else: self.assertEqual( len(reports), 0, f'Expected report count to match positions count for this "invalid" test case, Reports : {len(reports)}, Positions: 0.\n' + "\n".join(str(e) for e in reports), )