monocart-coverage-reports 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/README.zh-Hans.md +51 -0
- package/lib/cli.js +25 -60
- package/lib/converter/converter.js +20 -24
- package/lib/default/options.js +5 -0
- package/lib/generate.js +3 -2
- package/lib/index.d.ts +67 -0
- package/lib/packages/monocart-coverage-converter.js +9 -9
- package/lib/packages/monocart-coverage-vendor.js +2 -1
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +69 -9
- package/lib/test/test-reporter.js +191 -0
- package/lib/test/test-results.js +104 -0
- package/lib/test/test-runner.js +115 -0
- package/migrate-to-3.md +4 -0
- package/package.json +161 -161
- package/skills/monocart-coverage-reports/references/cli.md +5 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +2 -0
package/README.md
CHANGED
|
@@ -868,6 +868,53 @@ const coverageOptions = {
|
|
|
868
868
|
};
|
|
869
869
|
```
|
|
870
870
|
|
|
871
|
+
### `onTestEnd` — `mcr test` only
|
|
872
|
+
|
|
873
|
+
`onTestEnd(testResults)` runs once after the test runner exits and its JSON results are collected, before publishing the test results cache, `onReady`, and coverage generation. It is awaited even when assertions fail. Other commands, API calls, and runs skipped because no targets were supplied do not call it. The existing `onEnd(coverageResults)` is unchanged.
|
|
874
|
+
|
|
875
|
+
`mcr test` always collects structured test results using an internal JSON reporter, preserving terminal output and user-specified reporter/destination pairs. `onTestEnd` is optional; omitting it does not disable result collection. To retain results for later use, enable `raw` reports or debug logging; normal cache cleanup still applies.
|
|
876
|
+
|
|
877
|
+
```js
|
|
878
|
+
// mcr.config.js
|
|
879
|
+
export default {
|
|
880
|
+
test: './test/**/*.test.js',
|
|
881
|
+
onTestEnd: (testResults) => {
|
|
882
|
+
console.log(testResults.summary);
|
|
883
|
+
for (const test of testResults.tests) {
|
|
884
|
+
console.log(test.name, test.status, test.error);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
- `id`: a collection ID generated with the same `Util.uid()` mechanism as coverage cache data.
|
|
891
|
+
- `framework`: `'node:test'`.
|
|
892
|
+
- `complete`: whether the final runner summary was received, not whether tests passed.
|
|
893
|
+
- `summary`: `{ tests, suites, passed, failed, cancelled, skipped, todo, duration, success }`; `null` if incomplete. Counts come from the runner's final summary; `tests` excludes suites. Durations are in milliseconds.
|
|
894
|
+
- `tests`: completed test and suite records, including nested tests: `{ name, type, nesting, testNumber, file?, line?, column?, duration, status, skip?, todo?, error? }`. Status is `passed`, `failed`, `cancelled`, `skipped`, or `todo`. Errors always include string `name` and `message` (defaults: `'Error'` and `''`). Other own enumerable data properties are copied only when their values are strings, booleans, finite numbers, or `null`; BigInts become decimal strings. Getters/setters, objects/arrays, functions, symbols, `undefined`, `NaN`, and infinities are omitted without recursive traversal. Apart from the two fixed fields, inherited/non-enumerable properties are not collected, so normal `stack` is omitted and fields inside `cause` are not flattened. Explicitly enumerable string `stack` is copied like any other string. Reserved keys `__proto__`, `constructor`, and `prototype` are excluded; the fixed fields cannot be overwritten by extra properties.
|
|
895
|
+
|
|
896
|
+
Test failures, incomplete/missing/invalid reports, and hook errors keep the command nonzero. An incomplete report is delivered with `complete: false`; a missing or invalid JSON report cannot be delivered and is logged as a collection error. A nonzero `process.exitCode` set by `onTestEnd` cannot be cleared by later coverage hooks. Hook/collection errors do not prevent MCR from attempting coverage generation. If the hook throws, test results are not published or exported, rather than falling back to possibly unredacted data. Test results are independent of coverage, so this hook can run even if coverage generation later fails.
|
|
897
|
+
|
|
898
|
+
Test results use the same ID and filename mechanism as coverage cache data: `outputDir/.cache/test-results-<id>.json`, with the same `id` stored in the JSON. The reporter streams records into `.json.tmp` with its collection ID already included. A small `.meta.tmp` completion record verifies the final byte count. Without a hook, MCR reads only this completion record and renames the JSON, without loading or rewriting the full report. With a hook, MCR reads the full result object and publishes its mutations after the hook completes. Temporary output and metadata are removed before coverage generation. Finalized JSON follows the coverage cache lifecycle: normally cleaned after generation, retained in debug mode, or exported by `raw` (including ZIP). It is not embedded in HTML/coverage JSON reports, and importing raw data does not yet merge test results. Save results yourself if needed; files written into `outputDir` by this hook may be removed by the subsequent coverage cleanup when `clean: true`.
|
|
899
|
+
|
|
900
|
+
**Sensitive data:** Primitive does not mean non-sensitive: a string `token`, `password`, or response body is also copied. Messages, test names, and paths may contain credentials or personal data; large strings can also increase report size. MCR does not redact them automatically. Keep the output directory private and review raw/debug artifacts before uploading them. Mutate results in `onTestEnd` to sanitize the persisted JSON; this does not redact Node's terminal output or data already written to temporary files. For example, adapt the following rule to your application's data:
|
|
901
|
+
|
|
902
|
+
```js
|
|
903
|
+
export default {
|
|
904
|
+
onTestEnd: (results) => {
|
|
905
|
+
for (const test of results.tests) {
|
|
906
|
+
if (test.error) {
|
|
907
|
+
for (const [key, value] of Object.entries(test.error)) {
|
|
908
|
+
if (typeof value === 'string') {
|
|
909
|
+
test.error[key] = value.replace(/(Bearer\s+)\S+/gi, '$1[REDACTED]');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
```
|
|
917
|
+
|
|
871
918
|
### `onStart` / `onReady` — CLI-only
|
|
872
919
|
Only available when using `mcr <command>`:
|
|
873
920
|
- `onStart(coverageReport)` — runs before the child process is spawned.
|
|
@@ -985,6 +1032,7 @@ The options below are only available when using the CLI (`mcr <command>`):
|
|
|
985
1032
|
| `--require` | `string` | `null` | Deprecated alias for `--import`; uses ESM loading, not CommonJS resolution |
|
|
986
1033
|
| `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). |
|
|
987
1034
|
| `test` (config only) | `string \| string[] \| null` | `null` | Default test file paths or glob patterns accepted by `node --test`, passed unchanged when `mcr test` has no positional targets; ignored by other commands and the API. Requires Node.js `>= 22.15.0` |
|
|
1035
|
+
| `onTestEnd` (config only) | `(testResults) => void \| Promise<void>` | `null` | Only `mcr test`: runs after test result collection, before `onReady` and coverage generation; see [Hooks](#hooks) |
|
|
988
1036
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
|
|
989
1037
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
|
|
990
1038
|
|
|
@@ -1103,6 +1151,9 @@ The following usage scenarios may require merging coverage reports:
|
|
|
1103
1151
|
### Manual Merging
|
|
1104
1152
|
If the reports cannot be merged automatically, then here is how to manually merge the reports.
|
|
1105
1153
|
First, using the `raw` report to export the original coverage data to the specified directory.
|
|
1154
|
+
|
|
1155
|
+
**Before uploading raw/debug artifacts (3.2.0+):** `mcr test` also exports `test-results-<id>.json` in raw reports (including ZIP) and retains it in debug caches, even without a hook. Test names, paths and error strings may contain secrets or personal data. Review artifacts and use [`onTestEnd`](#ontestend--mcr-test-only) to redact persisted test results before publication; this does not redact terminal output or temporary files already written. Raw imports merge coverage only, not test results.
|
|
1156
|
+
|
|
1106
1157
|
- For example, we have `raw` coverage data from `unit test`, which is output to `./coverage-reports/unit/raw`. Unit test examples:
|
|
1107
1158
|
- `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
|
|
1108
1159
|
- `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
|
package/README.zh-Hans.md
CHANGED
|
@@ -871,6 +871,53 @@ const coverageOptions = {
|
|
|
871
871
|
};
|
|
872
872
|
```
|
|
873
873
|
|
|
874
|
+
### `onTestEnd` — 仅 `mcr test` 可用
|
|
875
|
+
|
|
876
|
+
`onTestEnd(testResults)` 在测试运行器退出、JSON 测试结果采集完成后触发一次,早于测试结果缓存发布、`onReady` 和覆盖率报告生成。支持异步等待,测试断言失败时也会触发。其他命令、API 调用,以及因未提供目标而跳过的测试,不触发该 hook。现有 `onEnd(coverageResults)` 保持不变。
|
|
877
|
+
|
|
878
|
+
`mcr test` 始终通过内部 JSON reporter 采集结构化测试结果,同时保留终端输出和用户指定的 reporter/destination 配对。`onTestEnd` 为可选回调,不配置也不会关闭采集。如需留待后续使用,请启用 `raw` 报告或 debug 日志;默认的缓存清理规则仍然生效。
|
|
879
|
+
|
|
880
|
+
```js
|
|
881
|
+
// mcr.config.js
|
|
882
|
+
export default {
|
|
883
|
+
test: './test/**/*.test.js',
|
|
884
|
+
onTestEnd: (testResults) => {
|
|
885
|
+
console.log(testResults.summary);
|
|
886
|
+
for (const test of testResults.tests) {
|
|
887
|
+
console.log(test.name, test.status, test.error);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
- `id`:采用与覆盖率缓存数据相同的 `Util.uid()` 机制生成的采集 ID。
|
|
894
|
+
- `framework`:`'node:test'`。
|
|
895
|
+
- `complete`:是否收到运行器最终汇总,不代表测试是否通过。
|
|
896
|
+
- `summary`:`{ tests, suites, passed, failed, cancelled, skipped, todo, duration, success }`,报告不完整时为 `null`。计数采用运行器最终汇总,`tests` 不包含 suite,耗时单位为毫秒。
|
|
897
|
+
- `tests`:已完成的测试及 suite 记录,包含嵌套测试:`{ name, type, nesting, testNumber, file?, line?, column?, duration, status, skip?, todo?, error? }`。状态为 `passed`、`failed`、`cancelled`、`skipped` 或 `todo`。错误固定包含字符串 `name`、`message`,不可用或非字符串时分别使用 `'Error'`、`''`。其余只复制自有、可枚举的数据属性,值仅限字符串、布尔值、有限数字和 `null`;BigInt 转为十进制字符串。不递归遍历,跳过 getter/setter、对象/数组、函数、Symbol、`undefined`、`NaN` 和无穷数。除两个固定字段外,不采集继承或不可枚举属性,因此通常不包含 `stack`,也不展开 `cause` 内的字段;明确设为可枚举的字符串 `stack` 仍按普通字符串复制。过滤 `__proto__`、`constructor`、`prototype` 等特殊键,额外属性不会覆盖固定字段。
|
|
898
|
+
|
|
899
|
+
测试失败、报告不完整/缺失/损坏或 hook 抛错,均保持非零退出状态。不完整报告以 `complete: false` 交付;缺失或无效的 JSON 无法交付,会明确记录采集错误。`onTestEnd` 设置的非零 `process.exitCode` 不会被后续覆盖率 hook 清零。hook 或采集失败后仍尝试生成覆盖率报告。hook 抛错时不会发布或导出测试结果,避免回退导出可能尚未脱敏的数据。测试结果独立于覆盖率,因此即使后续覆盖率生成失败,此 hook 仍可执行。
|
|
900
|
+
|
|
901
|
+
测试结果复用覆盖率缓存的 ID 和文件命名机制:`outputDir/.cache/test-results-<id>.json`,JSON 内容保存同一个 `id`。reporter 将记录逐条写入 `.json.tmp`,输出时即包含采集 ID,并通过很小的 `.meta.tmp` 完成记录校验最终字节数。没有 hook 时,MCR 只读取完成记录并重命名 JSON,不会加载或重写完整报告;配置 hook 时,读取完整结果对象,在 hook 完成后将修改后的数据发布为正式缓存。临时输出和元数据均在生成覆盖率前清理。正式 JSON 遵循覆盖率缓存生命周期:正常生成后清理、调试模式保留,或由 `raw` 报告导出(包括 ZIP)。它不会嵌入 HTML/覆盖率 JSON 报告,导入 raw 数据目前也不会合并测试结果。如需持久化,请自行保存;当 `clean: true` 时,此 hook 写入 `outputDir` 的文件可能被后续覆盖率清理流程删除。
|
|
902
|
+
|
|
903
|
+
**敏感信息提醒:** 基础类型不等于非敏感数据,字符串 `token`、`password` 或响应正文同样会被复制。错误消息、测试名称和路径也可能包含凭据或个人信息,大字符串仍可能增大报告体积,MCR 不会自动脱敏。请保护输出目录,并在上传 raw/debug 产物前审查内容。可在 `onTestEnd` 中修改结果,对正式保存的 JSON 脱敏;这不会处理 Node 的终端输出或已经写入临时文件的数据。以下规则仅为示例,请按业务数据调整:
|
|
904
|
+
|
|
905
|
+
```js
|
|
906
|
+
export default {
|
|
907
|
+
onTestEnd: (results) => {
|
|
908
|
+
for (const test of results.tests) {
|
|
909
|
+
if (test.error) {
|
|
910
|
+
for (const [key, value] of Object.entries(test.error)) {
|
|
911
|
+
if (typeof value === 'string') {
|
|
912
|
+
test.error[key] = value.replace(/(Bearer\s+)\S+/gi, '$1[REDACTED]');
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
```
|
|
920
|
+
|
|
874
921
|
### `onStart` / `onReady` — 仅CLI可用
|
|
875
922
|
只在使用 `mcr <command>` 时有效:
|
|
876
923
|
- `onStart(coverageReport)` — 子进程启动前触发
|
|
@@ -988,6 +1035,7 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
988
1035
|
| `--require` | `string` | `null` | `--import` 的已弃用别名;使用 ESM 加载,不使用 CommonJS 解析 |
|
|
989
1036
|
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env`)。|
|
|
990
1037
|
| `test`(仅配置文件) | `string \| string[] \| null` | `null` | 未指定命令行目标时,原样传给 `node --test` 的默认测试文件路径或 glob,**仅 `mcr test` 使用**;其他命令和 API 忽略。需要 Node.js `>= 22.15.0` |
|
|
1038
|
+
| `onTestEnd`(仅配置文件) | `(testResults) => void \| Promise<void>` | `null` | 仅 `mcr test`:测试结果采集完成后、`onReady` 和覆盖率生成前触发;参见 [Hooks](#hooks) |
|
|
991
1039
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | 子进程启动前触发;参见 [Hooks](#hooks) |
|
|
992
1040
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | 子进程退出后、MCR 读取覆盖率前触发;参见 [Hooks](#hooks) |
|
|
993
1041
|
|
|
@@ -1105,6 +1153,9 @@ npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=sp
|
|
|
1105
1153
|
|
|
1106
1154
|
### Manual Merging
|
|
1107
1155
|
手动合并覆盖率报告需要使用`raw`报告来导出原始的覆盖率数据到指定的目录
|
|
1156
|
+
|
|
1157
|
+
**上传 raw/debug 产物前请注意(3.2.0+):** 即使未配置 hook,`mcr test` 也会在 raw 报告(包括 ZIP)中导出 `test-results-<id>.json`,并在 debug 缓存中保留该文件。测试名称、路径及错误字符串可能包含凭据或个人信息。请先审查产物,并通过 [`onTestEnd`](#ontestend--仅-mcr-test-可用) 在发布前对正式保存的测试结果脱敏;这不会处理终端输出或已写入的临时文件。导入 raw 数据只合并覆盖率,不会合并测试结果。
|
|
1158
|
+
|
|
1108
1159
|
- 比如,单元测试保存到`./coverage-reports/unit/raw`,见例子
|
|
1109
1160
|
- `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
|
|
1110
1161
|
- `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
|
package/lib/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { program } from 'commander';
|
|
|
9
9
|
import { execa } from 'execa';
|
|
10
10
|
|
|
11
11
|
import { MCR } from './index.js';
|
|
12
|
+
import { getTestCommand, createTestRunner } from './test/test-runner.js';
|
|
12
13
|
import { Util } from './utils/util.js';
|
|
13
14
|
import pkg from '../package.json' with { type: 'json' };
|
|
14
15
|
|
|
@@ -85,17 +86,6 @@ const mergeCoverage = async (cliOptions) => {
|
|
|
85
86
|
await coverageReport.generate();
|
|
86
87
|
};
|
|
87
88
|
|
|
88
|
-
// Only check whether targets were supplied. Node owns all target parsing,
|
|
89
|
-
// discovery and validation; preserve each argument exactly as provided.
|
|
90
|
-
const getTestTargets = (test) => {
|
|
91
|
-
if (!test || (Array.isArray(test) && !test.length)) {
|
|
92
|
-
Util.logInfo('No test targets configured; skipping tests.');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return Array.isArray(test) ? test : [test];
|
|
97
|
-
};
|
|
98
|
-
|
|
99
89
|
// Preserve signals and failing commands even if a hook sets exitCode=0.
|
|
100
90
|
const getExitStatus = (code, signal) => signal || code || process.exitCode || 0;
|
|
101
91
|
|
|
@@ -177,24 +167,19 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
177
167
|
|
|
178
168
|
// console.log(options);
|
|
179
169
|
const coverageReport = MCR(cliOptions);
|
|
180
|
-
const isTest = command === 'test';
|
|
181
170
|
await coverageReport.loadConfig(cliOptions.config);
|
|
182
171
|
|
|
183
172
|
const coverageOptions = coverageReport.options;
|
|
184
|
-
let
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
if (!
|
|
173
|
+
let testRunner;
|
|
174
|
+
if (command === 'test') {
|
|
175
|
+
testRunner = createTestRunner(coverageOptions, testArgs);
|
|
176
|
+
if (!testRunner) {
|
|
188
177
|
return;
|
|
189
178
|
}
|
|
190
|
-
childArgs = ['--test', ... testArgs, ... targets];
|
|
191
179
|
}
|
|
192
180
|
coverageReport.cleanCache();
|
|
193
181
|
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
|
|
194
|
-
|
|
195
|
-
// Tests may spawn children with a different working directory.
|
|
196
|
-
process.env.NODE_V8_COVERAGE = path.resolve(nodeV8CoverageDir);
|
|
197
|
-
}
|
|
182
|
+
testRunner?.initCoverageDir(nodeV8CoverageDir);
|
|
198
183
|
|
|
199
184
|
// =========================================
|
|
200
185
|
// onStart hook
|
|
@@ -204,22 +189,26 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
204
189
|
}
|
|
205
190
|
// =========================================
|
|
206
191
|
|
|
207
|
-
const
|
|
192
|
+
const generateCoverage = async (subprocess) => {
|
|
193
|
+
// onReady hook before adding coverage data. Some children need
|
|
194
|
+
// additional time to finish writing their coverage files.
|
|
195
|
+
const onReady = coverageOptions.onReady;
|
|
196
|
+
if (typeof onReady === 'function') {
|
|
197
|
+
await onReady(coverageReport, nodeV8CoverageDir, subprocess);
|
|
198
|
+
}
|
|
199
|
+
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
200
|
+
return coverageReport.generate();
|
|
201
|
+
};
|
|
208
202
|
|
|
209
|
-
|
|
203
|
+
const onClose = async (code, signal, subprocess) => {
|
|
210
204
|
|
|
205
|
+
// Generate coverage even when the child failed (code != 0).
|
|
211
206
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
220
|
-
const report = await coverageReport.generate();
|
|
221
|
-
if (isTest && !report) {
|
|
222
|
-
throw new Error('No coverage report was generated');
|
|
207
|
+
const generate = () => generateCoverage(subprocess);
|
|
208
|
+
if (testRunner) {
|
|
209
|
+
await testRunner.onClose(generate);
|
|
210
|
+
} else {
|
|
211
|
+
await generate();
|
|
223
212
|
}
|
|
224
213
|
} catch (error) {
|
|
225
214
|
Util.logError(`Coverage report generation failed: ${error.message}`);
|
|
@@ -233,8 +222,8 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
233
222
|
return getExitStatus(code, signal);
|
|
234
223
|
};
|
|
235
224
|
|
|
236
|
-
if (
|
|
237
|
-
return executeSubprocess
|
|
225
|
+
if (testRunner) {
|
|
226
|
+
return testRunner.execute(executeSubprocess, onClose);
|
|
238
227
|
}
|
|
239
228
|
return executeSubprocess(command, commandArgs, onClose);
|
|
240
229
|
|
|
@@ -261,30 +250,6 @@ process.argv.forEach((it) => {
|
|
|
261
250
|
}
|
|
262
251
|
});
|
|
263
252
|
|
|
264
|
-
// Also recognize `mcr -c config -- test targets -- runner-options` without
|
|
265
|
-
// falling through to the shell's unrelated `test` command. Only MCR options
|
|
266
|
-
// before the first separator are parsed in that form.
|
|
267
|
-
const getTestCommand = (args, subArgs) => {
|
|
268
|
-
if (args[0] === 'test') {
|
|
269
|
-
return {
|
|
270
|
-
targets: args.slice(1), runnerArgs: subArgs.slice(1)
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (args.length || subArgs[1] !== 'test') {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
const remaining = subArgs.slice(2);
|
|
277
|
-
const index = remaining.indexOf('--');
|
|
278
|
-
if (index === -1) {
|
|
279
|
-
return {
|
|
280
|
-
targets: remaining, runnerArgs: []
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
targets: remaining.slice(0, index), runnerArgs: remaining.slice(index + 1)
|
|
285
|
-
};
|
|
286
|
-
};
|
|
287
|
-
|
|
288
253
|
program
|
|
289
254
|
.name('mcr')
|
|
290
255
|
.description('CLI to generate coverage reports')
|
|
@@ -761,31 +761,25 @@ const handleOriginalEmptyBytesCoverage = (state, originalStateMap) => {
|
|
|
761
761
|
}
|
|
762
762
|
|
|
763
763
|
const { decodedMappings } = originalState;
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (!endMapping.generatedEndOffset) {
|
|
775
|
-
// line last one
|
|
776
|
-
const line = state.locator.getLine(endMapping.generatedLine + 1);
|
|
777
|
-
// could be no line found
|
|
778
|
-
if (line) {
|
|
779
|
-
// last column
|
|
780
|
-
endMapping.generatedEndOffset = line.end;
|
|
781
|
-
} else {
|
|
782
|
-
endMapping.generatedEndOffset = endMapping.generatedOffset;
|
|
764
|
+
// The generated range the file's own mappings start in. Leave out the
|
|
765
|
+
// added line-end mappings: the last one is at the end of its line, which
|
|
766
|
+
// in a minified bundle is the end of the whole script, not of the wrapper
|
|
767
|
+
// the file is in.
|
|
768
|
+
let startOffset = Infinity;
|
|
769
|
+
let endOffset = -Infinity;
|
|
770
|
+
decodedMappings.forEach((mapping) => {
|
|
771
|
+
if (mapping.end) {
|
|
772
|
+
return;
|
|
783
773
|
}
|
|
784
|
-
|
|
785
|
-
|
|
774
|
+
startOffset = Math.min(startOffset, mapping.generatedOffset);
|
|
775
|
+
endOffset = Math.max(endOffset, mapping.generatedOffset);
|
|
776
|
+
});
|
|
786
777
|
|
|
787
|
-
//
|
|
788
|
-
//
|
|
778
|
+
// A single real mapping is enough; auxiliary line-end mappings are not
|
|
779
|
+
// guaranteed for every source. Do not infer coverage without mappings.
|
|
780
|
+
if (startOffset === Infinity) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
789
783
|
|
|
790
784
|
checkList.push({
|
|
791
785
|
originalState,
|
|
@@ -819,7 +813,9 @@ const handleOriginalEmptyBytesCoverage = (state, originalStateMap) => {
|
|
|
819
813
|
if (range.count > 0) {
|
|
820
814
|
continue;
|
|
821
815
|
}
|
|
822
|
-
|
|
816
|
+
// endOffset is the last mapping's start, not an exclusive end.
|
|
817
|
+
// V8 ranges exclude range.end, so a mapping there is outside.
|
|
818
|
+
if (startOffset >= range.start && endOffset < range.end) {
|
|
823
819
|
// console.log('------------', 'added');
|
|
824
820
|
originalState.bytes.push({
|
|
825
821
|
start: 0,
|
package/lib/default/options.js
CHANGED
|
@@ -111,6 +111,11 @@ const defaultOptions = {
|
|
|
111
111
|
// onEntry: async (entry) => {}
|
|
112
112
|
onEntry: null,
|
|
113
113
|
|
|
114
|
+
// (mcr test only) {function} Optional hook before test result publication, onReady and coverage generation.
|
|
115
|
+
// Results are always collected. Redact sensitive messages/names here before raw/debug export.
|
|
116
|
+
// onTestEnd: async (testResults) => {}
|
|
117
|
+
onTestEnd: null,
|
|
118
|
+
|
|
114
119
|
// {function} onEnd hook
|
|
115
120
|
// onEnd: async (reportData) => {}
|
|
116
121
|
onEnd: null
|
package/lib/generate.js
CHANGED
|
@@ -303,8 +303,9 @@ const addZipData = async (mcr, dataList, sourceCache, dir) => {
|
|
|
303
303
|
}
|
|
304
304
|
const entryName = entry.name;
|
|
305
305
|
const filename = path.basename(entryName);
|
|
306
|
-
//
|
|
307
|
-
|
|
306
|
+
// Do not decompress or parse test results (or other unrelated JSON)
|
|
307
|
+
// when merging coverage. Match the cache reader's accepted prefixes.
|
|
308
|
+
if (filename.endsWith('.json') && (filename.startsWith('coverage-') || filename.startsWith('source-'))) {
|
|
308
309
|
const buf = await zip.entryData(entryName);
|
|
309
310
|
const json = JSON.parse(buf.toString('utf-8'));
|
|
310
311
|
await addJsonData(mcr, dataList, sourceCache, json, filename);
|
package/lib/index.d.ts
CHANGED
|
@@ -271,6 +271,58 @@ declare namespace MCR {
|
|
|
271
271
|
files: CoverageFile[];
|
|
272
272
|
};
|
|
273
273
|
|
|
274
|
+
export interface TestError {
|
|
275
|
+
/** Fixed string fields; defaults are "Error" and "" for unavailable/non-string values. */
|
|
276
|
+
name: string;
|
|
277
|
+
message: string;
|
|
278
|
+
/** Own enumerable data properties only: strings, booleans, finite numbers,
|
|
279
|
+
* and null. BigInts become strings; objects, accessors and unsupported values
|
|
280
|
+
* are omitted. Missing properties read as undefined. Values may be sensitive.
|
|
281
|
+
*/
|
|
282
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface TestResult {
|
|
286
|
+
name: string;
|
|
287
|
+
type: "test" | "suite";
|
|
288
|
+
nesting: number;
|
|
289
|
+
testNumber: number;
|
|
290
|
+
file?: string;
|
|
291
|
+
line?: number;
|
|
292
|
+
column?: number;
|
|
293
|
+
/** Duration in milliseconds. */
|
|
294
|
+
duration: number;
|
|
295
|
+
status: "passed" | "failed" | "cancelled" | "skipped" | "todo";
|
|
296
|
+
skip?: string | boolean;
|
|
297
|
+
todo?: string | boolean;
|
|
298
|
+
error?: TestError;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface TestSummary {
|
|
302
|
+
tests: number;
|
|
303
|
+
suites: number;
|
|
304
|
+
passed: number;
|
|
305
|
+
failed: number;
|
|
306
|
+
cancelled: number;
|
|
307
|
+
skipped: number;
|
|
308
|
+
todo: number;
|
|
309
|
+
/** Duration in milliseconds. */
|
|
310
|
+
duration: number;
|
|
311
|
+
success: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface TestResults {
|
|
315
|
+
/** Unique collection ID, also used in test-results-<id>.json. */
|
|
316
|
+
id: string;
|
|
317
|
+
framework: "node:test";
|
|
318
|
+
/** Whether the runner emitted its final summary (not whether tests passed). */
|
|
319
|
+
complete: boolean;
|
|
320
|
+
/** Final runner counts, excluding suites from the test count; null if incomplete. */
|
|
321
|
+
summary: TestSummary | null;
|
|
322
|
+
/** Completed test and suite records, including nested tests. */
|
|
323
|
+
tests: TestResult[];
|
|
324
|
+
}
|
|
325
|
+
|
|
274
326
|
export type LoggingType = "off" | "error" | "info" | "debug";
|
|
275
327
|
export interface CoverageReportOptions {
|
|
276
328
|
|
|
@@ -449,6 +501,17 @@ declare namespace MCR {
|
|
|
449
501
|
*/
|
|
450
502
|
test?: string | string[] | null;
|
|
451
503
|
|
|
504
|
+
/** Optional hook for `mcr test`; results are collected even when omitted.
|
|
505
|
+
* Runs before publishing the test results cache, onReady and coverage generation,
|
|
506
|
+
* even when assertions fail. Mutate messages/names here to redact persisted raw/debug
|
|
507
|
+
* results. Hook errors prevent publication of test results but not coverage generation.
|
|
508
|
+
* Not called for skipped runs.
|
|
509
|
+
* Missing/invalid reports fail the command without calling this hook. An incomplete
|
|
510
|
+
* report has complete=false and summary=null, and also fails the command.
|
|
511
|
+
* Hook errors fail the command but do not prevent coverage generation.
|
|
512
|
+
*/
|
|
513
|
+
onTestEnd?: (testResults: TestResults) => void | Promise<void>;
|
|
514
|
+
|
|
452
515
|
/** (CLI only) {function} onStart hook */
|
|
453
516
|
onStart?: (coverageReport: CoverageReport) => void | Promise<void>;
|
|
454
517
|
|
|
@@ -639,6 +702,10 @@ export import CoverageRange = MCR.CoverageRange;
|
|
|
639
702
|
export import IgnoredRange = MCR.IgnoredRange;
|
|
640
703
|
export import CoverageFile = MCR.CoverageFile;
|
|
641
704
|
export import CoverageResults = MCR.CoverageResults;
|
|
705
|
+
export import TestError = MCR.TestError;
|
|
706
|
+
export import TestResult = MCR.TestResult;
|
|
707
|
+
export import TestSummary = MCR.TestSummary;
|
|
708
|
+
export import TestResults = MCR.TestResults;
|
|
642
709
|
export import LoggingType = MCR.LoggingType;
|
|
643
710
|
export import CoverageReportOptions = MCR.CoverageReportOptions;
|
|
644
711
|
export import McrCliOptions = MCR.McrCliOptions;
|