节点,使用Inspector API实现js精度覆盖

j9per5c4  于 2022-10-22  发布在  Java
关注(0)|答案(3)|浏览(174)

我正在使用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中,我只得到了整个函数被覆盖的信息,而这个函数的某些字节没有被覆盖。从这一点上,我既不能(据我所知)导出函数中基本块的数量,也不能导出执行的块。


小时

4si2a6ki

4si2a6ki1#

我是否使用了错误的API?
看起来你得到了预期的结果,所以不,你使用的API是正确的。
还是我对区块覆盖率的预期是错误的?
显然:-)
您得到的输出与您链接到的文档描述的内容相匹配。你为什么期望得到不同的东西?
“ac”:第1-7行,b,c
这个特定的期望显然是错误的:长度为2的'ac'肯定不会执行第3行,它是否执行第4行和/或第5行(这两行都不包含任何语句)是一个定义问题。从输出中可以看出,Inspector API认为第4行中的结束}(以及第2行中相应的开始{)没有针对该输入执行。
如何在JavaScript中获得“真实”的基本块覆盖率?
我不知道你的意思。
数据都在那里;如果需要,可以将偏移Map到行号。请注意,每行表示可能非常不准确:缩小的JS代码通常在同一行上有多个函数(事实上,整个target.js示例可以写成一行)。从Inspector API获得的每个字符的信息更准确,并且无论被检查的代码是如何格式化的,都同样有效。

vshtjzan

vshtjzan2#

你不能只是合并范围,不。你需要先构建抽象语法树和框图。
由此我无法导出函数中基本块的数量
这似乎是真的,您必须自己解析代码并将其拆分为块。
也不能导出执行的块
我不同意。子块的计数为0,这意味着它们被跳过。因此,对于示例输入ab,您必须计算[0 259] - [51 76] - [187 251]以获得块A, B, E, G, I, D。尽管通过控制流分析,您可以得出DI之后没有执行。

rryofs0p

rryofs0p3#

我在v8 source code中找到了负责合并基本块的函数。作为一个快速修复,我在RewritePositionSingletonsToRanges之后删除了这些调用,它似乎有效:

input:  ab
function: target

Start   | End   | Count
================================================
49      | 324   | 1     |
100     | 125   | 0     |
125     | 154   | 1     |
154     | 307   | 1     |
184     | 202   | 0     |
204     | 237   | 0     |
237     | 268   | 1     |
268     | 301   | 0     |
307     | 323   | 1     |

请注意,函数必须至少调用一次,因为分析开始出现在列表中。例如,如果我们有呼叫:

startCoverage();
foo()
takeCoverage();
rareFoo();
takeCoverage();
foo();
takeCoverage();

函数rareFoo(及其基本块)将仅出现在第二和第三覆盖信息中。

相关问题