Skip to content

Commit fe3e25d

Browse files
committed
chore(plugin-coverage): improve nx helper, error handling, update docs
1 parent 2b8678c commit fe3e25d

6 files changed

Lines changed: 96 additions & 27 deletions

File tree

code-pushup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const config: CoreConfig = {
6161
'unit-test',
6262
'integration-test',
6363
'--coverage',
64+
'--skipNxCache',
6465
],
6566
},
6667
reports: await getNxCoveragePaths(['unit-test', 'integration-test']),

packages/plugin-coverage/README.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Measured coverage types are mapped to Code PushUp audits in the following way
1010
- the score is value converted to 0-1 range
1111
- missing coverage is mapped to issues in the audit details (uncalled functions, uncovered branches or lines)
1212

13+
> [!IMPORTANT]
14+
> In order to successfully run your coverage tool and gather coverage results directly within the plugin, all your tests need to pass!
15+
1316
## Getting started
1417

1518
1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file.
@@ -75,11 +78,11 @@ Measured coverage types are mapped to Code PushUp audits in the following way
7578

7679
Code coverage is a metric that indicates what percentage of source code is executed by unit tests. It can give insights into test effectiveness and uncover parts of source code that would otherwise go untested.
7780

78-
- Statement coverage: Measures how many statements are executed in at least one test.
79-
- Line coverage: Measures how many lines are executed in at least one test. Unlike statement coverage, any partially executed line counts towards line coverage.
80-
- Condition coverage: Measures all condition values (`true`/`false`) evaluated for a conditional statement in at least one test.
81-
- Branch coverage: Measures how many branches are executed as a result of conditional statements (`if`/`else` and other) in at least one test. In case of short-circuit logic, only executed paths are counted in. Unlike condition coverage, it does not ensure all combinations of condition values are tested.
82-
- Function coverage: Measures how many functions are called in at least one test. Argument values, usage of optional arguments or default values is irrelevant for this metric.
81+
- **Statement coverage**: Measures how many statements are executed in at least one test.
82+
- **Line coverage**: Measures how many lines are executed in at least one test. Unlike statement coverage, any partially executed line counts towards line coverage.
83+
- **Condition coverage**: Measures all condition values (`true`/`false`) evaluated for a conditional statement in at least one test.
84+
- **Branch coverage**: Measures how many branches are executed as a result of conditional statements (`if`/`else` and other) in at least one test. In case of short-circuit logic, only executed paths are counted in. Unlike condition coverage, it does not ensure all combinations of condition values are tested.
85+
- **Function coverage**: Measures how many functions are called in at least one test. Argument values, usage of optional arguments or default values is irrelevant for this metric.
8386

8487
> [!IMPORTANT]
8588
> Please note that code coverage is not the same as test coverage. Test coverage measures the amount of acceptance criteria covered by tests and is hard to formally verify. This means that code coverage cannot guarantee that the designed software caters to the business requirements.
@@ -117,7 +120,7 @@ The plugin accepts the following parameters:
117120

118121
- `coverageTypes`: An array of types of coverage that you wish to track. Supported values: `function`, `branch`, `line`. Defaults to all available types.
119122
- `reports`: Array of information about files with code coverage results - paths to results, path to project root the results belong to. LCOV format is supported for now.
120-
- If you have an `nx` monorepo, you can adjust our helper function `getNxCoveragePaths` to get the path information automatically.
123+
- If you have an Nx monorepo, you can adjust our helper function `getNxCoveragePaths` to get the path information automatically.
121124
- (optional) `coverageToolCommand`: If you wish to run your coverage tool to generate the results first, you may define it here.
122125
- (optional) `perfectScoreThreshold`: If your coverage goal is not 100%, you may define it here in range 0-1. Any score above the defined threshold will be given the perfect score. The value will stay unaffected.
123126

@@ -204,3 +207,14 @@ For instance, the following can be an audit output for line coverage.
204207
}
205208
}
206209
```
210+
211+
### Providing coverage results in Nx monorepo
212+
213+
As a part of the plugin, there is a `getNxCoveragePaths` helper for setting up paths to coverage results if you are using Nx. The helper accepts all relevant targets (e.g. `test` or `unit-test`) and searches for a coverage path option.
214+
Jest and Vitest configuration options are currently supported:
215+
216+
- For `@nx/jest` executor it looks for the `coverageDirectory` option.
217+
- For `@nx/vite` executor it looks for the `reportsDirectory` option.
218+
219+
> [!IMPORTANT]
220+
> Please note that you need to set up the coverage directory option in your `project.json` target options. Test configuration files are not searched.

packages/plugin-coverage/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"@code-pushup/models": "*",
66
"@code-pushup/utils": "*",
77
"parse-lcov": "^1.0.4",
8+
"chalk": "^5.3.0",
89
"zod": "^3.22.4"
910
},
1011
"peerDependencies": {
Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import type { ProjectGraphProjectNode } from '@nx/devkit';
1+
import type { ProjectGraphProjectNode, TargetConfiguration } from '@nx/devkit';
2+
import chalk from 'chalk';
23
import { join } from 'node:path';
34
import { CoverageResult } from '../config';
45

56
/**
6-
*
7-
* @param coverageFolder root folder containing all coverage results
87
* @param targets nx targets to be used for measuring coverage, test by default
98
* @returns An array of coverage result information for the coverage plugin.
109
*/
1110
export async function getNxCoveragePaths(
1211
targets: string[] = ['test'],
1312
): Promise<CoverageResult[]> {
13+
console.info(
14+
chalk.bold('💡 Gathering coverage from the following nx projects:'),
15+
);
1416
const { createProjectGraphAsync } = await import('@nx/devkit');
1517
const { nodes } = await createProjectGraphAsync({ exitOnError: false });
1618

@@ -19,23 +21,21 @@ export async function getNxCoveragePaths(
1921
hasNxTarget(graph, target),
2022
);
2123

22-
return relevantNodes
23-
.map(node => node.data)
24-
.map<CoverageResult>(projectConfig => {
25-
const { reportsDirectory } = projectConfig.targets?.[target]
26-
?.options as {
27-
reportsDirectory: string;
28-
};
29-
30-
const rootToReportsDir = join(projectConfig.root, reportsDirectory);
31-
32-
return {
33-
pathToProject: projectConfig.root,
34-
resultsPath: join(rootToReportsDir, 'lcov.info'),
35-
};
36-
});
24+
return relevantNodes.map<CoverageResult>(({ name, data }) => {
25+
const targetConfig = data.targets?.[target] as TargetConfiguration;
26+
const coveragePath = getCoveragePathForTarget(target, targetConfig, name);
27+
const rootToReportsDir = join(data.root, coveragePath);
28+
29+
console.info(`- ${name}: ${target}`);
30+
31+
return {
32+
pathToProject: data.root,
33+
resultsPath: join(rootToReportsDir, 'lcov.info'),
34+
};
35+
});
3736
});
3837

38+
console.info('\n');
3939
return coverageResults.flat();
4040
}
4141

@@ -45,3 +45,40 @@ function hasNxTarget(
4545
): boolean {
4646
return project.data.targets != null && target in project.data.targets;
4747
}
48+
49+
function getCoveragePathForTarget(
50+
target: string,
51+
targetConfig: TargetConfiguration,
52+
projectName: string,
53+
): string {
54+
if (targetConfig.executor?.includes('@nx/vite')) {
55+
const { reportsDirectory } = targetConfig.options as {
56+
reportsDirectory?: string;
57+
};
58+
59+
if (reportsDirectory == null) {
60+
throw new Error(
61+
`Coverage configuration not found for target ${target} in ${projectName}. Define your Vitest coverage directory in the reportsDirectory option.`,
62+
);
63+
}
64+
65+
return reportsDirectory;
66+
}
67+
68+
if (targetConfig.executor?.includes('@nx/jest')) {
69+
const { coverageDirectory } = targetConfig.options as {
70+
coverageDirectory?: string;
71+
};
72+
73+
if (coverageDirectory == null) {
74+
throw new Error(
75+
`Coverage configuration not found for target ${target} in ${projectName}. Define your Jest coverage directory in the coverageDirectory option.`,
76+
);
77+
}
78+
return coverageDirectory;
79+
}
80+
81+
throw new Error(
82+
`Unsupported executor ${targetConfig.executor}. @nx/vite and @nx/jest are currently supported.`,
83+
);
84+
}

packages/plugin-coverage/src/lib/runner/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import chalk from 'chalk';
12
import { writeFile } from 'node:fs/promises';
23
import { dirname } from 'node:path';
3-
import { AuditOutputs, RunnerConfig } from '@code-pushup/models';
4+
import type { AuditOutputs, RunnerConfig } from '@code-pushup/models';
45
import {
6+
ProcessError,
57
ensureDirectoryExists,
68
executeProcess,
79
readJsonFile,
@@ -18,7 +20,21 @@ export async function executeRunner(): Promise<void> {
1820
// Run coverage tool if provided
1921
if (coverageToolCommand != null) {
2022
const { command, args } = coverageToolCommand;
21-
await executeProcess({ command, args });
23+
24+
try {
25+
await executeProcess({ command, args });
26+
} catch (error) {
27+
if (error instanceof ProcessError) {
28+
console.info(chalk.bold('stdout from failed process:'));
29+
console.info(error.stdout);
30+
console.error(chalk.bold('stderr from failed process:'));
31+
console.error(error.stderr);
32+
}
33+
34+
throw new Error(
35+
'Coverage plugin: Running coverage tool failed. Make sure all your provided tests are passing.',
36+
);
37+
}
2238
}
2339

2440
// Caculate coverage from LCOV results

packages/plugin-coverage/src/lib/runner/runner.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('createRunnerConfig', () => {
4444

4545
describe('executeRunner', () => {
4646
it('should successfully execute runner', async () => {
47-
const config = {
47+
const config: FinalCoveragePluginConfig = {
4848
reports: [
4949
{
5050
resultsPath: join(

0 commit comments

Comments
 (0)