@hughescr/stryker-bun-runner 1.2.1 → 1.2.2
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/dist/index.d.ts +6 -1
- package/dist/index.js +118 -47
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -83,7 +83,12 @@ export declare class BunTestRunner implements TestRunner {
|
|
|
83
83
|
*/
|
|
84
84
|
private loadRegistryFile;
|
|
85
85
|
/**
|
|
86
|
-
* Build test results from inspector data
|
|
86
|
+
* Build test results from inspector data.
|
|
87
|
+
*
|
|
88
|
+
* @param inspectorIdToProjectFile - Optional mapping from inspector ID to project file path.
|
|
89
|
+
* When provided, the project file is used for TestResult.id, name, and fileName instead of
|
|
90
|
+
* testInfo.url. This is important for tests defined via helpers (e.g. RuleTester.run()) where
|
|
91
|
+
* Bun's inspector reports a url pointing to node_modules rather than the user's test file.
|
|
87
92
|
*/
|
|
88
93
|
private buildTestsFromInspector;
|
|
89
94
|
/**
|
package/dist/index.js
CHANGED
|
@@ -5593,6 +5593,9 @@ function normalizeTestFilePath(url) {
|
|
|
5593
5593
|
function normalizeTestName(testName) {
|
|
5594
5594
|
return testName.replaceAll(/\p{Cc}/gu, "_").trim();
|
|
5595
5595
|
}
|
|
5596
|
+
function buildProjectFileTestName(filePrefix, fullName) {
|
|
5597
|
+
return normalizeTestName(`${filePrefix} > ${fullName}`);
|
|
5598
|
+
}
|
|
5596
5599
|
function buildUniqueTestName(fullName, url) {
|
|
5597
5600
|
const normalizedPath = normalizeTestFilePath(url);
|
|
5598
5601
|
if (normalizedPath) {
|
|
@@ -5604,16 +5607,16 @@ function buildUniqueTestName(fullName, url) {
|
|
|
5604
5607
|
// src/coverage/coverage-mapper.ts
|
|
5605
5608
|
function mapCoverageToInspectorIds(rawCoverage, executionOrder, testHierarchy, logger) {
|
|
5606
5609
|
if (!rawCoverage?.perTest || Object.keys(rawCoverage.perTest).length === 0) {
|
|
5607
|
-
return rawCoverage ?? undefined;
|
|
5610
|
+
return { coverage: rawCoverage ?? undefined, inspectorIdToProjectFile: new Map };
|
|
5608
5611
|
}
|
|
5609
5612
|
const firstKey = Object.keys(rawCoverage.perTest)[0];
|
|
5610
5613
|
if (/@@test-\d+$/.test(firstKey)) {
|
|
5611
5614
|
return mapFilePrefixedCounterKeys(rawCoverage, executionOrder, testHierarchy, logger);
|
|
5612
5615
|
}
|
|
5613
5616
|
if (/^test-\d+$/.test(firstKey)) {
|
|
5614
|
-
return mapLegacyCounterKeys(rawCoverage, executionOrder, testHierarchy, logger);
|
|
5617
|
+
return { coverage: mapLegacyCounterKeys(rawCoverage, executionOrder, testHierarchy, logger), inspectorIdToProjectFile: new Map };
|
|
5615
5618
|
}
|
|
5616
|
-
return rawCoverage;
|
|
5619
|
+
return { coverage: rawCoverage, inspectorIdToProjectFile: new Map };
|
|
5617
5620
|
}
|
|
5618
5621
|
function countPerTestAppearances(perTestEntries) {
|
|
5619
5622
|
const appearances = new Map;
|
|
@@ -5671,22 +5674,23 @@ function stabilizeCoverage(coverage) {
|
|
|
5671
5674
|
const newPerTest = buildFilteredPerTest(perTestEntries, promoteToStatic);
|
|
5672
5675
|
return { static: newStatic, perTest: newPerTest };
|
|
5673
5676
|
}
|
|
5674
|
-
function
|
|
5675
|
-
const
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5677
|
+
function pairKeysWithInspectorIds(globallyOrderedPerTestKeys, executionOrder, testHierarchy, logger) {
|
|
5678
|
+
const nonSkipped = executionOrder.filter((id) => {
|
|
5679
|
+
const status = testHierarchy.get(id)?.status;
|
|
5680
|
+
return status !== "skip" && status !== "todo";
|
|
5681
|
+
});
|
|
5682
|
+
if (globallyOrderedPerTestKeys.length < nonSkipped.length) {
|
|
5683
|
+
logger?.warn("Coverage/execution count mismatch: %s coverage entries vs %s non-skipped executed tests. " + "Performing partial mapping for %s tests.", globallyOrderedPerTestKeys.length, nonSkipped.length, Math.min(globallyOrderedPerTestKeys.length, nonSkipped.length));
|
|
5684
|
+
}
|
|
5685
|
+
const pairCount = Math.min(globallyOrderedPerTestKeys.length, nonSkipped.length);
|
|
5686
|
+
const pairs = [];
|
|
5687
|
+
for (let i = 0;i < pairCount; i++) {
|
|
5688
|
+
const key = globallyOrderedPerTestKeys[i];
|
|
5689
|
+
const sepIdx = key.indexOf("@@");
|
|
5690
|
+
const filePrefix = sepIdx === -1 ? key : key.slice(0, sepIdx);
|
|
5691
|
+
pairs.push({ filePrefix, inspectorId: nonSkipped[i] });
|
|
5688
5692
|
}
|
|
5689
|
-
return
|
|
5693
|
+
return pairs;
|
|
5690
5694
|
}
|
|
5691
5695
|
function resolveCounterKeys(counterIds, fileToInspectorIds, testHierarchy, logger) {
|
|
5692
5696
|
return counterIds.map((key) => {
|
|
@@ -5695,51 +5699,115 @@ function resolveCounterKeys(counterIds, fileToInspectorIds, testHierarchy, logge
|
|
|
5695
5699
|
const counterStr = key.slice(sepIdx + 2 + "test-".length);
|
|
5696
5700
|
const n = Number.parseInt(counterStr, 10);
|
|
5697
5701
|
const fileIds = fileToInspectorIds.get(filePrefix);
|
|
5698
|
-
const
|
|
5702
|
+
const clampedIdx = fileIds ? Math.min(n - 1, fileIds.length - 1) : undefined;
|
|
5703
|
+
const inspectorId = clampedIdx === undefined ? undefined : fileIds?.[clampedIdx];
|
|
5699
5704
|
const testInfo = inspectorId === undefined ? undefined : testHierarchy.get(inspectorId);
|
|
5700
5705
|
if (testInfo) {
|
|
5701
|
-
|
|
5706
|
+
const testName = buildProjectFileTestName(filePrefix, testInfo.fullName);
|
|
5707
|
+
return { name: testName, testInfo, inspectorId };
|
|
5702
5708
|
}
|
|
5703
5709
|
logger?.warn('Coverage key %s: no inspector test found for file "%s" at position %s ' + "(file has %s tests in execution order). Skipping this test in coverage mapping.", key, filePrefix, n, fileToInspectorIds.get(filePrefix)?.length ?? 0);
|
|
5704
|
-
return { name: `unknown-${key}`, testInfo: null };
|
|
5710
|
+
return { name: `unknown-${key}`, testInfo: null, inspectorId: undefined };
|
|
5705
5711
|
});
|
|
5706
5712
|
}
|
|
5707
|
-
function
|
|
5708
|
-
const
|
|
5709
|
-
for (const { name } of resolved) {
|
|
5710
|
-
|
|
5713
|
+
function buildNameInspectorIds(resolved) {
|
|
5714
|
+
const nameInspectorIds = new Map;
|
|
5715
|
+
for (const { name, inspectorId, testInfo } of resolved) {
|
|
5716
|
+
if (!testInfo) {
|
|
5717
|
+
continue;
|
|
5718
|
+
}
|
|
5719
|
+
let ids = nameInspectorIds.get(name);
|
|
5720
|
+
if (!ids) {
|
|
5721
|
+
ids = new Set;
|
|
5722
|
+
nameInspectorIds.set(name, ids);
|
|
5723
|
+
}
|
|
5724
|
+
ids.add(inspectorId);
|
|
5711
5725
|
}
|
|
5726
|
+
return nameInspectorIds;
|
|
5727
|
+
}
|
|
5728
|
+
function resolveEachTestName(baseName, inspectorId, nameInspectorIds, nameIndexes) {
|
|
5729
|
+
const distinctIds = nameInspectorIds.get(baseName);
|
|
5730
|
+
if ((distinctIds?.size ?? 1) <= 1) {
|
|
5731
|
+
return baseName;
|
|
5732
|
+
}
|
|
5733
|
+
const key_ = `${inspectorId}/${baseName}`;
|
|
5734
|
+
const existingIndex = nameIndexes.get(key_);
|
|
5735
|
+
if (existingIndex === undefined) {
|
|
5736
|
+
const nextIndex = nameIndexes.get(baseName) ?? 0;
|
|
5737
|
+
nameIndexes.set(key_, nextIndex);
|
|
5738
|
+
nameIndexes.set(baseName, nextIndex + 1);
|
|
5739
|
+
return `${baseName} [${nextIndex}]`;
|
|
5740
|
+
}
|
|
5741
|
+
return `${baseName} [${existingIndex}]`;
|
|
5742
|
+
}
|
|
5743
|
+
function buildRemappedPerTest(counterIds, resolved, rawPerTest) {
|
|
5744
|
+
const nameInspectorIds = buildNameInspectorIds(resolved);
|
|
5712
5745
|
const remappedPerTest = {};
|
|
5713
5746
|
const nameIndexes = new Map;
|
|
5714
5747
|
for (const [i, key] of counterIds.entries()) {
|
|
5715
|
-
const { name: baseName, testInfo } = resolved[i];
|
|
5748
|
+
const { name: baseName, testInfo, inspectorId } = resolved[i];
|
|
5716
5749
|
if (!testInfo) {
|
|
5717
5750
|
continue;
|
|
5718
5751
|
}
|
|
5719
|
-
const
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5752
|
+
const finalName = resolveEachTestName(baseName, inspectorId, nameInspectorIds, nameIndexes);
|
|
5753
|
+
const incoming = rawPerTest[key];
|
|
5754
|
+
const existing = remappedPerTest[finalName];
|
|
5755
|
+
if (existing) {
|
|
5756
|
+
for (const [mutantId, count_] of Object.entries(incoming)) {
|
|
5757
|
+
existing[mutantId] = (existing[mutantId] ?? 0) + count_;
|
|
5758
|
+
}
|
|
5759
|
+
} else {
|
|
5760
|
+
remappedPerTest[finalName] = { ...incoming };
|
|
5725
5761
|
}
|
|
5726
|
-
remappedPerTest[finalName] = rawPerTest[key];
|
|
5727
5762
|
}
|
|
5728
5763
|
return remappedPerTest;
|
|
5729
5764
|
}
|
|
5765
|
+
function warnInteriorGapIfPresent(pairs, testHierarchy, logger) {
|
|
5766
|
+
const warnedFiles = new Set;
|
|
5767
|
+
for (const { filePrefix, inspectorId } of pairs) {
|
|
5768
|
+
const testUrl = testHierarchy.get(inspectorId)?.url;
|
|
5769
|
+
if (!testUrl) {
|
|
5770
|
+
continue;
|
|
5771
|
+
}
|
|
5772
|
+
const inspectorFile = normalizeTestFilePath(testUrl);
|
|
5773
|
+
if (!inspectorFile || testUrl.includes("node_modules")) {
|
|
5774
|
+
continue;
|
|
5775
|
+
}
|
|
5776
|
+
if (filePrefix !== inspectorFile && !warnedFiles.has(filePrefix)) {
|
|
5777
|
+
warnedFiles.add(filePrefix);
|
|
5778
|
+
logger?.warn('Interior coverage gap detected for "%s": coverage key paired with inspector test from "%s". ' + "Some tests may have been aborted mid-run (e.g. beforeAll failure). Coverage mapping may be inaccurate.", filePrefix, inspectorFile);
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5730
5782
|
function mapFilePrefixedCounterKeys(rawCoverage, executionOrder, testHierarchy, logger) {
|
|
5731
|
-
const
|
|
5732
|
-
const
|
|
5783
|
+
const globallyOrderedKeys = Object.keys(rawCoverage.perTest);
|
|
5784
|
+
const pairs = pairKeysWithInspectorIds(globallyOrderedKeys, executionOrder, testHierarchy, logger);
|
|
5785
|
+
const fileToInspectorIds = new Map;
|
|
5786
|
+
const inspectorIdToProjectFile = new Map;
|
|
5787
|
+
for (const { filePrefix, inspectorId } of pairs) {
|
|
5788
|
+
const bucket = fileToInspectorIds.get(filePrefix);
|
|
5789
|
+
if (bucket) {
|
|
5790
|
+
bucket.push(inspectorId);
|
|
5791
|
+
} else {
|
|
5792
|
+
fileToInspectorIds.set(filePrefix, [inspectorId]);
|
|
5793
|
+
}
|
|
5794
|
+
inspectorIdToProjectFile.set(inspectorId, filePrefix);
|
|
5795
|
+
}
|
|
5796
|
+
if (globallyOrderedKeys.length === pairs.length) {
|
|
5797
|
+
warnInteriorGapIfPresent(pairs, testHierarchy, logger);
|
|
5798
|
+
}
|
|
5799
|
+
const counterIds = globallyOrderedKeys.toSorted((a, b) => {
|
|
5733
5800
|
const nA = Number.parseInt(a.split("@@test-")[1] ?? "0", 10);
|
|
5734
5801
|
const nB = Number.parseInt(b.split("@@test-")[1] ?? "0", 10);
|
|
5735
5802
|
return nA - nB;
|
|
5736
5803
|
});
|
|
5737
5804
|
const resolved = resolveCounterKeys(counterIds, fileToInspectorIds, testHierarchy, logger);
|
|
5738
5805
|
const remappedPerTest = buildRemappedPerTest(counterIds, resolved, rawCoverage.perTest);
|
|
5739
|
-
|
|
5806
|
+
const coverage = stabilizeCoverage({
|
|
5740
5807
|
static: rawCoverage.static,
|
|
5741
5808
|
perTest: remappedPerTest
|
|
5742
5809
|
});
|
|
5810
|
+
return { coverage, inspectorIdToProjectFile };
|
|
5743
5811
|
}
|
|
5744
5812
|
function mapLegacyCounterKeys(rawCoverage, executionOrder, testHierarchy, logger) {
|
|
5745
5813
|
const counterIds = Object.keys(rawCoverage.perTest).toSorted((a, b) => Number.parseInt(a.split("-")[1] ?? "0", 10) - Number.parseInt(b.split("-")[1] ?? "0", 10));
|
|
@@ -7870,7 +7938,7 @@ class BunTestRunner {
|
|
|
7870
7938
|
}
|
|
7871
7939
|
}
|
|
7872
7940
|
}
|
|
7873
|
-
buildTestsFromInspector(testHierarchy, executionOrder, parsed, totalElapsedMs) {
|
|
7941
|
+
buildTestsFromInspector(testHierarchy, executionOrder, parsed, totalElapsedMs, inspectorIdToProjectFile) {
|
|
7874
7942
|
if (executionOrder.length === 0) {
|
|
7875
7943
|
return parsed.tests.map((t) => {
|
|
7876
7944
|
const normalizedName = normalizeTestName(t.name);
|
|
@@ -7914,7 +7982,9 @@ class BunTestRunner {
|
|
|
7914
7982
|
timeSpentMs: timePerTest
|
|
7915
7983
|
};
|
|
7916
7984
|
}
|
|
7917
|
-
const
|
|
7985
|
+
const projectFile = inspectorIdToProjectFile?.get(inspectorId);
|
|
7986
|
+
const uniqueName = projectFile ? buildProjectFileTestName(projectFile, testInfo.fullName) : buildUniqueTestName(testInfo.fullName, testInfo.url);
|
|
7987
|
+
const fileName = projectFile ?? normalizeTestFilePath(testInfo.url);
|
|
7918
7988
|
const status = testInfo.status;
|
|
7919
7989
|
const elapsed = testInfo.elapsed === undefined ? timePerTest : Math.round(testInfo.elapsed / 1e6);
|
|
7920
7990
|
const startPosition = testInfo.line === undefined ? undefined : { line: testInfo.line, column: 0 };
|
|
@@ -7923,7 +7993,7 @@ class BunTestRunner {
|
|
|
7923
7993
|
return {
|
|
7924
7994
|
id: uniqueName,
|
|
7925
7995
|
name: uniqueName,
|
|
7926
|
-
fileName
|
|
7996
|
+
fileName,
|
|
7927
7997
|
startPosition,
|
|
7928
7998
|
status: TestStatus.Failed,
|
|
7929
7999
|
failureMessage: parsedTest?.failureMessage ?? testInfo.error?.message ?? "Test failed",
|
|
@@ -7934,7 +8004,7 @@ class BunTestRunner {
|
|
|
7934
8004
|
return {
|
|
7935
8005
|
id: uniqueName,
|
|
7936
8006
|
name: uniqueName,
|
|
7937
|
-
fileName
|
|
8007
|
+
fileName,
|
|
7938
8008
|
startPosition,
|
|
7939
8009
|
status: TestStatus.Skipped,
|
|
7940
8010
|
timeSpentMs: elapsed
|
|
@@ -7943,7 +8013,7 @@ class BunTestRunner {
|
|
|
7943
8013
|
return {
|
|
7944
8014
|
id: uniqueName,
|
|
7945
8015
|
name: uniqueName,
|
|
7946
|
-
fileName
|
|
8016
|
+
fileName,
|
|
7947
8017
|
startPosition,
|
|
7948
8018
|
status: TestStatus.Success,
|
|
7949
8019
|
timeSpentMs: elapsed
|
|
@@ -8073,8 +8143,8 @@ exit=%s timedOut=%s
|
|
|
8073
8143
|
if (earlyResult) {
|
|
8074
8144
|
return earlyResult;
|
|
8075
8145
|
}
|
|
8076
|
-
const mutantCoverage = await this.collectAndRemapCoverage(testHierarchy, executionOrder);
|
|
8077
|
-
const tests = this.buildTestsFromInspector(testHierarchy, executionOrder, parsed, totalElapsedMs);
|
|
8146
|
+
const { coverage: mutantCoverage, inspectorIdToProjectFile } = await this.collectAndRemapCoverage(testHierarchy, executionOrder);
|
|
8147
|
+
const tests = this.buildTestsFromInspector(testHierarchy, executionOrder, parsed, totalElapsedMs, inspectorIdToProjectFile);
|
|
8078
8148
|
tests.sort((a, b) => a.name.localeCompare(b.name));
|
|
8079
8149
|
await this.buildAndPersistTestRegistry(tests);
|
|
8080
8150
|
return {
|
|
@@ -8229,16 +8299,17 @@ ${result.stderr}`
|
|
|
8229
8299
|
return null;
|
|
8230
8300
|
}
|
|
8231
8301
|
async collectAndRemapCoverage(testHierarchy, executionOrder) {
|
|
8302
|
+
const testMap = new Map(testHierarchy.map((t) => [t.id, t]));
|
|
8232
8303
|
if (!this.coverageFilePath) {
|
|
8233
|
-
return;
|
|
8304
|
+
return { coverage: undefined, inspectorIdToProjectFile: new Map };
|
|
8234
8305
|
}
|
|
8235
8306
|
const rawCoverage = await collectCoverage(this.coverageFilePath, this.logger);
|
|
8236
8307
|
await cleanupCoverageFile(this.coverageFilePath);
|
|
8237
8308
|
if (!rawCoverage) {
|
|
8238
|
-
return;
|
|
8309
|
+
return { coverage: undefined, inspectorIdToProjectFile: new Map };
|
|
8239
8310
|
}
|
|
8240
|
-
const
|
|
8241
|
-
return
|
|
8311
|
+
const { coverage, inspectorIdToProjectFile } = mapCoverageToInspectorIds(rawCoverage, executionOrder, testMap, this.logger);
|
|
8312
|
+
return { coverage, inspectorIdToProjectFile };
|
|
8242
8313
|
}
|
|
8243
8314
|
buildLocalTestFilterIndex(testFilter) {
|
|
8244
8315
|
const localRegistry = new Set(testFilter);
|