Ejemplo n.º 1
0
 def assert_resolved(specs: Iterable[str], expected: set[str]) -> None:
     specs_obj = SpecsParser().parse_specs(
         specs,
         convert_dir_literal_to_address_literal=False,
         description_of_origin="tests")
     result = rule_runner.request(Addresses, [specs_obj])
     assert {addr.spec for addr in result} == expected
Ejemplo n.º 2
0
    def run_goal_rule(
        self,
        goal: Type[Goal],
        *,
        global_args: Optional[Iterable[str]] = None,
        args: Optional[Iterable[str]] = None,
        env: Optional[Mapping[str, str]] = None,
    ) -> GoalRuleResult:
        options_bootstrapper = create_options_bootstrapper(
            args=(*(global_args or []), goal.name, *(args or [])),
            env=env,
        )

        raw_specs = options_bootstrapper.get_full_options(
            [*GlobalOptions.known_scope_infos(), *goal.subsystem_cls.known_scope_infos()]
        ).specs
        specs = SpecsParser(self.build_root).parse_specs(raw_specs)

        stdout, stderr = StringIO(), StringIO()
        console = Console(stdout=stdout, stderr=stderr)

        exit_code = self.scheduler.run_goal_rule(
            goal,
            Params(
                specs,
                console,
                options_bootstrapper,
                Workspace(self.scheduler),
                InteractiveRunner(self.scheduler),
            ),
        )

        console.flush()
        return GoalRuleResult(exit_code, stdout.getvalue(), stderr.getvalue())
Ejemplo n.º 3
0
    def run_goal_rule(
        self,
        goal: type[Goal],
        *,
        global_args: Iterable[str] | None = None,
        args: Iterable[str] | None = None,
        env: Mapping[str, str] | None = None,
        env_inherit: set[str] | None = None,
    ) -> GoalRuleResult:
        merged_args = (*(global_args or []), goal.name, *(args or []))
        self.set_options(merged_args, env=env, env_inherit=env_inherit)

        raw_specs = self.options_bootstrapper.full_options_for_scopes([
            GlobalOptions.get_scope_info(),
            goal.subsystem_cls.get_scope_info()
        ]).specs
        specs = SpecsParser(self.build_root).parse_specs(raw_specs)

        stdout, stderr = StringIO(), StringIO()
        console = Console(stdout=stdout,
                          stderr=stderr,
                          use_colors=False,
                          session=self.scheduler)

        exit_code = self.scheduler.run_goal_rule(
            goal,
            Params(
                specs,
                console,
                Workspace(self.scheduler),
            ),
        )

        console.flush()
        return GoalRuleResult(exit_code, stdout.getvalue(), stderr.getvalue())
Ejemplo n.º 4
0
def test_streaming_workunits_expanded_specs(run_tracker: RunTracker) -> None:
    rule_runner = RuleRunner(
        target_types=[PythonSourcesGeneratorTarget],
        rules=[
            QueryRule(ProcessResult, (Process, )),
        ],
    )
    rule_runner.set_options(["--backend-packages=pants.backend.python"])
    rule_runner.write_files({
        "src/python/somefiles/BUILD": "python_sources()",
        "src/python/somefiles/a.py": "print('')",
        "src/python/somefiles/b.py": "print('')",
        "src/python/others/BUILD": "python_sources()",
        "src/python/others/a.py": "print('')",
        "src/python/others/b.py": "print('')",
    })
    specs = SpecsParser().parse_specs(
        ["src/python/somefiles::", "src/python/others/b.py"],
        convert_dir_literal_to_address_literal=False,
        description_of_origin="tests",
    )

    class Callback(WorkunitsCallback):
        @property
        def can_finish_async(self) -> bool:
            return False

        def __call__(self, **kwargs) -> None:
            context = kwargs["context"]
            assert isinstance(context, StreamingWorkunitContext)

            expanded = context.get_expanded_specs()
            targets = expanded.targets

            assert len(targets.keys()) == 2
            assert targets["src/python/others/b.py"] == [
                TargetInfo(filename="src/python/others/b.py")
            ]
            assert set(targets["src/python/somefiles"]) == {
                TargetInfo(filename="src/python/somefiles/a.py"),
                TargetInfo(filename="src/python/somefiles/b.py"),
            }

    handler = StreamingWorkunitHandler(
        scheduler=rule_runner.scheduler,
        run_tracker=run_tracker,
        callbacks=[Callback()],
        report_interval_seconds=0.01,
        max_workunit_verbosity=LogLevel.INFO,
        specs=specs,
        options_bootstrapper=create_options_bootstrapper(
            ["--backend-packages=pants.backend.python"]),
        allow_async_completion=False,
    )
    stdout_process = Process(argv=("/bin/bash", "-c",
                                   "/bin/echo 'stdout output'"),
                             description="Stdout process")
    with handler:
        rule_runner.request(ProcessResult, [stdout_process])
Ejemplo n.º 5
0
 def assert_paths(specs: Iterable[str], expected_files: set[str],
                  expected_dirs: set[str]) -> None:
     specs_obj = SpecsParser().parse_specs(
         specs,
         convert_dir_literal_to_address_literal=False,
         description_of_origin="tests")
     result = rule_runner.request(SpecsPaths, [specs_obj])
     assert set(result.files) == expected_files
     assert set(result.dirs) == expected_dirs
Ejemplo n.º 6
0
def calculate_specs(
    options_bootstrapper: OptionsBootstrapper,
    options: Options,
    session: SchedulerSession,
    *,
    build_root: Optional[str] = None,
) -> Specs:
    """Determine the specs for a given Pants run."""
    build_root = build_root or get_buildroot()
    specs = SpecsParser(build_root).parse_specs(options.specs)
    changed_options = ChangedOptions.from_options(options.for_scope("changed"))

    logger.debug("specs are: %s", specs)
    logger.debug("changed_options are: %s", changed_options)

    if specs.provided and changed_options.provided:
        changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
        if specs.filesystem_specs and specs.address_specs:
            specs_description = "target and file arguments"
        elif specs.filesystem_specs:
            specs_description = "file arguments"
        else:
            specs_description = "target arguments"
        raise InvalidSpecConstraint(
            f"You used `{changed_name}` at the same time as using {specs_description}. Please "
            "use only one.")

    if not changed_options.provided:
        return specs

    git = get_git()
    if not git:
        raise InvalidSpecConstraint(
            "The `--changed-*` options are only available if Git is used for the repository."
        )
    changed_request = ChangedRequest(
        sources=tuple(changed_options.changed_files(git)),
        dependees=changed_options.dependees,
    )
    (changed_addresses, ) = session.product_request(
        ChangedAddresses, [Params(changed_request, options_bootstrapper)])
    logger.debug("changed addresses: %s", changed_addresses)

    address_specs = []
    for address in cast(ChangedAddresses, changed_addresses):
        address_input = AddressInput.parse(address.spec)
        address_specs.append(
            AddressLiteralSpec(
                path_component=address_input.path_component,
                # NB: AddressInput.target_component may be None, but AddressLiteralSpec expects a
                # string.
                target_component=address_input.target_component
                or address.target_name,
            ))
    return Specs(AddressSpecs(address_specs, filter_by_global_options=True),
                 FilesystemSpecs([]))
Ejemplo n.º 7
0
def test_streaming_workunits_expanded_specs(run_tracker: RunTracker) -> None:

    rule_runner = RuleRunner(
        target_types=[PythonLibrary],
        rules=[
            QueryRule(ProcessResult, (Process, )),
        ],
    )

    rule_runner.set_options(["--backend-packages=pants.backend.python"])

    rule_runner.create_file("src/python/somefiles/BUILD", "python_library()")
    rule_runner.create_file("src/python/somefiles/a.py", "print('')")
    rule_runner.create_file("src/python/somefiles/b.py", "print('')")

    rule_runner.create_file("src/python/others/BUILD", "python_library()")
    rule_runner.create_file("src/python/others/a.py", "print('')")
    rule_runner.create_file("src/python/others/b.py", "print('')")

    specs = SpecsParser(get_buildroot()).parse_specs(
        ["src/python/somefiles::", "src/python/others/b.py"])

    def callback(**kwargs) -> None:
        context = kwargs["context"]
        assert isinstance(context, StreamingWorkunitContext)

        expanded = context.get_expanded_specs()
        targets = expanded.targets

        assert len(targets.keys()) == 2
        assert targets["src/python/others/b.py"] == [
            TargetInfo(filename="src/python/others/b.py")
        ]
        assert set(targets["src/python/somefiles"]) == {
            TargetInfo(filename="src/python/somefiles/a.py"),
            TargetInfo(filename="src/python/somefiles/b.py"),
        }

    handler = StreamingWorkunitHandler(
        scheduler=rule_runner.scheduler,
        run_tracker=run_tracker,
        callbacks=[callback],
        report_interval_seconds=0.01,
        max_workunit_verbosity=LogLevel.INFO,
        specs=specs,
        options_bootstrapper=create_options_bootstrapper(
            ["--backend-packages=pants.backend.python"]),
    )

    stdout_process = Process(argv=("/bin/bash", "-c",
                                   "/bin/echo 'stdout output'"),
                             description="Stdout process")

    with handler.session():
        rule_runner.request(ProcessResult, [stdout_process])
Ejemplo n.º 8
0
    def run(self, start_time: float) -> ExitCode:
        spec_parser = SpecsParser(get_buildroot())
        specs = [
            str(spec_parser.parse_spec(spec)) for spec in self.options.specs
        ]
        self.run_tracker.start(run_start_time=start_time, specs=specs)

        with maybe_profiled(self.profile_path):
            global_options = self.options.for_global_scope()

            if self.options.help_request:
                return self._print_help(self.options.help_request)

            streaming_reporter = StreamingWorkunitHandler(
                self.graph_session.scheduler_session,
                run_tracker=self.run_tracker,
                specs=self.specs,
                options_bootstrapper=self.options_bootstrapper,
                callbacks=self._get_workunits_callbacks(),
                report_interval_seconds=global_options.
                streaming_workunits_report_interval,
            )

            goals = tuple(self.options.goals)
            with streaming_reporter.session():
                if not goals:
                    return PANTS_SUCCEEDED_EXIT_CODE
                engine_result = PANTS_FAILED_EXIT_CODE
                try:
                    engine_result = self._perform_run(goals)
                except Exception as e:
                    ExceptionSink.log_exception(e)

                metrics = self.graph_session.scheduler_session.metrics()
                self.run_tracker.set_pantsd_scheduler_metrics(metrics)
                self.run_tracker.end_run(engine_result)

            return engine_result
Ejemplo n.º 9
0
    def execute_rule(
        self,
        *,
        args: Optional[Iterable[str]] = None,
        global_args: Optional[Iterable[str]] = None,
        env: Optional[Dict[str, str]] = None,
        exit_code: int = 0,
        additional_params: Optional[Iterable[Any]] = None,
    ) -> GoalRuleResult:
        """Executes the @goal_rule for this test class.

        Returns the return code, stdout, and stderr of the goal.
        """
        # Create an OptionsBootstrapper for these args/env, and a captured Console instance.
        options_bootstrapper = create_options_bootstrapper(
            args=(*(global_args or []), self.goal_cls.name, *(args or [])),
            env=env,
        )
        BuildConfigInitializer.get(options_bootstrapper)
        full_options = options_bootstrapper.get_full_options([
            *GlobalOptions.known_scope_infos(),
            *self.goal_cls.subsystem_cls.known_scope_infos()
        ])
        stdout, stderr = StringIO(), StringIO()
        console = Console(stdout=stdout, stderr=stderr)

        # Run for the specs parsed from the args.
        specs = SpecsParser(self.build_root).parse_specs(full_options.specs)
        params = Params(
            specs,
            console,
            options_bootstrapper,
            Workspace(self.scheduler),
            *(additional_params or []),
        )
        actual_exit_code = self.scheduler.run_goal_rule(self.goal_cls, params)

        # Flush and capture console output.
        console.flush()
        stdout_val = stdout.getvalue()
        stderr_val = stderr.getvalue()

        assert (
            exit_code == actual_exit_code
        ), f"Exited with {actual_exit_code} (expected {exit_code}):\nstdout:\n{stdout_val}\nstderr:\n{stderr_val}"

        return GoalRuleResult(actual_exit_code, stdout_val, stderr_val)
Ejemplo n.º 10
0
    def run_goal_rule(
        self,
        goal: Type[Goal],
        *,
        global_args: Optional[Iterable[str]] = None,
        args: Optional[Iterable[str]] = None,
        env: Optional[Mapping[str, str]] = None,
    ) -> GoalRuleResult:
        options_bootstrapper = create_options_bootstrapper(
            args=(*(global_args or []), goal.name, *(args or [])),
            env=env,
        )

        raw_specs = options_bootstrapper.get_full_options([
            *GlobalOptions.known_scope_infos(),
            *goal.subsystem_cls.known_scope_infos()
        ]).specs
        specs = SpecsParser(self.build_root).parse_specs(raw_specs)

        stdout, stderr = StringIO(), StringIO()
        console = Console(stdout=stdout, stderr=stderr)

        session = self.scheduler.scheduler.new_session(
            build_id="buildid_for_test",
            should_report_workunits=True,
            session_values=SessionValues({
                OptionsBootstrapper:
                options_bootstrapper,
                PantsEnvironment:
                PantsEnvironment(env)
            }),
        )

        exit_code = session.run_goal_rule(
            goal,
            Params(
                specs,
                console,
                Workspace(self.scheduler),
                InteractiveRunner(self.scheduler),
            ),
        )

        console.flush()
        return GoalRuleResult(exit_code, stdout.getvalue(), stderr.getvalue())
Ejemplo n.º 11
0
def test_resolve_addresses_from_raw_specs(rule_runner: RuleRunner) -> None:
    """This tests that we correctly handle resolving from both specs with and without owners."""
    rule_runner.write_files({
        "fs_spec/f.txt":
        "",
        "fs_spec/BUILD":
        "file_generator(sources=['f.txt'])",
        "address_spec/f.txt":
        "",
        "address_spec/BUILD":
        dedent("""\
                file_generator(sources=['f.txt'])
                nonfile_generator(name='nonfile')
                """),
        "multiple_files/f1.txt":
        "",
        "multiple_files/f2.txt":
        "",
        "multiple_files/BUILD":
        "file_generator(sources=['*.txt'])",
    })

    no_interaction_specs = [
        "fs_spec/f.txt",
        "address_spec:address_spec",
        "address_spec:nonfile#gen",
    ]
    multiple_files_specs = [
        "multiple_files/f2.txt", "multiple_files:multiple_files"
    ]
    specs = SpecsParser(rule_runner.build_root).parse_specs(
        [*no_interaction_specs, *multiple_files_specs],
        convert_dir_literal_to_address_literal=False,
        description_of_origin="tests",
    )

    result = rule_runner.request(Addresses, [specs])
    assert set(result) == {
        Address("fs_spec", relative_file_path="f.txt"),
        Address("address_spec"),
        Address("address_spec", target_name="nonfile", generated_name="gen"),
        Address("multiple_files"),
        Address("multiple_files", relative_file_path="f2.txt"),
    }
Ejemplo n.º 12
0
async def paths(
    console: Console, paths_subsystem: PathsSubsystem, global_options: GlobalOptions
) -> PathsGoal:

    path_from = paths_subsystem.path_from
    path_to = paths_subsystem.path_to

    if path_from is None:
        raise ValueError("Must set --from")

    if path_to is None:
        raise ValueError("Must set --to")

    specs_parser = SpecsParser()

    convert_dir_literals = global_options.use_deprecated_directory_cli_args_semantics
    from_tgts, to_tgts = await MultiGet(
        Get(
            Targets,
            Specs,
            specs_parser.parse_specs(
                [path_from],
                description_of_origin="the option `--paths-from`",
                convert_dir_literal_to_address_literal=convert_dir_literals,
            ),
        ),
        Get(
            Targets,
            Specs,
            specs_parser.parse_specs(
                [path_to],
                description_of_origin="the option `--paths-to`",
                convert_dir_literal_to_address_literal=convert_dir_literals,
            ),
        ),
    )
    root = from_tgts.expect_single()
    destination = to_tgts.expect_single()

    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest([root.address], include_special_cased_deps=True)
    )

    adjacent_targets_per_target = await MultiGet(
        Get(
            Targets,
            DependenciesRequest(tgt.get(Dependencies), include_special_cased_deps=True),
        )
        for tgt in transitive_targets.closure
    )

    transitive_targets_closure_addresses = (t.address for t in transitive_targets.closure)
    adjacency_lists = dict(zip(transitive_targets_closure_addresses, adjacent_targets_per_target))

    spec_paths = []
    for path in find_paths_breadth_first(adjacency_lists, root.address, destination.address):
        spec_path = [address.spec for address in path]
        spec_paths.append(spec_path)

    with paths_subsystem.output(console) as write_stdout:
        write_stdout(json.dumps(spec_paths, indent=2))

    return PathsGoal(exit_code=0)
Ejemplo n.º 13
0
def assert_filesystem_spec_parsed(build_root: Path, spec_str: str,
                                  expected_spec: FilesystemSpec) -> None:
    parser = SpecsParser(str(build_root))
    spec = parser.parse_spec(spec_str)
    assert isinstance(spec, FilesystemSpec)
    assert spec == expected_spec
Ejemplo n.º 14
0
def assert_address_spec_parsed(build_root: Path, spec_str: str,
                               expected_spec: AddressSpec) -> None:
    parser = SpecsParser(str(build_root))
    spec = parser.parse_spec(spec_str)
    assert isinstance(spec, AddressSpec)
    assert spec == expected_spec
Ejemplo n.º 15
0
import re
from dataclasses import asdict
from typing import Iterable, Iterator, List, Optional

import strawberry
from strawberry.types import Info

from pants.backend.explorer.graphql.context import GraphQLContext
from pants.backend.explorer.graphql.field_types import JSONScalar
from pants.backend.project_info.peek import TargetData, TargetDatas
from pants.base.specs_parser import SpecsParser
from pants.engine.target import AllUnexpandedTargets, UnexpandedTargets
from pants.help.help_info_extracter import TargetTypeHelpInfo
from pants.util.strutil import softwrap

specs_parser = SpecsParser()


@strawberry.type(description="Describes a target field type.")
class TargetTypeField:
    alias: str = strawberry.field(
        description=
        "The field name, as used in a target definition in a BUILD file.")
    provider: str = strawberry.field(
        description="Backend that registered the field type.")
    description: str = strawberry.field(description="Field documentation.")
    type_hint: str = strawberry.field(description="Field type hint.")
    required: bool = strawberry.field(description="Field required flag.")
    default: Optional[str] = strawberry.field(
        description="Field default value.")
Ejemplo n.º 16
0
def calculate_specs(
    options_bootstrapper: OptionsBootstrapper,
    options: Options,
    session: SchedulerSession,
) -> Specs:
    """Determine the specs for a given Pants run."""
    global_options = options.for_global_scope()
    unmatched_cli_globs = global_options.unmatched_cli_globs.to_glob_match_error_behavior(
    )
    convert_dir_literal_to_address_literal = (
        global_options.use_deprecated_directory_cli_args_semantics)
    if global_options.is_default(
            "use_deprecated_directory_cli_args_semantics"):
        warn_or_error(
            "2.14.0.dev0",
            "`use_deprecated_directory_cli_args_semantics` defaulting to True",
            softwrap(f"""
                Currently, a directory argument like `{bin_name()} test dir` is shorthand for the
                target `dir:dir`, i.e. the target that leaves off `name=`.

                In Pants 2.14, by default, a directory argument will instead match all
                targets/files in the directory.

                To opt into the new and more intuitive semantics early, set
                `use_deprecated_directory_cli_args_semantics = false` in the `[GLOBAL]` section in
                `pants.toml`. Otherwise, set to `true` to silence this warning.
                """),
        )
    specs = SpecsParser().parse_specs(
        options.specs,
        description_of_origin="CLI arguments",
        unmatched_glob_behavior=unmatched_cli_globs,
        convert_dir_literal_to_address_literal=
        convert_dir_literal_to_address_literal,
    )

    changed_options = ChangedOptions.from_options(options.for_scope("changed"))
    logger.debug("specs are: %s", specs)
    logger.debug("changed_options are: %s", changed_options)

    if specs and changed_options.provided:
        changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
        specs_description = specs.arguments_provided_description()
        assert specs_description is not None
        raise InvalidSpecConstraint(
            f"You used `{changed_name}` at the same time as using {specs_description}. You can "
            f"only use `{changed_name}` or use normal arguments.")

    if not changed_options.provided:
        return specs

    (git_binary, ) = session.product_request(GitBinary,
                                             [Params(GitBinaryRequest())])
    (maybe_git_worktree, ) = session.product_request(
        MaybeGitWorktree, [Params(GitWorktreeRequest(), git_binary)])
    if not maybe_git_worktree.git_worktree:
        raise InvalidSpecConstraint(
            "The `--changed-*` options are only available if Git is used for the repository."
        )

    changed_files = tuple(
        changed_options.changed_files(maybe_git_worktree.git_worktree))
    file_literal_specs = tuple(FileLiteralSpec(f) for f in changed_files)

    changed_request = ChangedRequest(changed_files, changed_options.dependees)
    (changed_addresses, ) = session.product_request(
        ChangedAddresses, [Params(changed_request, options_bootstrapper)])
    logger.debug("changed addresses: %s", changed_addresses)

    address_literal_specs = []
    for address in cast(ChangedAddresses, changed_addresses):
        address_input = AddressInput.parse(
            address.spec, description_of_origin="`--changed-since`")
        address_literal_specs.append(
            AddressLiteralSpec(
                path_component=address_input.path_component,
                target_component=address_input.target_component,
                generated_component=address_input.generated_component,
                parameters=address_input.parameters,
            ))

    return Specs(
        includes=RawSpecs(
            # We need both address_literals and file_literals to cover all our edge cases, including
            # target-aware vs. target-less goals, e.g. `list` vs `count-loc`.
            address_literals=tuple(address_literal_specs),
            file_literals=file_literal_specs,
            unmatched_glob_behavior=unmatched_cli_globs,
            filter_by_global_options=True,
            from_change_detection=True,
            description_of_origin="`--changed-since`",
        ),
        ignores=RawSpecs(description_of_origin="`--changed-since`"),
    )
Ejemplo n.º 17
0
def assert_spec_parsed(build_root: Path, spec_str: str, expected_spec: Spec) -> None:
    parser = SpecsParser(str(build_root))
    spec = parser.parse_spec(spec_str)
    assert isinstance(spec, type(expected_spec))
    assert spec == expected_spec
Ejemplo n.º 18
0
async def parse_one_bsp_mapping(
        request: _ParseOneBSPMappingRequest) -> BSPBuildTargetInternal:
    specs_parser = SpecsParser()
    specs = specs_parser.parse_specs(request.raw_specs)
    return BSPBuildTargetInternal(request.name, specs)
Ejemplo n.º 19
0
 def setUp(self) -> None:
     super().setUp()
     self._spec_parser = SpecsParser(self.build_root)
Ejemplo n.º 20
0
class SpecsParserTest(TestBase):
    def setUp(self) -> None:
        super().setUp()
        self._spec_parser = SpecsParser(self.build_root)

    def test_address_literal_specs(self) -> None:
        self.assert_address_spec_parsed(":root", address_literal("", "root"))
        self.assert_address_spec_parsed("//:root", address_literal("", "root"))

        self.assert_address_spec_parsed("a", address_literal("a"))
        self.assert_address_spec_parsed("a:a", address_literal("a", "a"))

        self.assert_address_spec_parsed("a/b", address_literal("a/b"))
        self.assert_address_spec_parsed("a/b:b", address_literal("a/b", "b"))
        self.assert_address_spec_parsed("a/b:c", address_literal("a/b", "c"))

    def test_sibling(self) -> None:
        self.assert_address_spec_parsed(":", sib(""))
        self.assert_address_spec_parsed("//:", sib(""))

        self.assert_address_spec_parsed("a:", sib("a"))
        self.assert_address_spec_parsed("//a:", sib("a"))

        self.assert_address_spec_parsed("a/b:", sib("a/b"))
        self.assert_address_spec_parsed("//a/b:", sib("a/b"))

    def test_descendant(self) -> None:
        self.assert_address_spec_parsed("::", desc(""))
        self.assert_address_spec_parsed("//::", desc(""))

        self.assert_address_spec_parsed("a::", desc("a"))
        self.assert_address_spec_parsed("//a::", desc("a"))

        self.assert_address_spec_parsed("a/b::", desc("a/b"))
        self.assert_address_spec_parsed("//a/b::", desc("a/b"))

    def test_files(self) -> None:
        # We assume that specs with an extension are meant to be interpreted as filesystem specs.
        for f in ["a.txt", "a.tmp.cache.txt.bak", "a/b/c.txt", ".a.txt"]:
            self.assert_filesystem_spec_parsed(f, file_literal(f))
        self.assert_filesystem_spec_parsed("./a.txt", file_literal("a.txt"))
        self.assert_filesystem_spec_parsed("//./a.txt", file_literal("a.txt"))

    def test_globs(self) -> None:
        for glob_str in [
                "*", "**/*", "a/b/*", "a/b/test_*.py", "a/b/**/test_*"
        ]:
            self.assert_filesystem_spec_parsed(glob_str, file_glob(glob_str))

    def test_excludes(self) -> None:
        for glob_str in ["!", "!a/b/", "!/a/b/*"]:
            self.assert_filesystem_spec_parsed(glob_str, ignore(glob_str[1:]))

    def test_files_with_original_targets(self) -> None:
        self.assert_address_spec_parsed("a.txt:tgt",
                                        address_literal("a.txt", "tgt"))
        self.assert_address_spec_parsed("a/b/c.txt:tgt",
                                        address_literal("a/b/c.txt", "tgt"))
        self.assert_address_spec_parsed("./a.txt:tgt",
                                        address_literal("a.txt", "tgt"))
        self.assert_address_spec_parsed("//./a.txt:tgt",
                                        address_literal("a.txt", "tgt"))
        self.assert_address_spec_parsed("a/b/c.txt:../tgt",
                                        address_literal("a/b/c.txt", "../tgt"))

    def test_ambiguous_files(self) -> None:
        # These could either be files or the shorthand for address_literal addresses. We check if
        # they exist on the file system to disambiguate.
        for spec in ["a", "b/c"]:
            self.assert_address_spec_parsed(spec, address_literal(spec))
            self.create_file(spec)
            self.assert_filesystem_spec_parsed(spec, file_literal(spec))

    def test_absolute(self) -> None:
        self.assert_address_spec_parsed(os.path.join(self.build_root, "a"),
                                        address_literal("a"))
        self.assert_address_spec_parsed(os.path.join(self.build_root, "a:a"),
                                        address_literal("a", "a"))
        self.assert_address_spec_parsed(os.path.join(self.build_root, "a:"),
                                        sib("a"))
        self.assert_address_spec_parsed(os.path.join(self.build_root, "a::"),
                                        desc("a"))
        self.assert_filesystem_spec_parsed(
            os.path.join(self.build_root, "a.txt"), file_literal("a.txt"))

        with self.assertRaises(SpecsParser.BadSpecError):
            self.assert_address_spec_parsed("/not/the/buildroot/a", sib("a"))
            self.assert_filesystem_spec_parsed("/not/the/buildroot/a.txt",
                                               file_literal("a.txt"))

    def test_absolute_double_slashed(self) -> None:
        # By adding a double slash, we are insisting that this absolute path is actually
        # relative to the buildroot. Thus, it should parse correctly.
        double_absolute_address = "/" + os.path.join(self.build_root, "a")
        double_absolute_file = "/" + os.path.join(self.build_root, "a.txt")
        for spec in [double_absolute_address, double_absolute_file]:
            assert "//" == spec[:2]
        self.assert_address_spec_parsed(
            double_absolute_address,
            address_literal(double_absolute_address[2:]))
        self.assert_filesystem_spec_parsed(
            double_absolute_file, file_literal(double_absolute_file[2:]))

    def test_cmd_line_affordances(self) -> None:
        self.assert_address_spec_parsed("./:root", address_literal("", "root"))
        self.assert_address_spec_parsed("//./:root",
                                        address_literal("", "root"))
        self.assert_address_spec_parsed("//./a/../:root",
                                        address_literal("", "root"))
        self.assert_address_spec_parsed(
            os.path.join(self.build_root, "./a/../:root"),
            address_literal("", "root"))

        self.assert_address_spec_parsed("a/", address_literal("a"))
        self.assert_address_spec_parsed("./a/", address_literal("a"))
        self.assert_address_spec_parsed(os.path.join(self.build_root, "./a/"),
                                        address_literal("a"))

        self.assert_address_spec_parsed("a/b/:b", address_literal("a/b", "b"))
        self.assert_address_spec_parsed("./a/b/:b",
                                        address_literal("a/b", "b"))
        self.assert_address_spec_parsed(
            os.path.join(self.build_root, "./a/b/:b"),
            address_literal("a/b", "b"))

    def assert_address_spec_parsed(self, spec_str: str,
                                   expected_spec: AddressSpec) -> None:
        spec = self._spec_parser.parse_spec(spec_str)
        assert isinstance(spec, AddressSpec)
        assert spec == expected_spec

    def assert_filesystem_spec_parsed(self, spec_str: str,
                                      expected_spec: FilesystemSpec) -> None:
        spec = self._spec_parser.parse_spec(spec_str)
        assert isinstance(spec, FilesystemSpec)
        assert spec == expected_spec