def test_single_language_with_multiple_targets(self) -> None: addresses = [Address.parse(":t1"), Address.parse(":t2")] def get_stderr(*, per_target_caching: bool) -> str: stderr = self.run_fmt_rule( language_target_collection_types=[FortranTargets], targets=[ self.make_target_with_origin(addr) for addr in addresses ], result_digest=self.fortran_digest, per_target_caching=per_target_caching, ) self.assert_workspace_modified(fortran_formatted=True, smalltalk_formatted=False) return stderr assert get_stderr(per_target_caching=False) == dedent(f"""\ 𐄂 FortranFormatter made changes. {FortranTargets.stdout(addresses)} """) assert get_stderr(per_target_caching=True) == dedent(f"""\ 𐄂 FortranFormatter made changes. {FortranTargets.stdout([addresses[0]])} 𐄂 FortranFormatter made changes. {FortranTargets.stdout([addresses[1]])} """)
def test_summary(self) -> None: """Test that we render the summary correctly. This tests that we: * Merge multiple results belonging to the same linter (`--per-file-caching`). * Decide correctly between skipped, failed, and succeeded. """ good_address = Address.parse(":good") bad_address = Address.parse(":bad") def assert_expected(*, per_file_caching: bool) -> None: exit_code, stderr = self.run_lint_rule( lint_request_types=[ ConditionallySucceedsRequest, FailingRequest, SkippedRequest, SuccessfulRequest, ], targets=[self.make_target(good_address), self.make_target(bad_address)], per_file_caching=per_file_caching, ) assert exit_code == FailingRequest.exit_code([bad_address]) assert stderr == dedent( """\ 𐄂 ConditionallySucceedsLinter failed. 𐄂 FailingLinter failed. - SkippedLinter skipped. ✓ SuccessfulLinter succeeded. """ ) assert_expected(per_file_caching=False) assert_expected(per_file_caching=True)
def test_summary(self) -> None: good_address = Address.parse(":good") bad_address = Address.parse(":bad") exit_code, stderr = self.run_typecheck_rule( request_types=[ ConditionallySucceedsRequest, FailingRequest, SkippedRequest, SuccessfulRequest, ], targets=[ self.make_target(good_address), self.make_target(bad_address), ], ) assert exit_code == FailingRequest.exit_code([bad_address]) assert stderr == dedent( """\ 𐄂 ConditionallySucceedsTypechecker failed. 𐄂 FailingTypechecker failed. - SkippedTypechecker skipped. ✓ SuccessfulTypechecker succeeded. """ )
def test_filter_by_target_type() -> None: class Fortran(Target): alias = "fortran" core_fields = () class Smalltalk(Target): alias = "smalltalk" core_fields = () fortran_targets = [ Fortran({}, address=Address.parse(addr)) for addr in (":f1", ":f2") ] smalltalk_targets = [ Smalltalk({}, address=Address.parse(addr)) for addr in (":s1", ":s2") ] targets = [*fortran_targets, *smalltalk_targets] assert run_goal(targets, target_type=["fortran"]).strip() == "//:f1\n//:f2" assert run_goal(targets, target_type=["+smalltalk"]).strip() == "//:s1\n//:s2" assert run_goal(targets, target_type=["-smalltalk"]).strip() == "//:f1\n//:f2" # The comma is inside the string, so these are ORed. assert run_goal(targets, target_type=["fortran,smalltalk"]) == dedent("""\ //:f1 //:f2 //:s1 //:s2 """) # A target can only have one type, so this output should be empty. assert run_goal(targets, target_type=["fortran", "smalltalk"]) == "" with pytest.raises(UnrecognizedTargetTypeException): run_goal(targets, target_type=["invalid"])
def assert_injected(deps_cls: Type[Dependencies], *, injected: List[str]) -> None: provided_addr = Address.parse("//:provided") deps_field = deps_cls([provided_addr], address=Address.parse("//:target")) result = self.request_single_product(Addresses, DependenciesRequest(deps_field)) assert result == Addresses( sorted([provided_addr, *(Address.parse(addr) for addr in injected)]) )
def test_multiple_targets_with_multiple_linters(self) -> None: good_address = Address.parse(":good") bad_address = Address.parse(":bad") def get_stdout(*, per_target_caching: bool) -> str: exit_code, stdout = self.run_lint_rule( config_collection_types=[ ConditionallySucceedsConfigurations, SuccessfulConfigurations, ], targets=[ self.make_target_with_origin(good_address), self.make_target_with_origin(bad_address), ], per_target_caching=per_target_caching, ) assert exit_code == ConditionallySucceedsConfigurations.exit_code([bad_address]) return stdout stdout = get_stdout(per_target_caching=False) assert stdout.splitlines() == [ config_collection.stdout([good_address, bad_address]) for config_collection in [ConditionallySucceedsConfigurations, SuccessfulConfigurations] ] stdout = get_stdout(per_target_caching=True) assert stdout.splitlines() == [ config_collection.stdout([address]) for config_collection in [ConditionallySucceedsConfigurations, SuccessfulConfigurations] for address in [good_address, bad_address] ]
def test_get_field() -> None: extensions = ("FortranExt1",) tgt = FortranTarget({FortranExtensions.alias: extensions}, address=Address.parse(":lib")) assert tgt[FortranExtensions].value == extensions assert tgt.get(FortranExtensions).value == extensions assert tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == extensions # Default field value. This happens when the field is registered on the target type, but the # user does not explicitly set the field in the BUILD file. # # NB: `default_raw_value` is not used in this case - that parameter is solely used when # the field is not registered on the target type. To override the default field value, either # subclass the Field and create a new target, or, in your call site, interpret the result and # and apply your default. default_field_tgt = FortranTarget({}, address=Address.parse(":default")) assert default_field_tgt[FortranExtensions].value == () assert default_field_tgt.get(FortranExtensions).value == () assert default_field_tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == () # Example of a call site applying its own default value instead of the field's default value. assert default_field_tgt[FortranExtensions].value or 123 == 123 # Field is not registered on the target. with pytest.raises(KeyError) as exc: default_field_tgt[UnrelatedField] assert UnrelatedField.__name__ in str(exc) assert default_field_tgt.get(UnrelatedField).value == UnrelatedField.default assert default_field_tgt.get( UnrelatedField, default_raw_value=not UnrelatedField.default ).value == (not UnrelatedField.default)
def test_map_third_party_modules_to_addresses(self) -> None: self.add_to_build_file( "3rdparty/python", dedent("""\ python_requirement_library( name='ansicolors', requirements=['ansicolors==1.21'], module_mapping={'ansicolors': ['colors']}, ) python_requirement_library( name='req1', requirements=['req1', 'two_owners'], ) python_requirement_library( name='un_normalized', requirements=['Un-Normalized-Project>3', 'two_owners'], ) """), ) result = self.request_single_product( ThirdPartyModuleToAddressMapping, Params(create_options_bootstrapper())) assert result.mapping == FrozenDict({ "colors": Address.parse("3rdparty/python:ansicolors"), "req1": Address.parse("3rdparty/python:req1"), "un_normalized_project": Address.parse("3rdparty/python:un_normalized"), })
def test_add_custom_fields() -> None: class CustomField(BoolField): alias: ClassVar = "custom_field" default: ClassVar = False union_membership = UnionMembership({FortranTarget.PluginField: [CustomField]}) tgt_values = {CustomField.alias: True} tgt = FortranTarget( tgt_values, address=Address.parse(":lib"), union_membership=union_membership ) assert tgt.field_types == (FortranExtensions, FortranSources, CustomField) assert tgt.core_fields == (FortranExtensions, FortranSources) assert tgt.plugin_fields == (CustomField,) assert tgt.has_field(CustomField) is True assert FortranTarget.class_field_types(union_membership=union_membership) == ( FortranExtensions, FortranSources, CustomField, ) assert FortranTarget.class_has_field(CustomField, union_membership=union_membership) is True assert tgt[CustomField].value is True default_tgt = FortranTarget( {}, address=Address.parse(":default"), union_membership=union_membership ) assert default_tgt[CustomField].value is False
def test_dependency_inference(self) -> None: self.add_to_build_file( "", dedent("""\ smalltalk(name='inferred1') smalltalk(name='inferred2') smalltalk(name='inferred3') smalltalk(name='provided') """), ) self.create_file("demo/f1.st", "//:inferred1\n//:inferred2\n") self.create_file("demo/f2.st", "//:inferred3\n") self.add_to_build_file( "demo", "smalltalk(sources=['*.st'], dependencies=['//:provided'])") deps_field = Dependencies([Address.parse("//:provided")], address=Address.parse("demo")) result = self.request_single_product( Addresses, Params( DependenciesRequest(deps_field), create_options_bootstrapper(args=["--dependency-inference"]), ), ) assert result == Addresses( sorted( Address.parse(addr) for addr in [ "//:inferred1", "//:inferred2", "//:inferred3", "//:provided" ]))
def test_map_first_party_modules_to_addresses(self) -> None: options_bootstrapper = create_options_bootstrapper(args=[ "--source-root-patterns=['src/python', 'tests/python', 'build-support']" ]) util_addr = Address.parse("src/python/project/util") self.create_files("src/python/project/util", ["dirutil.py", "tarutil.py"]) self.add_to_build_file("src/python/project/util", "python_library()") # A module with two owners should not be resolved. self.create_file("src/python/two_owners.py") self.add_to_build_file("src/python", "python_library()") self.create_file("build-support/two_owners.py") self.add_to_build_file("build-support", "python_library()") # A package module self.create_file("tests/python/project_test/demo_test/__init__.py") self.add_to_build_file("tests/python/project_test/demo_test", "python_library()") result = self.request_single_product(FirstPartyModuleToAddressMapping, options_bootstrapper) assert result.mapping == FrozenDict({ "project.util.dirutil": util_addr, "project.util.tarutil": util_addr, "project_test.demo_test": Address.parse("tests/python/project_test/demo_test"), })
def test_timeout_validation(self) -> None: with pytest.raises(InvalidFieldException): PythonTestsTimeout(-100, address=Address.parse(":tests")) with pytest.raises(InvalidFieldException): PythonTestsTimeout(0, address=Address.parse(":tests")) assert PythonTestsTimeout(5, address=Address.parse(":tests")).value == 5
def test_multiple_languages_with_single_targets(self) -> None: fortran_address = Address.parse(":fortran") smalltalk_address = Address.parse(":smalltalk") def assert_expected(*, per_target_caching: bool) -> None: stdout = self.run_fmt_rule( language_target_collection_types=[ FortranTargets, SmalltalkTargets ], targets=[ self.make_target_with_origin(fortran_address, target_cls=FortranTarget), self.make_target_with_origin(smalltalk_address, target_cls=SmalltalkTarget), ], result_digest=self.merged_digest, per_target_caching=per_target_caching, ) assert stdout.splitlines() == [ FortranTargets.stdout([fortran_address]), SmalltalkTargets.stdout([smalltalk_address]), ] self.assert_workspace_modified(fortran_formatted=True, smalltalk_formatted=True) assert_expected(per_target_caching=False) assert_expected(per_target_caching=True)
def test_multiple_languages_with_single_targets(self) -> None: fortran_address = Address.parse(":fortran") smalltalk_address = Address.parse(":smalltalk") def assert_expected(*, per_target_caching: bool) -> None: stderr = self.run_fmt_rule( language_target_collection_types=[FortranTargets, SmalltalkTargets], targets=[ self.make_target(fortran_address, target_cls=FortranTarget), self.make_target(smalltalk_address, target_cls=SmalltalkTarget), ], result_digest=self.merged_digest, per_target_caching=per_target_caching, ) assert stderr == dedent( f"""\ 𐄂 FortranFormatter made changes. {FortranTargets.stdout([fortran_address])} 𐄂 SmalltalkFormatter made changes. {SmalltalkTargets.stdout([smalltalk_address])} """ ) self.assert_workspace_modified(fortran_formatted=True, smalltalk_formatted=True) assert_expected(per_target_caching=False) assert_expected(per_target_caching=True)
def test_multiple_targets_with_one_linter(self) -> None: good_address = Address.parse(":good") bad_address = Address.parse(":bad") def get_stderr(*, per_target_caching: bool) -> str: exit_code, stderr = self.run_lint_rule( lint_request_types=[ConditionallySucceedsRequest], targets=[self.make_target(good_address), self.make_target(bad_address),], per_target_caching=per_target_caching, ) assert exit_code == ConditionallySucceedsRequest.exit_code([bad_address]) return stderr assert get_stderr(per_target_caching=False) == dedent( f"""\ 𐄂 ConditionallySucceedsLinter failed. {ConditionallySucceedsRequest.stdout([good_address, bad_address])} """ ) assert get_stderr(per_target_caching=True) == dedent( f"""\ ✓ ConditionallySucceedsLinter succeeded. {ConditionallySucceedsRequest.stdout([good_address])} 𐄂 ConditionallySucceedsLinter failed. {ConditionallySucceedsRequest.stdout([bad_address])} """ )
def test_list_provides() -> None: sample_artifact = PythonArtifact(name="project.demo", version="0.0.0.1") targets = [ MockTarget({ProvidesField.alias: sample_artifact}, address=Address.parse(":provided")), MockTarget({}, address=Address.parse(":not_provided")), ] stdout, _ = run_goal(targets, show_provides=True) assert stdout.strip() == f"//:provided {sample_artifact}"
def test_configuration() -> None: class UnrelatedField(StringField): alias = "unrelated_field" default = "default" value: str class UnrelatedTarget(Target): alias = "unrelated_target" core_fields = (UnrelatedField, ) class NoFieldsTarget(Target): alias = "no_fields_target" core_fields = () @dataclass(frozen=True) class FortranConfiguration(Configuration): required_fields = (FortranSources, ) sources: FortranSources unrelated_field: UnrelatedField @dataclass(frozen=True) class UnrelatedFieldConfiguration(ConfigurationWithOrigin): required_fields = () unrelated_field: UnrelatedField fortran_addr = Address.parse(":fortran") fortran_tgt = FortranTarget({}, address=fortran_addr) unrelated_addr = Address.parse(":unrelated") unrelated_tgt = UnrelatedTarget({UnrelatedField.alias: "configured"}, address=unrelated_addr) no_fields_addr = Address.parse(":no_fields") no_fields_tgt = NoFieldsTarget({}, address=no_fields_addr) assert FortranConfiguration.is_valid(fortran_tgt) is True assert FortranConfiguration.is_valid(unrelated_tgt) is False assert FortranConfiguration.is_valid(no_fields_tgt) is False # When no fields are required, every target is valid. for tgt in [fortran_tgt, unrelated_tgt, no_fields_tgt]: assert UnrelatedFieldConfiguration.is_valid(tgt) is True valid_fortran_config = FortranConfiguration.create(fortran_tgt) assert valid_fortran_config.address == fortran_addr assert valid_fortran_config.unrelated_field.value == UnrelatedField.default with pytest.raises(KeyError): FortranConfiguration.create(unrelated_tgt) origin = FilesystemLiteralSpec("f.txt") assert (UnrelatedFieldConfiguration.create( TargetWithOrigin(fortran_tgt, origin)).origin == origin) assert (UnrelatedFieldConfiguration.create( TargetWithOrigin(unrelated_tgt, origin)).unrelated_field.value == "configured") assert (UnrelatedFieldConfiguration.create( TargetWithOrigin( no_fields_tgt, origin)).unrelated_field.value == UnrelatedField.default)
def test_multiple_debug_targets_fail(self) -> None: with pytest.raises(ResolveError): self.run_test_rule( config=SuccessfulConfiguration, targets=[ self.make_target_with_origin(Address.parse(":t1")), self.make_target_with_origin(Address.parse(":t2")), ], debug=True, )
def test_coverage(self) -> None: addr1 = Address.parse(":t1") addr2 = Address.parse(":t2") exit_code, stderr = self.run_test_rule( field_set=SuccessfulFieldSet, targets=[self.make_target_with_origin(addr1), self.make_target_with_origin(addr2)], use_coverage=True, ) assert exit_code == 0 assert stderr.strip().endswith(f"Ran coverage on {addr1.spec}, {addr2.spec}")
def test_infer_python_dependencies(self) -> None: options_bootstrapper = create_options_bootstrapper( args=["--source-root-patterns=src/python"] ) self.add_to_build_file( "3rdparty/python", dedent( """\ python_requirement_library( name='Django', requirements=[python_requirement('Django==1.21')], ) """ ), ) self.create_file("src/python/no_owner/f.py") self.add_to_build_file("src/python/no_owner", "python_library()") self.create_file("src/python/util/dep.py") self.add_to_build_file("src/python/util", "python_library()") self.create_file( "src/python/app.py", dedent( """\ import django from util.dep import Demo from util import dep """ ), ) self.create_file( "src/python/f2.py", dedent( """\ import typing # Import from another file in the same target. from app import main """ ), ) self.add_to_build_file("src/python", "python_library()") tgt = self.request_single_product(WrappedTarget, Address.parse("src/python")).target result = self.request_single_product( InferredDependencies, Params(InferPythonDependencies(tgt[PythonSources]), options_bootstrapper), ) assert result == InferredDependencies( [Address.parse("3rdparty/python:Django"), Address.parse("src/python/util")] )
def test_generate_subtarget() -> None: class MockTarget(Target): alias = "mock_target" core_fields = (Dependencies, Tags, Sources) # When the target already only has a single source, the result should be the same, except for a # different address. single_source_tgt = MockTarget( {Sources.alias: ["demo.f95"], Tags.alias: ["demo"]}, address=Address.parse("src/fortran:demo"), ) expected_single_source_address = Address( "src/fortran", relative_file_path="demo.f95", target_name="demo" ) assert generate_subtarget( single_source_tgt, full_file_name="src/fortran/demo.f95" ) == MockTarget( {Sources.alias: ["demo.f95"], Tags.alias: ["demo"]}, address=expected_single_source_address ) assert ( generate_subtarget_address(single_source_tgt.address, full_file_name="src/fortran/demo.f95") == expected_single_source_address ) subdir_tgt = MockTarget( {Sources.alias: ["demo.f95", "subdir/demo.f95"]}, address=Address.parse("src/fortran:demo") ) expected_subdir_address = Address( "src/fortran", relative_file_path="subdir/demo.f95", target_name="demo" ) assert generate_subtarget( subdir_tgt, full_file_name="src/fortran/subdir/demo.f95" ) == MockTarget({Sources.alias: ["subdir/demo.f95"]}, address=expected_subdir_address) assert ( generate_subtarget_address(subdir_tgt.address, full_file_name="src/fortran/subdir/demo.f95") == expected_subdir_address ) # The full_file_name must match the filespec of the base target's Sources field. with pytest.raises(ValueError) as exc: generate_subtarget(single_source_tgt, full_file_name="src/fortran/fake_file.f95") assert "does not match a file src/fortran/fake_file.f95" in str(exc.value) class MissingFieldsTarget(Target): alias = "missing_fields_tgt" core_fields = (Tags,) missing_fields_tgt = MissingFieldsTarget( {Tags.alias: ["demo"]}, address=Address("", target_name="missing_fields") ) with pytest.raises(ValueError) as exc: generate_subtarget(missing_fields_tgt, full_file_name="fake.txt") assert "does not have both a `dependencies` and `sources` field" in str(exc.value)
def test_normal_resolution(self) -> None: self.add_to_build_file("src/smalltalk", "smalltalk()") addr = Address.parse("src/smalltalk") deps = Addresses([Address.parse("//:dep1"), Address.parse("//:dep2")]) deps_field = Dependencies(deps, address=addr) assert self.request_single_product(Addresses, DependenciesRequest(deps_field)) == deps # Also test that we handle no dependencies. empty_deps_field = Dependencies(None, address=addr) assert self.request_single_product( Addresses, DependenciesRequest(empty_deps_field) ) == Addresses([])
def test_resolve_cache(self) -> None: scheduler = self.create_json() nonstrict_address = Address.parse("graph_test:nonstrict") nonstrict = self.resolve(scheduler, nonstrict_address) self.assertEqual(nonstrict, self.resolve(scheduler, nonstrict_address)) # The already resolved `nonstrict` interior node should be re-used by `java1`. java1_address = Address.parse("graph_test:java1") java1 = self.resolve(scheduler, java1_address) self.assertEqual(nonstrict, java1.configurations[1]) self.assertEqual(java1, self.resolve(scheduler, java1_address))
def test_third_party_modules_mapping() -> None: colors_addr = Address.parse("//:ansicolors") pants_addr = Address.parse("//:pantsbuild") mapping = ThirdPartyModuleToAddressMapping( FrozenDict({ "colors": colors_addr, "pants": pants_addr })) assert mapping.address_for_module("colors") == colors_addr assert mapping.address_for_module("colors.red") == colors_addr assert mapping.address_for_module("pants") == pants_addr assert mapping.address_for_module("pants.task") == pants_addr assert mapping.address_for_module("pants.task.task") == pants_addr assert mapping.address_for_module("pants.task.task.Task") == pants_addr
def test_field_set() -> None: class UnrelatedField(StringField): alias = "unrelated_field" default = "default" value: str class UnrelatedTarget(Target): alias = "unrelated_target" core_fields = (UnrelatedField,) class NoFieldsTarget(Target): alias = "no_fields_target" core_fields = () @dataclass(frozen=True) class FortranFieldSet(FieldSet): required_fields = (FortranSources,) sources: FortranSources unrelated_field: UnrelatedField @dataclass(frozen=True) class UnrelatedFieldSet(FieldSet): required_fields = () unrelated_field: UnrelatedField fortran_addr = Address.parse(":fortran") fortran_tgt = FortranTarget({}, address=fortran_addr) unrelated_addr = Address.parse(":unrelated") unrelated_tgt = UnrelatedTarget({UnrelatedField.alias: "configured"}, address=unrelated_addr) no_fields_addr = Address.parse(":no_fields") no_fields_tgt = NoFieldsTarget({}, address=no_fields_addr) assert FortranFieldSet.is_applicable(fortran_tgt) is True assert FortranFieldSet.is_applicable(unrelated_tgt) is False assert FortranFieldSet.is_applicable(no_fields_tgt) is False # When no fields are required, every target is applicable. for tgt in [fortran_tgt, unrelated_tgt, no_fields_tgt]: assert UnrelatedFieldSet.is_applicable(tgt) is True valid_fortran_field_set = FortranFieldSet.create(fortran_tgt) assert valid_fortran_field_set.address == fortran_addr assert valid_fortran_field_set.unrelated_field.value == UnrelatedField.default with pytest.raises(KeyError): FortranFieldSet.create(unrelated_tgt) assert UnrelatedFieldSet.create(unrelated_tgt).unrelated_field.value == "configured" assert UnrelatedFieldSet.create(no_fields_tgt).unrelated_field.value == UnrelatedField.default
def test_map_module_to_targets(self) -> None: options_bootstrapper = create_options_bootstrapper(args=[ "--source-root-patterns=['source_root1', 'source_root2', '/']" ]) # First check that we can map 3rd-party modules. self.add_to_build_file( "3rdparty/python", dedent("""\ python_requirement_library( name='ansicolors', requirements=[python_requirement('ansicolors==1.21', modules=['colors'])], ) """), ) result = self.request_single_product( PythonModuleOwners, Params(PythonModule("colors.red"), options_bootstrapper)) assert result == PythonModuleOwners( [Address.parse("3rdparty/python:ansicolors")]) # We set up the same module in two source roots to confirm we properly handle source roots. # The first example uses a normal module path, whereas the second uses a package path. self.create_file("source_root1/project/app.py") self.add_to_build_file("source_root1/project", "python_library()") self.create_file("source_root2/project/app/__init__.py") self.add_to_build_file("source_root2/project/app", "python_library()") result = self.request_single_product( PythonModuleOwners, Params(PythonModule("project.app"), options_bootstrapper)) assert result == PythonModuleOwners([ Address.parse("source_root1/project"), Address.parse("source_root2/project/app") ]) # Test a module with no owner (stdlib). This also sanity checks that we can handle when # there is no parent module. result = self.request_single_product( PythonModuleOwners, Params(PythonModule("typing"), options_bootstrapper)) assert not result # Test a module with a single owner with a top-level source root of ".". Also confirm we # can handle when the module includes a symbol (like a class name) at the end. self.create_file("script.py") self.add_to_build_file("", "python_library(name='script')") result = self.request_single_product( PythonModuleOwners, Params(PythonModule("script.Demo"), options_bootstrapper)) assert result == PythonModuleOwners([Address.parse("//:script")])
def make_target_with_origin(address: Optional[Address] = None) -> TargetWithOrigin: if address is None: address = Address.parse(":tests") return TargetWithOrigin( MockTarget({}, address=address), origin=SingleAddress(directory=address.spec_path, name=address.target_name), )
def test_sequence_field() -> None: @dataclass(frozen=True) class CustomObject: pass class Example(SequenceField): alias = "example" expected_element_type = CustomObject expected_type_description = "an iterable of `CustomObject` instances" @classmethod def compute_value( cls, raw_value: Optional[Iterable[CustomObject]], *, address: Address) -> Optional[Tuple[CustomObject, ...]]: return super().compute_value(raw_value, address=address) addr = Address.parse(":example") def assert_flexible_constructor(raw_value: Iterable[CustomObject]) -> None: assert Example(raw_value, address=addr).value == tuple(raw_value) assert_flexible_constructor([CustomObject(), CustomObject()]) assert_flexible_constructor((CustomObject(), CustomObject())) assert_flexible_constructor(OrderedSet([CustomObject(), CustomObject()])) # Must be given a sequence, not a single element. with pytest.raises(InvalidFieldTypeException) as exc: Example(CustomObject(), address=addr) assert Example.expected_type_description in str(exc.value) # All elements must be the expected type. with pytest.raises(InvalidFieldTypeException): Example([CustomObject(), 1, CustomObject()], address=addr)
def test_dependencies_and_sources_fields_raw_value_sanitation() -> None: """Ensure that both Sources and Dependencies behave like a StringSequenceField does. Normally, we would use StringSequenceField. However, these are both AsyncFields, and StringSequenceField is a PrimitiveField, so we end up replicating that validation logic. """ addr = Address.parse(":test") def assert_flexible_constructor(raw_value: Iterable[str]) -> None: assert Sources(raw_value, address=addr).sanitized_raw_value == tuple(raw_value) assert Dependencies( raw_value, address=addr).sanitized_raw_value == tuple(raw_value) for v in [("f1.txt", "f2.txt"), ["f1.txt", "f2.txt"], OrderedSet(["f1.txt", "f2.txt"])]: assert_flexible_constructor(v) def assert_invalid_type(raw_value: Any) -> None: with pytest.raises(InvalidFieldTypeException): Sources(raw_value, address=addr) with pytest.raises(InvalidFieldTypeException): Dependencies(raw_value, address=addr) for v in [0, object(), "f1.txt"]: # type: ignore[assignment] assert_invalid_type(v)
def test_dict_string_to_string_sequence_field() -> None: class Example(DictStringToStringSequenceField): alias = "example" addr = Address.parse(":example") def assert_flexible_constructor( raw_value: Dict[str, Iterable[str]]) -> None: assert Example(raw_value, address=addr).value == FrozenDict( {k: tuple(v) for k, v in raw_value.items()}) for v in [("hello", "world"), ["hello", "world"], OrderedSet(["hello", "world"])]: assert_flexible_constructor({"greeting": v}) def assert_invalid_type(raw_value: Any) -> None: with pytest.raises(InvalidFieldTypeException): Example(raw_value, address=addr) for v in [ # type: ignore[assignment] 0, object(), "hello", ["hello"], { "hello": "world" }, { 0: ["world"] }, ]: assert_invalid_type(v)