Commit 1d0ff622 authored by Aleks Totic's avatar Aleks Totic Committed by Commit Bot

TestExpectations polish

Changed query ui into queries + filters
Added flaky tests query
Select all shortcut now selects only expectations.
Fixed sorting bug (dirs and files were mixed)
Speedup startup, and query display

Bug: 726520
Change-Id: I9356455c02e70424ae1e98901fe0e5f56ed675df
Reviewed-on: https://chromium-review.googlesource.com/580827
Commit-Queue: Aleks Totic <atotic@chromium.org>
Reviewed-by: default avatarQuinten Yearsley <qyearsley@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488789}
parent c1405389
...@@ -23,6 +23,7 @@ button { ...@@ -23,6 +23,7 @@ button {
background-color: white; background-color: white;
padding: 16px; padding: 16px;
box-shadow: 0 0 20px; box-shadow: 0 0 20px;
overflow: auto;
} }
.hidden { .hidden {
display: none; display: none;
...@@ -101,12 +102,22 @@ button { ...@@ -101,12 +102,22 @@ button {
resize: both; resize: both;
overflow: auto; overflow: auto;
} }
#filters {
margin-top: 8px;
}
#filters label {
font-family: sans-serif;
font-size: smaller;
}
#filters input {
vertical-align: middle;
}
</style> </style>
<body> <body>
<h3>layout test results viewer</h3> <h3>layout test results viewer</h3>
<div style="position:absolute; top:8px;right:0;font-size:smaller">go back to <a href="results.html">legacy results.html</a></div> <div style="position:absolute;top:8px;right:0;font-size:smaller">go back to <a href="results.html">legacy results.html</a></div>
<div id="help" class="hidden"> <div id="help" class="hidden">
<button onclick="toggleVisibility('help')">Close</button> <button style="position:fixed;right:30px;" onclick="GUI.toggleVisibility('help')">Close</button>
<pre> <pre>
This page lets you query and display test results. This page lets you query and display test results.
...@@ -135,8 +146,10 @@ your job as easy as copy and paste. ...@@ -135,8 +146,10 @@ your job as easy as copy and paste.
<span style="font-size:larger">### Keyboard navigation</span> <span style="font-size:larger">### Keyboard navigation</span>
<b>Tab</b> to select tests. <b>Tab</b> to select the next test.
<b>Enter</b> to see test details. This will automatically close other details. <b>Enter</b> to see test details. This will automatically close other details.
<b>S</b> to select the full name of the test under the cursor
<b>ctrl A</b> to select text of all tests for easy copying.
Modifiers: Modifiers:
...@@ -147,77 +160,55 @@ If you are unhappy with results, please file a bug, or fix it <a href="https://c ...@@ -147,77 +160,55 @@ If you are unhappy with results, please file a bug, or fix it <a href="https://c
</pre> </pre>
</div> </div>
<pre id="summary"> <pre id="summary">
Test run summary <a id="help_button" href="javascript:toggleVisibility('help')">?</a>: Test run summary <a id="help_button" href="javascript:GUI.toggleVisibility('help')">(help)</a>:
Passed : <span id="summary_passed"></span> Passed : <span id="summary_passed"></span>
Regressions: <span id="summary_regressions"></span> Regressions: <span id="summary_regressions"></span>
Total : <span id="summary_total"></span> Total : <span id="summary_total"></span>
Counts : <span id="summary_details"></span>
</pre> </pre>
<div>Display filtered results by picking a filter:</div> <div>Query results, refine with filters</div>
<div id="filters"> <div id="dashboard">
<div> <div>
<span class="fix-width">Unexpected:</span> <span class="fix-width">Query:</span>
<button id="button_unexpected_fail" onclick="javascript:generateReport('Unexpected failures', Filters.unexpectedFailure)"> <button id="button_unexpected_fail" onclick="javascript:Query.query('Unexpected failures', Filters.unexpectedFailure, true)">
Unexpected Failure Unexpected Failure
<span id="count_unexpected_fail"></span> <span id="count_unexpected_fail"></span>
</button> </button>
<button onclick="javascript:generateReport('Unexpected passes', Filters.unexpectedPass)"> <button onclick="javascript:Query.query('Unexpected passes', Filters.unexpectedPass, true)">
Unexpected Pass Unexpected Pass
<span id="count_unexpected_pass"></span> <span id="count_unexpected_pass"></span>
</button> </button>
</div> <button onclick="javascript:Query.query('TestExpectations', Filters.notpass, true)">
<div> TestExpectations
<span class="fix-width">All failures:</span> <span id="count_testexpectations"></span>
<button onclick="javascript:generateReport('Crash', Filters.actual('CRASH'))">
Crash
<span id="count_CRASH"></span>
</button>
<button onclick="javascript:generateReport('Timeout', Filters.actual('TIMEOUT'))">
Timeout
<span id="count_TIMEOUT"></span>
</button>
<button onclick="javascript:generateReport('Text failure', Filters.actual('TEXT'))">
Text failure
<span id="count_TEXT"></span>
</button> </button>
<button onclick="javascript:generateReport('Image failure', Filters.actual('IMAGE'))"> <button onclick="javascript:Query.query('All', Filters.all, true)">
Image failure All
<span id="count_IMAGE"></span> <span id="count_all"></span>
</button> </button>
<button onclick="javascript:generateReport('Image+text failure', Filters.actual('IMAGE+TEXT'))"> <button onclick="javascript:Query.query('Flaky', Filters.flaky, true)">
Image+text failure Flaky
<span id="count_IMAGE_TEXT"></span> <span id="count_flaky"></span>
</button> </button>
</div> </div>
<div> <div id="filters">
<span class="fix-width">Misc:</span> <span class="fix-width">Filters:</span>
<label id="CRASH"><input type="checkbox">Crash <span></span></label>
<button onclick="javascript:generateReport('Skipped', Filters.actual('SKIP'))"> <label id="TIMEOUT"><input type="checkbox">Timeout <span></span></label>
Skipped <label id="TEXT"><input type="checkbox">Text failure <span></span></label>
<span id="count_SKIP"></span> <label id="IMAGE"><input type="checkbox">Image failure <span></span></label>
</button> <label id="IMAGE_TEXT"><input type="checkbox">Image+text failure <span></span></label>
<button onclick="javascript:generateReport('Pass', Filters.actual('PASS'))"> <label id="SKIP"><input type="checkbox">Skipped <span></span></label>
Pass <label id="PASS"><input type="checkbox">Pass <span></span></label>
<span id="count_PASS"></span> <label id="WONTFIX"><input type="checkbox">WontFix <span></span></label>
</button> <label id="MISSING"><input type="checkbox">Missing <span></span></label>
<button onclick="javascript:generateReport('Wontfix -- defined in NeverFixTests', Filters.wontfix)">
WontFix
<span id="count_WONTFIX"></span>
</button>
<button onclick="javascript:generateReport('Missing', Filters.actual('MISSING'))">
Missing
<span id="count_MISSING"></span>
</button>
<button onclick="javascript:generateReport('TestExpectations', Filters.notpass)">
TestExpectations
<span id="count_testexpectations"></span>
</button>
</div> </div>
</div> </div>
<div id="report_header" style="margin-top:8px"> <div id="report_header" style="margin-top:8px">
Tests shown: <span id="report_title" style="font-weight:bold"></span> Tests shown: <span id="report_title" style="font-weight:bold"></span>
in format: in format:
<select id="report_format" onchange="generateReport()"> <select id="report_format" onchange="Query.generateReport()">
<option value="plain" selected>Plain text</option> <option value="plain" selected>Plain text</option>
<option value="expectation">TestExpectations</option> <option value="expectation">TestExpectations</option>
</select> </select>
...@@ -236,6 +227,7 @@ Total : <span id="summary_total"></span> ...@@ -236,6 +227,7 @@ Total : <span id="summary_total"></span>
<script> <script>
"use strict"; "use strict";
// Results loaded from full_results_jsonp.js.
let fullResults = {}; let fullResults = {};
let TestResultInformation = { let TestResultInformation = {
...@@ -248,9 +240,8 @@ let TestResultInformation = { ...@@ -248,9 +240,8 @@ let TestResultInformation = {
"SLOW": { index: 7, text: "Slow", isFailure: false, isSuccess: true }, "SLOW": { index: 7, text: "Slow", isFailure: false, isSuccess: true },
"SKIP": { index: 8, text: "Skip", isFailure: false, isSuccess: false }, "SKIP": { index: 8, text: "Skip", isFailure: false, isSuccess: false },
"MISSING": { index: 9, text: "Missing", isFailure: false, isSuccess: false }, "MISSING": { index: 9, text: "Missing", isFailure: false, isSuccess: false },
"WONTFIX": { index: 10, text: "Wontfix", isFailure: false, isSuccess: false }, "WONTFIX": { index: 10, text: "WontFix", isFailure: false, isSuccess: false },
"NEEDSMANUALREBASELINE": { index: 11, text: "NeedsManualRebaseline", isFailure: false, isSuccess: false }, "NEEDSMANUALREBASELINE": { index: 11, text: "NeedsManualRebaseline", isFailure: false, isSuccess: false },
"NEEDSREBASELINE": { index: 11, text: "NeedsRebaseline", isFailure: false, isSuccess: false },
"PASS": { index: 12, text: "Pass", isFailure: false, isSuccess: true }, "PASS": { index: 12, text: "Pass", isFailure: false, isSuccess: true },
} }
...@@ -264,45 +255,12 @@ let TestResultComparator = function (a, b) { ...@@ -264,45 +255,12 @@ let TestResultComparator = function (a, b) {
return -1; return -1;
} }
function printSummary(r) { // Traversal traverses all the tests.
document.querySelector("#summary_total").innerText = r.num_passes + r.num_regressions; // Use Traversal.traverse(filter, action) to perform action on selected tests.
document.querySelector("#summary_passed").innerText = r.num_passes;
document.querySelector("#summary_regressions").innerText = r.num_regressions;
let failures = r["num_failures_by_type"];
var totalFailures = 0;
for (let p in failures) {
if (failures[p]) {
try {
document.querySelector("#count_" + p.replace("+", "_")).innerText = failures[p];
} catch(e) {
console.error("Missing failure type", p);
}
}
}
var t = new Traversal(fullResults.tests);
t.traverse(Filters.unexpectedPass);
document.querySelector("#count_unexpected_pass").innerText = t.filteredCount;
t.reset().traverse(Filters.unexpectedFailure);
document.querySelector("#count_unexpected_fail").innerText = t.filteredCount;
t.reset().traverse(Filters.notpass);
document.querySelector("#count_testexpectations").innerText = t.filteredCount;
// Hide filters with zero count
for (let el of Array.from(
document.querySelector("#filters").querySelectorAll("*"))) {
if (el.id && el.id.startsWith("count")
&& el.innerText == ""
&& el.parentNode.nodeName == "BUTTON") {
el.parentNode.remove();
}
}
}
let Traversal = function(testRoot) { let Traversal = function(testRoot) {
this.root = testRoot; this.root = testRoot;
this.reset(); this.reset();
} }
Traversal.prototype = { Traversal.prototype = {
traverse: function(filter, action) { traverse: function(filter, action) {
action = action || function() {}; action = action || function() {};
...@@ -313,6 +271,7 @@ Traversal.prototype = { ...@@ -313,6 +271,7 @@ Traversal.prototype = {
this.filteredCount = 0; this.filteredCount = 0;
this.lastDir = ""; this.lastDir = "";
this.html = []; this.html = [];
this.resultCounts = {};
return this; return this;
}, },
...@@ -321,18 +280,21 @@ Traversal.prototype = { ...@@ -321,18 +280,21 @@ Traversal.prototype = {
this.testCount++; this.testCount++;
if (filter(node, path)) { if (filter(node, path)) {
this.filteredCount++; this.filteredCount++;
// Ignore all results except final one, or not?
if (!(node.actualFinal in this.resultCounts))
this.resultCounts[node.actualFinal] = 0;
this.resultCounts[node.actualFinal]++;
action(node, path, this); action(node, path, this);
} }
} }
else { else {
// TODO(atotic) we need name + type sort, tests go before dirs. for (let p of node.keys())
var keys = Object.keys(node).sort(); this._helper(node.get(p), path + "/" + p, filter, action);
for (let p of keys)
this._helper(node[p], path + "/" + p, filter, action);
} }
} }
} }
// Parses test path into parts: dir, extension, href.
let PathParser = function(path) { let PathParser = function(path) {
this.path = path; this.path = path;
let [href, dir, file] = path.match("/(.*)/(.*)"); let [href, dir, file] = path.match("/(.*)/(.*)");
...@@ -340,23 +302,38 @@ let PathParser = function(path) { ...@@ -340,23 +302,38 @@ let PathParser = function(path) {
this.file = file; this.file = file;
let tmp; let tmp;
[tmp, this.basename, this.extension] = file.match(/(.*)\.(\w+)/); [tmp, this.basename, this.extension] = file.match(/(.*)\.(\w+)/);
this.testHref = "../../../third_party/WebKit/LayoutTests" + href.replace(/\/virtual\/[^\/]*/, ""); this.testHref = this.testBaseHref() + href.replace(/\/virtual\/[^\/]*/, "");
} }
PathParser.prototype = { PathParser.prototype = {
resultLink: function(resultName) { resultLink: function(resultName) {
return this.dir + "/" + this.basename + resultName; return this.dir + "/" + this.basename + resultName;
},
testBaseHref: function() {
if (fullResults.layout_tests_dir) {
return fullResults.layout_tests_dir;
} else if (location.toString().indexOf('file://') == 0) {
// tests were run locally.
return "../../..";
} else if (window.localStorage.getItem("testLocationOverride")) {
// Experimental preference.
return window.localStorage.getItem("testLocationOverride");
} else if (fullResults.chromium_revision) {
return "https://crrev.com/" + fullResults.chromium_revision + "/third_party/WebKit/LayoutTests";
} else {
return "https://chromium.googlesource.com/chromium/src/+/master/third_party/WebKit/LayoutTests";
}
} }
} }
let Printers = { // Report deals with displaying a single test.
let Report = {
getDefaultPrinter: () => { getDefaultPrinter: () => {
switch(document.querySelector("#report_format").value) { switch(document.querySelector("#report_format").value) {
case "expectation": case "expectation":
return Printers.printExpectation; return Report.printExpectation;
case "plain": case "plain":
return Printers.printPlainTest; return Report.printPlainTest;
default: default:
console.error("Unknown printer type"); console.error("Unknown printer type");
} }
...@@ -364,10 +341,10 @@ let Printers = { ...@@ -364,10 +341,10 @@ let Printers = {
printPlainTest: (test, path, traversal) => { printPlainTest: (test, path, traversal) => {
let pathParser = new PathParser(path); let pathParser = new PathParser(path);
let html = " " + pathParser.dir + "/" let html = "" + pathParser.dir + "/"
+ "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>" + "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>"
+ pathParser.file + "</a>"; + pathParser.file + "</a>";
html = "<div class='expect' tabindex='0' data-id='"+ test.expect_id +"'><div class='details'></div>" + html + "</div>"; html = "<div class='expect' tabindex='0' data-id='"+ test.expectId +"'><div class='details'></div>" + html + "</div>";
traversal.html.push(html); traversal.html.push(html);
}, },
...@@ -412,61 +389,232 @@ let Printers = { ...@@ -412,61 +389,232 @@ let Printers = {
+ "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>" + "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>"
+ pathParser.file + "</a>"; + pathParser.file + "</a>";
html += " [ " + status + " ]"; html += " [ " + status + " ]";
html = "<div class='expect' tabindex='0' data-id='"+ test.expect_id +"'><div class='details'></div>" + html + "</div>"; html = "<div class='expect' tabindex='0' data-id='"+ test.expectId +"'><div class='details'></div>" + html + "</div>";
traversal.html.push(html); traversal.html.push(html);
} },
}
let lastReport; // Returns links to test results [ {text:, link:}, ... ]
let lastRAF; getResultLinks: function (test) {
function generateReport(name, filter, report) { let links = [];
if (lastRAF) let pathParser = new PathParser(test.expectPath);
window.cancelAnimationFrame(lastRAF); links.push({text: test.actual});
report = report || Printers.getDefaultPrinter(); for (let result of test.actualMap.keys()) {
filter = filter || lastReport.filter; switch(result) {
name = name || lastReport.name; case "PASS":
lastReport = { name: name, filter: filter }; case "SLOW":
document.querySelector("#report").innerHTML = ""; if (Filters.unexpectedPass(test))
document.querySelector("#report_title").innerHTML = name; links.push({text: "Expected: " + test.expected});
let t; if (!test.has_stderr)
links.push({text: "No errors"});
let chunkSize = 500; break;
let index = 0; case "SKIP":
let callback = function() { links.push({text: "Test did not run."});
lastRAF = null; break;
let pre = document.createElement("div"); case "CRASH":
pre.innerHTML = t.html.slice(index, index + chunkSize).join("\n"); links.push({text: "crash log", link: pathParser.resultLink("-crash-log.txt")});
document.querySelector("#report").appendChild(pre); break;
index += chunkSize; case "TIMEOUT":
console.log("added pre"); links.push({text: "Test timed out. "
if (index < t.html.length) + ("time" in test ? `(${test.time}s)` : "")});
lastRAF = window.requestAnimationFrame(callback); break;
case "TEXT":
links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')});
if (!test.is_testharness_test) {
links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')});
links.push({text: "diff", link: pathParser.resultLink('-diff.txt')});
}
break;
case "IMAGE":
links.push({text: "actual image", link: pathParser.resultLink("-actual.png")});
links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
break;
case "IMAGE+TEXT":
links.push({text: "actual image", link: pathParser.resultLink("-actual.png")});
links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')});
if (!test.is_testharness_test) {
links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')});
links.push({text: "diff", link: pathParser.resultLink('-diff.txt')});
}
break;
case "MISSING":
links.push({text: "Test is missing."});
break;
default:
console.error("unexpected actual", test.actual);
}
}
if (test.has_stderr) {
links.push({text: "stderr", link: pathParser.resultLink("-stderr.txt")});
}
return links;
},
getResultsDiv: function(test) {
let clone = document.importNode(
document.querySelector("#genericResult").content, true);
let div = clone.children[0];
// Initialize the results
let menu = div.querySelector(".result-menu");
menu.innerHTML = "";
for (let link of Report.getResultLinks(test)) {
let li = document.createElement("li");
if (link.link) {
let anchor = document.createElement("a");
anchor.setAttribute("onclick", "return GUI.loadResult(this)");
anchor.setAttribute("href", link.link || "");
anchor.setAttribute("onfocus", "return GUI.loadResult(this)");
anchor.innerText = link.text;
li.appendChild(anchor);
}
else {
li.innerText = link.text;
}
menu.appendChild(li);
}
return div;
} }
window.setTimeout( _ => {
t = new Traversal(fullResults.tests);
t.traverse(filter, report);
lastRAF = window.requestAnimationFrame(callback)
}, 0);
} }
function containsPass(map) { // Query generates a report for a given query.
return map.has("PASS") || map.has("SLOW"); let Query = {
} lastReport: null,
currentRAF: null,
currentPromise: null,
currentResolve: null,
currentReject: null,
createReportPromise: function() {
if (this.currentPromise) {
this.currentReject();
this.currentPromise = null;
}
this.currentPromise = new Promise( (resolve, reject) => {
this.currentResolve = resolve;
this.currentReject = reject;
});
},
completeReportPromise: function(traversal) {
this.currentResolve(traversal);
this.currentPromise = null;
this.currentResolve = null;
this.currentReject = null;
},
resetFilters: function() {
// Reset all filters
for (let el of Array.from(
document.querySelectorAll("#filters > label"))) {
el.classList.remove("hidden");
el.querySelector('input').checked = true;
el.querySelector('span').innerText = "";
}
},
updateFilters: function(traversal) {
for (let el of Array.from(
document.querySelectorAll("#filters > label"))) {
let count = traversal.resultCounts[el.id];
if (count > 0) {
el.classList.remove("hidden");
el.querySelector('input').checked = true;
el.querySelector('span').innerText = count;
} else {
el.classList.add("hidden");
el.querySelector("input").checked = false;
el.querySelector("span").innerText = "";
}
}
},
filterChanged: function(ev) {
console.log("filterChanged");
this.query();
},
function containsNoPass(map) { applyFilters: function(queryFilter) {
return map.has("FAIL") var filterMap = new Map();
for (let el of Array.from(
document.querySelectorAll("#filters > label"))) {
if (el.querySelector('input').checked)
filterMap.set(el.id, true);
}
return test => queryFilter(test) && filterMap.has(test.actualFinal);
},
query: function(name, queryFilter, reset) {
queryFilter = queryFilter || this.lastQueryFilter;
if (reset) {
this.resetFilters();
this.lastQueryFilter = queryFilter;
}
let composedFilter = this.applyFilters(queryFilter);
this.generateReport(name, composedFilter)
.then(
traversal => {
if (reset)
this.updateFilters(traversal);
},
_ => console.log("interrupted")
);
},
// generateReport is async, returns promise
// promise if fullfilled when traversal completes. Display will continue async.
generateReport: function(name, filter, report) {
if (this.currentRAF)
window.cancelAnimationFrame(this.currentRAF);
report = report || Report.getDefaultPrinter();
filter = filter || this.lastReport.filter;
name = name || this.lastReport.name;
// Store last report to redisplay
this.lastReport = { name: name, filter: filter };
this.createReportPromise();
document.querySelector("#report").innerHTML = "";
document.querySelector("#report_title").innerHTML = name;
let traversal = new Traversal(fullResults.tests);
let chunkSize = 1000;
let index = 0;
let callback = _ => {
this.currentRAF = null;
let pre = document.createElement("div");
pre.innerHTML = traversal.html.slice(index, index + chunkSize).join("\n");
document.querySelector("#report").appendChild(pre);
index += chunkSize;
if (index < traversal.html.length)
this.currentRAF = window.requestAnimationFrame(callback);
}
window.setTimeout( _ => {
traversal.traverse(filter, report);
this.completeReportPromise(traversal);
this.currentRAF = window.requestAnimationFrame(callback)
}, 0);
return this.currentPromise;
}
};
// Test filters for queries.
let Filters = {
containsPass: function (map) {
return map.has("PASS") || map.has("SLOW");
},
containsNoPass: function(map) {
return map.has("FAIL")
|| map.has("NEEDSMANUALREBASELINE") || map.has("NEEDSMANUALREBASELINE")
|| map.has("WONTFIX") || map.has("WONTFIX")
|| map.has("SKIP") || map.has("SKIP")
|| map.has("CRASH"); || map.has("CRASH");
} },
let Filters = {
unexpectedPass: test => { unexpectedPass: test => {
return !containsPass(test.expectedMap) && containsPass(test.actualMap); return !Filters.containsPass(test.expectedMap) && Filters.containsPass(test.actualMap);
}, },
unexpectedFailure: test => { unexpectedFailure: test => {
if (containsPass(test.actualMap)) if (Filters.containsPass(test.actualMap))
return false; return false;
if (test.expectedMap.has("NEEDSMANUALREBASELINE") if (test.expectedMap.has("NEEDSMANUALREBASELINE")
|| test.expectedMap.has("NEEDSREBASELINE") || test.expectedMap.has("NEEDSREBASELINE")
...@@ -482,7 +630,7 @@ let Filters = { ...@@ -482,7 +630,7 @@ let Filters = {
case "TEXT": case "TEXT":
case "IMAGE": case "IMAGE":
case "IMAGE+TEXT": case "IMAGE+TEXT":
if (containsNoPass(test.expectedMap)) if (Filters.containsNoPass(test.expectedMap))
return false; return false;
break; break;
case "MISSING": case "MISSING":
...@@ -499,227 +647,258 @@ let Filters = { ...@@ -499,227 +647,258 @@ let Filters = {
} }
}, },
wontfix: test => test.expected == "WONTFIX", wontfix: test => test.expected == "WONTFIX",
all: _ => true all: _ => true,
} flaky: test => { let i = test.actualMap.keys(); return !i.next().done && !i.next().done;}
}
// Event handling, initialization.
let GUI = {
initPage: function(results) {
results.tests = GUI.convertToMap(results.tests);
fullResults = results;
GUI.optimizeResults();
GUI.printSummary(fullResults);
GUI.initEvents();
// Show unexpected failures on startup.
document.querySelector("#button_unexpected_fail").click();
},
function getResultLinks(test) { convertToMap: function(o) {
let links = []; if ("actual" in o)
let pathParser = new PathParser(test.expect_path); return o;
links.push({text: test.actual}); else {
for (let result of test.actualMap.keys()) { let map = new Map();
switch(result) { var keys = Object.keys(o).sort((a, b) => {
case "PASS": let a_isTest = "actual" in o[a];
case "SLOW": let b_isTest = "actual" in o[b];
if (Filters.unexpectedPass(test)) if (a_isTest == b_isTest)
links.push({text: "Expected: " + test.expected}); return a < b ? -1 : +(a > b);
if (!test.has_stderr) return a_isTest ? -1 : 1;
links.push({text: "No errors"}); });
break; for (let p of keys)
case "SKIP": map.set(p, GUI.convertToMap(o[p]));
links.push({text: "Test did not run."}); return map;
break; }
case "CRASH": },
links.push({text: "crash log", link: pathParser.resultLink("-crash-log.txt")});
break; optimizeResults: function() {
case "TIMEOUT": // Optimizes fullResults for querying
links.push({text: "Test timed out. " let t = new Traversal(fullResults.tests);
+ ("time" in test ? `(${test.time}s)` : "")}); // To all tests add:
break; // - test.expectId, a unique id
case "TEXT": // - test.expectPath, full path to test
links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')}); // - test.actualMap, map of actual results
if (!test.is_testharness_test) { // - test.actualFinal, last result
links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')}); // - test.expectedMap, maps of expected results
links.push({text: "diff", link: pathParser.resultLink('-diff.txt')}); let nextId = 1;
t.traverse(
test => true,
(test, path) => {
test.expectId = nextId++;
test.expectPath = path;
test.actualMap = new Map();
for (let result of test.actual.split(" ")) {
test.actualFinal = result; // last result count as definite.
test.actualMap.set(result, true);
} }
break; test.expectedMap = new Map();
case "IMAGE": for (let result of test.expected.split(" ")) {
links.push({text: "actual image", link: pathParser.resultLink("-actual.png")}); test.expectedMap.set(result, true);
links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
break;
case "IMAGE+TEXT":
links.push({text: "actual image", link: pathParser.resultLink("-actual.png")});
links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')});
if (!test.is_testharness_test) {
links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')});
links.push({text: "diff", link: pathParser.resultLink('-diff.txt')});
} }
break; }
case "MISSING": );
links.push({text: "Test is missing."}); },
break;
default:
console.error("unexpected actual", test.actual);
}
}
if (test.has_stderr) {
links.push({text: "stderr", link: pathParser.resultLink("-stderr.txt")});
}
return links;
}
function getResultsDiv(test) { initEvents: function() {
let clone = document.importNode( document.addEventListener("click", function(ev) {
document.querySelector("#genericResult").content, true); if (GUI.isExpectation(ev.target)) {
let div = clone.children[0]; GUI.toggleResults(ev.target, ev);
// Initialize the results ev.preventDefault();
let menu = div.querySelector(".result-menu"); ev.stopPropagation();
menu.innerHTML = ""; }
for (let link of getResultLinks(test)) { });
let li = document.createElement("li"); document.addEventListener('keydown', ev => {
if (link.link) { {
let anchor = document.createElement("a"); switch(ev.key) {
anchor.setAttribute("onclick", "return loadResult(this)"); case "Enter":
anchor.setAttribute("href", link.link || ""); if (GUI.isExpectation(ev.target))
anchor.setAttribute("onfocus", "return loadResult(this)"); GUI.toggleResults(ev.target, ev);
anchor.innerText = link.text; break;
li.appendChild(anchor); case "s":
} case "S":
else { if (GUI.isExpectation(ev.target))
li.innerText = link.text; GUI.selectText(ev.altKey ? document.querySelector("#report") : ev.target);
else {
GUI.selectText(document.querySelector("#report"));
}
break;
case "a":
case "A":
if (ev.ctrlKey) {
GUI.selectText(document.querySelector("#report"));
ev.preventDefault();
}
break;
default:
;
}
}
});
for (let checkbox of Array.from(document.querySelectorAll("#filters input"))) {
checkbox.addEventListener("change", ev => Query.filterChanged(ev));
} }
menu.appendChild(li); },
}
return div;
}
function closest(el, className) { selectText: function(el) {
while (el) { let range = document.createRange();
if (el.classList.contains(className)) range.setStart(el, 0);
return el; range.setEnd(el, el.childNodes.length);
else let selection = document.getSelection();
el = el.parentNode; selection.removeAllRanges();
} selection.addRange(range);
} },
function loadResult(anchor) { isExpectation: function(el) {
if (!anchor.getAttribute("href")) return el.classList.contains("expect")
return false; },
let frame = closest(anchor, "result-frame");
let output = frame.querySelector(".result-output");
let iframe = output.querySelector("iframe");
if (!iframe) {
iframe = document.createElement("iframe");
output.appendChild(iframe);
}
iframe.src = anchor.href;
iframe.setAttribute("tabindex", -1);
return false;
}
function showResults(expectation, doNotScroll) { printSummary: function (r) {
let details = expectation.querySelector(".details"); document.querySelector("#summary_total").innerText = r.num_passes + r.num_regressions;
if (details.classList.contains("open")) document.querySelector("#summary_passed").innerText = r.num_passes;
return; document.querySelector("#summary_regressions").innerText = r.num_regressions;
details.classList.add("open"); let failures = r["num_failures_by_type"];
let testId = parseInt(expectation.getAttribute("data-id")); var totalFailures = 0;
let test;
(new Traversal(fullResults.tests)).traverse(function(thisTest) { let resultsText = "";
if (thisTest.expect_id == testId) for (let p in failures) {
test = thisTest; if (failures[p])
return false; resultsText += p + ":" + failures[p] + " ";
}); }
if (!test) document.querySelector("#summary_details").innerText = resultsText;
console.error("could not find test by id");
let results = getResultsDiv(test); // Initialize query counts.
results.classList.add("results"); let counts = {
expectation.parentNode.insertBefore(results, expectation.nextSibling); "count_unexpected_pass": 0,
let firstLink = results.querySelector(".result-menu a"); "count_unexpected_fail": 0,
if (firstLink) { "count_testexpectations": 0,
firstLink.click(); "count_flaky": 0
} }
if (doNotScroll) { var t = new Traversal(fullResults.tests);
return; t.traverse( test => {
} if (Filters.unexpectedPass(test))
// Scroll into view counts.count_unexpected_pass++;
let bottomDelta = results.offsetTop + results.offsetHeight - document.documentElement.clientHeight - window.scrollY + 48; if (Filters.unexpectedFailure(test))
if (bottomDelta > 0) counts.count_unexpected_fail++;
window.scrollBy(0, bottomDelta); if (Filters.notpass(test))
let topDelta = results.offsetTop - document.documentElement.clientHeight - window.scrollY - 24; counts.count_testexpectations++;
if (topDelta > 0) if (Filters.flaky(test))
window.scrollBy(0, topDelta); counts.count_flaky++;
} });
for (let p in counts)
document.getElementById(p).innerText = counts[p];
document.querySelector("#count_all").innerText = r.num_passes + r.num_regressions;
},
function hideResults(expectation) { toggleVisibility: function(id) {
let details = expectation.querySelector(".details"); document.querySelector("#" + id).classList.toggle("hidden");
if (!details.classList.contains("open")) },
return;
expectation.querySelector(".details").classList.remove("open");
expectation.nextSibling.remove();
}
function toggleResults(expectation, event) { toggleResults: function(expectation, event) {
let applyToAll = event && event.altKey; let applyToAll = event && event.altKey;
let closeOthers = !applyToAll && event && !event.shiftKey; let closeOthers = !applyToAll && event && !event.shiftKey;
let details = expectation.querySelector(".details"); let details = expectation.querySelector(".details");
let isOpen = details.classList.contains("open"); let isOpen = details.classList.contains("open");
if (applyToAll) { if (applyToAll) {
let allExpectations = Array.from(document.querySelectorAll(".expect")); let allExpectations = Array.from(document.querySelectorAll(".expect"));
if (allExpectations.length > 100) { if (allExpectations.length > 100) {
console.error("Too many details to be shown at once"); console.error("Too many details to be shown at once");
} else { } else {
for (e of allExpectations) for (e of allExpectations)
if (e != expectation) if (e != expectation)
isOpen ? hideResults(e) : showResults(e, true); isOpen ? GUI.hideResults(e) : GUI.showResults(e, true);
}
} }
} if (closeOthers) {
if (closeOthers) { for (let el of Array.from(document.querySelectorAll(".details.open")))
for (let el of Array.from(document.querySelectorAll(".details.open"))) GUI.hideResults(el.parentNode);
hideResults(el.parentNode); }
} if (isOpen) {
if (isOpen) { GUI.hideResults(expectation);
hideResults(expectation); }
} else {
else { GUI.showResults(expectation);
showResults(expectation); }
} },
}
function toggleVisibility(id) { closest: function (el, className) {
document.querySelector("#" + id).classList.toggle("hidden"); while (el) {
} if (el.classList.contains(className))
return el;
else
el = el.parentNode;
}
},
function initPage() { loadResult: function(anchor) {
let t = new Traversal(fullResults.tests); if (!anchor.getAttribute("href"))
// Add ids and paths to all the tests return false;
let nextId = 1; let frame = GUI.closest(anchor, "result-frame");
t.traverse( let output = frame.querySelector(".result-output");
test => true, let iframe = output.querySelector("iframe");
(test, path) => { if (!iframe) {
test.expect_id = nextId++; iframe = document.createElement("iframe");
test.expect_path = path; output.appendChild(iframe);
test.actualMap = new Map();
for (let result of test.actual.split(" ")) {
test.actualFinal = result; // last result count as definite.
test.actualMap.set(result, true);
}
test.expectedMap = new Map();
for (let result of test.expected.split(" ")) {
test.expectedMap.set(result, true);
}
} }
); iframe.src = anchor.href;
printSummary(fullResults); iframe.setAttribute("tabindex", -1);
document.addEventListener("click", function(ev) { return false;
if (ev.target.classList.contains("expect")) { },
toggleResults(ev.target, ev);
ev.preventDefault(); showResults: function(expectation, doNotScroll) {
ev.stopPropagation(); let details = expectation.querySelector(".details");
if (details.classList.contains("open"))
return;
details.classList.add("open");
let testId = parseInt(expectation.getAttribute("data-id"));
let test;
(new Traversal(fullResults.tests)).traverse(function(thisTest) {
if (thisTest.expectId == testId)
test = thisTest;
return false;
});
if (!test)
console.error("could not find test by id");
let results = Report.getResultsDiv(test);
results.classList.add("results");
expectation.parentNode.insertBefore(results, expectation.nextSibling);
let firstLink = results.querySelector(".result-menu a");
if (firstLink) {
firstLink.click();
} }
}); if (doNotScroll) {
document.addEventListener('keydown', ev => { return;
if (ev.key == "Enter" && ev.target.classList.contains("expect")) }
toggleResults(ev.target, ev); // Scroll into view
}); let bottomDelta = results.offsetTop + results.offsetHeight - document.documentElement.clientHeight - window.scrollY + 48;
// Show unexpected failures on startup. if (bottomDelta > 0)
document.querySelector("#button_unexpected_fail").click(); window.scrollBy(0, bottomDelta);
let topDelta = results.offsetTop - document.documentElement.clientHeight - window.scrollY - 24;
if (topDelta > 0)
window.scrollBy(0, topDelta);
},
hideResults: function(expectation) {
let details = expectation.querySelector(".details");
if (!details.classList.contains("open"))
return;
expectation.querySelector(".details").classList.remove("open");
expectation.nextSibling.remove();
}
} }
// jsonp callback
function ADD_FULL_RESULTS(results) { function ADD_FULL_RESULTS(results) {
fullResults = results; GUI.initPage(results);
initPage();
} }
</script> </script>
<script src="full_results_jsonp.js"></script> <script src="full_results_jsonp.js"></script>
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