Merge branch 'test-in-browser-with-finer-deps' into devel

This commit is contained in:
Nick Martin
2013-06-18 20:54:56 -07:00
2 changed files with 308 additions and 222 deletions

View File

@@ -4,11 +4,13 @@
<body>
<div class="container-fluid">
{{> test_table}}
{{> navBars}}
{{> failedTests}}
{{> testTable}}
</div>
</body>
<template name="test_table">
<template name="navBars">
<div class="navbar navbar-fixed-top navbar-inverse">
<div class="navbar-inner">
<div class="row-fluid">
@@ -37,19 +39,6 @@
</div>
</div>
{{> groupNav}}
<div class="row-fluid"><div class="span12">
<ul class="failedTests">
{{#each failedTests}}
<li>{{this}}</li>
{{/each}}
</ul>
<div class="test_table">
{{#each data}}
{{> test_group this}}
{{/each}}
</div>
</div></div>
</template>
<template name="progressBar">
@@ -85,7 +74,28 @@
</div>
</template>
<template name="failedTests">
<div class="row-fluid"><div class="span12">
<ul class="failedTests">
{{#each failedTests}}
<li>{{this}}</li>
{{/each}}
</ul>
</div></div>
</template>
<template name="testTable">
<div class="row-fluid"><div class="span12">
<div class="test_table">
{{#each data}}
{{> test_group this}}
{{/each}}
</div>
</div></div>
</template>
<template name="test_group">
{{groupDep}}
<div class="group">
<div class="groupname"><a>{{name}}</a></div>
{{#each tests}}
@@ -98,6 +108,7 @@
</template>
<template name="test">
{{testDep}}
<div class="test {{test_class}}">
<div class="testrow">
<div class="teststatus">

View File

@@ -1,12 +1,39 @@
var running = true;
////
//// Setup
////
var resultTree = [];
var failedTests = [];
var resultsDeps = new Deps.Dependency;
var countDeps = new Deps.Dependency;
// dependency for the count of tests running/passed/failed, etc. drives
// the navbar and the like.
var countDep = new Deps.Dependency;
// things that change on countDep
var running = true;
var totalCount = 0;
var passedCount = 0;
var failedCount = 0;
var failedTests = [];
// Dependency for when a new top level group is added. Each group and
// each test have their own dependency objects.
var topLevelGroupsDep = new Deps.Dependency;
// An array of top-level groups.
//
// Each group is an object with:
// - name: string
// - path: array of strings (names of parent groups)
// - parent: parent group object (back reference)
// - dep: Deps.Dependency object for this group. fires when new tests added.
// - groups: list of sub-groups
// - tests: list of tests in this group
//
// Each test is an object with:
// - name: string
// - parent: parent group object (back reference)
// - server: boolean
// - fullName: string
// - dep: Deps.Dependency object for this test. fires when the test completes.
var resultTree = [];
Session.setDefault("groupPath", ["tinytest"]);
@@ -17,7 +44,7 @@ Meteor.startup(function () {
Meteor._runTestsEverywhere(reportResults, function () {
running = false;
Meteor.onTestsComplete && Meteor.onTestsComplete();
resultsDeps.changed();
countDep.changed();
Deps.flush();
Meteor.default_connection._unsubscribeAll();
@@ -25,38 +52,245 @@ Meteor.startup(function () {
});
////
//// Take incoming results and drive resultsTree
////
// report a series of events in a single test, or just the existence of
// that test if no events. this is the entry point for test results to
// this module.
var reportResults = function(results) {
var test = _findTestForResults(results);
if (_.isArray(results.events)) {
// append events, if present
Array.prototype.push.apply((test.events || (test.events = [])),
results.events);
// sort and de-duplicate, based on sequence number
test.events.sort(function (a, b) {
return a.sequence - b.sequence;
});
var out = [];
_.each(test.events, function (e) {
if (out.length === 0 || out[out.length - 1].sequence !== e.sequence)
out.push(e);
});
test.events = out;
}
var status = _testStatus(test);
if (status === "failed") {
failedCount++;
// Expand a failed test (but only set this if the user hasn't clicked on the
// test name yet).
if (test.expanded === undefined)
test.expanded = true;
if (!_.contains(failedTests, test.fullName))
failedTests.push(test.fullName);
countDep.changed();
test.dep.changed();
} else if (status === "succeeded") {
passedCount++;
countDep.changed();
test.dep.changed();
} else if (test.expanded) {
// re-render the test if new results come in and the test is
// currently expanded.
test.dep.changed();
}
};
// forget all of the events for a particular test
var forgetEvents = function (results) {
var test = _findTestForResults(results);
var status = _testStatus(test);
if (status === "failed") {
failedCount--;
countDep.changed();
} else if (status === "succeeded") {
passedCount--;
countDep.changed();
}
delete test.events;
test.dep.changed();
};
// given a 'results' as delivered via reportResults, find the
// corresponding leaf object in resultTree, creating one if it doesn't
// exist. it will be an object with attributes 'name', 'parent', and
// possibly 'events'.
var _findTestForResults = function (results) {
var groupPath = results.groupPath; // array
if ((! _.isArray(groupPath)) || (groupPath.length < 1)) {
throw new Error("Test must be part of a group");
}
var group;
var i = 0;
_.each(groupPath, function(gname) {
var array = (group ? (group.groups || (group.groups = []))
: resultTree);
var newGroup = _.find(array, function(g) { return g.name === gname; });
if (! newGroup) {
newGroup = {
name: gname,
parent: (group || null),
path: groupPath.slice(0, i+1),
dep: new Deps.Dependency
}; // create group
array.push(newGroup);
if (group)
group.dep.changed();
else
topLevelGroupsDep.changed();
}
group = newGroup;
i++;
});
var testName = results.test;
var server = !!results.server;
var test = _.find(group.tests || (group.tests = []),
function(t) { return t.name === testName &&
t.server === server; });
if (! test) {
// create test
var nameParts = _.clone(groupPath);
nameParts.push(testName);
var fullName = nameParts.join(' - ');
test = {
name: testName,
parent: group,
server: server,
fullName: fullName,
dep: new Deps.Dependency
};
group.tests.push(test);
group.dep.changed();
totalCount++;
countDep.changed();
}
return test;
};
////
//// Helpers on test objects
////
var _testTime = function(t) {
if (t.events && t.events.length > 0) {
var lastEvent = _.last(t.events);
if (lastEvent.type === "finish") {
if ((typeof lastEvent.timeMs) === "number") {
return lastEvent.timeMs;
}
}
}
return null;
};
var _testStatus = function(t) {
var events = t.events || [];
if (_.find(events, function(x) { return x.type === "exception"; })) {
// "exception" should be last event, except race conditions on the
// server can make this not the case. Technically we can't tell
// if the test is still running at this point, but it can only
// result in FAIL.
return "failed";
} else if (events.length == 0 || (_.last(events).type != "finish")) {
return "running";
} else if (_.any(events, function(e) {
return e.type == "fail" || e.type == "exception"; })) {
return "failed";
} else {
return "succeeded";
}
};
////
//// Templates
////
//// Template - navBars
Template.navBars.running = function() {
countDep.depend();
return running;
};
Template.navBars.passed = function() {
countDep.depend();
return failedCount === 0;
};
Template.navBars.total_test_time = function() {
countDep.depend();
// walk whole tree to get all tests
var walk = function (groups) {
var total = 0;
_.each(groups || [], function (group) {
_.each(group.tests || [], function (t) {
total += _testTime(t);
});
total += walk(group.groups);
});
return total;
};
return walk(resultTree);
};
//// Template - progressBar
Template.progressBar.running = function () {
countDeps.depend();
return passedCount + failedCount < totalCount;
countDep.depend();
return running;
};
Template.progressBar.percentPass = function () {
countDeps.depend();
countDep.depend();
if (totalCount === 0)
return 0;
return 100*passedCount/totalCount;
};
Template.progressBar.totalCount = function () {
countDep.depend();
return totalCount;
};
Template.progressBar.passedCount = function () {
countDep.depend();
return passedCount;
};
Template.progressBar.percentFail = function () {
countDeps.depend();
countDep.depend();
if (totalCount === 0)
return 0;
return 100*failedCount/totalCount;
};
Template.progressBar.anyFail = function () {
countDeps.depend();
countDep.depend();
return failedCount > 0;
};
//// Template - groupNav
Template.groupNav.groupPaths = function () {
var groupPath = Session.get("groupPath");
var ret = [];
@@ -88,76 +322,47 @@ Template.groupNav.events({
}
});
//// Template - failedTests
Template.failedTests.failedTests = function() {
countDep.depend();
return failedTests;
};
//// Template - testTable
Template.testTable.data = function() {
topLevelGroupsDep.depend();
return resultTree;
};
//// Template - test_group
Template.test_group.groupDep = function () {
// this template just establishes a dependency. It doesn't actually
// render anything.
this.dep.depend();
return "";
};
Template.test_group.events({
"click .groupname": function () {
changeToPath(this.path);
}
});
Template.test_table.running = function() {
resultsDeps.depend();
return running;
};
Template.test_table.passed = function() {
resultsDeps.depend();
//// Template - test
// walk whole tree to look for failed tests
var walk = function (groups) {
var ret = true;
_.each(groups || [], function (group) {
if (!ret)
return;
_.each(group.tests || [], function (t) {
if (!ret)
return;
if (_testStatus(t) === "failed")
ret = false;
});
if (!walk(group.groups))
ret = false;
});
return ret;
};
return walk(resultTree);
};
Template.test_table.total_test_time = function() {
resultsDeps.depend();
// walk whole tree to get all tests
var walk = function (groups) {
var total = 0;
_.each(groups || [], function (group) {
_.each(group.tests || [], function (t) {
total += _testTime(t);
});
total += walk(group.groups);
});
return total;
};
return walk(resultTree);
};
Template.test_table.data = function() {
resultsDeps.depend();
return resultTree;
};
Template.test_table.failedTests = function() {
resultsDeps.depend();
return failedTests;
Template.test.testDep = function () {
// this template just establishes a dependency. It doesn't actually
// render anything.
this.dep.depend();
return "";
};
Template.test.test_status_display = function() {
@@ -192,7 +397,7 @@ Template.test.test_class = function() {
Template.test.events({
'click .testname': function() {
this.expanded = ! this.expanded;
resultsDeps.changed();
this.dep.changed();
}
});
@@ -231,6 +436,9 @@ Template.test.eventsArray = function() {
});
};
//// Template - event
Template.event.events({
'click .debug': function () {
// the way we manage groupPath, shortName, cookies, etc, is really
@@ -285,136 +493,3 @@ Template.event.is_debuggable = function() {
return !!this.cookie;
};
var _testTime = function(t) {
if (t.events && t.events.length > 0) {
var lastEvent = _.last(t.events);
if (lastEvent.type === "finish") {
if ((typeof lastEvent.timeMs) === "number") {
return lastEvent.timeMs;
}
}
}
return null;
};
var _testStatus = function(t) {
var events = t.events || [];
if (_.find(events, function(x) { return x.type === "exception"; })) {
// "exception" should be last event, except race conditions on the
// server can make this not the case. Technically we can't tell
// if the test is still running at this point, but it can only
// result in FAIL.
return "failed";
} else if (events.length == 0 || (_.last(events).type != "finish")) {
return "running";
} else if (_.any(events, function(e) {
return e.type == "fail" || e.type == "exception"; })) {
return "failed";
} else {
return "succeeded";
}
};
// given a 'results' as delivered via setReporter, find the
// corresponding leaf object in resultTree, creating one if it doesn't
// exist. it will be an object with attributes 'name', 'parent', and
// possibly 'events'.
var _findTestForResults = function (results) {
var groupPath = results.groupPath; // array
if ((! _.isArray(groupPath)) || (groupPath.length < 1)) {
throw new Error("Test must be part of a group");
}
var group;
var i = 0;
_.each(groupPath, function(gname) {
var array = (group ? (group.groups || (group.groups = []))
: resultTree);
var newGroup = _.find(array, function(g) { return g.name === gname; });
if (! newGroup) {
newGroup = {
name: gname,
parent: (group || null),
path: groupPath.slice(0, i+1)
}; // create group
array.push(newGroup);
}
group = newGroup;
i++;
});
var testName = results.test;
var server = !!results.server;
var test = _.find(group.tests || (group.tests = []),
function(t) { return t.name === testName &&
t.server === server; });
if (! test) {
// create test
var nameParts = _.clone(groupPath);
nameParts.push(testName);
var fullName = nameParts.join(' - ');
test = {name: testName, parent: group, server: server, fullName: fullName};
group.tests.push(test);
totalCount++;
countDeps.changed();
}
return test;
};
// report a series of events in a single test, or just
// the existence of that test if no events
var reportResults = function(results) {
var test = _findTestForResults(results);
if (_.isArray(results.events)) {
// append events, if present
Array.prototype.push.apply((test.events || (test.events = [])),
results.events);
// sort and de-duplicate, based on sequence number
test.events.sort(function (a, b) {
return a.sequence - b.sequence;
});
var out = [];
_.each(test.events, function (e) {
if (out.length === 0 || out[out.length - 1].sequence !== e.sequence)
out.push(e);
});
test.events = out;
}
var status = _testStatus(test);
if (status === "failed") {
failedCount++;
countDeps.changed();
// Expand a failed test (but only set this if the user hasn't clicked on the
// test name yet).
if (test.expanded === undefined)
test.expanded = true;
if (!_.contains(failedTests, test.fullName))
failedTests.push(test.fullName);
} else if (status === "succeeded") {
passedCount++;
countDeps.changed();
}
_throttled_update();
};
// forget all of the events for a particular test
var forgetEvents = function (results) {
var test = _findTestForResults(results);
var status = _testStatus(test);
if (status === "failed") {
failedCount--;
countDeps.changed();
} else if (status === "succeeded") {
passedCount--;
countDeps.changed();
}
delete test.events;
resultsDeps.changed();
};
var _throttled_update = _.throttle(function() {
resultsDeps.changed();
}, 1000);