我正在使用Inspector API来获得JavaScript的精确覆盖率。我在target.js
中实现了一个目标函数(在解释中添加了target.js中的行注解以引用它们),并在m1n 1o1p上实现了一条测试运行程序,它应该收集精确的覆盖信息。您可以通过运行node main.js
自行测试设置。
const inspector = require("node:inspector");
const fs = require("fs");
const target = require("./target.js").target;
async function run(session, data) {
await new Promise((resolve, reject) => {
session.post("Profiler.startPreciseCoverage", { callCount: true, detailed: true }, (err) => {
err ? reject(err) : resolve();
});
});
console.log("Input: ", data, " - Output: ", target(data));
return await new Promise((resolve, reject) => {
session.post("Profiler.takePreciseCoverage", (err, params) => {
err ? reject(err) : resolve(params);
});
});
}
function printCoverage(result) {
const content = fs.readFileSync("./target.js", { encoding: "utf-8" });
for (let scriptCoverage of result) {
if (!scriptCoverage.url.endsWith("target.js")) {
continue;
}
for (let func of scriptCoverage.functions) {
for (let range of func.ranges) {
console.log(
func.functionName,
range.startOffset,
range.endOffset,
range.count,
content.substring(range.startOffset, range.endOffset).replaceAll("\n", "").replaceAll("\t", " ").replaceAll(" ", " ")
);
}
}
}
}
async function main() {
inspector.open(9696);
const session = new inspector.Session();
session.connect();
await new Promise((resolve, reject) => {
session.post("Profiler.enable", (err) => {
if (err) {
reject(err);
}
resolve();
});
});
const param1 = await run(session, "a");
printCoverage(param1.result);
const param2 = await run(session, "ab");
printCoverage(param2.result);
const param3 = await run(session, "ac");
printCoverage(param3.result);
}
main();
// main.js
/* 1: */ function target(data) {
/* 2: */ if (data.length === 1) {
/* 3: */ return 1;
/* 4: */ }
/* 5: */
/* 6: */ if (data.length == 2) {
/* 7: */ if (data[0] === "a" && data[1] === "b") {
/* 8: */ return 2;
/* 9: */ }
/* a: */
/* b: */ if (data[0] === "a") {
/* c: */ return 3;
/* d: */ }
/* e: */ }
/* f: */ }
module.exports = {
target: target
};
// target.js
不幸的是,下面的输出对我来说很不直观。我本以为覆盖率信息是:
- “a”:第1-3行
- “ab”:第1-8行
- “ac”:第1、2、6、7、b、c行(编辑1:固定范围)
相反,coverage输出似乎只是告诉我target()函数已被覆盖,而target(()中的某些范围未被覆盖。我是否使用了错误的API?还是我对区块覆盖率的预期是错误的?如何在JavaScript中获得“真实”的基本块覆盖率?
Input: a - Output: 1
target 0 259 1 function target(data) { if (data.length === 1) { return 1; } if (data.length == 2) { if (data[0] === "a" && data[1] === "b") { return 2; } if (data[0] === "a") { return 3; } }}
target 76 257 0 if (data.length == 2) { if (data[0] === "a" && data[1] === "b") { return 2; } if (data[0] === "a") { return 3; } }
Input: ab - Output: 2
target 0 259 1 function target(data) { if (data.length === 1) { return 1; } if (data.length == 2) { if (data[0] === "a" && data[1] === "b") { return 2; } if (data[0] === "a") { return 3; } }}
target 51 76 0 { return 1; }
target 187 251 0 if (data[0] === "a") { return 3; }
Input: ac - Output: 3
target 0 259 1 function target(data) { if (data.length === 1) { return 1; } if (data.length == 2) { if (data[0] === "a" && data[1] === "b") { return 2; } if (data[0] === "a") { return 3; } }}
target 51 76 0 { return 1; }
target 154 187 0 { return 2; }
编辑1:
我认为下面的图片更适合解释我的期望。源代码中有9个基本块(如果包含未定义的隐式返回),我想检索哪些基本块已被覆盖。
例如,输入“ab”将覆盖五个块A、B、E、G、I。然而,从Inspector API中,我只得到了整个函数被覆盖的信息,而这个函数的某些字节没有被覆盖。从这一点上,我既不能(据我所知)导出函数中基本块的数量,也不能导出执行的块。
小时
3条答案
按热度按时间4si2a6ki1#
我是否使用了错误的API?
看起来你得到了预期的结果,所以不,你使用的API是正确的。
还是我对区块覆盖率的预期是错误的?
显然:-)
您得到的输出与您链接到的文档描述的内容相匹配。你为什么期望得到不同的东西?
“ac”:第1-7行,b,c
这个特定的期望显然是错误的:长度为2的
'ac'
肯定不会执行第3行,它是否执行第4行和/或第5行(这两行都不包含任何语句)是一个定义问题。从输出中可以看出,Inspector API认为第4行中的结束}
(以及第2行中相应的开始{
)没有针对该输入执行。如何在JavaScript中获得“真实”的基本块覆盖率?
我不知道你的意思。
数据都在那里;如果需要,可以将偏移Map到行号。请注意,每行表示可能非常不准确:缩小的JS代码通常在同一行上有多个函数(事实上,整个
target.js
示例可以写成一行)。从Inspector API获得的每个字符的信息更准确,并且无论被检查的代码是如何格式化的,都同样有效。vshtjzan2#
你不能只是合并范围,不。你需要先构建抽象语法树和框图。
由此我无法导出函数中基本块的数量
这似乎是真的,您必须自己解析代码并将其拆分为块。
也不能导出执行的块
我不同意。子块的计数为
0
,这意味着它们被跳过。因此,对于示例输入ab
,您必须计算[0 259] - [51 76] - [187 251]
以获得块A, B, E, G, I, D
。尽管通过控制流分析,您可以得出D
在I
之后没有执行。rryofs0p3#
我在v8 source code中找到了负责合并基本块的函数。作为一个快速修复,我在
RewritePositionSingletonsToRanges
之后删除了这些调用,它似乎有效:请注意,函数必须至少调用一次,因为分析开始出现在列表中。例如,如果我们有呼叫:
函数
rareFoo
(及其基本块)将仅出现在第二和第三覆盖信息中。