Skip to content

Commit 541736b

Browse files
authored
Implement uncovered branch highlight (#2)
Implement uncovered branch highlight
1 parent 922d6e2 commit 541736b

File tree

8 files changed

+130
-16
lines changed

8 files changed

+130
-16
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"build": "tsc --build ./transform/tsconfig.json && node ./build/esbuild.js",
1616
"test": "node bin/as-test.js && cross-env NODE_OPTIONS=--experimental-vm-modules jest",
1717
"lint": "eslint src assembly tests-ts/test --max-warnings=0 && prettier -c .",
18-
"lint:fix": "eslint src assembly --fix",
18+
"lint:fix": "eslint src assembly --fix && npx prettier --write .",
1919
"example": "node bin/as-test.js --config example/as-test.config.cjs ; node bin/as-test.js --config example/as-test.config.js"
2020
},
2121
"dependencies": {

src/generator/html-generator/genCode.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CodeCoverage, FileCoverageResult, OrganizationName, Repository } from "../../interface.js";
1+
import { CodeCoverage, FileCoverageResult, UncoveredLines, OrganizationName, Repository } from "../../interface.js";
22
import { escape } from "../../utils/escape.js";
33

44
function generateLineCount(totalLines: number): string {
@@ -23,10 +23,15 @@ function generateLineCoverage(codes: CodeCoverage[]): string {
2323
return str.join("\n");
2424
}
2525

26-
function generateSource(codes: CodeCoverage[]): string {
26+
function generateSource(codes: CodeCoverage[], uncoveredlines: UncoveredLines): string {
2727
const str: string[] = [];
28-
for (const code of codes) {
29-
str.push(escape(code.source));
28+
for (const [index, code] of codes.entries()) {
29+
if (uncoveredlines.has(index + 1)) {
30+
// IMPORTANT! to add "nocode" here to preventing prettify from adding unwanted pln class
31+
str.push('<span class="missing-if-branch nocode" title="Branch not taken">!</span>' + escape(code.source));
32+
} else {
33+
str.push(escape(code.source));
34+
}
3035
}
3136
return str.join("\n");
3237
}
@@ -36,7 +41,7 @@ export function generateCodeHtml(relativePathofRoot: string, result: FileCoverag
3641

3742
const lineCoutHtml = generateLineCount(codes.length);
3843
const lineCov = generateLineCoverage(codes);
39-
const lineSource = generateSource(codes);
44+
const lineSource = generateSource(codes, result.uncoveredlines);
4045

4146
return `
4247
<!DOCTYPE html>

src/interface.ts

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export type FunctionIndex = number;
1616
export type LineIndex = number;
1717
export type ColumnIndex = number;
1818
export type FileIndex = number;
19+
export type UncoveredBasicBlocks = Set<CodeSnippetIndex>;
20+
export type UncoveredLines = Set<LineIndex>;
1921

2022
// input cov
2123
export type BranchInfo = [CodeSnippetIndex, CodeSnippetIndex];
@@ -87,11 +89,13 @@ export class FileCoverageResult {
8789
functionCoverageRate: Rate = new Rate();
8890
lineCoverageRate: Rate = new Rate();
8991
sourceUsedCount: CodeCoverage[] = [];
92+
uncoveredlines: Set<number> = new Set();
9093
}
9194

9295
export class FunctionCoverageResult {
9396
constructor(public functionName: string) {}
9497
branchCoverageRate: Rate = new Rate();
98+
uncoveredlines: UncoveredLines = new Set();
9599
lineRange: [number, number] = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
96100
/**
97101
* first means lineIndex;
@@ -111,6 +115,7 @@ export class FunctionCoverageResult {
111115
];
112116
result.branchCoverageRate = Rate.summarize(infos.map((info) => info.branchCoverageRate));
113117
for (const info of infos) {
118+
for (const line of info.uncoveredlines) result.uncoveredlines.add(line);
114119
for (const [lineIndex, count] of info.sourceUsedCount.entries()) {
115120
const srcLineUsedCount = result.sourceUsedCount.get(lineIndex);
116121
result.sourceUsedCount.set(lineIndex, srcLineUsedCount === undefined ? count : srcLineUsedCount + count);

src/parser/singleFileAnalysis.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,23 @@ export class SingleFileCoverageAnalysis {
1919
for (let index = startLine - 1; index < endLine; index++) {
2020
const codeCoverage = this.result.sourceUsedCount[index];
2121
if (codeCoverage === undefined) {
22-
throw new Error(`unknowm error: There is no ${index} Line in file ${this.result.filename}`);
22+
throw new Error(`unknown error: There is no ${index} Line in file ${this.result.filename}`);
2323
}
2424
codeCoverage.usedCount = 0;
2525
}
2626
}
2727
}
2828

2929
merge(results: FunctionCoverageResult[]) {
30+
// SingleFileCoverageAnalysis contains FileCoverageResult
3031
if (results.length === 0) return;
3132
for (const functionCovResult of results) {
33+
for (const line of functionCovResult.uncoveredlines) this.result.uncoveredlines.add(line);
3234
for (const [lineIndex, count] of functionCovResult.sourceUsedCount.entries()) {
3335
const srcLineUsedCount = this.result.sourceUsedCount[lineIndex - 1];
3436
if (srcLineUsedCount === undefined) {
3537
throw new Error(
36-
`unknowm error: There is not Line ${lineIndex} in ${JSON.stringify(this.result.sourceUsedCount)}`
38+
`unknown error: There is not Line ${lineIndex} in ${JSON.stringify(this.result.sourceUsedCount)}`
3739
);
3840
}
3941
if (srcLineUsedCount.usedCount === CodeCoverage.default) {

src/parser/singleFunctionAnalysis.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import assert from "node:assert";
2-
import { CodeSnippetIndex, CovInfo, FunctionCoverageResult } from "../interface.js";
2+
import { CodeSnippetIndex, CovInfo, FunctionCoverageResult, UncoveredBasicBlocks } from "../interface.js";
33

44
type BranchGraph = Map<number, Map<number, boolean>>;
55

66
export class SingleFunctionCoverageAnalysis {
77
result: FunctionCoverageResult;
88
branchGraph: BranchGraph = new Map();
9+
notFullyCoveredBasicBlock: UncoveredBasicBlocks = new Set();
910
constructor(
1011
public covInfo: CovInfo,
1112
name: string
@@ -72,12 +73,22 @@ export class SingleFunctionCoverageAnalysis {
7273
toNodes.set(second, true);
7374
}
7475
}
75-
for (const toNodes of this.branchGraph.values()) {
76+
for (const [currentBasicBlock, branchesForThatBasicBlock] of this.branchGraph) {
7677
let used = 0;
77-
for (const toNode of toNodes.values()) {
78-
if (toNode) used++;
78+
for (const isCovered of branchesForThatBasicBlock.values()) {
79+
if (isCovered) {
80+
used++;
81+
} else {
82+
this.notFullyCoveredBasicBlock.add(currentBasicBlock);
83+
}
7984
}
8085
this.result.branchCoverageRate.used += used;
8186
}
87+
for (const block of this.notFullyCoveredBasicBlock) {
88+
const lineInfo = this.covInfo.lineInfo.get(block);
89+
if (lineInfo !== undefined && lineInfo.size > 0) {
90+
this.result.uncoveredlines.add(Math.max(...lineInfo));
91+
}
92+
}
8293
}
8394
}

tests-ts/test/parser/__snapshots__/parser.test.ts.snap

+13
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ exports[`Parser generateFileCoverage 1`] = `
226226
"total": 18,
227227
"used": 9,
228228
},
229+
"uncoveredlines": Set {},
229230
},
230231
FileCoverageResult {
231232
"branchCoverageRate": Rate {
@@ -451,6 +452,7 @@ exports[`Parser generateFileCoverage 1`] = `
451452
"total": 1,
452453
"used": 1,
453454
},
455+
"uncoveredlines": Set {},
454456
},
455457
FileCoverageResult {
456458
"branchCoverageRate": Rate {
@@ -676,6 +678,9 @@ exports[`Parser generateFileCoverage 1`] = `
676678
"total": 13,
677679
"used": 9,
678680
},
681+
"uncoveredlines": Set {
682+
25,
683+
},
679684
},
680685
FileCoverageResult {
681686
"branchCoverageRate": Rate {
@@ -901,6 +906,7 @@ exports[`Parser generateFileCoverage 1`] = `
901906
"total": 2,
902907
"used": 2,
903908
},
909+
"uncoveredlines": Set {},
904910
},
905911
]
906912
`;
@@ -920,6 +926,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
920926
"sourceUsedCount": Map {
921927
39 => 3,
922928
},
929+
"uncoveredlines": Set {},
923930
},
924931
FunctionCoverageResult {
925932
"branchCoverageRate": Rate {
@@ -941,6 +948,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
941948
22 => 1,
942949
24 => 2,
943950
},
951+
"uncoveredlines": Set {},
944952
},
945953
FunctionCoverageResult {
946954
"branchCoverageRate": Rate {
@@ -955,6 +963,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
955963
"sourceUsedCount": Map {
956964
45 => 4,
957965
},
966+
"uncoveredlines": Set {},
958967
},
959968
FunctionCoverageResult {
960969
"branchCoverageRate": Rate {
@@ -978,6 +987,9 @@ exports[`Parser generateFunctionCoverage 1`] = `
978987
26 => 1,
979988
29 => 0,
980989
},
990+
"uncoveredlines": Set {
991+
25,
992+
},
981993
},
982994
FunctionCoverageResult {
983995
"branchCoverageRate": Rate {
@@ -993,6 +1005,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
9931005
10 => 2,
9941006
11 => 2,
9951007
},
1008+
"uncoveredlines": Set {},
9961009
},
9971010
]
9981011
`;

tests-ts/test/parser/singleFileAnalysis.test.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ describe("singleFileAnalysis", () => {
3636
functionName: "A",
3737
lineRange: [6, 9],
3838
branchCoverageRate: rate_A,
39+
uncoveredlines: new Set<number>([]),
3940
sourceUsedCount: new Map([
4041
[6, 3],
4142
[7, 0],
@@ -49,6 +50,7 @@ describe("singleFileAnalysis", () => {
4950
functionName: "B",
5051
lineRange: [10, 14],
5152
branchCoverageRate: rate_B,
53+
uncoveredlines: new Set<number>([]),
5254
sourceUsedCount: new Map([
5355
[10, 2],
5456
[11, 0],
@@ -73,9 +75,7 @@ describe("singleFileAnalysis", () => {
7375

7476
test("setUnTestedFunction error", () => {
7577
const analyzer = new SingleFileCoverageAnalysis("main", source);
76-
expect(() => analyzer.setUnTestedFunction([[30, 31]])).toThrowError(
77-
"unknowm error: There is no 29 Line in file main"
78-
);
78+
expect(() => analyzer.setUnTestedFunction([[30, 31]])).toThrow("unknown error: There is no 29 Line in file main");
7979
});
8080

8181
test("merge error", () => {
@@ -88,12 +88,13 @@ describe("singleFileAnalysis", () => {
8888
functionName: "A",
8989
lineRange: [6, 30],
9090
branchCoverageRate: rate,
91+
uncoveredlines: new Set<number>([]),
9192
sourceUsedCount: new Map([
9293
[6, 3],
9394
[7, 0],
9495
[30, 3],
9596
]),
9697
};
97-
expect(() => analyzer.merge([funcResult])).toThrowError();
98+
expect(() => analyzer.merge([funcResult])).toThrow();
9899
});
99100
});

tests-ts/test/parser/singleFunctionAnalysis.test.ts

+77
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,83 @@ describe("singleFunctionAnalysis", () => {
5858
])
5959
);
6060
});
61+
62+
test("forLoop", () => {
63+
const covInfo: CovInfo = {
64+
branchInfo: [
65+
[1, 2],
66+
[1, 3],
67+
],
68+
lineInfo: new Map([
69+
[0, new Set([17, 18])],
70+
[1, new Set([18])],
71+
[2, new Set([18, 20])],
72+
[3, new Set([])],
73+
[4, new Set([22])],
74+
]),
75+
};
76+
const traceInfo = [0, 1, 3, 4];
77+
const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
78+
const result = analyzer.update(traceInfo);
79+
expect(result.uncoveredlines).toEqual(new Set([18]));
80+
});
81+
82+
test("ifWithoutElse", () => {
83+
const covInfo: CovInfo = {
84+
branchInfo: [
85+
[0, 1],
86+
[0, 2],
87+
],
88+
lineInfo: new Map([
89+
[0, new Set([2])],
90+
[1, new Set([3])],
91+
[2, new Set([5])],
92+
]),
93+
};
94+
const traceInfo = [0, 1];
95+
const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
96+
const result = analyzer.update(traceInfo);
97+
expect(result.uncoveredlines).toEqual(new Set([2]));
98+
});
99+
100+
test("threeOperandOperator", () => {
101+
const covInfo: CovInfo = {
102+
branchInfo: [
103+
[0, 1],
104+
[0, 2],
105+
],
106+
lineInfo: new Map([
107+
[0, new Set([26])],
108+
[1, new Set([26])],
109+
[2, new Set([26])],
110+
[3, new Set([26])],
111+
]),
112+
};
113+
const traceInfo = [3, 0, 1];
114+
const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
115+
const result = analyzer.update(traceInfo);
116+
expect(result.uncoveredlines).toEqual(new Set([26]));
117+
});
118+
119+
test("whileLoop", () => {
120+
const covInfo: CovInfo = {
121+
branchInfo: [
122+
[1, 2],
123+
[1, 3],
124+
],
125+
lineInfo: new Map([
126+
[0, new Set([9])],
127+
[1, new Set([10])],
128+
[2, new Set([11])],
129+
[3, new Set([])],
130+
[4, new Set([10, 13])],
131+
]),
132+
};
133+
const traceInfo = [0, 1, 3, 4];
134+
const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
135+
const result = analyzer.update(traceInfo);
136+
expect(result.uncoveredlines).toEqual(new Set([10]));
137+
});
61138
});
62139

63140
test("mergeFromGeneric()", () => {

0 commit comments

Comments
 (0)