Commit 384dfbd0 authored by Pavel Feldman's avatar Pavel Feldman Committed by Commit Bot

DevTools: allow exporting code coverage data.

Bug: 717195
Change-Id: I0360d0dea935d2da11179dd7380f36bf52b85ced
Reviewed-on: https://chromium-review.googlesource.com/c/1370862
Commit-Queue: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615693}
parent a20964bd
......@@ -274,6 +274,65 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
urlCoverage._usedSize += coverageInfo._usedSize - oldUsedSize;
return coverageInfo;
}
/**
* @param {!Bindings.FileOutputStream} fos
*/
async exportReport(fos) {
const result = [];
for (const urlInfo of this._coverageByURL.values()) {
const url = urlInfo.url();
if (url.startsWith('extensions::') || url.startsWith('chrome-extension://'))
continue;
// For .html resources, multiple scripts share URL, but have different offsets.
let useFullText = false;
for (const info of urlInfo._coverageInfoByLocation.values()) {
if (info._lineOffset || info._columnOffset) {
useFullText = !!url;
break;
}
}
let fullText = null;
if (useFullText) {
const resource = SDK.ResourceTreeModel.resourceForURL(url);
fullText = resource ? new TextUtils.Text(await resource.requestContent()) : null;
}
// We have full text for this resource, resolve the offsets using the text line endings.
if (fullText) {
const entry = {url, ranges: [], text: fullText.value()};
for (const info of urlInfo._coverageInfoByLocation.values()) {
const offset = fullText ? fullText.offsetFromPosition(info._lineOffset, info._columnOffset) : 0;
let start = 0;
for (const segment of info._segments) {
if (segment.count)
entry.ranges.push({start: start + offset, end: segment.end + offset});
else
start = segment.end;
}
}
result.push(entry);
continue;
}
// Fall back to the per-script operation.
for (const info of urlInfo._coverageInfoByLocation.values()) {
const entry = {url, ranges: [], text: await info.contentProvider().requestContent()};
let start = 0;
for (const segment of info._segments) {
if (segment.count)
entry.ranges.push({start: start, end: segment.end});
else
start = segment.end;
}
result.push(entry);
}
}
await fos.write(JSON.stringify(result, undefined, 2));
fos.close();
}
};
Coverage.URLCoverageInfo = class {
......@@ -357,7 +416,7 @@ Coverage.URLCoverageInfo = class {
if ((type & Coverage.CoverageType.JavaScript) && !this._coverageInfoByLocation.size)
this._isContentScript = /** @type {!SDK.Script} */ (contentProvider).isContentScript();
entry = new Coverage.CoverageInfo(contentProvider, contentLength, type);
entry = new Coverage.CoverageInfo(contentProvider, contentLength, lineOffset, columnOffset, type);
this._coverageInfoByLocation.set(key, entry);
this._size += contentLength;
......@@ -369,12 +428,16 @@ Coverage.CoverageInfo = class {
/**
* @param {!Common.ContentProvider} contentProvider
* @param {number} size
* @param {number} lineOffset
* @param {number} columnOffset
* @param {!Coverage.CoverageType} type
*/
constructor(contentProvider, size, type) {
constructor(contentProvider, size, lineOffset, columnOffset, type) {
this._contentProvider = contentProvider;
this._size = size;
this._usedSize = 0;
this._lineOffset = lineOffset;
this._columnOffset = columnOffset;
this._coverageType = type;
/** !Array<!Coverage.CoverageSegment> */
......
......@@ -36,9 +36,13 @@ Coverage.CoverageView = class extends UI.VBox {
this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._clear.bind(this));
toolbar.appendToolbarItem(this._clearButton);
toolbar.appendSeparator();
const saveButton = new UI.ToolbarButton(Common.UIString('Export...'), 'largeicon-download');
saveButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._exportReport());
toolbar.appendToolbarItem(saveButton);
/** @type {?RegExp} */
this._textFilterRegExp = null;
toolbar.appendSeparator();
this._filterInput = new UI.ToolbarInput(Common.UIString('URL filter'), 0.4, 1);
this._filterInput.setEnabled(false);
......@@ -222,6 +226,15 @@ Coverage.CoverageView = class extends UI.VBox {
return false;
return ignoreTextFilter || !this._textFilterRegExp || this._textFilterRegExp.test(url);
}
async _exportReport() {
const fos = new Bindings.FileOutputStream();
const fileName = `Coverage-${new Date().toISO8601Compact()}.json`;
const accepted = await fos.open(fileName);
if (!accepted)
return;
this._model.exportReport(fos);
}
};
Coverage.CoverageView._extensionBindingsURLPrefix = 'extensions::';
......
......@@ -29,6 +29,16 @@ CoverageTestRunner.pollCoverage = function() {
return coverageView._poll();
};
/**
* @return {!Promise<string>}
*/
CoverageTestRunner.exportReport = async function() {
const coverageView = self.runtime.sharedInstance(Coverage.CoverageView);
let data;
await coverageView._model.exportReport({write: d => data = d, close: _ => 0});
return data;
};
/**
* @return {!Promise<!SourceFrame.SourceFrame>}
*/
......
Tests the coverage export functionality and format.
File: http://127.0.0.1:8000/devtools/coverage/resources/highlight-in-source.css
Usage:
body {
background-color: lightblue;
}
Usage:
.class {
color: red;
}
File: http://127.0.0.1:8000/devtools/coverage/resources/basic-coverage.html
Usage:
function foo() {}
foo();
Usage:
function bar() {}
bar();
File: http://127.0.0.1:8000/devtools/coverage/resources/coverage.js
Usage:
function outer(index) {
Usage:
function inner2(a) {
return a + 2;
Usage:
}
Usage:
function inner4(a) { return a + 4;
Usage:
}
Usage:
if (index === 7)
Usage:
// Make sure these are not collected.
if (!self.__funcs)
self.__funcs = [inner1, inner2, inner3, inner4, inner5];
return self.__funcs[index];
Usage:
}
function performActions() {
return outer(1)(0) + outer(3)(0);
Usage:
}
Usage:
File: test://evaluations/0/coverage-export.js
Usage:
performActions()//# sourceURL=test://evaluations/0/coverage-export.js
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function() {
TestRunner.addResult(`Tests the coverage export functionality and format.\n`);
await TestRunner.loadModule('coverage_test_runner');
await TestRunner.navigatePromise(TestRunner.url('resources/basic-coverage.html'));
CoverageTestRunner.startCoverage();
await TestRunner.evaluateInPagePromise('performActions()');
await CoverageTestRunner.stopCoverage();
const report = JSON.parse(await CoverageTestRunner.exportReport());
for (const entry of report) {
TestRunner.addResult('\n\nFile: ' + entry.url);
for (const range of entry.ranges) {
TestRunner.addResult('\nUsage:');
TestRunner.addResult(entry.text.substring(range.start, range.end).trim());
}
}
TestRunner.completeTest();
})();
......@@ -2,8 +2,10 @@ Tests the coverage list view after finishing recording in the Coverage view.
.../devtools/coverage/resources/coverage.js JS (coarse) used: 411 unused: 157 total: 568
.../devtools/coverage/resources/highlight-in-source.css CSS used: 67 unused: 142 total: 209
.../devtools/coverage/resources/basic-coverage.html JS (coarse) used: 51 unused: 0 total: 51
Reloading Page
Page reloaded.
.../devtools/coverage/resources/coverage.js JS used: 354 unused: 214 total: 568
.../devtools/coverage/resources/highlight-in-source.css CSS used: 67 unused: 142 total: 209
.../devtools/coverage/resources/basic-coverage.html JS used: 51 unused: 0 total: 51
......@@ -3,10 +3,12 @@ Tests the filter is properly applied to coverage list view.
Filter: devtools
.../devtools/coverage/resources/coverage.js JS used: 354 unused: 214 total: 568
.../devtools/coverage/resources/highlight-in-source.css CSS used: 67 unused: 142 total: 209
.../devtools/coverage/resources/basic-coverage.html JS used: 51 unused: 0 total: 51
Filter: CES/COV
.../devtools/coverage/resources/coverage.js JS used: 354 unused: 214 total: 568
Filter: no pasaran
Filter:
.../devtools/coverage/resources/coverage.js JS used: 354 unused: 214 total: 568
.../devtools/coverage/resources/highlight-in-source.css CSS used: 67 unused: 142 total: 209
.../devtools/coverage/resources/basic-coverage.html JS used: 51 unused: 0 total: 51
......@@ -3,6 +3,15 @@
<link rel="stylesheet" type="text/css" href="highlight-in-source.css">
</head>
<body>
<script>
function foo() {}
foo();
</script>
<script>function bar() {}
bar();
</script>
<script src="coverage.js"></script>
<p class="class"></p>
</body>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment