async def addresses_with_origins_from_filesystem_specs( filesystem_specs: FilesystemSpecs, global_options: GlobalOptions, ) -> AddressesWithOrigins: """Find the owner(s) for each FilesystemSpec while preserving the original FilesystemSpec those owners come from. """ pathglobs_per_include = (filesystem_specs.path_globs_for_spec(spec) for spec in filesystem_specs.includes) snapshot_per_include = await MultiGet(Get[Snapshot](PathGlobs, pg) for pg in pathglobs_per_include) owners_per_include = await MultiGet( Get[Owners](OwnersRequest(sources=snapshot.files)) for snapshot in snapshot_per_include) result: List[AddressWithOrigin] = [] for spec, owners in zip(filesystem_specs.includes, owners_per_include): if (global_options.owners_not_found_behavior != OwnersNotFoundBehavior.ignore and isinstance(spec, FilesystemLiteralSpec) and not owners.addresses): file_path = PurePath(spec.to_spec_string()) msg = ( f"No owning targets could be found for the file `{file_path}`.\n\nPlease check " f"that there is a BUILD file in `{file_path.parent}` with a target whose `sources` field " f"includes `{file_path}`. See https://www.pantsbuild.org/build_files.html." ) if global_options.owners_not_found_behavior == OwnersNotFoundBehavior.warn: logger.warning(msg) else: raise ResolveError(msg) result.extend( AddressWithOrigin(address=bfa, origin=spec) for bfa in owners.addresses) return AddressesWithOrigins(result)
def make_addresses_with_origins( *addresses: Address) -> AddressesWithOrigins: return AddressesWithOrigins([ AddressWithOrigin( address=address, origin=SingleAddress(directory=address.spec_path, name=address.target_name), ) for address in addresses ])
async def addresses_with_origins_from_address_families( address_mapper: AddressMapper, address_specs: AddressSpecs, ) -> AddressesWithOrigins: """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins. :raises: :class:`ResolveError` if: - there were no matching AddressFamilies, or - the AddressSpec matches no addresses for SingleAddresses. :raises: :class:`AddressLookupError` if no targets are matched for non-SingleAddress specs. """ # Capture a Snapshot covering all paths for these AddressSpecs, then group by directory. snapshot = await Get[Snapshot](PathGlobs, _address_spec_to_globs( address_mapper, address_specs)) dirnames = {os.path.dirname(f) for f in snapshot.files} address_families = await MultiGet(Get[AddressFamily](Dir(d)) for d in dirnames) address_family_by_directory = {af.namespace: af for af in address_families} matched_addresses: OrderedSet[Address] = OrderedSet() addr_to_origin: Dict[Address, AddressSpec] = {} for address_spec in address_specs: # NB: if an address spec is provided which expands to some number of targets, but those targets # match --exclude-target-regexp, we do NOT fail! This is why we wait to apply the tag and # exclude patterns until we gather all the targets the address spec would have matched # without them. try: addr_families_for_spec = address_spec.matching_address_families( address_family_by_directory) except AddressSpec.AddressFamilyResolutionError as e: raise ResolveError(e) from e try: all_bfaddr_tgt_pairs = address_spec.address_target_pairs_from_address_families( addr_families_for_spec) for bfaddr, _ in all_bfaddr_tgt_pairs: addr = bfaddr.to_address() # A target might be covered by multiple specs, so we take the most specific one. addr_to_origin[addr] = more_specific(addr_to_origin.get(addr), address_spec) except AddressSpec.AddressResolutionError as e: raise AddressLookupError(e) from e except SingleAddress._SingleAddressResolutionError as e: _raise_did_you_mean(e.single_address_family, e.name, source=e) matched_addresses.update( bfaddr.to_address() for (bfaddr, tgt) in all_bfaddr_tgt_pairs if address_specs.matcher.matches_target_address_pair(bfaddr, tgt)) # NB: This may be empty, as the result of filtering by tag and exclude patterns! return AddressesWithOrigins( AddressWithOrigin(address=addr, origin=addr_to_origin[addr]) for addr in matched_addresses)
async def addresses_with_origins_from_filesystem_specs( filesystem_specs: FilesystemSpecs, global_options: GlobalOptions, ) -> AddressesWithOrigins: """Find the owner(s) for each FilesystemSpec while preserving the original FilesystemSpec those owners come from. This will merge FilesystemSpecs that come from the same owning target into a single FilesystemMergedSpec. """ pathglobs_per_include = (filesystem_specs.path_globs_for_spec(spec) for spec in filesystem_specs.includes) snapshot_per_include = await MultiGet(Get[Snapshot](PathGlobs, pg) for pg in pathglobs_per_include) owners_per_include = await MultiGet( Get[Owners](OwnersRequest(sources=snapshot.files)) for snapshot in snapshot_per_include) addresses_to_specs: DefaultDict[ Address, List[Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec]]] = defaultdict(list) for spec, snapshot, owners in zip(filesystem_specs.includes, snapshot_per_include, owners_per_include): if (global_options.options.owners_not_found_behavior != OwnersNotFoundBehavior.ignore and isinstance(spec, FilesystemLiteralSpec) and not owners.addresses): file_path = PurePath(spec.to_spec_string()) msg = ( f"No owning targets could be found for the file `{file_path}`.\n\nPlease check " f"that there is a BUILD file in `{file_path.parent}` with a target whose `sources` field " f"includes `{file_path}`. See https://www.pantsbuild.org/build_files.html." ) if global_options.options.owners_not_found_behavior == OwnersNotFoundBehavior.warn: logger.warning(msg) else: raise ResolveError(msg) # We preserve what literal files any globs resolved to. This allows downstream goals to be # more precise in which files they operate on. origin: Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec] = ( spec if isinstance(spec, FilesystemLiteralSpec) else FilesystemResolvedGlobSpec(glob=spec.glob, files=snapshot.files)) for address in owners.addresses: addresses_to_specs[address].append(origin) return AddressesWithOrigins( AddressWithOrigin( address, specs[0] if len(specs) == 1 else FilesystemMergedSpec.create(specs)) for address, specs in addresses_to_specs.items())
async def run_tests( console: Console, options: TestOptions, runner: InteractiveRunner, addresses_with_origins: AddressesWithOrigins, workspace: Workspace, ) -> Test: if options.values.debug: address_with_origin = addresses_with_origins.expect_single() addr_debug_request = await Get[AddressAndDebugRequest]( AddressWithOrigin, address_with_origin ) result = runner.run_local_interactive_process(addr_debug_request.request.ipr) return Test(result.process_exit_code) results = await MultiGet( Get[AddressAndTestResult](AddressWithOrigin, address_with_origin) for address_with_origin in addresses_with_origins ) if options.values.run_coverage: # TODO: consider warning if a user uses `--coverage` but the language backend does not # provide coverage support. This might be too chatty to be worth doing? results_with_coverage = [ x for x in results if x.test_result is not None and x.test_result.coverage_data is not None ] coverage_data_collections = itertools.groupby( results_with_coverage, lambda address_and_test_result: address_and_test_result.test_result.coverage_data.batch_cls, # type: ignore[union-attr] ) coverage_reports = await MultiGet( Get[CoverageReport]( CoverageDataBatch, coverage_batch_cls(tuple(addresses_and_test_results)) # type: ignore[call-arg] ) for coverage_batch_cls, addresses_and_test_results in coverage_data_collections ) for report in coverage_reports: workspace.materialize_directory( DirectoryToMaterialize( report.result_digest, path_prefix=str(report.directory_to_materialize_to), ) ) console.print_stdout(f"Wrote coverage report to `{report.directory_to_materialize_to}`") did_any_fail = False filtered_results = [(x.address, x.test_result) for x in results if x.test_result is not None] for address, test_result in filtered_results: if test_result.status == Status.FAILURE: did_any_fail = True if test_result.stdout: console.write_stdout(f"{address.reference()} stdout:\n{test_result.stdout}\n") if test_result.stderr: # NB: we write to stdout, rather than to stderr, to avoid potential issues interleaving the # two streams. console.write_stdout(f"{address.reference()} stderr:\n{test_result.stderr}\n") console.write_stdout("\n") for address, test_result in filtered_results: console.print_stdout(f"{address.reference():80}.....{test_result.status.value:>10}") if did_any_fail: console.print_stderr(console.red("\nTests failed")) exit_code = PANTS_FAILED_EXIT_CODE else: exit_code = PANTS_SUCCEEDED_EXIT_CODE return Test(exit_code)