def mock_coverage_report_generation( coverage_data_collection: MockCoverageDataCollection, ) -> CoverageReports: addresses = ", ".join(coverage_data.address.spec for coverage_data in coverage_data_collection) console_report = ConsoleCoverageReport(f"Ran coverage on {addresses}") return CoverageReports(reports=(console_report, ))
async def generate_coverage_reports( merged_coverage_data: MergedCoverageData, coverage_setup: CoverageSetup, coverage_config: CoverageConfig, coverage_subsystem: CoverageSubsystem, transitive_targets: TransitiveTargets, ) -> CoverageReports: """Takes all Python test results and generates a single coverage report.""" sources = await Get( PythonSourceFiles, PythonSourceFilesRequest(transitive_targets.closure, include_resources=False), ) input_digest = await Get( Digest, MergeDigests(( merged_coverage_data.coverage_data, coverage_config.digest, coverage_setup.pex.digest, sources.source_files.snapshot.digest, )), ) pex_processes = [] report_types = [] coverage_reports: List[CoverageReport] = [] for report_type in coverage_subsystem.reports: if report_type == CoverageReportType.RAW: coverage_reports.append( FilesystemCoverageReport( report_type=CoverageReportType.RAW, result_digest=merged_coverage_data.coverage_data, directory_to_materialize_to=coverage_subsystem.output_dir, report_file=coverage_subsystem.output_dir / ".coverage", )) continue report_types.append(report_type) output_file = (f"coverage.{report_type.value}" if report_type in { CoverageReportType.XML, CoverageReportType.JSON } else None) pex_processes.append( PexProcess( coverage_setup.pex, # We pass `--ignore-errors` because Pants dynamically injects missing `__init__.py` # files and this will cause Coverage to fail. argv=(report_type.report_name, "--ignore-errors"), input_digest=input_digest, output_directories=("htmlcov", ) if report_type == CoverageReportType.HTML else None, output_files=(output_file, ) if output_file else None, description= f"Generate Pytest {report_type.report_name} coverage report.", level=LogLevel.DEBUG, )) results = await MultiGet( Get(ProcessResult, PexProcess, process) for process in pex_processes) coverage_reports.extend( _get_coverage_reports(coverage_subsystem.output_dir, report_types, results)) return CoverageReports(tuple(coverage_reports))
async def generate_coverage_reports( merged_coverage_data: MergedCoverageData, coverage_setup: CoverageSetup, coverage_config: CoverageConfig, coverage_subsystem: CoverageSubsystem, global_options: GlobalOptions, ) -> CoverageReports: """Takes all Python test results and generates a single coverage report.""" transitive_targets = await Get( TransitiveTargets, TransitiveTargetsRequest(merged_coverage_data.addresses)) sources = await Get( PythonSourceFiles, # Coverage sometimes includes non-Python files in its `.coverage` data. We need to # ensure that they're present when generating the report. We include all the files included # by `pytest_runner.py`. PythonSourceFilesRequest(transitive_targets.closure, include_files=True, include_resources=True), ) input_digest = await Get( Digest, MergeDigests(( merged_coverage_data.coverage_data, coverage_config.digest, sources.source_files.snapshot.digest, )), ) pex_processes = [] report_types = [] result_snapshot = await Get(Snapshot, Digest, merged_coverage_data.coverage_data) coverage_reports: list[CoverageReport] = [] for report_type in coverage_subsystem.reports: if report_type == CoverageReportType.RAW: coverage_reports.append( FilesystemCoverageReport( # We don't know yet if the coverage is sufficient, so we let some other report # trigger the failure if necessary. coverage_insufficient=False, report_type=CoverageReportType.RAW.value, result_snapshot=result_snapshot, directory_to_materialize_to=coverage_subsystem.output_dir, report_file=coverage_subsystem.output_dir / ".coverage", )) continue report_types.append(report_type) output_file = (f"coverage.{report_type.value}" if report_type in { CoverageReportType.XML, CoverageReportType.JSON } else None) args = [report_type.report_name, f"--rcfile={coverage_config.path}"] if coverage_subsystem.fail_under is not None: args.append(f"--fail-under={coverage_subsystem.fail_under}") pex_processes.append( VenvPexProcess( coverage_setup.pex, argv=tuple(args), input_digest=input_digest, output_directories=("htmlcov", ) if report_type == CoverageReportType.HTML else None, output_files=(output_file, ) if output_file else None, description= f"Generate Pytest {report_type.report_name} coverage report.", level=LogLevel.DEBUG, )) results = await MultiGet( Get(FallibleProcessResult, VenvPexProcess, process) for process in pex_processes) for proc, res in zip(pex_processes, results): if res.exit_code not in {0, 2}: # coverage.py uses exit code 2 if --fail-under triggers, in which case the # reports are still generated. raise ProcessExecutionFailure( res.exit_code, res.stdout, res.stderr, proc.description, local_cleanup=global_options.options. process_execution_local_cleanup, ) # In practice if one result triggers --fail-under, they all will, but no need to rely on that. result_exit_codes = tuple(res.exit_code for res in results) result_stdouts = tuple(res.stdout for res in results) result_snapshots = await MultiGet( Get(Snapshot, Digest, res.output_digest) for res in results) coverage_reports.extend( _get_coverage_report(coverage_subsystem.output_dir, report_type, exit_code != 0, stdout, snapshot) for (report_type, exit_code, stdout, snapshot) in zip( report_types, result_exit_codes, result_stdouts, result_snapshots)) return CoverageReports(tuple(coverage_reports))
async def generate_coverage_reports( merged_coverage_data: MergedCoverageData, coverage_setup: CoverageSetup, coverage_config: CoverageConfig, coverage_subsystem: CoverageSubsystem, all_used_addresses: Addresses, ) -> CoverageReports: """Takes all Python test results and generates a single coverage report.""" transitive_targets = await Get( TransitiveTargets, TransitiveTargetsRequest(all_used_addresses)) sources = await Get( PythonSourceFiles, # Coverage sometimes includes non-Python files in its `.coverage` data. We need to # ensure that they're present when generating the report. We include all the files included # by `pytest_runner.py`. PythonSourceFilesRequest(transitive_targets.closure, include_files=True, include_resources=True), ) input_digest = await Get( Digest, MergeDigests(( merged_coverage_data.coverage_data, coverage_config.digest, sources.source_files.snapshot.digest, )), ) pex_processes = [] report_types = [] result_snapshot = await Get(Snapshot, Digest, merged_coverage_data.coverage_data) coverage_reports: List[CoverageReport] = [] for report_type in coverage_subsystem.reports: if report_type == CoverageReportType.RAW: coverage_reports.append( FilesystemCoverageReport( report_type=CoverageReportType.RAW.value, result_snapshot=result_snapshot, directory_to_materialize_to=coverage_subsystem.output_dir, report_file=coverage_subsystem.output_dir / ".coverage", )) continue report_types.append(report_type) output_file = (f"coverage.{report_type.value}" if report_type in { CoverageReportType.XML, CoverageReportType.JSON } else None) pex_processes.append( VenvPexProcess( coverage_setup.pex, argv=(report_type.report_name, f"--rcfile={coverage_config.path}"), input_digest=input_digest, output_directories=("htmlcov", ) if report_type == CoverageReportType.HTML else None, output_files=(output_file, ) if output_file else None, description= f"Generate Pytest {report_type.report_name} coverage report.", level=LogLevel.DEBUG, )) results = await MultiGet( Get(ProcessResult, VenvPexProcess, process) for process in pex_processes) result_stdouts = tuple(res.stdout for res in results) result_snapshots = await MultiGet( Get(Snapshot, Digest, res.output_digest) for res in results) coverage_reports.extend( _get_coverage_report(coverage_subsystem.output_dir, report_type, stdout, snapshot) for (report_type, stdout, snapshot) in zip(report_types, result_stdouts, result_snapshots)) return CoverageReports(tuple(coverage_reports))
async def generate_coverage_report( merged_coverage_data: MergedCoverageData, coverage_setup: CoverageSetup, coverage_subsystem: PytestCoverage, transitive_targets: TransitiveTargets, python_setup: PythonSetup, subprocess_encoding_environment: SubprocessEncodingEnvironment, ) -> CoverageReports: """Takes all Python test results and generates a single coverage report.""" requirements_pex = coverage_setup.requirements_pex coverage_config = await Get[CoverageConfig](CoverageConfigRequest( Targets(transitive_targets.closure), is_test_time=False)) sources = await Get[SourceFiles](AllSourceFilesRequest( (tgt.get(Sources) for tgt in transitive_targets.closure), strip_source_roots=False)) sources_with_inits_snapshot = await Get[InitInjectedSnapshot]( InjectInitRequest(sources.snapshot)) input_digest: Digest = await Get( Digest, MergeDigests(( merged_coverage_data.coverage_data, coverage_config.digest, requirements_pex.digest, sources_with_inits_snapshot.snapshot.digest, )), ) report_type = coverage_subsystem.options.report # The -i flag causes coverage to ignore files it doesn't have sources for. # Specifically, it will ignore the injected __init__.py files, which is what we want since # those are empty and don't correspond to a real source file the user is aware of. coverage_args = [report_type.report_name, "-i"] process = requirements_pex.create_process( pex_path=f"./{coverage_setup.requirements_pex.output_filename}", pex_args=coverage_args, input_digest=input_digest, output_directories=("htmlcov", ), output_files=("coverage.xml", ), description="Generate Pytest coverage report.", python_setup=python_setup, subprocess_encoding_environment=subprocess_encoding_environment, ) result = await Get[ProcessResult](Process, process) if report_type == CoverageReportType.CONSOLE: return CoverageReports( reports=(ConsoleCoverageReport(result.stdout.decode()), )) report_dir = PurePath(coverage_subsystem.options.report_output_path) report_file: Optional[PurePath] = None if report_type == CoverageReportType.HTML: report_file = report_dir / "htmlcov" / "index.html" elif report_type == CoverageReportType.XML: report_file = report_dir / "coverage.xml" fs_report = FilesystemCoverageReport( result_digest=result.output_digest, directory_to_materialize_to=report_dir, report_file=report_file, ) return CoverageReports(reports=(fs_report, ))