diff --git a/package.json b/package.json
index ebbcb7a..afdfe77 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"build": "tsc --build ./transform/tsconfig.json && node ./build/esbuild.js",
"test": "node bin/as-test.js && cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"lint": "eslint src assembly tests-ts/test --max-warnings=0 && prettier -c .",
- "lint:fix": "eslint src assembly --fix",
+ "lint:fix": "eslint src assembly --fix && npx prettier --write .",
"example": "node bin/as-test.js --config example/as-test.config.cjs ; node bin/as-test.js --config example/as-test.config.js"
},
"dependencies": {
diff --git a/src/generator/html-generator/genCode.ts b/src/generator/html-generator/genCode.ts
index 983068f..c61d79f 100644
--- a/src/generator/html-generator/genCode.ts
+++ b/src/generator/html-generator/genCode.ts
@@ -1,4 +1,4 @@
-import { CodeCoverage, FileCoverageResult, OrganizationName, Repository } from "../../interface.js";
+import { CodeCoverage, FileCoverageResult, UncoveredLines, OrganizationName, Repository } from "../../interface.js";
import { escape } from "../../utils/escape.js";
function generateLineCount(totalLines: number): string {
@@ -23,10 +23,15 @@ function generateLineCoverage(codes: CodeCoverage[]): string {
return str.join("\n");
}
-function generateSource(codes: CodeCoverage[]): string {
+function generateSource(codes: CodeCoverage[], uncoveredlines: UncoveredLines): string {
const str: string[] = [];
- for (const code of codes) {
- str.push(escape(code.source));
+ for (const [index, code] of codes.entries()) {
+ if (uncoveredlines.has(index + 1)) {
+ // IMPORTANT! to add "nocode" here to preventing prettify from adding unwanted pln class
+ str.push('!' + escape(code.source));
+ } else {
+ str.push(escape(code.source));
+ }
}
return str.join("\n");
}
@@ -36,7 +41,7 @@ export function generateCodeHtml(relativePathofRoot: string, result: FileCoverag
const lineCoutHtml = generateLineCount(codes.length);
const lineCov = generateLineCoverage(codes);
- const lineSource = generateSource(codes);
+ const lineSource = generateSource(codes, result.uncoveredlines);
return `
diff --git a/src/interface.ts b/src/interface.ts
index 15921fd..e664323 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -16,6 +16,8 @@ export type FunctionIndex = number;
export type LineIndex = number;
export type ColumnIndex = number;
export type FileIndex = number;
+export type UncoveredBasicBlocks = Set;
+export type UncoveredLines = Set;
// input cov
export type BranchInfo = [CodeSnippetIndex, CodeSnippetIndex];
@@ -87,11 +89,13 @@ export class FileCoverageResult {
functionCoverageRate: Rate = new Rate();
lineCoverageRate: Rate = new Rate();
sourceUsedCount: CodeCoverage[] = [];
+ uncoveredlines: Set = new Set();
}
export class FunctionCoverageResult {
constructor(public functionName: string) {}
branchCoverageRate: Rate = new Rate();
+ uncoveredlines: UncoveredLines = new Set();
lineRange: [number, number] = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
/**
* first means lineIndex;
@@ -111,6 +115,7 @@ export class FunctionCoverageResult {
];
result.branchCoverageRate = Rate.summarize(infos.map((info) => info.branchCoverageRate));
for (const info of infos) {
+ for (const line of info.uncoveredlines) result.uncoveredlines.add(line);
for (const [lineIndex, count] of info.sourceUsedCount.entries()) {
const srcLineUsedCount = result.sourceUsedCount.get(lineIndex);
result.sourceUsedCount.set(lineIndex, srcLineUsedCount === undefined ? count : srcLineUsedCount + count);
diff --git a/src/parser/singleFileAnalysis.ts b/src/parser/singleFileAnalysis.ts
index 6990f89..5c783b9 100644
--- a/src/parser/singleFileAnalysis.ts
+++ b/src/parser/singleFileAnalysis.ts
@@ -19,7 +19,7 @@ export class SingleFileCoverageAnalysis {
for (let index = startLine - 1; index < endLine; index++) {
const codeCoverage = this.result.sourceUsedCount[index];
if (codeCoverage === undefined) {
- throw new Error(`unknowm error: There is no ${index} Line in file ${this.result.filename}`);
+ throw new Error(`unknown error: There is no ${index} Line in file ${this.result.filename}`);
}
codeCoverage.usedCount = 0;
}
@@ -27,13 +27,15 @@ export class SingleFileCoverageAnalysis {
}
merge(results: FunctionCoverageResult[]) {
+ // SingleFileCoverageAnalysis contains FileCoverageResult
if (results.length === 0) return;
for (const functionCovResult of results) {
+ for (const line of functionCovResult.uncoveredlines) this.result.uncoveredlines.add(line);
for (const [lineIndex, count] of functionCovResult.sourceUsedCount.entries()) {
const srcLineUsedCount = this.result.sourceUsedCount[lineIndex - 1];
if (srcLineUsedCount === undefined) {
throw new Error(
- `unknowm error: There is not Line ${lineIndex} in ${JSON.stringify(this.result.sourceUsedCount)}`
+ `unknown error: There is not Line ${lineIndex} in ${JSON.stringify(this.result.sourceUsedCount)}`
);
}
if (srcLineUsedCount.usedCount === CodeCoverage.default) {
diff --git a/src/parser/singleFunctionAnalysis.ts b/src/parser/singleFunctionAnalysis.ts
index e850a93..d707220 100644
--- a/src/parser/singleFunctionAnalysis.ts
+++ b/src/parser/singleFunctionAnalysis.ts
@@ -1,11 +1,12 @@
import assert from "node:assert";
-import { CodeSnippetIndex, CovInfo, FunctionCoverageResult } from "../interface.js";
+import { CodeSnippetIndex, CovInfo, FunctionCoverageResult, UncoveredBasicBlocks } from "../interface.js";
type BranchGraph = Map>;
export class SingleFunctionCoverageAnalysis {
result: FunctionCoverageResult;
branchGraph: BranchGraph = new Map();
+ notFullyCoveredBasicBlock: UncoveredBasicBlocks = new Set();
constructor(
public covInfo: CovInfo,
name: string
@@ -72,12 +73,22 @@ export class SingleFunctionCoverageAnalysis {
toNodes.set(second, true);
}
}
- for (const toNodes of this.branchGraph.values()) {
+ for (const [currentBasicBlock, branchesForThatBasicBlock] of this.branchGraph) {
let used = 0;
- for (const toNode of toNodes.values()) {
- if (toNode) used++;
+ for (const isCovered of branchesForThatBasicBlock.values()) {
+ if (isCovered) {
+ used++;
+ } else {
+ this.notFullyCoveredBasicBlock.add(currentBasicBlock);
+ }
}
this.result.branchCoverageRate.used += used;
}
+ for (const block of this.notFullyCoveredBasicBlock) {
+ const lineInfo = this.covInfo.lineInfo.get(block);
+ if (lineInfo !== undefined && lineInfo.size > 0) {
+ this.result.uncoveredlines.add(Math.max(...lineInfo));
+ }
+ }
}
}
diff --git a/tests-ts/test/parser/__snapshots__/parser.test.ts.snap b/tests-ts/test/parser/__snapshots__/parser.test.ts.snap
index 3bb5987..bc8fbbf 100644
--- a/tests-ts/test/parser/__snapshots__/parser.test.ts.snap
+++ b/tests-ts/test/parser/__snapshots__/parser.test.ts.snap
@@ -226,6 +226,7 @@ exports[`Parser generateFileCoverage 1`] = `
"total": 18,
"used": 9,
},
+ "uncoveredlines": Set {},
},
FileCoverageResult {
"branchCoverageRate": Rate {
@@ -451,6 +452,7 @@ exports[`Parser generateFileCoverage 1`] = `
"total": 1,
"used": 1,
},
+ "uncoveredlines": Set {},
},
FileCoverageResult {
"branchCoverageRate": Rate {
@@ -676,6 +678,9 @@ exports[`Parser generateFileCoverage 1`] = `
"total": 13,
"used": 9,
},
+ "uncoveredlines": Set {
+ 25,
+ },
},
FileCoverageResult {
"branchCoverageRate": Rate {
@@ -901,6 +906,7 @@ exports[`Parser generateFileCoverage 1`] = `
"total": 2,
"used": 2,
},
+ "uncoveredlines": Set {},
},
]
`;
@@ -920,6 +926,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
"sourceUsedCount": Map {
39 => 3,
},
+ "uncoveredlines": Set {},
},
FunctionCoverageResult {
"branchCoverageRate": Rate {
@@ -941,6 +948,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
22 => 1,
24 => 2,
},
+ "uncoveredlines": Set {},
},
FunctionCoverageResult {
"branchCoverageRate": Rate {
@@ -955,6 +963,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
"sourceUsedCount": Map {
45 => 4,
},
+ "uncoveredlines": Set {},
},
FunctionCoverageResult {
"branchCoverageRate": Rate {
@@ -978,6 +987,9 @@ exports[`Parser generateFunctionCoverage 1`] = `
26 => 1,
29 => 0,
},
+ "uncoveredlines": Set {
+ 25,
+ },
},
FunctionCoverageResult {
"branchCoverageRate": Rate {
@@ -993,6 +1005,7 @@ exports[`Parser generateFunctionCoverage 1`] = `
10 => 2,
11 => 2,
},
+ "uncoveredlines": Set {},
},
]
`;
diff --git a/tests-ts/test/parser/singleFileAnalysis.test.ts b/tests-ts/test/parser/singleFileAnalysis.test.ts
index 2609ca7..bbd7399 100644
--- a/tests-ts/test/parser/singleFileAnalysis.test.ts
+++ b/tests-ts/test/parser/singleFileAnalysis.test.ts
@@ -36,6 +36,7 @@ describe("singleFileAnalysis", () => {
functionName: "A",
lineRange: [6, 9],
branchCoverageRate: rate_A,
+ uncoveredlines: new Set([]),
sourceUsedCount: new Map([
[6, 3],
[7, 0],
@@ -49,6 +50,7 @@ describe("singleFileAnalysis", () => {
functionName: "B",
lineRange: [10, 14],
branchCoverageRate: rate_B,
+ uncoveredlines: new Set([]),
sourceUsedCount: new Map([
[10, 2],
[11, 0],
@@ -73,9 +75,7 @@ describe("singleFileAnalysis", () => {
test("setUnTestedFunction error", () => {
const analyzer = new SingleFileCoverageAnalysis("main", source);
- expect(() => analyzer.setUnTestedFunction([[30, 31]])).toThrowError(
- "unknowm error: There is no 29 Line in file main"
- );
+ expect(() => analyzer.setUnTestedFunction([[30, 31]])).toThrow("unknown error: There is no 29 Line in file main");
});
test("merge error", () => {
@@ -88,12 +88,13 @@ describe("singleFileAnalysis", () => {
functionName: "A",
lineRange: [6, 30],
branchCoverageRate: rate,
+ uncoveredlines: new Set([]),
sourceUsedCount: new Map([
[6, 3],
[7, 0],
[30, 3],
]),
};
- expect(() => analyzer.merge([funcResult])).toThrowError();
+ expect(() => analyzer.merge([funcResult])).toThrow();
});
});
diff --git a/tests-ts/test/parser/singleFunctionAnalysis.test.ts b/tests-ts/test/parser/singleFunctionAnalysis.test.ts
index b55bfe6..0847ae9 100644
--- a/tests-ts/test/parser/singleFunctionAnalysis.test.ts
+++ b/tests-ts/test/parser/singleFunctionAnalysis.test.ts
@@ -58,6 +58,83 @@ describe("singleFunctionAnalysis", () => {
])
);
});
+
+ test("forLoop", () => {
+ const covInfo: CovInfo = {
+ branchInfo: [
+ [1, 2],
+ [1, 3],
+ ],
+ lineInfo: new Map([
+ [0, new Set([17, 18])],
+ [1, new Set([18])],
+ [2, new Set([18, 20])],
+ [3, new Set([])],
+ [4, new Set([22])],
+ ]),
+ };
+ const traceInfo = [0, 1, 3, 4];
+ const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
+ const result = analyzer.update(traceInfo);
+ expect(result.uncoveredlines).toEqual(new Set([18]));
+ });
+
+ test("ifWithoutElse", () => {
+ const covInfo: CovInfo = {
+ branchInfo: [
+ [0, 1],
+ [0, 2],
+ ],
+ lineInfo: new Map([
+ [0, new Set([2])],
+ [1, new Set([3])],
+ [2, new Set([5])],
+ ]),
+ };
+ const traceInfo = [0, 1];
+ const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
+ const result = analyzer.update(traceInfo);
+ expect(result.uncoveredlines).toEqual(new Set([2]));
+ });
+
+ test("threeOperandOperator", () => {
+ const covInfo: CovInfo = {
+ branchInfo: [
+ [0, 1],
+ [0, 2],
+ ],
+ lineInfo: new Map([
+ [0, new Set([26])],
+ [1, new Set([26])],
+ [2, new Set([26])],
+ [3, new Set([26])],
+ ]),
+ };
+ const traceInfo = [3, 0, 1];
+ const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
+ const result = analyzer.update(traceInfo);
+ expect(result.uncoveredlines).toEqual(new Set([26]));
+ });
+
+ test("whileLoop", () => {
+ const covInfo: CovInfo = {
+ branchInfo: [
+ [1, 2],
+ [1, 3],
+ ],
+ lineInfo: new Map([
+ [0, new Set([9])],
+ [1, new Set([10])],
+ [2, new Set([11])],
+ [3, new Set([])],
+ [4, new Set([10, 13])],
+ ]),
+ };
+ const traceInfo = [0, 1, 3, 4];
+ const analyzer = new SingleFunctionCoverageAnalysis(covInfo, "main");
+ const result = analyzer.update(traceInfo);
+ expect(result.uncoveredlines).toEqual(new Set([10]));
+ });
});
test("mergeFromGeneric()", () => {