mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-08-07 12:45:57 +00:00
Change-Id: I70f3ffa40dcfe60cdee46bdf0f5db21370be1a3e Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/113861 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com> Kokoro: Ben Clayton <bclayton@google.com>
583 lines
21 KiB
HTML
583 lines
21 KiB
HTML
<!doctype html>
|
|
<!--
|
|
Copyright 2022 The Dawn and Tint Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
-->
|
|
|
|
<html>
|
|
|
|
<head>
|
|
<title>Dawn Code Coverage viewer</title>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.0/codemirror.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.0/theme/seti.min.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.0/codemirror.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.0/mode/clike/clike.min.js"></script>
|
|
<script src=https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.10/pako.min.js></script>
|
|
|
|
<style>
|
|
::-webkit-scrollbar {
|
|
background-color: #30353530;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background-color: #80858050;
|
|
}
|
|
|
|
::-webkit-scrollbar-corner {
|
|
background-color: #00000000;
|
|
}
|
|
|
|
.frame {
|
|
display: flex;
|
|
left: 0px;
|
|
right: 0px;
|
|
top: 0px;
|
|
bottom: 0px;
|
|
position: absolute;
|
|
font-family: monospace;
|
|
background-color: #151515;
|
|
color: #c0b070;
|
|
}
|
|
|
|
.left-pane {
|
|
flex: 1;
|
|
}
|
|
|
|
.center-pane {
|
|
flex: 3;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
}
|
|
|
|
.top-pane {
|
|
flex: 1;
|
|
overflow: scroll;
|
|
}
|
|
|
|
.v-flex {
|
|
display: flex;
|
|
height: 100%;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.file-tree {
|
|
font-size: small;
|
|
overflow: auto;
|
|
padding: 5px;
|
|
}
|
|
|
|
.test-tree {
|
|
font-size: small;
|
|
overflow: auto;
|
|
padding: 5px;
|
|
}
|
|
|
|
.CodeMirror {
|
|
flex: 3;
|
|
height: 100%;
|
|
border: 1px solid #eee;
|
|
}
|
|
|
|
.file-div {
|
|
margin: 0px;
|
|
white-space: nowrap;
|
|
padding: 2px;
|
|
margin-top: 1px;
|
|
margin-bottom: 1px;
|
|
}
|
|
|
|
.file-div:hover {
|
|
background-color: #303030;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.file-div.selected {
|
|
background-color: #505050;
|
|
color: #f0f0a0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.test-name {
|
|
margin: 0px;
|
|
white-space: nowrap;
|
|
padding: 2px;
|
|
margin-top: 1px;
|
|
margin-bottom: 1px;
|
|
}
|
|
|
|
.file-coverage {
|
|
color: black;
|
|
width: 20pt;
|
|
padding-right: 3pt;
|
|
padding-left: 3px;
|
|
margin-right: 5pt;
|
|
display: inline-block;
|
|
text-align: center;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.with-coverage {
|
|
background-color: #20d04080;
|
|
border-width: 0px 0px 0px 0px;
|
|
}
|
|
|
|
.with-coverage-start {
|
|
border-left: solid 1px;
|
|
border-color: #20f02080;
|
|
margin-left: -1px;
|
|
}
|
|
|
|
.with-coverage-end {
|
|
border-right: solid 1px;
|
|
border-color: #20f02080;
|
|
margin-right: -1px;
|
|
}
|
|
|
|
.without-coverage {
|
|
background-color: #d0204080;
|
|
border-width: 0px 0px 0px 0px;
|
|
}
|
|
|
|
.without-coverage-start {
|
|
border-left: solid 1px;
|
|
border-color: #f0202080;
|
|
margin-left: -1px;
|
|
}
|
|
|
|
.without-coverage-end {
|
|
border-right: solid 1px;
|
|
border-color: #f0202080;
|
|
margin-right: -1px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="frame">
|
|
<div id="file_tree" class="left-pane file-tree"></div>
|
|
<div class="center-pane">
|
|
<div id="source" class="v-flex">
|
|
<div class="top-pane">
|
|
<div class="test-tree" id="test_tree"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// "Download" the coverage.dat file if the user presses ctrl-s
|
|
document.addEventListener('keydown', e => {
|
|
if (e.ctrlKey && e.key === 's') {
|
|
e.preventDefault();
|
|
window.open("coverage.dat");
|
|
}
|
|
});
|
|
|
|
let current = {
|
|
file: "",
|
|
start_line: 0,
|
|
start_column: 0,
|
|
end_line: 0,
|
|
end_column: 0,
|
|
};
|
|
|
|
let pending = { ...current };
|
|
{
|
|
let url = new URL(location.href);
|
|
let query_string = url.search;
|
|
let search_params = new URLSearchParams(query_string);
|
|
var f = search_params.get('f');
|
|
var s = search_params.get('s');
|
|
var e = search_params.get('e');
|
|
if (f) {
|
|
pending.file = f;
|
|
}
|
|
if (s) {
|
|
s = s.split('.');
|
|
pending.start_line = s.length > 0 ? parseInt(s[0]) : 0;
|
|
pending.start_column = s.length > 1 ? parseInt(s[1]) : 0;
|
|
}
|
|
if (e) {
|
|
e = e.split('.');
|
|
pending.end_line = e.length > 0 ? parseInt(e[0]) : 0;
|
|
pending.end_column = e.length > 1 ? parseInt(e[1]) : 0;
|
|
}
|
|
};
|
|
|
|
let set_location = (file, start_line, start_column, end_line, end_column) => {
|
|
current.file = file;
|
|
current.start_line = start_line;
|
|
current.start_column = start_column;
|
|
current.end_line = end_line;
|
|
current.end_column = end_column;
|
|
|
|
let url = new URL(location.href);
|
|
let query_string = url.search;
|
|
// Don't use URLSearchParams, as it will unnecessarily escape
|
|
// characters, such as '/'.
|
|
url.search = "f=" + file +
|
|
"&s=" + start_line + "." + end_line +
|
|
"&e=" + end_line + "." + end_column;
|
|
window.history.replaceState(null, "", url.toString());
|
|
};
|
|
|
|
let before = (line, col, span) => {
|
|
if (line < span[0]) { return true; }
|
|
if (line == span[0]) { return col < span[1]; }
|
|
return false;
|
|
};
|
|
|
|
let after = (line, col, span) => {
|
|
if (line > span[2]) { return true; }
|
|
if (line == span[2]) { return col > span[3]; }
|
|
return false;
|
|
};
|
|
|
|
let intersects = (span, from, to) => {
|
|
if (!before(to.line + 1, to.ch + 1, span) &&
|
|
!after(from.line + 1, from.ch + 1, span)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
let el_file_tree = document.getElementById("file_tree");
|
|
let el_test_tree = document.getElementById("test_tree");
|
|
let el_source = CodeMirror(document.getElementById("source"), {
|
|
lineNumbers: true,
|
|
theme: "seti",
|
|
mode: "text/x-c++src",
|
|
readOnly: true,
|
|
});
|
|
|
|
addEventListener('beforeunload', () => {
|
|
fetch("viewer.closed");
|
|
});
|
|
|
|
window.onload = function () {
|
|
el_source.doc.setValue("// Loading... ");
|
|
fetch("coverage.dat").then(response =>
|
|
response.arrayBuffer()
|
|
).then(compressed =>
|
|
pako.inflate(new Uint8Array(compressed))
|
|
).then(decompressed =>
|
|
JSON.parse(new TextDecoder("utf-8").decode(decompressed))
|
|
).then(json => {
|
|
el_source.doc.setValue("// Select file from the left... ");
|
|
|
|
let revision = json.r;
|
|
let names = json.n;
|
|
let tests = json.t;
|
|
let spans = json.s;
|
|
let files = json.f;
|
|
|
|
let glob_group = (file, groupID, span_ids) => {
|
|
while (true) {
|
|
let group = file.g[groupID];
|
|
group.s.forEach(span_id => span_ids.add(span_id));
|
|
if (!group.e) {
|
|
break;
|
|
}
|
|
groupID = group.e;
|
|
};
|
|
};
|
|
|
|
let coverage_spans = (file, data, span_ids) => {
|
|
if (data.g != undefined) {
|
|
glob_group(file, data.g, span_ids);
|
|
}
|
|
if (data.s != undefined) {
|
|
data.s.forEach(span_id => span_ids.add(span_id));
|
|
}
|
|
};
|
|
|
|
let glob_node = (file, nodes, span_ids) => {
|
|
nodes.forEach(node => {
|
|
let data = node[1];
|
|
coverage_spans(file, data, span_ids);
|
|
if (data.c) {
|
|
glob_node(file, data.c, span_ids);
|
|
}
|
|
});
|
|
};
|
|
|
|
let markup = file => {
|
|
if (file.u) {
|
|
for (span of file.u) {
|
|
el_source.doc.markText(
|
|
{ "line": span[0] - 1, "ch": span[1] - 1 },
|
|
{ "line": span[2] - 1, "ch": span[3] - 1 },
|
|
{
|
|
// inclusiveLeft: true,
|
|
className: "without-coverage",
|
|
startStyle: "without-coverage-start",
|
|
endStyle: "without-coverage-end",
|
|
});
|
|
}
|
|
}
|
|
let span_ids = new Set();
|
|
glob_node(file, file.c, span_ids);
|
|
el_source.operation(() => {
|
|
span_ids.forEach((span_id) => {
|
|
let span = spans[span_id];
|
|
el_source.doc.markText(
|
|
{ "line": span[0] - 1, "ch": span[1] - 1 },
|
|
{ "line": span[2] - 1, "ch": span[3] - 1 },
|
|
{
|
|
// inclusiveLeft: true,
|
|
className: "with-coverage",
|
|
startStyle: "with-coverage-start",
|
|
endStyle: "with-coverage-end",
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
let NONE_OVERLAP = 0;
|
|
let ALL_OVERLAP = 1;
|
|
let SOME_OVERLAP = 2;
|
|
|
|
let gather_overlaps = (parent, file, coverage_nodes, from, to) => {
|
|
if (!coverage_nodes) { return; }
|
|
|
|
// Start by populating all the children nodes from the full
|
|
// test lists. This includes nodes that do not have child
|
|
// coverage data.
|
|
for (var index = 0; index < parent.test.length; index++) {
|
|
if (parent.children.has(index)) { continue; }
|
|
|
|
let test_node = parent.test[index];
|
|
let test_name_id = test_node[0];
|
|
let test_name = names[test_name_id];
|
|
let test_children = test_node[1];
|
|
|
|
let node = {
|
|
test: test_children,
|
|
name: parent.name ? parent.name + test_name : test_name,
|
|
overlaps: new Map(parent.overlaps), // map: span_id -> OVERLAP
|
|
children: new Map(), // map: index -> struct
|
|
is_leaf: test_children.length == 0,
|
|
};
|
|
parent.children.set(index, node);
|
|
}
|
|
|
|
// Now update the children that do have coverage data.
|
|
for (const coverage_node of coverage_nodes) {
|
|
let index = coverage_node[0];
|
|
let coverage = coverage_node[1];
|
|
let node = parent.children.get(index);
|
|
|
|
let span_ids = new Set();
|
|
coverage_spans(file, coverage, span_ids);
|
|
|
|
// Update the node overlaps based on the coverage spans.
|
|
for (const span_id of span_ids) {
|
|
if (intersects(spans[span_id], from, to)) {
|
|
let overlap = parent.overlaps.get(span_id) || NONE_OVERLAP;
|
|
overlap = (overlap == NONE_OVERLAP) ? ALL_OVERLAP : NONE_OVERLAP;
|
|
node.overlaps.set(span_id, overlap);
|
|
}
|
|
}
|
|
|
|
// Generate the child nodes.
|
|
gather_overlaps(node, file, coverage.c, from, to);
|
|
|
|
// Gather all the spans used by the children.
|
|
let all_spans = new Set();
|
|
for (const [_, child] of node.children) {
|
|
for (const [span, _] of child.overlaps) {
|
|
all_spans.add(span);
|
|
}
|
|
}
|
|
|
|
// Update the node.overlaps based on the child overlaps.
|
|
for (const span of all_spans) {
|
|
let overlap = undefined;
|
|
for (const [_, child] of node.children) {
|
|
let child_overlap = child.overlaps.get(span);
|
|
child_overlap = (child_overlap == undefined) ? NONE_OVERLAP : child_overlap;
|
|
if (overlap == undefined) {
|
|
overlap = child_overlap;
|
|
} else {
|
|
overlap = (child_overlap == overlap) ? overlap : SOME_OVERLAP
|
|
}
|
|
}
|
|
node.overlaps.set(span, overlap);
|
|
}
|
|
|
|
// If all the node.overlaps are NONE_OVERLAP or ALL_OVERLAP
|
|
// then there's no point holding on to the children -
|
|
// we know all transitive children either fully overlap
|
|
// or don't at all.
|
|
let some_overlap = false;
|
|
for (const [_, overlap] of node.overlaps) {
|
|
if (overlap == SOME_OVERLAP) {
|
|
some_overlap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!some_overlap) {
|
|
node.children = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
let gather_tests = (file, coverage_nodes, test_nodes, from, to) => {
|
|
let out = [];
|
|
|
|
let traverse = (parent) => {
|
|
for (const [idx, node] of parent.children) {
|
|
let do_traversal = false;
|
|
let do_add = false;
|
|
|
|
for (const [_, overlap] of node.overlaps) {
|
|
switch (overlap) {
|
|
case SOME_OVERLAP:
|
|
do_traversal = true;
|
|
break;
|
|
case ALL_OVERLAP:
|
|
do_add = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (do_add) {
|
|
out.push(node.name + (node.is_leaf ? "" : "*"));
|
|
} else if (do_traversal) {
|
|
traverse(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
let tree = {
|
|
test: test_nodes,
|
|
overlaps: new Map(), // map: span_id -> OVERLAP
|
|
children: new Map(), // map: index -> struct
|
|
};
|
|
|
|
gather_overlaps(tree, file, coverage_nodes, from, to);
|
|
|
|
traverse(tree);
|
|
|
|
return out;
|
|
};
|
|
|
|
let update_selection = (from, to) => {
|
|
if (from.line > to.line || (from.line == to.line && from.ch > to.ch)) {
|
|
let tmp = from;
|
|
from = to;
|
|
to = tmp;
|
|
}
|
|
|
|
let file = files[current.file];
|
|
let filtered = gather_tests(file, file.c, tests, from, to);
|
|
el_test_tree.innerHTML = "";
|
|
filtered.forEach(test_name => {
|
|
let element = document.createElement('p');
|
|
element.className = "test-name";
|
|
element.innerText = test_name;
|
|
el_test_tree.appendChild(element);
|
|
});
|
|
};
|
|
|
|
let load_source = (path) => {
|
|
if (!files[path]) { return; }
|
|
|
|
for (let i = 0; i < el_file_tree.childNodes.length; i++) {
|
|
let el = el_file_tree.childNodes[i];
|
|
if (el.path == path) {
|
|
el.classList.add("selected");
|
|
} else {
|
|
el.classList.remove("selected");
|
|
}
|
|
}
|
|
el_source.doc.setValue("// Loading... ");
|
|
fetch(`${path}`)
|
|
.then(response => response.text())
|
|
.then(source => {
|
|
el_source.doc.setValue(source);
|
|
current.file = path;
|
|
markup(files[path]);
|
|
if (pending.start_line) {
|
|
var start = {
|
|
line: pending.start_line - 1,
|
|
ch: pending.start_column ? pending.start_column - 1 : 0
|
|
};
|
|
var end = {
|
|
line: pending.end_line ? pending.end_line - 1 : pending.start_line - 1,
|
|
ch: pending.end_column ? pending.end_column - 1 : 0
|
|
};
|
|
el_source.doc.setSelection(start, end);
|
|
update_selection(start, end);
|
|
}
|
|
pending = {};
|
|
});
|
|
};
|
|
|
|
el_source.doc.on("beforeSelectionChange", (doc, selection) => {
|
|
if (!files[current.file]) { return; }
|
|
|
|
let range = selection.ranges[0];
|
|
let from = range.head;
|
|
let to = range.anchor;
|
|
|
|
set_location(current.file, from.line + 1, from.ch + 1, to.line + 1, to.ch + 1);
|
|
|
|
update_selection(from, to);
|
|
});
|
|
|
|
for (const path of Object.keys(files)) {
|
|
let file = files[path];
|
|
|
|
let div = document.createElement('div');
|
|
div.className = "file-div";
|
|
div.onclick = () => { pending = {}; load_source(path); }
|
|
div.path = path;
|
|
el_file_tree.appendChild(div);
|
|
|
|
let coverage = document.createElement('span');
|
|
coverage.className = "file-coverage";
|
|
if (file.p != undefined) {
|
|
let red = 1.0 - file.p;
|
|
let green = file.p;
|
|
let normalize = 1.0 / (red * red + green * green);
|
|
red *= normalize;
|
|
green *= normalize;
|
|
coverage.innerText = Math.round(file.p * 100);
|
|
coverage.style = "background-color: RGB(" + 255 * red + "," + 255 * green + ", 0" + ")";
|
|
} else {
|
|
coverage.innerText = "--";
|
|
coverage.style = "background-color: RGB(180,180,180)";
|
|
}
|
|
div.appendChild(coverage);
|
|
|
|
let filepath = document.createElement('span');
|
|
filepath.className = "file-path";
|
|
filepath.innerText = path;
|
|
div.appendChild(filepath);
|
|
}
|
|
|
|
if (pending.file) {
|
|
load_source(pending.file);
|
|
}
|
|
});
|
|
};
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|