[inspector] extend protocol for code coverage.
R=jgruber@chromium.org, kozyatinskiy@chromium.org, pfeldman@chromium.org BUG=v8:5808 Review-Url: https://codereview.chromium.org/2700743002 Cr-Commit-Position: refs/heads/master@{#43363}
This commit is contained in:
parent
e2de1b8696
commit
901c29eb1c
44
src/api.cc
44
src/api.cc
@ -9541,46 +9541,50 @@ Local<String> CpuProfileNode::GetFunctionName() const {
|
||||
}
|
||||
}
|
||||
|
||||
debug::Coverage::Range::Range(i::CoverageRange* range,
|
||||
Local<debug::Script> script)
|
||||
: range_(range), script_(script) {
|
||||
debug::Coverage::FunctionData::FunctionData(i::CoverageFunction* function,
|
||||
Local<debug::Script> script)
|
||||
: function_(function) {
|
||||
i::Handle<i::Script> i_script = v8::Utils::OpenHandle(*script);
|
||||
i::Script::PositionInfo start;
|
||||
i::Script::PositionInfo end;
|
||||
i::Script::GetPositionInfo(i_script, range->start, &start,
|
||||
i::Script::GetPositionInfo(i_script, function->start, &start,
|
||||
i::Script::WITH_OFFSET);
|
||||
i::Script::GetPositionInfo(i_script, range->end, &end,
|
||||
i::Script::GetPositionInfo(i_script, function->end, &end,
|
||||
i::Script::WITH_OFFSET);
|
||||
start_ = Location(start.line, start.column);
|
||||
end_ = Location(end.line, end.column);
|
||||
}
|
||||
|
||||
uint32_t debug::Coverage::Range::Count() { return range_->count; }
|
||||
uint32_t debug::Coverage::FunctionData::Count() { return function_->count; }
|
||||
|
||||
size_t debug::Coverage::Range::NestedCount() { return range_->inner.size(); }
|
||||
|
||||
debug::Coverage::Range debug::Coverage::Range::GetNested(size_t i) {
|
||||
return Range(&range_->inner[i], script_);
|
||||
MaybeLocal<String> debug::Coverage::FunctionData::Name() {
|
||||
return ToApiHandle<String>(function_->name);
|
||||
}
|
||||
|
||||
MaybeLocal<String> debug::Coverage::Range::Name() {
|
||||
return ToApiHandle<String>(range_->name);
|
||||
Local<debug::Script> debug::Coverage::ScriptData::GetScript() {
|
||||
return ToApiHandle<debug::Script>(script_->script);
|
||||
}
|
||||
|
||||
size_t debug::Coverage::ScriptData::FunctionCount() {
|
||||
return script_->functions.size();
|
||||
}
|
||||
|
||||
debug::Coverage::FunctionData debug::Coverage::ScriptData::GetFunctionData(
|
||||
size_t i) {
|
||||
return FunctionData(&script_->functions.at(i), GetScript());
|
||||
}
|
||||
|
||||
debug::Coverage::~Coverage() { delete coverage_; }
|
||||
|
||||
size_t debug::Coverage::ScriptCount() { return coverage_->size(); }
|
||||
|
||||
Local<debug::Script> debug::Coverage::GetScript(size_t i) {
|
||||
return ToApiHandle<debug::Script>(coverage_->at(i).script);
|
||||
debug::Coverage::ScriptData debug::Coverage::GetScriptData(size_t i) {
|
||||
return ScriptData(&coverage_->at(i));
|
||||
}
|
||||
|
||||
debug::Coverage::Range debug::Coverage::GetRange(size_t i) {
|
||||
return Range(&coverage_->at(i).toplevel, GetScript(i));
|
||||
}
|
||||
|
||||
debug::Coverage debug::Coverage::Collect(Isolate* isolate) {
|
||||
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate)));
|
||||
debug::Coverage debug::Coverage::Collect(Isolate* isolate, bool reset_count) {
|
||||
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate),
|
||||
reset_count));
|
||||
}
|
||||
|
||||
void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) {
|
||||
|
70
src/d8.cc
70
src/d8.cc
@ -1657,51 +1657,15 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
|
||||
JSON::Stringify(context, dispatch_counters).ToLocalChecked());
|
||||
}
|
||||
|
||||
namespace {
|
||||
void ReadRange(std::ofstream* s, std::vector<uint32_t>* lines,
|
||||
debug::Coverage::Range range) {
|
||||
// Ensure space in the array.
|
||||
lines->resize(std::max(static_cast<size_t>(range.End().GetLineNumber() + 1),
|
||||
lines->size()),
|
||||
0);
|
||||
// Boundary lines could be shared between two functions with different
|
||||
// invocation counts. Take the maximum.
|
||||
lines->at(range.Start().GetLineNumber()) =
|
||||
std::max(lines->at(range.Start().GetLineNumber()), range.Count());
|
||||
lines->at(range.End().GetLineNumber()) =
|
||||
std::max(lines->at(range.End().GetLineNumber()), range.Count());
|
||||
// Invocation counts for non-boundary lines are overwritten.
|
||||
int line_plus_one = range.Start().GetLineNumber() + 1;
|
||||
for (int i = line_plus_one; i < range.End().GetLineNumber(); i++) {
|
||||
lines->at(i) = range.Count();
|
||||
}
|
||||
// Note that we use 0-based line numbers. But LCOV uses 1-based line numbers.
|
||||
// Recurse over inner ranges.
|
||||
for (size_t i = 0; i < range.NestedCount(); i++) {
|
||||
ReadRange(s, lines, range.GetNested(i));
|
||||
}
|
||||
// Write function stats.
|
||||
Local<String> name;
|
||||
std::stringstream name_stream;
|
||||
if (range.Name().ToLocal(&name)) {
|
||||
name_stream << ToSTLString(name);
|
||||
} else {
|
||||
name_stream << "<" << line_plus_one << "-";
|
||||
name_stream << range.Start().GetColumnNumber() << ">";
|
||||
}
|
||||
*s << "FN:" << line_plus_one << "," << name_stream.str() << std::endl;
|
||||
*s << "FNDA:" << range.Count() << "," << name_stream.str() << std::endl;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// Write coverage data in LCOV format. See man page for geninfo(1).
|
||||
void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
|
||||
if (!file) return;
|
||||
HandleScope handle_scope(isolate);
|
||||
debug::Coverage coverage = debug::Coverage::Collect(isolate);
|
||||
debug::Coverage coverage = debug::Coverage::Collect(isolate, false);
|
||||
std::ofstream sink(file, std::ofstream::app);
|
||||
for (size_t i = 0; i < coverage.ScriptCount(); i++) {
|
||||
Local<debug::Script> script = coverage.GetScript(i);
|
||||
debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
|
||||
Local<debug::Script> script = script_data.GetScript();
|
||||
// Skip unnamed scripts.
|
||||
Local<String> name;
|
||||
if (!script->Name().ToLocal(&name)) continue;
|
||||
@ -1711,7 +1675,33 @@ void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
|
||||
sink << "SF:";
|
||||
sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
|
||||
std::vector<uint32_t> lines;
|
||||
ReadRange(&sink, &lines, coverage.GetRange(i));
|
||||
for (size_t j = 0; j < script_data.FunctionCount(); j++) {
|
||||
debug::Coverage::FunctionData function_data =
|
||||
script_data.GetFunctionData(j);
|
||||
int start_line = function_data.Start().GetLineNumber();
|
||||
int end_line = function_data.End().GetLineNumber();
|
||||
uint32_t count = function_data.Count();
|
||||
// Ensure space in the array.
|
||||
lines.resize(std::max(static_cast<size_t>(end_line + 1), lines.size()),
|
||||
0);
|
||||
// Boundary lines could be shared between two functions with different
|
||||
// invocation counts. Take the maximum.
|
||||
lines[start_line] = std::max(lines[start_line], count);
|
||||
lines[end_line] = std::max(lines[end_line], count);
|
||||
// Invocation counts for non-boundary lines are overwritten.
|
||||
for (int k = start_line + 1; k < end_line; k++) lines[k] = count;
|
||||
// Write function stats.
|
||||
Local<String> name;
|
||||
std::stringstream name_stream;
|
||||
if (function_data.Name().ToLocal(&name)) {
|
||||
name_stream << ToSTLString(name);
|
||||
} else {
|
||||
name_stream << "<" << start_line + 1 << "-";
|
||||
name_stream << function_data.Start().GetColumnNumber() << ">";
|
||||
}
|
||||
sink << "FN:" << start_line + 1 << "," << name_stream.str() << std::endl;
|
||||
sink << "FNDA:" << count << "," << name_stream.str() << std::endl;
|
||||
}
|
||||
// Write per-line coverage. LCOV uses 1-based line numbers.
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
sink << "DA:" << (i + 1) << "," << lines[i] << std::endl;
|
||||
|
@ -58,12 +58,7 @@ bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
CoverageScript::CoverageScript(Isolate* isolate, Handle<Script> s,
|
||||
int source_length)
|
||||
: script(s),
|
||||
toplevel(0, source_length, 1, isolate->factory()->empty_string()) {}
|
||||
|
||||
Coverage* Coverage::Collect(Isolate* isolate) {
|
||||
Coverage* Coverage::Collect(Isolate* isolate, bool reset_count) {
|
||||
SharedToCounterMap counter_map;
|
||||
|
||||
// Feed invocation count into the counter map.
|
||||
@ -76,6 +71,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
|
||||
SharedFunctionInfo* shared = vector->shared_function_info();
|
||||
DCHECK(shared->IsSubjectToDebugging());
|
||||
uint32_t count = static_cast<uint32_t>(vector->invocation_count());
|
||||
if (reset_count) vector->clear_invocation_count();
|
||||
counter_map.Add(shared, count);
|
||||
}
|
||||
} else {
|
||||
@ -88,6 +84,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
|
||||
SharedFunctionInfo* shared = vector->shared_function_info();
|
||||
if (!shared->IsSubjectToDebugging()) continue;
|
||||
uint32_t count = static_cast<uint32_t>(vector->invocation_count());
|
||||
if (reset_count) vector->clear_invocation_count();
|
||||
counter_map.Add(shared, count);
|
||||
}
|
||||
}
|
||||
@ -101,47 +98,39 @@ Coverage* Coverage::Collect(Isolate* isolate) {
|
||||
if (script->type() != Script::TYPE_NORMAL) continue;
|
||||
|
||||
// Create and add new script data.
|
||||
int source_end = String::cast(script->source())->length();
|
||||
Handle<Script> script_handle(script, isolate);
|
||||
result->emplace_back(isolate, script_handle, source_end);
|
||||
result->emplace_back(isolate, script_handle);
|
||||
std::vector<CoverageFunction>* functions = &result->back().functions;
|
||||
|
||||
std::vector<SharedFunctionInfo*> sorted;
|
||||
bool has_toplevel = false;
|
||||
|
||||
{
|
||||
// Collect a list of shared function infos sorted by start position.
|
||||
// Shared function infos are usually already sorted. Except for classes.
|
||||
// If the start position is the same, sort from outer to inner function.
|
||||
// Sort functions by start position, from outer to inner functions.
|
||||
SharedFunctionInfo::ScriptIterator infos(script_handle);
|
||||
while (SharedFunctionInfo* info = infos.Next()) sorted.push_back(info);
|
||||
while (SharedFunctionInfo* info = infos.Next()) {
|
||||
has_toplevel |= info->is_toplevel();
|
||||
sorted.push_back(info);
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo);
|
||||
}
|
||||
|
||||
std::vector<CoverageRange*> stack;
|
||||
stack.push_back(&result->back().toplevel);
|
||||
functions->reserve(sorted.size() + (has_toplevel ? 0 : 1));
|
||||
|
||||
if (!has_toplevel) {
|
||||
// Add a replacement toplevel function if it does not exist.
|
||||
int source_end = String::cast(script->source())->length();
|
||||
functions->emplace_back(0, source_end, 1u,
|
||||
isolate->factory()->empty_string());
|
||||
}
|
||||
|
||||
// Use sorted list to reconstruct function nesting.
|
||||
for (SharedFunctionInfo* info : sorted) {
|
||||
int start = StartPosition(info);
|
||||
int end = info->end_position();
|
||||
uint32_t count = counter_map.Get(info);
|
||||
if (info->is_toplevel()) {
|
||||
// Top-level function is available.
|
||||
DCHECK_EQ(1, stack.size());
|
||||
result->back().toplevel.start = start;
|
||||
result->back().toplevel.end = end;
|
||||
result->back().toplevel.count = count;
|
||||
} else {
|
||||
// The shared function infos are sorted by start.
|
||||
DCHECK_LE(stack.back()->start, start);
|
||||
// Drop the stack to the outer function.
|
||||
while (start >= stack.back()->end) stack.pop_back();
|
||||
CoverageRange* outer = stack.back();
|
||||
// New nested function.
|
||||
DCHECK_LE(end, outer->end);
|
||||
Handle<String> name(info->DebugName(), isolate);
|
||||
outer->inner.emplace_back(start, end, count, name);
|
||||
stack.push_back(&outer->inner.back());
|
||||
}
|
||||
Handle<String> name(info->DebugName(), isolate);
|
||||
functions->emplace_back(start, end, count, name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -16,28 +16,28 @@ namespace internal {
|
||||
// Forward declaration.
|
||||
class Isolate;
|
||||
|
||||
struct CoverageRange {
|
||||
CoverageRange(int s, int e, uint32_t c, Handle<String> n)
|
||||
struct CoverageFunction {
|
||||
CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
|
||||
: start(s), end(e), count(c), name(n) {}
|
||||
int start;
|
||||
int end;
|
||||
uint32_t count;
|
||||
Handle<String> name;
|
||||
std::vector<CoverageRange> inner;
|
||||
};
|
||||
|
||||
struct CoverageScript {
|
||||
// Initialize top-level function in case it has been garbage-collected.
|
||||
CoverageScript(Isolate* isolate, Handle<Script> s, int source_length);
|
||||
CoverageScript(Isolate* isolate, Handle<Script> s) : script(s) {}
|
||||
Handle<Script> script;
|
||||
CoverageRange toplevel;
|
||||
// Functions are sorted by start position, from outer to inner function.
|
||||
std::vector<CoverageFunction> functions;
|
||||
};
|
||||
|
||||
class Coverage : public std::vector<CoverageScript> {
|
||||
public:
|
||||
// Allocate a new Coverage object and populate with result.
|
||||
// The ownership is transferred to the caller.
|
||||
static Coverage* Collect(Isolate* isolate);
|
||||
static Coverage* Collect(Isolate* isolate, bool reset_count);
|
||||
|
||||
// Enable precise code coverage. This disables optimization and makes sure
|
||||
// invocation count is not affected by GC.
|
||||
|
@ -17,7 +17,8 @@
|
||||
namespace v8 {
|
||||
|
||||
namespace internal {
|
||||
struct CoverageRange;
|
||||
struct CoverageFunction;
|
||||
struct CoverageScript;
|
||||
class Coverage;
|
||||
class Script;
|
||||
}
|
||||
@ -211,33 +212,45 @@ class GeneratorObject {
|
||||
*/
|
||||
class V8_EXPORT_PRIVATE Coverage {
|
||||
public:
|
||||
class V8_EXPORT_PRIVATE Range {
|
||||
class ScriptData; // Forward declaration.
|
||||
|
||||
class V8_EXPORT_PRIVATE FunctionData {
|
||||
public:
|
||||
// 0-based line and colum numbers.
|
||||
Location Start() { return start_; }
|
||||
Location End() { return end_; }
|
||||
uint32_t Count();
|
||||
size_t NestedCount();
|
||||
Range GetNested(size_t i);
|
||||
MaybeLocal<String> Name();
|
||||
|
||||
private:
|
||||
Range(i::CoverageRange* range, Local<debug::Script> script);
|
||||
i::CoverageRange* range_;
|
||||
FunctionData(i::CoverageFunction* function, Local<debug::Script> script);
|
||||
i::CoverageFunction* function_;
|
||||
Location start_;
|
||||
Location end_;
|
||||
Local<debug::Script> script_;
|
||||
|
||||
friend class debug::Coverage;
|
||||
friend class v8::debug::Coverage::ScriptData;
|
||||
};
|
||||
|
||||
static Coverage Collect(Isolate* isolate);
|
||||
class V8_EXPORT_PRIVATE ScriptData {
|
||||
public:
|
||||
Local<debug::Script> GetScript();
|
||||
size_t FunctionCount();
|
||||
FunctionData GetFunctionData(size_t i);
|
||||
|
||||
private:
|
||||
explicit ScriptData(i::CoverageScript* script) : script_(script) {}
|
||||
i::CoverageScript* script_;
|
||||
|
||||
friend class v8::debug::Coverage;
|
||||
};
|
||||
|
||||
static Coverage Collect(Isolate* isolate, bool reset_count);
|
||||
|
||||
static void TogglePrecise(Isolate* isolate, bool enable);
|
||||
|
||||
size_t ScriptCount();
|
||||
Local<debug::Script> GetScript(size_t i);
|
||||
Range GetRange(size_t i);
|
||||
ScriptData GetScriptData(size_t i);
|
||||
bool IsEmpty() { return coverage_ == nullptr; }
|
||||
|
||||
~Coverage();
|
||||
|
||||
|
@ -204,6 +204,38 @@
|
||||
{ "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." },
|
||||
{ "name": "promiseCreationFrame", "$ref": "CallFrame", "optional": true, "experimental": true, "description": "Creation frame of the Promise which produced the next synchronous trace when resolved, if available." }
|
||||
]
|
||||
},
|
||||
{ "id": "CoverageRange",
|
||||
"type": "object",
|
||||
"description": "Coverage data for a source range.",
|
||||
"properties": [
|
||||
{ "name": "startLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range start." },
|
||||
{ "name": "startColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range start." },
|
||||
{ "name": "endLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range end." },
|
||||
{ "name": "endColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range end." },
|
||||
{ "name": "count", "type": "integer", "description": "Collected execution count of the source range." }
|
||||
],
|
||||
"experimental": "true"
|
||||
},
|
||||
{ "id": "FunctionCoverage",
|
||||
"type": "object",
|
||||
"description": "Coverage data for a JavaScript function.",
|
||||
"properties": [
|
||||
{ "name": "functionName", "type": "string", "description": "JavaScript function name." },
|
||||
{ "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." }
|
||||
],
|
||||
"experimental": "true"
|
||||
},
|
||||
{
|
||||
"id": "ScriptCoverage",
|
||||
"type": "object",
|
||||
"description": "Coverage data for a JavaScript script.",
|
||||
"properties": [
|
||||
{ "name": "scriptId", "$ref": "ScriptId", "description": "JavaScript script id." },
|
||||
{ "name": "url", "type": "string", "description": "JavaScript script name or url." },
|
||||
{ "name": "functions", "type": "array", "items": { "$ref": "FunctionCoverage" }, "description": "Functions contained in the script that has coverage data." }
|
||||
],
|
||||
"experimental": "true"
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
@ -343,6 +375,32 @@
|
||||
{ "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."}
|
||||
],
|
||||
"description": "Runs script with given id in a given context."
|
||||
},
|
||||
{
|
||||
"name": "startPreciseCoverage",
|
||||
"description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters.",
|
||||
"experimental": true
|
||||
},
|
||||
{
|
||||
"name": "stopPreciseCoverage",
|
||||
"description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code.",
|
||||
"experimental": true
|
||||
},
|
||||
{
|
||||
"name": "takePreciseCoverage",
|
||||
"returns": [
|
||||
{ "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
|
||||
],
|
||||
"description": "Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started.",
|
||||
"experimental": true
|
||||
},
|
||||
{
|
||||
"name": "getBestEffortCoverage",
|
||||
"returns": [
|
||||
{ "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
|
||||
],
|
||||
"description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection.",
|
||||
"experimental": true
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "src/inspector/v8-runtime-agent-impl.h"
|
||||
|
||||
#include "src/debug/debug-interface.h"
|
||||
#include "src/inspector/injected-script.h"
|
||||
#include "src/inspector/inspected-context.h"
|
||||
#include "src/inspector/protocol/Protocol.h"
|
||||
@ -51,6 +52,7 @@ namespace V8RuntimeAgentImplState {
|
||||
static const char customObjectFormatterEnabled[] =
|
||||
"customObjectFormatterEnabled";
|
||||
static const char runtimeEnabled[] = "runtimeEnabled";
|
||||
static const char preciseCoverageStarted[] = "preciseCoverageStarted";
|
||||
};
|
||||
|
||||
using protocol::Runtime::RemoteObject;
|
||||
@ -645,6 +647,88 @@ void V8RuntimeAgentImpl::runScript(
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::startPreciseCoverage() {
|
||||
m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, true);
|
||||
v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), true);
|
||||
return Response::OK();
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::stopPreciseCoverage() {
|
||||
m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, false);
|
||||
v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), false);
|
||||
return Response::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
Response takeCoverage(
|
||||
v8::Isolate* isolate, bool reset_count,
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
|
||||
out_result) {
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>> result =
|
||||
protocol::Array<protocol::Runtime::ScriptCoverage>::create();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::debug::Coverage coverage =
|
||||
v8::debug::Coverage::Collect(isolate, reset_count);
|
||||
for (size_t i = 0; i < coverage.ScriptCount(); i++) {
|
||||
v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
|
||||
v8::Local<v8::debug::Script> script = script_data.GetScript();
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::FunctionCoverage>>
|
||||
functions =
|
||||
protocol::Array<protocol::Runtime::FunctionCoverage>::create();
|
||||
for (size_t j = 0; j < script_data.FunctionCount(); j++) {
|
||||
v8::debug::Coverage::FunctionData function_data =
|
||||
script_data.GetFunctionData(j);
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::CoverageRange>>
|
||||
ranges = protocol::Array<protocol::Runtime::CoverageRange>::create();
|
||||
// At this point we only have per-function coverage data, so there is
|
||||
// only one range per function.
|
||||
ranges->addItem(
|
||||
protocol::Runtime::CoverageRange::create()
|
||||
.setStartLineNumber(function_data.Start().GetLineNumber())
|
||||
.setStartColumnNumber(function_data.Start().GetColumnNumber())
|
||||
.setEndLineNumber(function_data.End().GetLineNumber())
|
||||
.setEndColumnNumber(function_data.End().GetColumnNumber())
|
||||
.setCount(function_data.Count())
|
||||
.build());
|
||||
functions->addItem(
|
||||
protocol::Runtime::FunctionCoverage::create()
|
||||
.setFunctionName(toProtocolString(
|
||||
function_data.Name().FromMaybe(v8::Local<v8::String>())))
|
||||
.setRanges(std::move(ranges))
|
||||
.build());
|
||||
}
|
||||
String16 url;
|
||||
v8::Local<v8::String> name;
|
||||
if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name)) {
|
||||
url = toProtocolString(name);
|
||||
}
|
||||
result->addItem(protocol::Runtime::ScriptCoverage::create()
|
||||
.setScriptId(String16::fromInteger(script->Id()))
|
||||
.setUrl(url)
|
||||
.setFunctions(std::move(functions))
|
||||
.build());
|
||||
}
|
||||
*out_result = std::move(result);
|
||||
return Response::OK();
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
Response V8RuntimeAgentImpl::takePreciseCoverage(
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
|
||||
out_result) {
|
||||
if (!m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
|
||||
false)) {
|
||||
return Response::Error("Precise coverage has not been started.");
|
||||
}
|
||||
return takeCoverage(m_inspector->isolate(), true, out_result);
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::getBestEffortCoverage(
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
|
||||
out_result) {
|
||||
return takeCoverage(m_inspector->isolate(), false, out_result);
|
||||
}
|
||||
|
||||
void V8RuntimeAgentImpl::restore() {
|
||||
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
|
||||
return;
|
||||
@ -653,6 +737,9 @@ void V8RuntimeAgentImpl::restore() {
|
||||
if (m_state->booleanProperty(
|
||||
V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
|
||||
m_session->setCustomObjectFormatterEnabled(true);
|
||||
if (m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
|
||||
false))
|
||||
startPreciseCoverage();
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::enable() {
|
||||
@ -680,6 +767,7 @@ Response V8RuntimeAgentImpl::disable() {
|
||||
reset();
|
||||
m_inspector->client()->endEnsureAllContextsInGroup(
|
||||
m_session->contextGroupId());
|
||||
stopPreciseCoverage();
|
||||
return Response::OK();
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,14 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
|
||||
Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
|
||||
Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
|
||||
std::unique_ptr<RunScriptCallback>) override;
|
||||
Response startPreciseCoverage() override;
|
||||
Response stopPreciseCoverage() override;
|
||||
Response takePreciseCoverage(
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
|
||||
out_result) override;
|
||||
Response getBestEffortCoverage(
|
||||
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
|
||||
out_result) override;
|
||||
|
||||
void reset();
|
||||
void reportExecutionContextCreated(InspectedContext*);
|
||||
|
@ -1895,49 +1895,16 @@ RUNTIME_FUNCTION(Runtime_DebugIsActive) {
|
||||
return Smi::FromInt(isolate->debug()->is_active());
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
|
||||
UNIMPLEMENTED();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Handle<JSObject> CreateRangeObject(Isolate* isolate, const CoverageRange* range,
|
||||
Handle<String> inner_string,
|
||||
Handle<String> start_string,
|
||||
Handle<String> end_string,
|
||||
Handle<String> count_string) {
|
||||
HandleScope scope(isolate);
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
|
||||
JSObject::AddProperty(range_obj, start_string,
|
||||
factory->NewNumberFromInt(range->start), NONE);
|
||||
JSObject::AddProperty(range_obj, end_string,
|
||||
factory->NewNumberFromInt(range->end), NONE);
|
||||
JSObject::AddProperty(range_obj, count_string,
|
||||
factory->NewNumberFromUint(range->count), NONE);
|
||||
JSObject::AddProperty(range_obj, factory->name_string(), range->name, NONE);
|
||||
if (!range->inner.empty()) {
|
||||
int size = static_cast<int>(range->inner.size());
|
||||
Handle<FixedArray> inner_array = factory->NewFixedArray(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Handle<JSObject> element =
|
||||
CreateRangeObject(isolate, &range->inner[i], inner_string,
|
||||
start_string, end_string, count_string);
|
||||
inner_array->set(i, *element);
|
||||
}
|
||||
Handle<JSArray> inner =
|
||||
factory->NewJSArrayWithElements(inner_array, FAST_ELEMENTS);
|
||||
JSObject::AddProperty(range_obj, inner_string, inner, NONE);
|
||||
}
|
||||
return scope.CloseAndEscape(range_obj);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(0, args.length());
|
||||
// Collect coverage data.
|
||||
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate));
|
||||
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate, false));
|
||||
Factory* factory = isolate->factory();
|
||||
// Turn the returned data structure into JavaScript.
|
||||
// Create an array of scripts.
|
||||
@ -1945,22 +1912,31 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
|
||||
// Prepare property keys.
|
||||
Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
|
||||
Handle<String> script_string = factory->NewStringFromStaticChars("script");
|
||||
Handle<String> toplevel_string =
|
||||
factory->NewStringFromStaticChars("toplevel");
|
||||
Handle<String> inner_string = factory->NewStringFromStaticChars("inner");
|
||||
Handle<String> start_string = factory->NewStringFromStaticChars("start");
|
||||
Handle<String> end_string = factory->NewStringFromStaticChars("end");
|
||||
Handle<String> count_string = factory->NewStringFromStaticChars("count");
|
||||
for (int i = 0; i < num_scripts; i++) {
|
||||
const auto& data = coverage->at(i);
|
||||
const auto& script_data = coverage->at(i);
|
||||
HandleScope inner_scope(isolate);
|
||||
Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto();
|
||||
Handle<JSObject> wrapper = Script::GetWrapper(data.script);
|
||||
int num_functions = static_cast<int>(script_data.functions.size());
|
||||
Handle<FixedArray> functions_array = factory->NewFixedArray(num_functions);
|
||||
for (int j = 0; j < num_functions; j++) {
|
||||
const auto& function_data = script_data.functions[j];
|
||||
Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
|
||||
JSObject::AddProperty(range_obj, start_string,
|
||||
factory->NewNumberFromInt(function_data.start),
|
||||
NONE);
|
||||
JSObject::AddProperty(range_obj, end_string,
|
||||
factory->NewNumberFromInt(function_data.end), NONE);
|
||||
JSObject::AddProperty(range_obj, count_string,
|
||||
factory->NewNumberFromUint(function_data.count),
|
||||
NONE);
|
||||
functions_array->set(j, *range_obj);
|
||||
}
|
||||
Handle<JSArray> script_obj =
|
||||
factory->NewJSArrayWithElements(functions_array, FAST_ELEMENTS);
|
||||
Handle<JSObject> wrapper = Script::GetWrapper(script_data.script);
|
||||
JSObject::AddProperty(script_obj, script_string, wrapper, NONE);
|
||||
Handle<JSObject> toplevel =
|
||||
CreateRangeObject(isolate, &data.toplevel, inner_string, start_string,
|
||||
end_string, count_string);
|
||||
JSObject::AddProperty(script_obj, toplevel_string, toplevel, NONE);
|
||||
scripts_array->set(i, *script_obj);
|
||||
}
|
||||
return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS);
|
||||
|
@ -6637,27 +6637,28 @@ TEST(DebugCoverage) {
|
||||
"f();\n"
|
||||
"f();");
|
||||
CompileRun(source);
|
||||
v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate);
|
||||
v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate, false);
|
||||
CHECK_EQ(1u, coverage.ScriptCount());
|
||||
v8::Local<v8::debug::Script> script = coverage.GetScript(0);
|
||||
v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(0);
|
||||
v8::Local<v8::debug::Script> script = script_data.GetScript();
|
||||
CHECK(script->Source()
|
||||
.ToLocalChecked()
|
||||
->Equals(env.local(), source)
|
||||
.FromMaybe(false));
|
||||
|
||||
v8::debug::Coverage::Range range = coverage.GetRange(0);
|
||||
CHECK_EQ(0, range.Start().GetLineNumber());
|
||||
CHECK_EQ(0, range.Start().GetColumnNumber());
|
||||
CHECK_EQ(3, range.End().GetLineNumber());
|
||||
CHECK_EQ(4, range.End().GetColumnNumber());
|
||||
CHECK_EQ(1, range.Count());
|
||||
CHECK_EQ(1u, range.NestedCount());
|
||||
CHECK_EQ(2u, script_data.FunctionCount());
|
||||
v8::debug::Coverage::FunctionData function_data =
|
||||
script_data.GetFunctionData(0);
|
||||
CHECK_EQ(0, function_data.Start().GetLineNumber());
|
||||
CHECK_EQ(0, function_data.Start().GetColumnNumber());
|
||||
CHECK_EQ(3, function_data.End().GetLineNumber());
|
||||
CHECK_EQ(4, function_data.End().GetColumnNumber());
|
||||
CHECK_EQ(1, function_data.Count());
|
||||
|
||||
range = range.GetNested(0);
|
||||
CHECK_EQ(0, range.Start().GetLineNumber());
|
||||
CHECK_EQ(0, range.Start().GetColumnNumber());
|
||||
CHECK_EQ(1, range.End().GetLineNumber());
|
||||
CHECK_EQ(1, range.End().GetColumnNumber());
|
||||
CHECK_EQ(2, range.Count());
|
||||
CHECK_EQ(0, range.NestedCount());
|
||||
function_data = script_data.GetFunctionData(1);
|
||||
CHECK_EQ(0, function_data.Start().GetLineNumber());
|
||||
CHECK_EQ(0, function_data.Start().GetColumnNumber());
|
||||
CHECK_EQ(1, function_data.End().GetLineNumber());
|
||||
CHECK_EQ(1, function_data.End().GetColumnNumber());
|
||||
CHECK_EQ(2, function_data.Count());
|
||||
}
|
||||
|
333
test/inspector/runtime/coverage-expected.txt
Normal file
333
test/inspector/runtime/coverage-expected.txt
Normal file
@ -0,0 +1,333 @@
|
||||
Test collecting code coverage data with Runtime.collectCoverage.
|
||||
|
||||
Running test: testPreciseCoverage
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
[0] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 0
|
||||
endLineNumber : 9
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
[1] : {
|
||||
functionName : fib
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 15
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 4
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
[2] : {
|
||||
functionName : iife
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 7
|
||||
startColumnNumber : 1
|
||||
startLineNumber : 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url : 1
|
||||
}
|
||||
[1] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 11
|
||||
endLineNumber : 0
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
[0] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 0
|
||||
endColumnNumber : 0
|
||||
endLineNumber : 9
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
[1] : {
|
||||
functionName : fib
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 0
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 4
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
[2] : {
|
||||
functionName : iife
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 0
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 7
|
||||
startColumnNumber : 1
|
||||
startLineNumber : 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url : 1
|
||||
}
|
||||
[1] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 0
|
||||
endColumnNumber : 11
|
||||
endLineNumber : 0
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Running test: testPreciseCoverageFail
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : {
|
||||
description : 8
|
||||
type : number
|
||||
value : 8
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
error : {
|
||||
code : -32000
|
||||
message : Precise coverage has not been started.
|
||||
}
|
||||
id : <messageId>
|
||||
}
|
||||
|
||||
Running test: testBestEffortCoverage
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : {
|
||||
description : 8
|
||||
type : number
|
||||
value : 8
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Running test: testBestEffortCoveragePrecise
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : {
|
||||
description : 8
|
||||
type : number
|
||||
value : 8
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
[0] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 0
|
||||
endLineNumber : 9
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
[1] : {
|
||||
functionName : fib
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 15
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 4
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
[2] : {
|
||||
functionName : iife
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 7
|
||||
startColumnNumber : 1
|
||||
startLineNumber : 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url : 4
|
||||
}
|
||||
[1] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 11
|
||||
endLineNumber : 0
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
result : [
|
||||
[0] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 0
|
||||
endLineNumber : 9
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
[1] : {
|
||||
functionName : fib
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 15
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 4
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
[2] : {
|
||||
functionName : iife
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 1
|
||||
endLineNumber : 7
|
||||
startColumnNumber : 1
|
||||
startLineNumber : 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url : 4
|
||||
}
|
||||
[1] : {
|
||||
functions : [
|
||||
[0] : {
|
||||
functionName :
|
||||
ranges : [
|
||||
[0] : {
|
||||
count : 1
|
||||
endColumnNumber : 11
|
||||
endLineNumber : 0
|
||||
startColumnNumber : 0
|
||||
startLineNumber : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
89
test/inspector/runtime/coverage.js
Normal file
89
test/inspector/runtime/coverage.js
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2017 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
var source =
|
||||
`
|
||||
function fib(x) {
|
||||
if (x < 2) return 1;
|
||||
return fib(x-1) + fib(x-2);
|
||||
}
|
||||
(function iife() {
|
||||
return 1;
|
||||
})();
|
||||
fib(5);
|
||||
`;
|
||||
|
||||
print("Test collecting code coverage data with Runtime.collectCoverage.");
|
||||
|
||||
function ClearAndGC() {
|
||||
return Protocol.Runtime.evaluate({ expression: "fib = null;" })
|
||||
.then(() => Protocol.HeapProfiler.enable())
|
||||
.then(() => Protocol.HeapProfiler.collectGarbage())
|
||||
.then(() => Protocol.HeapProfiler.disable());
|
||||
}
|
||||
|
||||
InspectorTest.runTestSuite([
|
||||
function testPreciseCoverage(next)
|
||||
{
|
||||
Protocol.Runtime.enable()
|
||||
.then(Protocol.Runtime.startPreciseCoverage)
|
||||
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "1", persistScript: true }))
|
||||
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
|
||||
.then(ClearAndGC)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(Protocol.Runtime.takePreciseCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(Protocol.Runtime.takePreciseCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.stopPreciseCoverage)
|
||||
.then(Protocol.Runtime.disable)
|
||||
.then(next);
|
||||
},
|
||||
function testPreciseCoverageFail(next)
|
||||
{
|
||||
Protocol.Runtime.enable()
|
||||
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "2", persistScript: true }))
|
||||
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.takePreciseCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.disable)
|
||||
.then(next);
|
||||
},
|
||||
function testBestEffortCoverage(next)
|
||||
{
|
||||
Protocol.Runtime.enable()
|
||||
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "3", persistScript: true }))
|
||||
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.getBestEffortCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(Protocol.Runtime.getBestEffortCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.disable)
|
||||
.then(next);
|
||||
},
|
||||
function testBestEffortCoveragePrecise(next)
|
||||
{
|
||||
Protocol.Runtime.enable()
|
||||
.then(Protocol.Runtime.startPreciseCoverage)
|
||||
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "4", persistScript: true }))
|
||||
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.getBestEffortCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(Protocol.Runtime.getBestEffortCoverage)
|
||||
.then(InspectorTest.logMessage)
|
||||
.then(ClearAndGC)
|
||||
.then(Protocol.Runtime.stopPreciseCoverage)
|
||||
.then(Protocol.Runtime.disable)
|
||||
.then(next);
|
||||
},
|
||||
]);
|
@ -8,31 +8,18 @@
|
||||
|
||||
function GetCoverage(source) {
|
||||
for (var script of %DebugCollectCoverage()) {
|
||||
if (script.script.source == source) return script.toplevel;
|
||||
if (script.script.source == source) return script;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function ApplyCoverageToSource(source, range) {
|
||||
var content = "";
|
||||
var cursor = range.start;
|
||||
if (range.inner) for (var inner of range.inner) {
|
||||
content += source.substring(cursor, inner.start);
|
||||
content += ApplyCoverageToSource(source, inner);
|
||||
cursor = inner.end;
|
||||
}
|
||||
content += source.substring(cursor, range.end);
|
||||
return `[${content}](${range.name}:${range.count})`;
|
||||
}
|
||||
|
||||
function TestCoverage(name, source, expectation) {
|
||||
source = source.trim();
|
||||
expectation = expectation.trim();
|
||||
eval(source);
|
||||
var coverage = GetCoverage(source);
|
||||
var result = ApplyCoverageToSource(source, coverage);
|
||||
var result = JSON.stringify(coverage);
|
||||
print(result);
|
||||
assertEquals(expectation, result, name + " failed");
|
||||
assertEquals(JSON.stringify(expectation), result, name + " failed");
|
||||
}
|
||||
|
||||
TestCoverage(
|
||||
@ -42,11 +29,8 @@ function f() {}
|
||||
f();
|
||||
f();
|
||||
`,
|
||||
`
|
||||
[[function f() {}](f:2)
|
||||
f();
|
||||
f();](:1)
|
||||
`
|
||||
[{"start":0,"end":25,"count":1},
|
||||
{"start":0,"end":15,"count":2}]
|
||||
);
|
||||
|
||||
TestCoverage(
|
||||
@ -56,11 +40,8 @@ var f = () => 1;
|
||||
f();
|
||||
f();
|
||||
`,
|
||||
`
|
||||
[var f = [() => 1](f:2);
|
||||
f();
|
||||
f();](:1)
|
||||
`
|
||||
[{"start":0,"end":26,"count":1},
|
||||
{"start":8,"end":15,"count":2}]
|
||||
);
|
||||
|
||||
TestCoverage(
|
||||
@ -74,16 +55,9 @@ function f() {
|
||||
f();
|
||||
f();
|
||||
`,
|
||||
`
|
||||
[[function f() {
|
||||
[function g() {}](g:4)
|
||||
g();
|
||||
g();
|
||||
}](f:2)
|
||||
f();
|
||||
f();](:1)
|
||||
|
||||
`
|
||||
[{"start":0,"end":58,"count":1},
|
||||
{"start":0,"end":48,"count":2},
|
||||
{"start":17,"end":32,"count":4}]
|
||||
);
|
||||
|
||||
TestCoverage(
|
||||
@ -95,11 +69,6 @@ function fib(x) {
|
||||
}
|
||||
fib(5);
|
||||
`,
|
||||
`
|
||||
[[function fib(x) {
|
||||
if (x < 2) return 1;
|
||||
return fib(x-1) + fib(x-2);
|
||||
}](fib:15)
|
||||
fib(5);](:1)
|
||||
`
|
||||
[{"start":0,"end":80,"count":1},
|
||||
{"start":0,"end":72,"count":15}]
|
||||
);
|
||||
|
@ -8,39 +8,21 @@
|
||||
|
||||
function GetCoverage(source) {
|
||||
for (var script of %DebugCollectCoverage()) {
|
||||
if (script.script.source == source) return script.toplevel;
|
||||
if (script.script.source == source) return script;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function ApplyCoverageToSource(source, range) {
|
||||
var content = "";
|
||||
var cursor = range.start;
|
||||
if (range.inner) for (var inner of range.inner) {
|
||||
content += source.substring(cursor, inner.start);
|
||||
content += ApplyCoverageToSource(source, inner);
|
||||
cursor = inner.end;
|
||||
}
|
||||
content += source.substring(cursor, range.end);
|
||||
return `[${content}](${range.name}:${range.count})`;
|
||||
}
|
||||
|
||||
function TestCoverage(name, source, expectation) {
|
||||
source = source.trim();
|
||||
eval(source);
|
||||
%CollectGarbage("remove dead objects");
|
||||
%CollectGarbage("collect dead objects");
|
||||
var coverage = GetCoverage(source);
|
||||
if (expectation === undefined) {
|
||||
assertEquals(undefined, coverage);
|
||||
} else {
|
||||
expectation = expectation.trim();
|
||||
var result = ApplyCoverageToSource(source, coverage);
|
||||
print(result);
|
||||
assertEquals(expectation, result, name + " failed");
|
||||
}
|
||||
var result = JSON.stringify(coverage);
|
||||
print(result);
|
||||
assertEquals(JSON.stringify(expectation), result, name + " failed");
|
||||
}
|
||||
|
||||
|
||||
// Without precise coverage enabled, we lose coverage data to the GC.
|
||||
TestCoverage(
|
||||
"call an IIFE",
|
||||
@ -69,9 +51,7 @@ TestCoverage(
|
||||
`
|
||||
(function f() {})();
|
||||
`,
|
||||
`
|
||||
[([function f() {}](f:1))();](:1)
|
||||
`
|
||||
[{"start":0,"end":20,"count":1},{"start":1,"end":16,"count":1}]
|
||||
);
|
||||
|
||||
TestCoverage(
|
||||
@ -82,12 +62,7 @@ for (var i = 0; i < 10; i++) {
|
||||
i += f();
|
||||
}
|
||||
`,
|
||||
`
|
||||
[for (var i = 0; i < 10; i++) {
|
||||
let f = [() => 1](f:5);
|
||||
i += f();
|
||||
}](:1)
|
||||
`
|
||||
[{"start":0,"end":63,"count":1},{"start":41,"end":48,"count":5}]
|
||||
);
|
||||
|
||||
%DebugTogglePreciseCoverage(false);
|
||||
|
Loading…
Reference in New Issue
Block a user