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()", () => {