def test_imports_banned() -> None: parser = Parser(target_type_aliases=[], object_aliases=BuildFileAliases()) with pytest.raises(ParseError) as exc: parser.parse( "dir/BUILD", "\nx = 'hello'\n\nimport os\n", BuildFilePreludeSymbols(FrozenDict()) ) assert "Import used in dir/BUILD at line 4" in str(exc.value)
def test_empty(self) -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" address_mapper = AddressMapper( parser=JsonParser(TEST_TABLE), prelude_glob_patterns=(), build_file_imports_behavior=BuildFileImportsBehavior.error, ) af = run_rule( parse_address_family, rule_args=[ address_mapper, BuildFilePreludeSymbols(FrozenDict()), Dir("/dev/null") ], mock_gets=[ MockGet( product_type=Snapshot, subject_type=PathGlobs, mock=lambda _: Snapshot(Digest("abc", 10), ("/dev/null/BUILD", ), ()), ), MockGet( product_type=FilesContent, subject_type=Digest, mock=lambda _: FilesContent( [FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) self.assertEqual(len(af.objects_by_name), 0)
def parse_address_map(build_file: str) -> AddressMap: path = "/dev/null" parser = Parser(target_type_aliases=["thing"], object_aliases=BuildFileAliases()) address_map = AddressMap.parse(path, build_file, parser, BuildFilePreludeSymbols(FrozenDict())) assert path == address_map.path return address_map
def test_no_import_sideeffects(self) -> None: # A parser with no symbols registered. parser = LegacyPythonCallbacksParser( SymbolTable({}), BuildFileAliases(), build_file_imports_behavior=BuildFileImportsBehavior.warn, ) # Call to import a module should succeed. parser.parse( "/dev/null", b"""import os; os.path.join('x', 'y')""", BuildFilePreludeSymbols(FrozenDict()), ) # But the imported module should not be visible as a symbol in further parses. with self.assertRaises(NameError): parser.parse("/dev/null", b"""os.path.join('x', 'y')""", BuildFilePreludeSymbols(FrozenDict()))
def test_unrecognized_symbol() -> None: parser = Parser( target_type_aliases=["tgt"], object_aliases=BuildFileAliases( objects={"obj": 0}, context_aware_object_factories={ "caof": lambda parse_context: lambda _: None }, ), ) prelude_symbols = BuildFilePreludeSymbols(FrozenDict({"prelude": 0})) with pytest.raises(ParseError) as exc: parser.parse("dir/BUILD", "fake", prelude_symbols) assert ( str(exc.value) == "Name 'fake' is not defined.\n\nAll registered symbols: ['caof', 'obj', 'prelude', 'tgt']" )
def test_parse_address_family_empty() -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" af = run_rule_with_mocks( parse_address_family, rule_args=[ Parser(build_root="", target_type_aliases=[], object_aliases=BuildFileAliases()), BuildFileOptions(("BUILD",)), BuildFilePreludeSymbols(FrozenDict()), AddressFamilyDir("/dev/null"), ], mock_gets=[ MockGet( output_type=DigestContents, input_type=PathGlobs, mock=lambda _: DigestContents([FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) assert len(af.name_to_target_adaptors) == 0
def perform_test(extra_targets: list[str], dym: str) -> None: parser = Parser( target_type_aliases=["tgt", *extra_targets], object_aliases=BuildFileAliases( objects={"obj": 0}, context_aware_object_factories={"caof": lambda parse_context: lambda _: None}, ), ) prelude_symbols = BuildFilePreludeSymbols(FrozenDict({"prelude": 0})) fmt_extra_sym = str(extra_targets)[1:-1] + (", ") if len(extra_targets) != 0 else "" with pytest.raises(ParseError) as exc: parser.parse("dir/BUILD", "fake", prelude_symbols) assert str(exc.value) == ( f"Name 'fake' is not defined.\n\n{dym}" "If you expect to see more symbols activated in the below list," f" refer to {docs_url('enabling-backends')} for all available" " backends to activate.\n\n" f"All registered symbols: ['caof', {fmt_extra_sym}'obj', 'prelude', 'tgt']" )
def test_parse_address_family_empty() -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" address_mapper = AddressMapper(parser=Parser( target_type_aliases=[], object_aliases=BuildFileAliases())) af = run_rule( parse_address_family, rule_args=[ address_mapper, BuildFilePreludeSymbols(FrozenDict()), Dir("/dev/null") ], mock_gets=[ MockGet( product_type=DigestContents, subject_type=PathGlobs, mock=lambda _: DigestContents( [FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) assert len(af.name_to_target_adaptors) == 0
async def evalute_preludes( address_mapper: AddressMapper) -> BuildFilePreludeSymbols: snapshot = await Get[Snapshot](PathGlobs( address_mapper.prelude_glob_patterns, glob_match_error_behavior=GlobMatchErrorBehavior.ignore, )) prelude_files_content = await Get[FilesContent](Digest, snapshot.digest) values: Dict[str, Any] = {} for file_content in prelude_files_content: try: file_content_str = file_content.content.decode() content = compile(file_content_str, file_content.path, "exec") exec(content, values) except Exception as e: raise Exception( f"Error parsing prelude file {file_content.path}: {e}") error_on_imports(file_content_str, file_content.path) # __builtins__ is a dict, so isn't hashable, and can't be put in a FrozenDict. # Fortunately, we don't care about it - preludes should not be able to override builtins, so we just pop it out. # TODO: Give a nice error message if a prelude tries to set a expose a non-hashable value. values.pop("__builtins__", None) return BuildFilePreludeSymbols(FrozenDict(values))
async def evaluate_preludes(global_options: GlobalOptions) -> BuildFilePreludeSymbols: prelude_digest_contents = await Get( DigestContents, PathGlobs( global_options.options.build_file_prelude_globs, glob_match_error_behavior=GlobMatchErrorBehavior.ignore, ), ) values: Dict[str, Any] = {} for file_content in prelude_digest_contents: try: file_content_str = file_content.content.decode() content = compile(file_content_str, file_content.path, "exec") exec(content, values) except Exception as e: raise Exception(f"Error parsing prelude file {file_content.path}: {e}") error_on_imports(file_content_str, file_content.path) # __builtins__ is a dict, so isn't hashable, and can't be put in a FrozenDict. # Fortunately, we don't care about it - preludes should not be able to override builtins, so we just pop it out. # TODO: Give a nice error message if a prelude tries to set a expose a non-hashable value. values.pop("__builtins__", None) return BuildFilePreludeSymbols(FrozenDict(values))
def test_parse_address_family_empty() -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" af = run_rule_with_mocks( parse_address_family, rule_args=[ Parser(target_type_aliases=[], object_aliases=BuildFileAliases()), create_subsystem(GlobalOptions, build_patterns=["BUILD"], build_ignore=[]), BuildFilePreludeSymbols(FrozenDict()), Dir("/dev/null"), ], mock_gets=[ MockGet( product_type=DigestContents, subject_type=PathGlobs, mock=lambda _: DigestContents( [FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) assert len(af.name_to_target_adaptors) == 0
def parse(parser, document): return parser.parse("/dev/null", document, BuildFilePreludeSymbols(FrozenDict()))
def test_error_presentation(self): document = dedent( """ # An example with several Bobs. # One with hobbies. { "type_alias": "pants.engine.internals.parsers_test.Bob", # And internal comment and blank lines. "hobbies": [1, 2, 3]} { # This comment is inside an empty object that started on the prior line! } # Another that is imaginary aged. { "type_alias": "pants.engine.internals.parsers_test.Bob", "age": 42i, "four": 1, "five": 1, "six": 1, "seven": 1, "eight": 1, "nine": 1 } """ ).strip() filepath = "/dev/null" with self.assertRaises(parser.ParseError) as exc: parsers.JsonParser(EMPTY_TABLE).parse( filepath, document, BuildFilePreludeSymbols(FrozenDict()) ) # Strip trailing whitespace from the message since our expected literal below will have # trailing ws stripped via editors and code reviews calling for it. actual_lines = [line.rstrip() for line in str(exc.exception).splitlines()] # This message from the json stdlib varies between python releases, so fuzz the match a bit. self.assertRegex( actual_lines[0], r'Expecting (?:,|\',\'|",") delimiter: line 3 column 12 \(char 72\)' ) self.assertEqual( dedent( """ In document at {filepath}: # An example with several Bobs. # One with hobbies. {{ "type_alias": "pants.engine.internals.parsers_test.Bob", # And internal comment and blank lines. "hobbies": [1, 2, 3]}} {{ # This comment is inside an empty object that started on the prior line! }} # Another that is imaginary aged. 1: {{ 2: "type_alias": "pants.engine.internals.parsers_test.Bob", 3: "age": 42i, 4: "four": 1, 5: "five": 1, 6: "six": 1, 7: "seven": 1, 8: "eight": 1, 9: "nine": 1 10: }} """.format( filepath=filepath ) ).strip(), "\n".join(actual_lines[1:]), )
def parse_address_map(self, json): path = "/dev/null" address_map = AddressMap.parse(path, json, self._parser, BuildFilePreludeSymbols(FrozenDict())) self.assertEqual(path, address_map.path) yield address_map