<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[199191] trunk/Websites/perf.webkit.org</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/199191">199191</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-04-07 15:10:50 -0700 (Thu, 07 Apr 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Migrate legacy perf dashboard tests to mocha.js based tests
https://bugs.webkit.org/show_bug.cgi?id=156335

Reviewed by Chris Dumez.

Migrated all legacy run-tests.js tests to mocha.js based tests. Since the new harness uses Promise
for most of asynchronous operations, refactored the tests to use Promises as well, and added more
assertions where appropriate.

Also consolidated common helper functions into server-tests/resources/common-operations.js.
Unfortunately there were multiple inconsistent implementations of addBuilder/addSlave. Some were
taking an array of reports while others were taking a single report. New shared implementation in
common-operations.js now takes a single report.

Also decreased the timeout in most tests from 10s to 1s so that tests fail early when they timeout.
Most of tests are passing under 100ms on my computer so 1s should be plenty still.

* run-tests.js: Removed.
* server-tests/admin-platforms-tests.js: Moved from tests/admin-platforms.js.
(reportsForDifferentPlatforms):
* server-tests/admin-reprocess-report-tests.js: Moved from tests/admin-reprocess-report.js.
(.addBuilder): Moved to common-operations.js.
* server-tests/api-build-requests-tests.js:
* server-tests/api-manifest.js: Use MockData.resetV3Models() instead of manually clearing maps.
* server-tests/api-measurement-set-tests.js: Moved from tests/api-measurement-set.js.
(.queryPlatformAndMetric):
(.format):
* server-tests/api-report-commits-tests.js: Moved from tests/api-report-commits.js.
* server-tests/api-report-tests.js: Moved from tests/api-report.js.
(.emptyReport):
(.emptySlaveReport):
(.reportWithSameSubtestName):
* server-tests/resources/common-operations.js: Added.
(addBuilderForReport): Extracted from tests.
(addSlaveForReport): Ditto.
(connectToDatabaseInEveryTest): Added.
(submitReport): Extracted from admin-platforms-tests.js.
* server-tests/resources/test-server.js:
(TestServer): Make TestServer a singleton since it doesn't make any sense for each module to start
its own Apache instance (that would certainly will fail).
* server-tests/tools-buildbot-triggerable-tests.js:
* tests: Removed.
* tools/js/database.js:
(Database.prototype.selectAll): Added.
(Database.prototype.selectFirstRow): Added.
(Database.prototype.selectRows): Added. Dynamically construct a query string based on arguments.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgruntestsjs">trunk/Websites/perf.webkit.org/run-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapibuildrequeststestsjs">trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapimanifestjs">trunk/Websites/perf.webkit.org/server-tests/api-manifest.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsresourcestestserverjs">trunk/Websites/perf.webkit.org/server-tests/resources/test-server.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgserverteststoolsbuildbottriggerabletestsjs">trunk/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtoolsjsdatabasejs">trunk/Websites/perf.webkit.org/tools/js/database.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgservertestsadminplatformstestsjs">trunk/Websites/perf.webkit.org/server-tests/admin-platforms-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsadminreprocessreporttestsjs">trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapimeasurementsettestsjs">trunk/Websites/perf.webkit.org/server-tests/api-measurement-set-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapireportcommitstestsjs">trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapireporttestsjs">trunk/Websites/perf.webkit.org/server-tests/api-report-tests.js</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgtestsadminplatformsjs">trunk/Websites/perf.webkit.org/tests/admin-platforms.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtestsadminreprocessreportjs">trunk/Websites/perf.webkit.org/tests/admin-reprocess-report.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtestsapimeasurementsetjs">trunk/Websites/perf.webkit.org/tests/api-measurement-set.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtestsapireportcommitsjs">trunk/Websites/perf.webkit.org/tests/api-report-commits.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtestsapireportjs">trunk/Websites/perf.webkit.org/tests/api-report.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,3 +1,52 @@
</span><ins>+2016-04-07  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Migrate legacy perf dashboard tests to mocha.js based tests
+        https://bugs.webkit.org/show_bug.cgi?id=156335
+
+        Reviewed by Chris Dumez.
+
+        Migrated all legacy run-tests.js tests to mocha.js based tests. Since the new harness uses Promise
+        for most of asynchronous operations, refactored the tests to use Promises as well, and added more
+        assertions where appropriate.
+
+        Also consolidated common helper functions into server-tests/resources/common-operations.js.
+        Unfortunately there were multiple inconsistent implementations of addBuilder/addSlave. Some were
+        taking an array of reports while others were taking a single report. New shared implementation in
+        common-operations.js now takes a single report.
+
+        Also decreased the timeout in most tests from 10s to 1s so that tests fail early when they timeout.
+        Most of tests are passing under 100ms on my computer so 1s should be plenty still.
+
+        * run-tests.js: Removed.
+        * server-tests/admin-platforms-tests.js: Moved from tests/admin-platforms.js.
+        (reportsForDifferentPlatforms):
+        * server-tests/admin-reprocess-report-tests.js: Moved from tests/admin-reprocess-report.js.
+        (.addBuilder): Moved to common-operations.js.
+        * server-tests/api-build-requests-tests.js:
+        * server-tests/api-manifest.js: Use MockData.resetV3Models() instead of manually clearing maps.
+        * server-tests/api-measurement-set-tests.js: Moved from tests/api-measurement-set.js.
+        (.queryPlatformAndMetric):
+        (.format):
+        * server-tests/api-report-commits-tests.js: Moved from tests/api-report-commits.js.
+        * server-tests/api-report-tests.js: Moved from tests/api-report.js.
+        (.emptyReport):
+        (.emptySlaveReport):
+        (.reportWithSameSubtestName):
+        * server-tests/resources/common-operations.js: Added.
+        (addBuilderForReport): Extracted from tests.
+        (addSlaveForReport): Ditto.
+        (connectToDatabaseInEveryTest): Added.
+        (submitReport): Extracted from admin-platforms-tests.js.
+        * server-tests/resources/test-server.js:
+        (TestServer): Make TestServer a singleton since it doesn't make any sense for each module to start
+        its own Apache instance (that would certainly will fail).
+        * server-tests/tools-buildbot-triggerable-tests.js:
+        * tests: Removed.
+        * tools/js/database.js:
+        (Database.prototype.selectAll): Added.
+        (Database.prototype.selectFirstRow): Added.
+        (Database.prototype.selectRows): Added. Dynamically construct a query string based on arguments.
+
</ins><span class="cx"> 2016-04-05  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         New buildbot syncing scripts that supports multiple builders and slaves
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgruntestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/run-tests.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/run-tests.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/run-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,340 +0,0 @@
</span><del>-#!/usr/local/bin/node
-
-var assert = require('assert');
-var crypto = require('crypto');
-var fs = require('fs');
-var http = require('http');
-var path = require('path');
-var vm = require('vm');
-
-function connect(keepAlive) {
-    var pg = require('pg');
-    var database = config('database');
-    var connectionString = 'tcp://' + database.username + ':' + database.password + '@' + database.host + ':' + database.port
-        + '/' + database.name;
-
-    var client = new pg.Client(connectionString);
-    if (!keepAlive) {
-        client.on('drain', function () {
-            client.end();
-            client = undefined;
-        });
-    }
-    client.connect();
-
-    return client;
-}
-
-function pathToDatabseSQL(relativePath) {
-    return path.resolve(__dirname, 'init-database.sql');
-}
-
-function pathToTests(testName) {
-    return testName ? path.resolve(__dirname, 'tests', testName) : path.resolve(__dirname, 'tests');
-}
-
-var configurationJSON = require('./config.json');
-function config(key) {
-    return configurationJSON[key];
-}
-
-function TaskQueue() {
-    var queue = [];
-    var numberOfRemainingTasks = 0;
-    var emptyQueueCallback;
-
-    function startTasksInQueue() {
-        if (!queue.length)
-            return emptyQueueCallback();
-
-        var swappedQueue = queue;
-        queue = [];
-
-        // Increase the counter before the loop in the case taskCallback is called synchronously.
-        numberOfRemainingTasks += swappedQueue.length;
-        for (var i = 0; i &lt; swappedQueue.length; ++i)
-            swappedQueue[i](null, taskCallback);
-    }
-
-    function taskCallback(error) {
-        // FIXME: Handle error.
-        console.assert(numberOfRemainingTasks &gt; 0);
-        numberOfRemainingTasks--;
-        if (!numberOfRemainingTasks)
-            setTimeout(startTasksInQueue, 0);
-    }
-
-    this.addTask = function (task) { queue.push(task); }
-    this.start = function (callback) {
-        emptyQueueCallback = callback;
-        startTasksInQueue();
-    }
-}
-
-function SerializedTaskQueue() {
-    var queue = [];
-
-    function executeNextTask(error) {
-        // FIXME: Handle error.
-        var callback = queue.pop();
-        setTimeout(function () { callback(null, executeNextTask); }, 0);
-    }
-
-    this.addTask = function (task) { queue.push(task); }
-    this.start = function (callback) {
-        queue.push(callback);
-        queue.reverse();
-        executeNextTask();
-    }
-}
-
-function main(argv) {
-    var client = connect(true);
-    var filter = argv[2];
-    confirmUserWantsDatabaseToBeInitializedIfNeeded(client, function (error, shouldContinue) {
-        if (error)
-            console.error(error);
-
-        if (error || !shouldContinue) {
-            client.end();
-            process.exit(1);
-            return;
-        }
-
-        initializeDatabase(client, function (error) {
-            if (error) {
-                console.error('Failed to initialize the database', error);
-                client.end();
-                process.exit(1);
-            }
-
-            var testCaseQueue = new SerializedTaskQueue();
-            var testFileQueue = new SerializedTaskQueue();
-            fs.readdirSync(pathToTests()).map(function (testFile) {
-                if (!testFile.match(/.js$/) || (filter &amp;&amp; testFile.indexOf(filter) != 0))
-                    return;
-                testFileQueue.addTask(function (error, callback) {
-                    var testContent = fs.readFileSync(pathToTests(testFile), 'utf-8');
-                    var environment = new TestEnvironment(testCaseQueue);
-                    vm.runInNewContext(testContent, environment, pathToTests(testFile));
-                    callback();
-                });
-            });
-            testFileQueue.start(function () {
-                client.end();
-                testCaseQueue.start(function () {
-                    console.log('DONE');
-                });
-            });
-        });
-    });
-}
-
-function confirmUserWantsDatabaseToBeInitializedIfNeeded(client, callback) {
-    function fetchTableNames(error, callback) {
-        if (error)
-            return callback(error);
-
-        client.query('SELECT table_name FROM information_schema.tables WHERE table_type = \'BASE TABLE\' and table_schema = \'public\'', function (error, result) {
-            if (error)
-                return callback(error);
-            callback(null, result.rows.map(function (row) { return row['table_name']; }));            
-        });
-    }
-
-    function findNonEmptyTable(error, list, callback) {
-        if (error || !list.length)
-            return callback(error);
-
-        var tableName = list.shift();
-        client.query('SELECT COUNT(*) FROM ' + tableName + ' LIMIT 1', function (error, result) {
-            if (error)
-                return callback(error);
-
-            if (result.rows[0]['count'])
-                return callback(null, tableName);
-
-            findNonEmptyTable(null, list, callback);
-        });
-    }
-
-    fetchTableNames(null, function (error, tableNames) {
-        if (error)
-            return callback(error, false);
-
-        findNonEmptyTable(null, tableNames, function (error, nonEmptyTable) {
-            if (error)
-                return callback(error, false);
-
-            if (!nonEmptyTable)
-                return callback(null, true);
-
-            console.warn('Table ' + nonEmptyTable + ' is not empty but running tests will drop all tables.');
-            askYesOrNoQuestion(null, 'Do you really want to continue?', callback);
-        });
-    });
-}
-
-function askYesOrNoQuestion(error, question, callback) {
-    if (error)
-        return callback(error);
-
-    process.stdout.write(question + ' (y/n):');
-    process.stdin.resume();
-    process.stdin.setEncoding('utf-8');
-    process.stdin.on('data', function (line) {
-        line = line.trim();
-        if (line === 'y') {
-            process.stdin.pause();
-            callback(null, true);
-        } else if (line === 'n') {
-            process.stdin.pause();
-            callback(null, false);
-        } else
-            console.warn('Invalid input:', line);
-    });
-}
-
-function initializeDatabase(client, callback) {
-    var commaSeparatedSqlStatements = fs.readFileSync(pathToDatabseSQL(), &quot;utf8&quot;);
-
-    var firstError;
-    var queue = new TaskQueue();
-    commaSeparatedSqlStatements.split(/;\s*(?=CREATE|DROP)/).forEach(function (statement) {
-        queue.addTask(function (error, callback) {
-            client.query(statement, function (error) {
-                if (error &amp;&amp; !firstError)
-                    firstError = error;
-                callback();
-            });
-        })
-    });
-
-    queue.start(function () { callback(firstError); });
-}
-
-var currentTestContext;
-function TestEnvironment(testCaseQueue) {
-    var currentTestGroup;
-
-    this.assert = assert;
-    this.console = console;
-
-    // describe(&quot;~&quot;, function () {
-    //     it(&quot;~&quot;, function () { assert(true); });
-    // });
-    this.describe = function (testGroup, callback) {
-        currentTestGroup = testGroup;
-        callback();
-    }
-
-    this.it = function (testCaseDescription, testCase) {
-        testCaseQueue.addTask(function (error, callback) {
-            currentTestContext = new TestContext(currentTestGroup, testCaseDescription, function () {
-                currentTestContext = null;
-                initializeDatabase(connect(), callback);
-            });
-            testCase();
-        });
-    }
-
-    this.postJSON = function (path, content, callback) {
-        sendHttpRequest(path, 'POST', 'application/json', JSON.stringify(content), function (error, response) {
-            assert.ifError(error);
-            callback(response);
-        });
-    }
-
-    this.httpGet = function (path, callback) {
-        sendHttpRequest(path, 'GET', null, '', function (error, response) {
-            assert.ifError(error);
-            callback(response);
-        });
-    }
-
-    this.httpPost= function (path, content, callback) {
-        var contentType = null;
-        if (typeof(content) != &quot;string&quot;) {
-            contentType = 'application/x-www-form-urlencoded';
-            var components = [];
-            for (var key in content)
-                components.push(key + '=' + escape(content[key]));
-            content = components.join('&amp;');
-        }
-        sendHttpRequest(path, 'POST', contentType, content, function (error, response) {
-            assert.ifError(error);
-            callback(response);
-        });
-    }
-
-    this.queryAndFetchAll = function (query, parameters, callback) {
-        var client = connect();
-        client.query(query, parameters, function (error, result) {
-            setTimeout(function () {
-                assert.ifError(error);
-                callback(result.rows);
-            }, 0);
-        });
-    }
-
-    this.sha256 = function (data) {
-        var hash = crypto.createHash('sha256');
-        hash.update(data);
-        return hash.digest('hex');
-    }
-
-    this.config = config;
-
-    this.notifyDone = function () { currentTestContext.done(); }
-}
-
-process.on('uncaughtException', function (error) {
-    if (!currentTestContext)
-        throw error;
-    currentTestContext.logError('Uncaught exception', error);
-    currentTestContext.done();
-});
-
-function sendHttpRequest(path, method, contentType, content, callback) {
-    var options = config('testServer');
-    options.path = path;
-    options.method = method;
-
-    var request = http.request(options, function (response) {
-        var responseText = '';
-        response.setEncoding('utf8');
-        response.on('data', function (chunk) { responseText += chunk; });
-        response.on('end', function () {
-            setTimeout(function () {
-                callback(null, {statusCode: response.statusCode, responseText: responseText});
-            }, 0);
-        });
-    });
-    request.on('error', callback);
-    if (contentType)
-        request.setHeader('Content-Type', contentType);
-    if (content)
-        request.write(content);
-    request.end();
-}
-
-function TestContext(testGroup, testCase, callback) {
-    var failed = false;
-
-    this.description = function () {
-        return testGroup + ' ' + testCase;
-    }
-    this.done = function () {
-        if (!failed)
-            console.log('PASS');
-        callback();
-    }
-    this.logError = function (error, details) {
-        failed = true;
-        console.error(error, details);
-    }
-
-    process.stdout.write(this.description() + ': ');
-}
-
-main(process.argv);
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsadminplatformstestsjsfromrev199122trunkWebsitesperfwebkitorgtestsadminplatformsjs"></a>
<div class="copfile"><h4>Copied: trunk/Websites/perf.webkit.org/server-tests/admin-platforms-tests.js (from rev 199122, trunk/Websites/perf.webkit.org/tests/admin-platforms.js) (0 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/admin-platforms-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/server-tests/admin-platforms-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -0,0 +1,145 @@
</span><ins>+'use strict';
+
+const assert = require('assert');
+
+const TestServer = require('./resources/test-server.js');
+const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
+const submitReport = require('./resources/common-operations.js').submitReport;
+
+describe(&quot;/admin/platforms&quot;, function () {
+    this.timeout(1000);
+    TestServer.inject();
+    connectToDatabaseInEveryTest();
+
+    function reportsForDifferentPlatforms()
+    {
+        return [
+            {
+                &quot;buildNumber&quot;: &quot;3001&quot;,
+                &quot;buildTime&quot;: &quot;2013-02-28T09:01:47&quot;,
+                &quot;builderName&quot;: &quot;someBuilder&quot;,
+                &quot;builderPassword&quot;: &quot;somePassword&quot;,
+                &quot;platform&quot;: &quot;Mavericks&quot;,
+                &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[1, 1, 1], [1, 1, 1]] } } } },
+            },
+            {
+                &quot;buildNumber&quot;: &quot;3001&quot;,
+                &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
+                &quot;builderName&quot;: &quot;someBuilder&quot;,
+                &quot;builderPassword&quot;: &quot;somePassword&quot;,
+                &quot;platform&quot;: &quot;Mountain Lion&quot;,
+                &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[2, 2, 2], [2, 2, 2]] }, &quot;Combined&quot;: { &quot;current&quot;: [[3, 3, 3], [3, 3, 3]] }} } },
+            },
+            {
+                &quot;buildNumber&quot;: &quot;3003&quot;,
+                &quot;buildTime&quot;: &quot;2013-02-28T12:56:26&quot;,
+                &quot;builderName&quot;: &quot;someBuilder&quot;,
+                &quot;builderPassword&quot;: &quot;somePassword&quot;,
+                &quot;platform&quot;: &quot;Trunk Mountain Lion&quot;,
+                &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[4, 4, 4], [4, 4, 4]] } } } }
+            }];
+    } 
+
+    it(&quot;should delete the platform that got merged into another one&quot;, function (done) {
+        const db = TestServer.database();
+        let oldPlatforms;        
+        submitReport(reportsForDifferentPlatforms()).then(function () {
+            return db.selectAll('platforms', 'name');
+        }).then(function (platforms) {
+            oldPlatforms = platforms;
+            assert.equal(oldPlatforms.length, 3);
+            assert.equal(oldPlatforms[0]['name'], 'Mavericks');
+            assert.equal(oldPlatforms[1]['name'], 'Mountain Lion');
+            assert.equal(oldPlatforms[2]['name'], 'Trunk Mountain Lion');
+        }).then(function () {
+            return TestServer.remoteAPI().postFormUrlencodedData('/admin/platforms.php',
+                {'action': 'merge', 'id': oldPlatforms[1]['id'], 'destination': oldPlatforms[2]['id']});
+        }).then(function () {
+            return db.selectAll('platforms');
+        }).then(function (newPlatforms) {
+            assert.equal(newPlatforms.length, 2);
+            assert.deepEqual(newPlatforms[0], oldPlatforms[0]);
+            assert.deepEqual(newPlatforms[1], oldPlatforms[2]);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should move test runs from the merged platform to the destination platform&quot;, function (done) {
+        let oldTestRuns;
+        const queryForRuns = 'SELECT * FROM test_runs, test_configurations, platforms WHERE run_config = config_id AND config_platform = platform_id ORDER by run_mean_cache';
+        const db = TestServer.database();
+        submitReport(reportsForDifferentPlatforms()).then(function () {
+            return db.query(queryForRuns);
+        }).then(function (result) {
+            oldTestRuns = result.rows;
+            assert.equal(oldTestRuns.length, 4);
+            assert.equal(oldTestRuns[0]['platform_name'], 'Mavericks');
+            assert.equal(oldTestRuns[0]['run_sum_cache'], 6);
+            assert.equal(oldTestRuns[1]['platform_name'], 'Mountain Lion');
+            assert.equal(oldTestRuns[1]['run_sum_cache'], 12);
+            assert.equal(oldTestRuns[2]['platform_name'], 'Mountain Lion');
+            assert.equal(oldTestRuns[2]['run_sum_cache'], 18);
+            assert.equal(oldTestRuns[3]['platform_name'], 'Trunk Mountain Lion');
+            assert.equal(oldTestRuns[3]['run_sum_cache'], 24);
+        }).then(function () {
+            return TestServer.remoteAPI().postFormUrlencodedData('/admin/platforms.php',
+                {'action': 'merge', 'id': oldTestRuns[1]['platform_id'], 'destination': oldTestRuns[3]['platform_id']});
+        }).then(function () {
+            return db.query(queryForRuns);
+        }).then(function (result) {
+            const newTestRuns = result.rows;
+            assert.equal(newTestRuns.length, 4);
+            assert.equal(newTestRuns[0]['run_id'], oldTestRuns[0]['run_id']);
+            assert.equal(newTestRuns[0]['platform_name'], 'Mavericks');
+            assert.equal(newTestRuns[0]['run_sum_cache'], 6);
+            assert.equal(newTestRuns[1]['run_id'], oldTestRuns[1]['run_id']);
+            assert.equal(newTestRuns[1]['platform_name'], 'Trunk Mountain Lion');
+            assert.equal(newTestRuns[1]['run_sum_cache'], 12);
+            assert.equal(newTestRuns[2]['run_id'], oldTestRuns[2]['run_id']);
+            assert.equal(newTestRuns[2]['platform_name'], 'Trunk Mountain Lion');
+            assert.equal(newTestRuns[2]['run_sum_cache'], 18);
+            assert.equal(newTestRuns[3]['run_id'], oldTestRuns[3]['run_id']);
+            assert.equal(newTestRuns[3]['platform_name'], 'Trunk Mountain Lion');
+            assert.equal(newTestRuns[3]['run_sum_cache'], 24);
+            assert.equal(newTestRuns[1]['run_config'], newTestRuns[3]['run_config']);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should move test configurations from the merged platform to the destination platform&quot;, function (done) {
+        let oldConfigs;
+        const reports = reportsForDifferentPlatforms();
+        reports[0]['tests'] = {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;baseline&quot;: [[1, 1, 1], [1, 1, 1]] } } } };
+        const queryForConfig = 'SELECT * from test_configurations, platforms, test_metrics'
+            + ' where config_platform = platform_id and config_metric = metric_id and platform_name in ($1, $2) order by config_id';
+        const db = TestServer.database();
+        submitReport(reports).then(function () {
+            return db.query(queryForConfig, [reports[0]['platform'], reports[2]['platform']]);
+        }).then(function (result) {
+            oldConfigs = result.rows;
+            assert.equal(oldConfigs.length, 2);
+            assert.equal(oldConfigs[0]['platform_name'], reports[0]['platform']);
+            assert.equal(oldConfigs[0]['metric_name'], 'FrameRate');
+            assert.equal(oldConfigs[0]['config_type'], 'baseline');
+            assert.equal(oldConfigs[1]['platform_name'], reports[2]['platform']);
+            assert.equal(oldConfigs[1]['metric_name'], 'FrameRate');
+            assert.equal(oldConfigs[1]['config_type'], 'current');
+        }).then(function () {
+            return TestServer.remoteAPI().postFormUrlencodedData('/admin/platforms.php',
+                {'action': 'merge', 'id': oldConfigs[0]['platform_id'], 'destination': oldConfigs[1]['platform_id']});
+        }).then(function () {
+            return db.query(queryForConfig, [reports[0]['platform'], reports[2]['platform']]);
+        }).then(function (result) {
+            const newConfigs = result.rows;
+            assert.equal(newConfigs.length, 2);
+            assert.equal(newConfigs[0]['platform_name'], reports[2]['platform']);
+            assert.equal(newConfigs[0]['metric_name'], 'FrameRate');
+            assert.equal(newConfigs[0]['config_type'], 'baseline');
+            assert.equal(newConfigs[1]['platform_name'], reports[2]['platform']);
+            assert.equal(newConfigs[1]['metric_name'], 'FrameRate');
+            assert.equal(newConfigs[1]['config_type'], 'current');
+            done();
+        }).catch(done);
+    });
+
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsadminreprocessreporttestsjsfromrev199122trunkWebsitesperfwebkitorgtestsadminreprocessreportjs"></a>
<div class="copfile"><h4>Copied: trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js (from rev 199122, trunk/Websites/perf.webkit.org/tests/admin-reprocess-report.js) (0 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -0,0 +1,86 @@
</span><ins>+'use strict';
+
+const assert = require('assert');
+
+const TestServer = require('./resources/test-server.js');
+const addBuilderForReport = require('./resources/common-operations.js').addBuilderForReport;
+const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
+
+describe(&quot;/admin/reprocess-report&quot;, function () {
+    this.timeout(1000);
+    TestServer.inject();
+    connectToDatabaseInEveryTest();
+
+    const simpleReport = [{
+        &quot;buildNumber&quot;: &quot;1986&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+                &quot;test&quot;: {
+                    &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[1, 2, 3], [4, 5, 6]] }}
+                },
+            },
+        }];
+
+    it(&quot;should add build&quot;, function (done) {
+        let db = TestServer.database();
+        let reportId;
+        addBuilderForReport(simpleReport[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', simpleReport);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return Promise.all([db.selectAll('builds'), db.selectAll('reports')]);
+        }).then(function (result) {
+            const builds = result[0];
+            const reports = result[1];
+            assert.equal(builds.length, 1);
+            assert.equal(builds[0]['number'], 1986);
+            assert.equal(reports.length, 1);
+            reportId = reports[0]['id'];
+            assert.equal(reports[0]['build_number'], 1986);
+            return db.query('UPDATE reports SET report_build = NULL; DELETE FROM builds');
+        }).then(function () {
+            return db.selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 0);
+            return TestServer.remoteAPI().getJSONWithStatus(`/admin/reprocess-report?report=${reportId}`);
+        }).then(function () {
+            return db.selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 1);
+            assert.equal(builds[0]['number'], 1986);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should not duplicate the reprocessed report&quot;, function (done) {
+        let db = TestServer.database();
+        let originalReprot;
+        addBuilderForReport(simpleReport[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', simpleReport);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return db.selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            originalReprot = reports[0];
+            return db.query('UPDATE reports SET report_build = NULL; DELETE FROM builds');
+        }).then(function () {
+            return TestServer.remoteAPI().getJSONWithStatus(`/admin/reprocess-report?report=${originalReprot['id']}`);
+        }).then(function () {
+            return db.selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            const newPort = reports[0];
+            originalReprot['committed_at'] = null;
+            newPort['committed_at'] = null;
+            assert.notEqual(originalReprot['build'], newPort['build']);
+            originalReprot['build'] = null;
+            newPort['build'] = null;
+            assert.deepEqual(originalReprot, newPort);
+            done();
+        }).catch(done);
+    });
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapibuildrequeststestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -6,7 +6,7 @@
</span><span class="cx"> let TestServer = require('./resources/test-server.js');
</span><span class="cx"> 
</span><span class="cx"> describe('/api/build-requests', function () {
</span><del>-    this.timeout(10000);
</del><ins>+    this.timeout(1000);
</ins><span class="cx">     TestServer.inject();
</span><span class="cx"> 
</span><span class="cx">     beforeEach(function () {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapimanifestjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -7,16 +7,11 @@
</span><span class="cx"> let TestServer = require('./resources/test-server.js');
</span><span class="cx"> 
</span><span class="cx"> describe('/api/manifest', function () {
</span><del>-    this.timeout(10000);
</del><ins>+    this.timeout(1000);
</ins><span class="cx">     TestServer.inject();
</span><span class="cx"> 
</span><span class="cx">     beforeEach(function () {
</span><del>-        Builder.clearStaticMap();
-        BugTracker.clearStaticMap();
-        Test.clearStaticMap();
-        Metric.clearStaticMap();
-        Platform.clearStaticMap();
-        Repository.clearStaticMap();
</del><ins>+        MockData.resetV3Models();
</ins><span class="cx">     });
</span><span class="cx"> 
</span><span class="cx">     it(&quot;should generate an empty manifest when database is empty&quot;, function (done) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapimeasurementsettestsjsfromrev199122trunkWebsitesperfwebkitorgtestsapimeasurementsetjs"></a>
<div class="copfile"><h4>Copied: trunk/Websites/perf.webkit.org/server-tests/api-measurement-set-tests.js (from rev 199122, trunk/Websites/perf.webkit.org/tests/api-measurement-set.js) (0 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-measurement-set-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/server-tests/api-measurement-set-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -0,0 +1,413 @@
</span><ins>+'use strict';
+
+const assert = require('assert');
+const crypto = require('crypto');
+
+const TestServer = require('./resources/test-server.js');
+const addBuilderForReport = require('./resources/common-operations.js').addBuilderForReport;
+const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
+
+describe(&quot;/api/measurement-set&quot;, function () {
+    this.timeout(1000);
+    TestServer.inject();
+    connectToDatabaseInEveryTest();
+
+    function queryPlatformAndMetric(platformName, metricName)
+    {
+        const db = TestServer.database();
+        return Promise.all([
+            db.selectFirstRow('platforms', {name: 'Mountain Lion'}),
+            db.selectFirstRow('test_metrics', {name: 'Time'}),
+        ]).then(function (result) {
+            return {platformId: result[0]['id'], metricId: result[1]['id']};
+        });
+    }
+
+    function format(formatMap, row)
+    {
+        var result = {};
+        for (var i = 0; i &lt; formatMap.length; i++) {
+            var key = formatMap[i];
+            if (key == 'id' || key == 'build' || key == 'builder')
+                continue;
+            result[key] = row[i];
+        }
+        return result;
+    }
+
+    let clusterStart = TestServer.testConfig().clusterStart;
+    clusterStart = +Date.UTC(clusterStart[0], clusterStart[1] - 1, clusterStart[2], clusterStart[3], clusterStart[4]);
+
+    let clusterSize = TestServer.testConfig().clusterSize;
+    const DAY = 24 * 3600 * 1000;
+    const YEAR = 365.24 * DAY;
+    const MONTH = 30 * DAY;
+    clusterSize = clusterSize[0] * YEAR + clusterSize[1] * MONTH + clusterSize[2] * DAY;
+
+    function clusterTime(index) { return new Date(clusterStart + clusterSize * index); }
+
+    const reportWithBuildTime = [{
+        &quot;buildNumber&quot;: &quot;123&quot;,
+        &quot;buildTime&quot;: clusterTime(7.8).toISOString(),
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;Suite&quot;: {
+                &quot;tests&quot;: {
+                    &quot;test1&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [1, 2, 3, 4, 5] }}
+                    },
+                }
+            },
+        }}];
+    reportWithBuildTime.startTime = +clusterTime(7);
+
+    const reportWithRevision = [{
+        &quot;buildNumber&quot;: &quot;124&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T15:34:51&quot;,
+        &quot;revisions&quot;: {
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;144000&quot;,
+                &quot;timestamp&quot;: clusterTime(10.3).toISOString(),
+            },
+        },
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;Suite&quot;: {
+                &quot;tests&quot;: {
+                    &quot;test1&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [11, 12, 13, 14, 15] }}
+                    }
+                }
+            },
+        }}];
+
+    const reportWithNewRevision = [{
+        &quot;buildNumber&quot;: &quot;125&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T21:45:17&quot;,
+        &quot;revisions&quot;: {
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;160609&quot;,
+                &quot;timestamp&quot;: clusterTime(12.1).toISOString()
+            },
+        },
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;Suite&quot;: {
+                &quot;tests&quot;: {
+                    &quot;test1&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [16, 17, 18, 19, 20] }}
+                    }
+                }
+            },
+        }}];
+
+    const reportWithAncentRevision = [{
+        &quot;buildNumber&quot;: &quot;126&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T23:07:25&quot;,
+        &quot;revisions&quot;: {
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;137793&quot;,
+                &quot;timestamp&quot;: clusterTime(1.8).toISOString()
+            },
+        },
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;Suite&quot;: {
+                &quot;tests&quot;: {
+                    &quot;test1&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [21, 22, 23, 24, 25] }}
+                    }
+                }
+            },
+        }}];
+
+    it(&quot;should reject when platform ID is missing&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return TestServer.remoteAPI().getJSON(`/api/measurement-set/?metric=${result.metricId}`);
+        }).then(function (response) {
+            assert.equal(response['status'], 'AmbiguousRequest');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject when metric ID is missing&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}`);
+        }).then(function (response) {
+            assert.equal(response['status'], 'AmbiguousRequest');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject an invalid platform name&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}a&amp;metric=${result.metricId}`);
+        }).then(function (response) {
+            assert.equal(response['status'], 'InvalidPlatform');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject an invalid metric name&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}b`);
+        }).then(function (response) {
+            assert.equal(response['status'], 'InvalidMetric');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should be able to retrieve a reported value&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return TestServer.remoteAPI().getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`);
+        }).then(function (response) {
+            const buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
+
+            assert.deepEqual(Object.keys(response).sort(),
+                ['clusterCount', 'clusterSize', 'clusterStart',
+                  'configurations', 'elapsedTime', 'endTime', 'formatMap', 'lastModified', 'startTime', 'status']);
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['clusterCount'], 1);
+            assert.deepEqual(response['formatMap'], [
+                'id', 'mean', 'iterationCount', 'sum', 'squareSum', 'markedOutlier',
+                'revisions', 'commitTime', 'build', 'buildTime', 'buildNumber', 'builder']);
+
+            assert.equal(response['startTime'], reportWithBuildTime.startTime);
+            assert(typeof(response['lastModified']) == 'number', 'lastModified time should be a numeric');
+
+            assert.deepEqual(Object.keys(response['configurations']), ['current']);
+
+            var currentRows = response['configurations']['current'];
+            assert.equal(currentRows.length, 1);
+            assert.equal(currentRows[0].length, response['formatMap'].length);
+            assert.deepEqual(format(response['formatMap'], currentRows[0]), {
+                mean: 3,
+                iterationCount: 5,
+                sum: 15,
+                squareSum: 55,
+                markedOutlier: false,
+                revisions: [],
+                commitTime: buildTime,
+                buildTime: buildTime,
+                buildNumber: '123'});
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should return return the right IDs for measurement, build, and builder&quot;, function (done) {
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            const db = TestServer.database();
+            return Promise.all([
+                db.selectAll('test_runs'),
+                db.selectAll('builds'),
+                db.selectAll('builders'),
+                TestServer.remoteAPI().getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`),
+            ]);
+        }).then(function (result) {
+            const runs = result[0];
+            const builds = result[1];
+            const builders = result[2];
+            const response = result[3];
+
+            assert.equal(runs.length, 1);
+            assert.equal(builds.length, 1);
+            assert.equal(builders.length, 1);
+            const measurementId = runs[0]['id'];
+            const buildId = builds[0]['id'];
+            const builderId = builders[0]['id'];
+
+            assert.equal(response['configurations']['current'].length, 1);
+            const measurement = response['configurations']['current'][0];
+            assert.equal(response['status'], 'OK');
+
+            assert.equal(measurement[response['formatMap'].indexOf('id')], measurementId);
+            assert.equal(measurement[response['formatMap'].indexOf('build')], buildId);
+            assert.equal(measurement[response['formatMap'].indexOf('builder')], builderId);
+
+            done();
+        }).catch(done);
+    });
+
+    function postReports(reports, callback)
+    {
+        if (!reports.length)
+            return callback();
+
+        postJSON('/api/report/', reports[0], function (response) {
+            assert.equal(response.statusCode, 200);
+            assert.equal(JSON.parse(response.responseText)['status'], 'OK');
+
+            postReports(reports.slice(1), callback);
+        });
+    }
+
+    function queryPlatformAndMetricWithRepository(platformName, metricName, repositoryName)
+    {
+        const db = TestServer.database();
+        return Promise.all([
+            db.selectFirstRow('platforms', {name: platformName}),
+            db.selectFirstRow('test_metrics', {name: metricName}),
+            db.selectFirstRow('repositories', {name: repositoryName}),
+        ]).then(function (result) {
+            return {platformId: result[0]['id'], metricId: result[1]['id'], repositoryId: result[2]['id']};
+        });
+    }
+
+    it(&quot;should order results by commit time&quot;, function (done) {
+        const remote = TestServer.remoteAPI();
+        let repositoryId;
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return remote.postJSON('/api/report/', reportWithBuildTime);
+        }).then(function () {
+            return remote.postJSON('/api/report/', reportWithRevision);
+        }).then(function () {
+            return queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit');
+        }).then(function (result) {
+            repositoryId = result.repositoryId;
+            return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`);
+        }).then(function (response) {
+            const currentRows = response['configurations']['current'];
+            const buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
+            const revisionTime = +(new Date(reportWithRevision[0]['revisions']['WebKit']['timestamp']));
+            const revisionBuildTime = +(new Date(reportWithRevision[0]['buildTime']));
+
+            assert.equal(currentRows.length, 2);
+            assert.deepEqual(format(response['formatMap'], currentRows[0]), {
+               mean: 13,
+               iterationCount: 5,
+               sum: 65,
+               squareSum: 855,
+               markedOutlier: false,
+               revisions: [[1, repositoryId, '144000', revisionTime]],
+               commitTime: revisionTime,
+               buildTime: revisionBuildTime,
+               buildNumber: '124' });
+            assert.deepEqual(format(response['formatMap'], currentRows[1]), {
+                mean: 3,
+                iterationCount: 5,
+                sum: 15,
+                squareSum: 55,
+                markedOutlier: false,
+                revisions: [],
+                commitTime: buildTime,
+                buildTime: buildTime,
+                buildNumber: '123' });
+            done();
+        }).catch(done);
+    });
+
+    function buildNumbers(parsedResult, config)
+    {
+        return parsedResult['configurations'][config].map(function (row) {
+            return format(parsedResult['formatMap'], row)['buildNumber'];
+        });
+    }
+
+    it(&quot;should include one data point after the current time range&quot;, function (done) {
+        const remote = TestServer.remoteAPI();
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return remote.postJSON('/api/report/', reportWithAncentRevision);
+        }).then(function () {
+            return remote.postJSON('/api/report/', reportWithNewRevision);
+        }).then(function () {
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['clusterCount'], 2, 'should have two clusters');
+            assert.deepEqual(buildNumbers(response, 'current'),
+                [reportWithAncentRevision[0]['buildNumber'], reportWithNewRevision[0]['buildNumber']]);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should always include one old data point before the current time range&quot;, function (done) {
+        const remote = TestServer.remoteAPI();
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return remote.postJSON('/api/report/', reportWithBuildTime);
+        }).then(function () {
+            return remote.postJSON('/api/report/', reportWithAncentRevision);
+        }).then(function () {
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`);
+        }).then(function (response) {
+            assert.equal(response['clusterCount'], 2, 'should have two clusters');
+            let currentRows = response['configurations']['current'];
+            assert.equal(currentRows.length, 2, 'should contain two data points');
+            assert.deepEqual(buildNumbers(response, 'current'), [reportWithAncentRevision[0]['buildNumber'], reportWithBuildTime[0]['buildNumber']]);
+            done();
+        }).catch(done);
+    });
+
+
+    it(&quot;should create cache results&quot;, function (done) {
+        const remote = TestServer.remoteAPI();
+        let cachePrefix;
+        addBuilderForReport(reportWithBuildTime[0]).then(function () {
+            return remote.postJSON('/api/report/', reportWithAncentRevision);
+        }).then(function () {
+            return remote.postJSON('/api/report/', reportWithRevision);
+        }).then(function () {
+            return remote.postJSON('/api/report/', reportWithNewRevision);
+        }).then(function () {
+            return queryPlatformAndMetric('Mountain Lion', 'Time');
+        }).then(function (result) {
+            cachePrefix = '/data/measurement-set-' + result.platformId + '-' + result.metricId;
+            return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&amp;metric=${result.metricId}`);
+        }).then(function (newResult) {
+            return remote.getJSONWithStatus(`${cachePrefix}.json`).then(function (cachedResult) {
+                assert.deepEqual(newResult, cachedResult);
+                return remote.getJSONWithStatus(`${cachePrefix}-${cachedResult['startTime']}.json`);
+            }).then(function (oldResult) {
+                var oldBuildNumbers = buildNumbers(oldResult, 'current');
+                var newBuildNumbers = buildNumbers(newResult, 'current');
+                assert(oldBuildNumbers.length &gt;= 2, 'The old cluster should contain at least two data points');
+                assert(newBuildNumbers.length &gt;= 2, 'The new cluster should contain at least two data points');
+                assert.deepEqual(oldBuildNumbers.slice(oldBuildNumbers.length - 2), newBuildNumbers.slice(0, 2),
+                    'Two conseqcutive clusters should share two data points');
+                done();
+            });
+        }).catch(done);
+    });
+
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapireportcommitstestsjsfromrev199122trunkWebsitesperfwebkitorgtestsapireportcommitsjs"></a>
<div class="copfile"><h4>Copied: trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js (from rev 199122, trunk/Websites/perf.webkit.org/tests/api-report-commits.js) (0 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -0,0 +1,261 @@
</span><ins>+'use strict';
+
+const assert = require('assert');
+const crypto = require('crypto');
+
+const TestServer = require('./resources/test-server.js');
+const addSlaveForReport = require('./resources/common-operations.js').addSlaveForReport;
+const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
+
+describe(&quot;/api/report-commits/&quot;, function () {
+    this.timeout(1000);
+    TestServer.inject();
+    connectToDatabaseInEveryTest();
+
+    const emptyReport = {
+        &quot;slaveName&quot;: &quot;someSlave&quot;,
+        &quot;slavePassword&quot;: &quot;somePassword&quot;,
+    };
+    const subversionCommit = {
+        &quot;slaveName&quot;: &quot;someSlave&quot;,
+        &quot;slavePassword&quot;: &quot;somePassword&quot;,
+        &quot;commits&quot;: [
+            {
+                &quot;repository&quot;: &quot;WebKit&quot;,
+                &quot;revision&quot;: &quot;141977&quot;,
+                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
+                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
+                &quot;message&quot;: &quot;some message&quot;,
+            }
+        ],
+    };
+    const subversionInvalidCommit = {
+        &quot;slaveName&quot;: &quot;someSlave&quot;,
+        &quot;slavePassword&quot;: &quot;somePassword&quot;,
+        &quot;commits&quot;: [
+            {
+                &quot;repository&quot;: &quot;WebKit&quot;,
+                &quot;revision&quot;: &quot;_141977&quot;,
+                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
+                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
+                &quot;message&quot;: &quot;some message&quot;,
+            }
+        ],
+    };
+    const subversionTwoCommits = {
+        &quot;slaveName&quot;: &quot;someSlave&quot;,
+        &quot;slavePassword&quot;: &quot;somePassword&quot;,
+        &quot;commits&quot;: [
+            {
+                &quot;repository&quot;: &quot;WebKit&quot;,
+                &quot;revision&quot;: &quot;141977&quot;,
+                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
+                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
+                &quot;message&quot;: &quot;some message&quot;,
+            },
+            {
+                &quot;repository&quot;: &quot;WebKit&quot;,
+                &quot;parent&quot;: &quot;141977&quot;,
+                &quot;revision&quot;: &quot;141978&quot;,
+                &quot;time&quot;: &quot;2013-02-06T09:54:56.0Z&quot;,
+                &quot;author&quot;: {&quot;name&quot;: &quot;Mikhail Pozdnyakov&quot;, &quot;account&quot;: &quot;mikhail.pozdnyakov@intel.com&quot;},
+                &quot;message&quot;: &quot;another message&quot;,
+            }
+        ]
+    }
+
+    it(&quot;should reject error when slave name is missing&quot;, function (done) {
+        TestServer.remoteAPI().postJSON('/api/report-commits/', {}).then(function (response) {
+            assert.equal(response['status'], 'MissingSlaveName');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject when there are no slaves&quot;, function (done) {
+        TestServer.remoteAPI().postJSON('/api/report-commits/', emptyReport).then(function (response) {
+            assert.equal(response['status'], 'SlaveNotFound');
+            return TestServer.database().selectAll('commits');
+        }).then(function (rows) {
+            assert.equal(rows.length, 0);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should accept an empty report&quot;, function (done) {
+        addSlaveForReport(emptyReport).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', emptyReport);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add a missing repository&quot;, function (done) {
+        const db = TestServer.database();
+        addSlaveForReport(subversionCommit).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return db.selectAll('repositories');
+        }).then(function (rows) {
+            assert.equal(rows.length, 1);
+            assert.equal(rows[0]['name'], subversionCommit.commits[0]['repository']);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store a commit from a valid slave&quot;, function (done) {
+        const db = TestServer.database();
+        addSlaveForReport(subversionCommit).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return Promise.all([db.selectAll('commits'), db.selectAll('committers')]);
+        }).then(function (result) {
+            let commits = result[0];
+            let committers = result[1];
+            let reportedData = subversionCommit.commits[0];
+
+            assert.equal(commits.length, 1);
+            assert.equal(committers.length, 1);
+            assert.equal(commits[0]['revision'], reportedData['revision']);
+            assert.equal(commits[0]['time'].toString(), new Date('2013-02-06 08:55:20.9').toString());
+            assert.equal(commits[0]['message'], reportedData['message']);
+            assert.equal(commits[0]['committer'], committers[0]['id']);
+            assert.equal(committers[0]['name'], reportedData['author']['name']);
+            assert.equal(committers[0]['account'], reportedData['author']['account']);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject an invalid revision number&quot;, function (done) {
+        addSlaveForReport(subversionCommit).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionInvalidCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'InvalidRevision');
+            return TestServer.database().selectAll('commits');
+        }).then(function (rows) {
+            assert.equal(rows.length, 0);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store two commits from a valid slave&quot;, function (done) {
+        const db = TestServer.database();
+        addSlaveForReport(subversionTwoCommits).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionTwoCommits);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return Promise.all([db.selectAll('commits'), db.selectAll('committers')]);
+        }).then(function (result) {
+            const commits = result[0];
+            const committers = result[1];
+            assert.equal(commits.length, 2);
+            assert.equal(committers.length, 2);
+
+            let reportedData = subversionTwoCommits.commits[0];
+            assert.equal(commits[0]['revision'], reportedData['revision']);
+            assert.equal(commits[0]['time'].toString(), new Date('2013-02-06 08:55:20.9').toString());
+            assert.equal(commits[0]['message'], reportedData['message']);
+            assert.equal(commits[0]['committer'], committers[0]['id']);
+            assert.equal(committers[0]['name'], reportedData['author']['name']);
+            assert.equal(committers[0]['account'], reportedData['author']['account']);
+
+            reportedData = subversionTwoCommits.commits[1];
+            assert.equal(commits[1]['revision'], reportedData['revision']);
+            assert.equal(commits[1]['time'].toString(), new Date('2013-02-06 09:54:56.0').toString());
+            assert.equal(commits[1]['message'], reportedData['message']);
+            assert.equal(commits[1]['committer'], committers[1]['id']);
+            assert.equal(committers[1]['name'], reportedData['author']['name']);
+            assert.equal(committers[1]['account'], reportedData['author']['account']);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should update an existing commit if there is one&quot;, function (done) {
+        const db = TestServer.database();
+        const reportedData = subversionCommit.commits[0];
+        addSlaveForReport(subversionCommit).then(function () {
+            return Promise.all([
+                db.insert('repositories', {'id': 1, 'name': 'WebKit'}),
+                db.insert('commits', {'repository': 1, 'revision': reportedData['revision'], 'time': reportedData['time']})
+            ]);
+        }).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return Promise.all([db.selectAll('commits'), db.selectAll('committers')]);
+        }).then(function (result) {
+            const commits = result[0];
+            const committers = result[1];
+
+            assert.equal(commits.length, 1);
+            assert.equal(committers.length, 1);
+            assert.equal(commits[0]['message'], reportedData['message']);
+            assert.equal(commits[0]['committer'], committers[0]['id']);
+            assert.equal(committers[0]['name'], reportedData['author']['name']);
+            assert.equal(committers[0]['account'], reportedData['author']['account']);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should not update an unrelated commit&quot;, function (done) {
+        const db = TestServer.database();
+        const firstData = subversionTwoCommits.commits[0];
+        const secondData = subversionTwoCommits.commits[1];
+        addSlaveForReport(subversionCommit).then(function () {
+            return Promise.all([
+                db.insert('repositories', {'id': 1, 'name': 'WebKit'}),
+                db.insert('commits', {'id': 2, 'repository': 1, 'revision': firstData['revision'], 'time': firstData['time']}),
+                db.insert('commits', {'id': 3, 'repository': 1, 'revision': secondData['revision'], 'time': secondData['time']})
+            ]);
+        }).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return Promise.all([db.selectAll('commits'), db.selectAll('committers')]);
+        }).then(function (result) {
+            const commits = result[0];
+            const committers = result[1];
+
+            assert.equal(commits.length, 2);
+            assert.equal(committers.length, 1);
+            assert.equal(commits[0]['id'], 2);
+            assert.equal(commits[0]['message'], firstData['message']);
+            assert.equal(commits[0]['committer'], committers[0]['id']);
+            assert.equal(committers[0]['name'], firstData['author']['name']);
+            assert.equal(committers[0]['account'], firstData['author']['account']);
+            
+            assert.equal(commits[1]['id'], 3);
+            assert.equal(commits[1]['message'], null);
+            assert.equal(commits[1]['committer'], null);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should update an existing committer if there is one&quot;, function (done) {
+        const db = TestServer.database();
+        const author = subversionCommit.commits[0]['author'];
+        addSlaveForReport(subversionCommit).then(function () {
+            return Promise.all([
+                db.insert('repositories', {'id': 1, 'name': 'WebKit'}),
+                db.insert('committers', {'repository': 1, 'account': author['account']}),
+            ]);
+        }).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report-commits/', subversionCommit);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return db.selectAll('committers');
+        }).then(function (committers) {
+            assert.equal(committers.length, 1);
+            assert.equal(committers[0]['name'], author['name']);
+            assert.equal(committers[0]['account'], author['account']);
+            done();
+        }).catch(done);
+    });
+
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapireporttestsjsfromrev199122trunkWebsitesperfwebkitorgtestsapireportjs"></a>
<div class="copfile"><h4>Copied: trunk/Websites/perf.webkit.org/server-tests/api-report-tests.js (from rev 199122, trunk/Websites/perf.webkit.org/tests/api-report.js) (0 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-report-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/server-tests/api-report-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -0,0 +1,773 @@
</span><ins>+'use strict';
+
+const assert = require('assert');
+
+const TestServer = require('./resources/test-server.js');
+const addBuilderForReport = require('./resources/common-operations.js').addBuilderForReport;
+const addSlaveForReport = require('./resources/common-operations.js').addSlaveForReport;
+const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
+
+describe(&quot;/api/report&quot;, function () {
+    this.timeout(1000);
+    TestServer.inject();
+    connectToDatabaseInEveryTest();
+
+    function emptyReport()
+    {
+        return {
+            &quot;buildNumber&quot;: &quot;123&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;slaveName&quot;: &quot;someSlave&quot;,
+            &quot;builderPassword&quot;: &quot;somePassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {},
+            &quot;revisions&quot;: {
+                &quot;OS X&quot;: {
+                    &quot;revision&quot;: &quot;10.8.2 12C60&quot;
+                },
+                &quot;WebKit&quot;: {
+                    &quot;revision&quot;: &quot;141977&quot;,
+                    &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
+                }
+            }
+        };
+    }
+
+    function emptySlaveReport()
+    {
+        return {
+            &quot;buildNumber&quot;: &quot;123&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;slaveName&quot;: &quot;someSlave&quot;,
+            &quot;slavePassword&quot;: &quot;otherPassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {},
+            &quot;revisions&quot;: {
+                &quot;OS X&quot;: {
+                    &quot;revision&quot;: &quot;10.8.2 12C60&quot;
+                },
+                &quot;WebKit&quot;: {
+                    &quot;revision&quot;: &quot;141977&quot;,
+                    &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
+                }
+            }
+        };
+    }
+
+    it(&quot;should reject error when builder name is missing&quot;, function (done) {
+        TestServer.remoteAPI().postJSON('/api/report/', [{&quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;}]).then(function (response) {
+            assert.equal(response['status'], 'MissingBuilderName');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject error when build time is missing&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [{&quot;builderName&quot;: &quot;someBuilder&quot;, &quot;builderPassword&quot;: &quot;somePassword&quot;}]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'MissingBuildTime');
+            done();
+        });
+    });
+
+    it(&quot;should reject when there are no builders&quot;, function (done) {
+        TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]).then(function (response) {
+            assert.equal(response['status'], 'BuilderNotFound');
+            assert.equal(response['failureStored'], false);
+            assert.equal(response['processedRuns'], 0);
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 0);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject a report without a builder password&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            var report = [{
+                &quot;buildNumber&quot;: &quot;123&quot;,
+                &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+                &quot;builderName&quot;: &quot;someBuilder&quot;,
+                &quot;tests&quot;: {},
+                &quot;revisions&quot;: {}}];
+            return TestServer.remoteAPI().postJSON('/api/report/', report);
+        }).then(function (response) {
+            assert.equal(response['status'], 'BuilderNotFound');
+            assert.equal(response['failureStored'], false);
+            assert.equal(response['processedRuns'], 0);
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 0);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store a report from a valid builder&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['failureStored'], false);
+            assert.equal(response['processedRuns'], 1);
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            const submittedContent = emptyReport();
+            const storedContent = JSON.parse(reports[0]['content']);
+
+            delete submittedContent['builderPassword'];
+            delete submittedContent['tests'];
+            delete storedContent['tests'];
+            assert.deepEqual(storedContent, submittedContent);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should treat the slave password as the builder password if there is no matching slave&quot;, function (done) {
+        let report = emptyReport();
+        report['slavePassword'] = report['builderPassword'];
+        delete report['builderPassword'];
+
+        addSlaveForReport(report).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [report]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['failureStored'], false);
+            assert.equal(response['processedRuns'], 1);
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            const storedContent = JSON.parse(reports[0]['content']);
+
+            delete report['slavePassword'];
+            delete report['tests'];
+            delete storedContent['tests'];
+            assert.deepEqual(storedContent, report);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store a report from a valid slave&quot;, function (done) {
+        addSlaveForReport(emptySlaveReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptySlaveReport()]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['failureStored'], false);
+            assert.equal(response['processedRuns'], 1);
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            const submittedContent = emptySlaveReport();
+            const storedContent = JSON.parse(reports[0]['content']);
+
+            delete submittedContent['slavePassword'];
+            delete submittedContent['tests'];
+            delete storedContent['tests'];
+            assert.deepEqual(storedContent, submittedContent);
+
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store the builder name but not the builder password&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function (response) {
+            return TestServer.database().selectAll('reports');
+        }).then(function (reports) {
+            assert.equal(reports.length, 1);
+            const storedContent = JSON.parse(reports[0]['content']);
+            assert.equal(storedContent['builderName'], emptyReport()['builderName']);
+            assert(!('builderPassword' in storedContent));
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add a slave if there isn't one and the report was authenticated by a builder&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function (response) {
+            return TestServer.database().selectAll('build_slaves');
+        }).then(function (slaves) {
+            assert.equal(slaves.length, 1);
+            assert.equal(slaves[0]['name'], emptyReport()['slaveName']);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add a builder if there isn't one and the report was authenticated by a slave&quot;, function (done) {
+        addSlaveForReport(emptySlaveReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptySlaveReport()]);
+        }).then(function (response) {
+            return TestServer.database().selectAll('builders');
+        }).then(function (builders) {
+            assert.equal(builders.length, 1);
+            assert.equal(builders[0]['name'], emptyReport()['builderName']);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add a build&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function () {
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.strictEqual(builds[0]['number'], 123);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add the platform&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function () {
+            return TestServer.database().selectAll('platforms');
+        }).then(function (platforms) {
+            assert.equal(platforms.length, 1);
+            assert.equal(platforms[0]['name'], 'Mountain Lion');
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add repositories and build revisions&quot;, function (done) {
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [emptyReport()]);
+        }).then(function (response) {
+            const db = TestServer.database();
+            return Promise.all([
+                db.selectAll('repositories'),
+                db.selectAll('commits'),
+                db.selectAll('build_commits', 'build_commit'),
+            ]);
+        }).then(function (result) {
+            const repositories = result[0];
+            const commits = result[1];
+            const buildCommitsRelations = result[2];
+            assert.equal(repositories.length, 2);
+            assert.deepEqual(repositories.map(function (row) { return row['name']; }).sort(), ['OS X', 'WebKit']);
+
+            assert.equal(commits.length, 2);
+            assert.equal(buildCommitsRelations.length, 2);
+            assert.equal(buildCommitsRelations[0]['build_commit'], commits[0]['id']);
+            assert.equal(buildCommitsRelations[1]['build_commit'], commits[1]['id']);
+            assert.equal(buildCommitsRelations[0]['commit_build'], buildCommitsRelations[1]['commit_build']);
+
+            let repositoryIdToName = {};
+            for (let repository of repositories)
+                repositoryIdToName[repository['id']] = repository['name'];
+
+            let repositoryNameToRevisionRow = {};
+            for (let commit of commits)
+                repositoryNameToRevisionRow[repositoryIdToName[commit['repository']]] = commit;
+
+            assert.equal(repositoryNameToRevisionRow['OS X']['revision'], '10.8.2 12C60');
+            assert.equal(repositoryNameToRevisionRow['WebKit']['revision'], '141977');
+            assert.equal(repositoryNameToRevisionRow['WebKit']['time'].toString(),
+                new Date('2013-02-06 08:55:20.9').toString());
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should not create a duplicate build for the same build number if build times are close&quot;, function (done) {
+        let firstReport = emptyReport();
+        firstReport['buildTime'] = '2013-02-28T10:12:04';
+        let secondReport = emptyReport();
+        secondReport['buildTime'] = '2013-02-28T10:22:03';
+
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [firstReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 1);
+            return TestServer.remoteAPI().postJSON('/api/report/', [secondReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 1);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should create distinct builds for the same build number if build times are far apart&quot;, function (done) {
+        let firstReport = emptyReport();
+        firstReport['buildTime'] = '2013-02-28T10:12:03';
+        let secondReport = emptyReport();
+        secondReport['buildTime'] = '2014-01-20T22:23:34';
+
+        addBuilderForReport(emptyReport()).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [firstReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 1);
+            return TestServer.remoteAPI().postJSON('/api/report/', [secondReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 2);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should reject a report with mismatching revision info&quot;, function (done) {
+        let firstReport = emptyReport();
+        firstReport['revisions'] = {
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;141977&quot;,
+                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.96Z&quot;
+            }
+        };
+
+        let secondReport = emptyReport();
+        secondReport['revisions'] = {
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;150000&quot;,
+                &quot;timestamp&quot;: &quot;2013-05-13T10:50:29.6Z&quot;
+            }
+        };
+
+        addBuilderForReport(firstReport).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [firstReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('builds');
+        }).then(function (builds) {
+            assert.equal(builds.length, 1);
+            return TestServer.remoteAPI().postJSON('/api/report/', [secondReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'MismatchingCommitRevision');
+            assert(JSON.stringify(response).indexOf('141977') &gt;= 0);
+            assert(JSON.stringify(response).indexOf('150000') &gt;= 0);
+            assert.equal(response['failureStored'], true);
+            assert.equal(response['processedRuns'], 0);
+            done();
+        }).catch(done);
+    });
+
+    const reportWithTwoLevelsOfAggregations = {
+        &quot;buildNumber&quot;: &quot;123&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;DummyPageLoading&quot;: {
+                &quot;metrics&quot;: {&quot;Time&quot;: { &quot;aggregators&quot; : [&quot;Arithmetic&quot;], &quot;current&quot;: [300, 310, 320, 330] }},
+                &quot;tests&quot;: {
+                    &quot;apple.com&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [500, 510, 520, 530] }},
+                        &quot;url&quot;: &quot;http://www.apple.com&quot;
+                    },
+                    &quot;webkit.org&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [100, 110, 120, 130] }},
+                        &quot;url&quot;: &quot;http://www.webkit.org&quot;
+                    }
+                }
+            },
+            &quot;DummyBenchmark&quot;: {
+                &quot;metrics&quot;: {&quot;Time&quot;: [&quot;Arithmetic&quot;]},
+                &quot;tests&quot;: {
+                    &quot;DOM&quot;: {
+                        &quot;metrics&quot;: {&quot;Time&quot;: [&quot;Geometric&quot;, &quot;Arithmetic&quot;]},
+                        &quot;tests&quot;: {
+                            &quot;ModifyNodes&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]] }}},
+                            &quot;TraverseNodes&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[31, 32, 33, 34, 35], [36, 37, 38, 39, 40], [41, 42, 43, 44, 45]] }}}
+                        }
+                    },
+                    &quot;CSS&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[101, 102, 103, 104, 105], [106, 107, 108, 109, 110], [111, 112, 113, 114, 115]] }}}
+                }
+            }
+        },
+        &quot;revisions&quot;: {
+            &quot;OS X&quot;: {
+                &quot;revision&quot;: &quot;10.8.2 12C60&quot;
+            },
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;141977&quot;,
+                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
+            }
+        }};
+
+    function reportAfterAddingBuilderAndAggregators(report)
+    {
+        return addBuilderForReport(report).then(function () {
+            const db = TestServer.database();
+            return Promise.all([
+                db.insert('aggregators', {name: 'Arithmetic'}),
+                db.insert('aggregators', {name: 'Geometric'}),
+            ]);
+        }).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [report]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            assert.equal(response['failureStored'], false);
+            return response;
+        });
+    }
+
+    function fetchRunForMetric(testName, metricName,callback) {
+        queryAndFetchAll('SELECT * FROM test_runs WHERE run_config IN'
+            + '(SELECT config_id FROM test_configurations, test_metrics, tests WHERE config_metric = metric_id AND metric_test = test_id AND'
+            + 'test_name = $1 AND metric_name = $2)',
+            ['Arithmetic', 'values.reduce(function (a, b) { return a + b; }) / values.length'], function () {
+            queryAndFetchAll('INSERT INTO aggregators (aggregator_name, aggregator_definition) values ($1, $2)',
+                ['Geometric', 'Math.pow(values.reduce(function (a, b) { return a * b; }), 1 / values.length)'], callback);
+        });
+    }
+
+    it(&quot;should accept a report with aggregators&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add tests&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            return TestServer.database().selectAll('tests');
+        }).then(function (tests) {
+            assert.deepEqual(tests.map(function (row) { return row['name']; }).sort(),
+                ['CSS', 'DOM', 'DummyBenchmark', 'DummyPageLoading', 'ModifyNodes', 'TraverseNodes', 'apple.com', 'webkit.org']);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should add metrics&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            return TestServer.database().query('SELECT * FROM tests, test_metrics LEFT JOIN aggregators ON metric_aggregator = aggregator_id WHERE metric_test = test_id');
+        }).then(function (result) {
+            let testNameToMetrics = {};
+            result.rows.forEach(function (row) {
+                if (!(row['test_name'] in testNameToMetrics))
+                    testNameToMetrics[row['test_name']] = new Array;
+                testNameToMetrics[row['test_name']].push([row['metric_name'], row['aggregator_name']]);
+            });
+            assert.deepEqual(testNameToMetrics['CSS'], [['Time', null]]);
+            assert.deepEqual(testNameToMetrics['DOM'].sort(), [['Time', 'Arithmetic'], ['Time', 'Geometric']]);
+            assert.deepEqual(testNameToMetrics['DummyBenchmark'], [['Time', 'Arithmetic']]);
+            assert.deepEqual(testNameToMetrics['DummyPageLoading'], [['Time', 'Arithmetic']]);
+            assert.deepEqual(testNameToMetrics['ModifyNodes'], [['Time', null]]);
+            assert.deepEqual(testNameToMetrics['TraverseNodes'], [['Time', null]]);
+            assert.deepEqual(testNameToMetrics['apple.com'], [['Time', null]]);
+            assert.deepEqual(testNameToMetrics['webkit.org'], [['Time', null]]);
+            done();
+        }).catch(done);
+    });
+
+    function fetchTestConfig(testName, metricName)
+    {
+        return TestServer.database().query(`SELECT * FROM tests, test_metrics, test_configurations
+            WHERE test_id = metric_test AND metric_id = config_metric
+            AND test_name = $1 AND metric_name = $2`, [testName, metricName]).then(function (result) {
+                assert.equal(result.rows.length, 1);
+                return result.rows[0];
+            });
+    }
+
+    function fetchTestRunIterationsForMetric(testName, metricName)
+    {
+        const db = TestServer.database();
+        return fetchTestConfig(testName, metricName).then(function (config) {
+            return db.selectFirstRow('test_runs', {config: config['config_id']});
+        }).then(function (run) {
+            return db.selectRows('run_iterations', {run: run['id']}, {sortBy: 'order'}).then(function (iterations) {
+                return {run: run, iterations: iterations};
+            });
+        });
+    }
+
+    it(&quot;should store run values&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            return fetchTestRunIterationsForMetric('apple.com', 'Time');
+        }).then(function (result) {
+            const run = result.run;
+            const runId = run['id'];
+            assert.deepEqual(result.iterations, [
+                {run: runId, order: 0, group: null, value: 500, relative_time: null},
+                {run: runId, order: 1, group: null, value: 510, relative_time: null},
+                {run: runId, order: 2, group: null, value: 520, relative_time: null},
+                {run: runId, order: 3, group: null, value: 530, relative_time: null}]);
+            var sum = 500 + 510 + 520 + 530;
+            assert.equal(run['mean_cache'], sum / result.iterations.length);
+            assert.equal(run['sum_cache'], sum);
+            assert.equal(run['square_sum_cache'], 500 * 500 + 510 * 510 + 520 * 520 + 530 * 530);
+            return fetchTestRunIterationsForMetric('CSS', 'Time');
+        }).then(function (result) {
+            const run = result.run;
+            const runId = run['id'];
+            assert.deepEqual(result.iterations, [
+                {run: runId, order: 0, group: 0, value: 101, relative_time: null},
+                {run: runId, order: 1, group: 0, value: 102, relative_time: null},
+                {run: runId, order: 2, group: 0, value: 103, relative_time: null},
+                {run: runId, order: 3, group: 0, value: 104, relative_time: null},
+                {run: runId, order: 4, group: 0, value: 105, relative_time: null},
+                {run: runId, order: 5, group: 1, value: 106, relative_time: null},
+                {run: runId, order: 6, group: 1, value: 107, relative_time: null},
+                {run: runId, order: 7, group: 1, value: 108, relative_time: null},
+                {run: runId, order: 8, group: 1, value: 109, relative_time: null},
+                {run: runId, order: 9, group: 1, value: 110, relative_time: null},
+                {run: runId, order: 10, group: 2, value: 111, relative_time: null},
+                {run: runId, order: 11, group: 2, value: 112, relative_time: null},
+                {run: runId, order: 12, group: 2, value: 113, relative_time: null},
+                {run: runId, order: 13, group: 2, value: 114, relative_time: null},
+                {run: runId, order: 14, group: 2, value: 115, relative_time: null}]);
+            let sum = 0;
+            let squareSum = 0;
+            for (let value = 101; value &lt;= 115; ++value) {
+                sum += value;
+                squareSum += value * value;
+            }
+            assert.equal(run['mean_cache'], sum / result.iterations.length);
+            assert.equal(run['sum_cache'], sum);
+            assert.equal(run['square_sum_cache'], squareSum);
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should store aggregated run values&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            return fetchTestRunIterationsForMetric('DummyPageLoading', 'Time');
+        }).then(function (result) {
+            const run = result.run;
+            const runId = result.run['id'];
+            const expectedValues = [(500 + 100) / 2, (510 + 110) / 2, (520 + 120) / 2, (530 + 130) / 2];
+            assert.deepEqual(result.iterations, [
+                {run: runId, order: 0, group: null, value: expectedValues[0], relative_time: null},
+                {run: runId, order: 1, group: null, value: expectedValues[1], relative_time: null},
+                {run: runId, order: 2, group: null, value: expectedValues[2], relative_time: null},
+                {run: runId, order: 3, group: null, value: expectedValues[3], relative_time: null}]);
+            const sum = expectedValues.reduce(function (sum, value) { return sum + value; }, 0);
+            assert.equal(run['mean_cache'], sum / result.iterations.length);
+            assert.equal(run['sum_cache'], sum);
+            assert.equal(run['square_sum_cache'], expectedValues.reduce(function (sum, value) { return sum + value * value; }, 0));
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should be able to compute the aggregation of aggregated values&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithTwoLevelsOfAggregations).then(function () {
+            return fetchTestRunIterationsForMetric('DummyBenchmark', 'Time');
+        }).then(function (result) {
+            const run = result.run;
+            const runId = run['id'];
+            const expectedIterations = [];
+            let sum = 0;
+            let squareSum = 0;
+            for (let i = 0; i &lt; 15; ++i) {
+                const value = i + 1;
+                const DOMMean = ((10 + value) + (30 + value)) / 2;
+                const expectedValue = (DOMMean + 100 + value) / 2;
+                sum += expectedValue;
+                squareSum += expectedValue * expectedValue;
+                expectedIterations.push({run: runId, order: i, group: Math.floor(i / 5), value: expectedValue, relative_time: null});
+            }
+            assert.deepEqual(result.iterations, expectedIterations);
+            assert.equal(run['mean_cache'], sum / result.iterations.length);
+            assert.equal(run['sum_cache'], sum);
+            assert.equal(run['square_sum_cache'], squareSum);
+            done();
+        }).catch(done);
+    });
+
+    function reportWithSameSubtestName()
+    {
+        return {
+            &quot;buildNumber&quot;: &quot;123&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;builderPassword&quot;: &quot;somePassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {
+                &quot;Suite1&quot;: {
+                    &quot;tests&quot;: {
+                        &quot;test1&quot;: {
+                            &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [1, 2, 3, 4, 5] }}
+                        },
+                        &quot;test2&quot;: {
+                            &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [6, 7, 8, 9, 10] }}
+                        }
+                    }
+                },
+                &quot;Suite2&quot;: {
+                    &quot;tests&quot;: {
+                        &quot;test1&quot;: {
+                            &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [11, 12, 13, 14, 15] }}
+                        },
+                        &quot;test2&quot;: {
+                            &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [16, 17, 18, 19, 20] }}
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    it(&quot;should be able to add a report with same subtest name&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithSameSubtestName()).then(function () {
+            done();
+        }).catch(done);
+    });
+
+    it(&quot;should be able to reuse the same test rows&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithSameSubtestName()).then(function () {
+            return TestServer.database().selectAll('tests');
+        }).then(function (tests) {
+            assert.equal(tests.length, 6);
+            let newReport = reportWithSameSubtestName();
+            newReport.buildNumber = &quot;125&quot;;
+            newReport.buildTime = &quot;2013-02-28T12:17:24.1&quot;;
+            return TestServer.remoteAPI().postJSON('/api/report/', [newReport]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return TestServer.database().selectAll('tests');
+        }).then(function (tests) {
+            assert.equal(tests.length, 6);
+            done();
+        }).catch(done);
+    });
+
+    const reportWithSameSingleValue = {
+        &quot;buildNumber&quot;: &quot;123&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+            &quot;suite&quot;: {
+                &quot;metrics&quot;: {&quot;Combined&quot;: [&quot;Arithmetic&quot;]},
+                &quot;tests&quot;: {
+                    &quot;test1&quot;: {
+                        &quot;metrics&quot;: {&quot;Combined&quot;: { &quot;current&quot;: 3 }}
+                    },
+                    &quot;test2&quot;: {
+                        &quot;metrics&quot;: {&quot;Combined&quot;: { &quot;current&quot;: 7 }}
+                    }
+                }
+            },
+        }};
+
+    it(&quot;should be able to add a report with single value results&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithSameSingleValue).then(function () {
+            return fetchTestRunIterationsForMetric('test1', 'Combined');
+        }).then(function (result) {
+            const run = result.run;
+            assert.equal(run['iteration_count_cache'], 1);
+            assert.equal(run['mean_cache'], 3);
+            assert.equal(run['sum_cache'], 3);
+            assert.equal(run['square_sum_cache'], 9);
+            return fetchTestRunIterationsForMetric('suite', 'Combined');
+        }).then(function (result) {
+            const run = result.run;
+            assert.equal(run['iteration_count_cache'], 1);
+            assert.equal(run['mean_cache'], 5);
+            assert.equal(run['sum_cache'], 5);
+            assert.equal(run['square_sum_cache'], 25);
+            done();
+        }).catch(done);
+    });
+
+    const reportWithSameValuePairs = {
+        &quot;buildNumber&quot;: &quot;123&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {
+                &quot;test&quot;: {
+                    &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[[0, 4], [100, 5], [205, 3]]] }}
+                },
+            },
+        };
+
+    it(&quot;should be able to add a report with (relative time, value) pairs&quot;, function (done) {
+        reportAfterAddingBuilderAndAggregators(reportWithSameValuePairs).then(function () {
+            return fetchTestRunIterationsForMetric('test', 'FrameRate');
+        }).then(function (result) {
+            const run = result.run;
+            assert.equal(run['iteration_count_cache'], 3);
+            assert.equal(run['mean_cache'], 4);
+            assert.equal(run['sum_cache'], 12);
+            assert.equal(run['square_sum_cache'], 16 + 25 + 9);
+
+            const runId = run['id'];
+            assert.deepEqual(result.iterations, [
+                {run: runId, order: 0, group: null, value: 4, relative_time: 0},
+                {run: runId, order: 1, group: null, value: 5, relative_time: 100},
+                {run: runId, order: 2, group: null, value: 3, relative_time: 205}]);
+            done();
+        }).catch(done);
+    });
+
+    const reportsUpdatingDifferentTests = [
+        {
+            &quot;buildNumber&quot;: &quot;123&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;builderPassword&quot;: &quot;somePassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {&quot;test1&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
+        },
+        {
+            &quot;buildNumber&quot;: &quot;124&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T11:31:21&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;builderPassword&quot;: &quot;somePassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {&quot;test2&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
+        },
+        {
+            &quot;buildNumber&quot;: &quot;125&quot;,
+            &quot;buildTime&quot;: &quot;2013-02-28T12:45:34&quot;,
+            &quot;builderName&quot;: &quot;someBuilder&quot;,
+            &quot;builderPassword&quot;: &quot;somePassword&quot;,
+            &quot;platform&quot;: &quot;Mountain Lion&quot;,
+            &quot;tests&quot;: {&quot;test1&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
+        },
+    ];
+
+    it(&quot;should update the last modified date of test configurations with new runs&quot;, function (done) {
+        addBuilderForReport(reportsUpdatingDifferentTests[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [reportsUpdatingDifferentTests[0]]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return fetchTestConfig('test1', 'Time');
+        }).then(function (originalConfig) {
+            return TestServer.remoteAPI().postJSON('/api/report/', [reportsUpdatingDifferentTests[2]]).then(function () {
+                return fetchTestConfig('test1', 'Time');
+            }).then(function (config) {
+                assert(originalConfig['config_runs_last_modified'] instanceof Date);
+                assert(config['config_runs_last_modified'] instanceof Date);
+                assert(+originalConfig['config_runs_last_modified'] &lt; +config['config_runs_last_modified']);
+                done();
+            });
+        }).catch(done);
+    });
+
+    it(&quot;should not update the last modified date of unrelated test configurations&quot;, function (done) {
+        addBuilderForReport(reportsUpdatingDifferentTests[0]).then(function () {
+            return TestServer.remoteAPI().postJSON('/api/report/', [reportsUpdatingDifferentTests[0]]);
+        }).then(function (response) {
+            assert.equal(response['status'], 'OK');
+            return fetchTestConfig('test1', 'Time');
+        }).then(function (originalConfig) {
+            return TestServer.remoteAPI().postJSON('/api/report/', [reportsUpdatingDifferentTests[1]]).then(function (response) {
+                assert.equal(response['status'], 'OK');
+                return fetchTestConfig('test1', 'Time');
+            }).then(function (config) {
+                assert(originalConfig['config_runs_last_modified'] instanceof Date);
+                assert(config['config_runs_last_modified'] instanceof Date);
+                assert.equal(+originalConfig['config_runs_last_modified'], +config['config_runs_last_modified']);
+                done();
+            });
+        }).catch(done);
+    });
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsresourcestestserverjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/resources/test-server.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/resources/test-server.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/server-tests/resources/test-server.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -9,7 +9,7 @@
</span><span class="cx"> let Database = require('../../tools/js/database.js');
</span><span class="cx"> let RemoteAPI = require('../../tools/js/remote.js').RemoteAPI;
</span><span class="cx"> 
</span><del>-let TestServer = (new class TestServer {
</del><ins>+class TestServer {
</ins><span class="cx">     constructor()
</span><span class="cx">     {
</span><span class="cx">         this._pidFile = null;
</span><span class="lines">@@ -241,8 +241,10 @@
</span><span class="cx">             return self.stop();
</span><span class="cx">         });
</span><span class="cx">     }
</span><del>-});
</del><ins>+}
</ins><span class="cx"> 
</span><ins>+if (!global.TestServer)
+    global.TestServer = new TestServer;
</ins><span class="cx"> 
</span><span class="cx"> if (typeof module != 'undefined')
</span><del>-    module.exports = TestServer;
</del><ins>+    module.exports = global.TestServer;
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgserverteststoolsbuildbottriggerabletestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -18,7 +18,7 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> describe('BuildbotTriggerable', function () {
</span><del>-    this.timeout(10000);
</del><ins>+    this.timeout(1000);
</ins><span class="cx">     TestServer.inject();
</span><span class="cx"> 
</span><span class="cx">     beforeEach(function () {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsadminplatformsjs"></a>
<div class="delfile"><h4>Deleted: trunk/Websites/perf.webkit.org/tests/admin-platforms.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/admin-platforms.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tests/admin-platforms.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,122 +0,0 @@
</span><del>-describe(&quot;/admin/platforms&quot;, function () {
-    var reportsForDifferentPlatforms = [
-    {
-        &quot;buildNumber&quot;: &quot;3001&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T09:01:47&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mavericks&quot;,
-        &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[1, 1, 1], [1, 1, 1]] } } } },
-    },
-    {
-        &quot;buildNumber&quot;: &quot;3001&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[2, 2, 2], [2, 2, 2]] }, &quot;Combined&quot;: { &quot;current&quot;: [[3, 3, 3], [3, 3, 3]] }} } },
-    },
-    {
-        &quot;buildNumber&quot;: &quot;3003&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T12:56:26&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Trunk Mountain Lion&quot;,
-        &quot;tests&quot;: {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[4, 4, 4], [4, 4, 4]] } } } }
-    }];
-
-    function submitReport(report, callback) {
-        queryAndFetchAll('INSERT INTO builders (builder_name, builder_password_hash) values ($1, $2)',
-            [report[0].builderName, sha256(report[0].builderPassword)], function () {
-                postJSON('/api/report/', reportsForDifferentPlatforms, function (response) {
-                    callback();
-                });
-            });
-    }
-
-    it(&quot;should delete the platform that got merged into another one&quot;, function () {
-        submitReport(reportsForDifferentPlatforms, function () {
-            queryAndFetchAll('SELECT * FROM platforms ORDER by platform_name', [], function (oldPlatforms) {
-                assert.equal(oldPlatforms.length, 3);
-                assert.equal(oldPlatforms[0]['platform_name'], 'Mavericks');
-                assert.equal(oldPlatforms[1]['platform_name'], 'Mountain Lion');
-                assert.equal(oldPlatforms[2]['platform_name'], 'Trunk Mountain Lion');
-                httpPost('/admin/platforms.php', {'action': 'merge', 'id': oldPlatforms[1]['platform_id'], 'destination': oldPlatforms[2]['platform_id']}, function (response) {
-                    assert.equal(response.statusCode, 200);
-                    queryAndFetchAll('SELECT * FROM platforms ORDER by platform_name', [], function (newPlatforms) {
-                        assert.deepEqual(newPlatforms, [oldPlatforms[0], oldPlatforms[2]]);
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should move test runs from the merged platform to the destination platform&quot;, function () {
-        submitReport(reportsForDifferentPlatforms, function () {
-            var queryForRuns = 'SELECT * FROM test_runs, test_configurations, platforms WHERE run_config = config_id AND config_platform = platform_id ORDER by run_mean_cache';
-            queryAndFetchAll(queryForRuns, [], function (oldTestRuns) {
-                assert.equal(oldTestRuns.length, 4);
-                assert.equal(oldTestRuns[0]['platform_name'], 'Mavericks');
-                assert.equal(oldTestRuns[0]['run_sum_cache'], 6);
-                assert.equal(oldTestRuns[1]['platform_name'], 'Mountain Lion');
-                assert.equal(oldTestRuns[1]['run_sum_cache'], 12);
-                assert.equal(oldTestRuns[2]['platform_name'], 'Mountain Lion');
-                assert.equal(oldTestRuns[2]['run_sum_cache'], 18);
-                assert.equal(oldTestRuns[3]['platform_name'], 'Trunk Mountain Lion');
-                assert.equal(oldTestRuns[3]['run_sum_cache'], 24);
-                httpPost('/admin/platforms.php', {'action': 'merge', 'id': oldTestRuns[1]['platform_id'], 'destination': oldTestRuns[3]['platform_id']}, function (response) {
-                    assert.equal(response.statusCode, 200);
-                    queryAndFetchAll(queryForRuns, [], function (newTestRuns) {
-                        assert.equal(newTestRuns.length, 4);
-                        assert.equal(newTestRuns[0]['run_id'], oldTestRuns[0]['run_id']);
-                        assert.equal(newTestRuns[0]['platform_name'], 'Mavericks');
-                        assert.equal(newTestRuns[0]['run_sum_cache'], 6);
-                        assert.equal(newTestRuns[1]['run_id'], oldTestRuns[1]['run_id']);
-                        assert.equal(newTestRuns[1]['platform_name'], 'Trunk Mountain Lion');
-                        assert.equal(newTestRuns[1]['run_sum_cache'], 12);
-                        assert.equal(newTestRuns[2]['run_id'], oldTestRuns[2]['run_id']);
-                        assert.equal(newTestRuns[2]['platform_name'], 'Trunk Mountain Lion');
-                        assert.equal(newTestRuns[2]['run_sum_cache'], 18);
-                        assert.equal(newTestRuns[3]['run_id'], oldTestRuns[3]['run_id']);
-                        assert.equal(newTestRuns[3]['platform_name'], 'Trunk Mountain Lion');
-                        assert.equal(newTestRuns[3]['run_sum_cache'], 24);
-                        assert.equal(newTestRuns[1]['run_config'], newTestRuns[3]['run_config']);
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should move test configurations from the merged platform to the destination platform&quot;, function () {
-        reportsForDifferentPlatforms[0]['tests'] = {&quot;test&quot;: { &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;baseline&quot;: [[1, 1, 1], [1, 1, 1]] } } } };
-        submitReport(reportsForDifferentPlatforms, function () {
-            var queryForConfig = 'SELECT * from test_configurations, platforms, test_metrics'
-                + ' where config_platform = platform_id and config_metric = metric_id and platform_name in ($1, $2) order by config_id';
-            queryAndFetchAll(queryForConfig, [reportsForDifferentPlatforms[0]['platform'], reportsForDifferentPlatforms[2]['platform']], function (configs) {
-                assert.equal(configs.length, 2);
-                assert.equal(configs[0]['platform_name'], reportsForDifferentPlatforms[0]['platform']);
-                assert.equal(configs[0]['metric_name'], 'FrameRate');
-                assert.equal(configs[0]['config_type'], 'baseline');
-                assert.equal(configs[1]['platform_name'], reportsForDifferentPlatforms[2]['platform']);
-                assert.equal(configs[1]['metric_name'], 'FrameRate');
-                assert.equal(configs[1]['config_type'], 'current');
-                httpPost('/admin/platforms.php', {'action': 'merge', 'id': configs[0]['platform_id'], 'destination': configs[1]['platform_id']}, function (response) {
-                    assert.equal(response.statusCode, 200);
-                    queryAndFetchAll(queryForConfig, [reportsForDifferentPlatforms[0]['platform'], reportsForDifferentPlatforms[2]['platform']], function (newConfigs) {
-                        assert.equal(newConfigs.length, 2);
-                        assert.equal(newConfigs[0]['platform_name'], reportsForDifferentPlatforms[2]['platform']);
-                        assert.equal(newConfigs[0]['metric_name'], 'FrameRate');
-                        assert.equal(newConfigs[0]['config_type'], 'baseline');
-                        assert.equal(newConfigs[1]['platform_name'], reportsForDifferentPlatforms[2]['platform']);
-                        assert.equal(newConfigs[1]['metric_name'], 'FrameRate');
-                        assert.equal(newConfigs[1]['config_type'], 'current');
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-});
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsadminreprocessreportjs"></a>
<div class="delfile"><h4>Deleted: trunk/Websites/perf.webkit.org/tests/admin-reprocess-report.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/admin-reprocess-report.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tests/admin-reprocess-report.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,76 +0,0 @@
</span><del>-describe(&quot;/admin/reprocess-report&quot;, function () {
-    var simpleReport = [{
-        &quot;buildNumber&quot;: &quot;1986&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-                &quot;test&quot;: {
-                    &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[1, 2, 3], [4, 5, 6]] }}
-                },
-            },
-        }];
-
-    function addBuilder(report, callback) {
-        queryAndFetchAll('INSERT INTO builders (builder_name, builder_password_hash) values ($1, $2)',
-            [report[0].builderName, sha256(report[0].builderPassword)], callback);
-    }
-
-    it(&quot;should add build&quot;, function () {
-        addBuilder(simpleReport, function () {
-            postJSON('/api/report/', simpleReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM builds', [], function (buildRows) {
-                    assert.equal(buildRows.length, 1);
-                    assert.equal(buildRows[0]['build_number'], 1986);
-                    queryAndFetchAll('SELECT * FROM reports', [], function (reportRows) {
-                        assert.equal(reportRows.length, 1);
-                        assert.equal(reportRows[0]['report_build_number'], 1986);
-                        queryAndFetchAll('UPDATE reports SET report_build = NULL; DELETE FROM builds; SELECT * FROM builds', [], function (buildRows) {
-                            assert.equal(buildRows.length, 0);
-                            var reportId = reportRows[0]['report_id'];
-                            httpGet('/admin/reprocess-report?report=' + reportId, function (response) {
-                                assert.equal(response.statusCode, 200);
-                                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                                queryAndFetchAll('SELECT * FROM builds', [], function (buildRows) {
-                                    assert.equal(buildRows.length, 1);
-                                    assert.equal(buildRows[0]['build_number'], 1986);
-                                    notifyDone();
-                                });
-                            });
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should not duplicate the reprocessed report&quot;, function () {
-        addBuilder(simpleReport, function () {
-            postJSON('/api/report/', simpleReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM reports', [], function (originalReprotRows) {
-                    assert.equal(originalReprotRows.length, 1);
-                    queryAndFetchAll('UPDATE reports SET report_build = NULL; DELETE FROM builds', [], function () {
-                        httpGet('/admin/reprocess-report?report=' + originalReprotRows[0]['report_id'], function (response) {
-                            assert.equal(response.statusCode, 200);
-                            assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                            queryAndFetchAll('SELECT * FROM reports', [], function (reportRows) {
-                                originalReprotRows[0]['report_committed_at'] = null;
-                                reportRows[0]['report_committed_at'] = null;
-                                assert.notEqual(originalReprotRows[0]['report_build'], reportRows[0]['report_build']);
-                                originalReprotRows[0]['report_build'] = null;
-                                reportRows[0]['report_build'] = null;
-                                assert.deepEqual(reportRows, originalReprotRows);
-                                notifyDone();
-                            });
-                        });
-                    });
-                });
-            });
-        });
-    });
-});
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsapimeasurementsetjs"></a>
<div class="delfile"><h4>Deleted: trunk/Websites/perf.webkit.org/tests/api-measurement-set.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/api-measurement-set.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tests/api-measurement-set.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,404 +0,0 @@
</span><del>-describe(&quot;/api/measurement-set&quot;, function () {
-    function addBuilder(report, callback) {
-        queryAndFetchAll('INSERT INTO builders (builder_name, builder_password_hash) values ($1, $2)',
-            [report[0].builderName, sha256(report[0].builderPassword)], callback);
-    }
-
-    function queryPlatformAndMetric(platformName, metricName, callback) {
-        queryAndFetchAll('SELECT * FROM platforms WHERE platform_name = $1', [platformName], function (platformRows) {
-            queryAndFetchAll('SELECT * FROM test_metrics WHERE metric_name = $1', [metricName], function (metricRows) {
-                callback(platformRows[0]['platform_id'], metricRows[0]['metric_id']);
-            });
-        });
-    }
-
-    function format(formatMap, row) {
-        var result = {};
-        for (var i = 0; i &lt; formatMap.length; i++) {
-            var key = formatMap[i];
-            if (key == 'id' || key == 'build' || key == 'builder')
-                continue;
-            result[key] = row[i];
-        }
-        return result;
-    }
-
-    var clusterStart = config('clusterStart');
-    clusterStart = +Date.UTC(clusterStart[0], clusterStart[1] - 1, clusterStart[2], clusterStart[3], clusterStart[4]);
-
-    var clusterSize = config('clusterSize');
-    var DAY = 24 * 3600 * 1000;
-    var YEAR = 365.24 * DAY;
-    var MONTH = 30 * DAY;
-    clusterSize = clusterSize[0] * YEAR + clusterSize[1] * MONTH + clusterSize[2] * DAY;
-
-    function clusterTime(index) { return new Date(clusterStart + clusterSize * index); }
-
-    var reportWithBuildTime = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: clusterTime(7.8).toISOString(),
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;Suite&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [1, 2, 3, 4, 5] }}
-                    },
-                }
-            },
-        }}];
-    reportWithBuildTime.startTime = +clusterTime(7);
-
-    var reportWithRevision = [{
-        &quot;buildNumber&quot;: &quot;124&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T15:34:51&quot;,
-        &quot;revisions&quot;: {
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;144000&quot;,
-                &quot;timestamp&quot;: clusterTime(10.3).toISOString(),
-            },
-        },
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;Suite&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [11, 12, 13, 14, 15] }}
-                    }
-                }
-            },
-        }}];
-
-    var reportWithNewRevision = [{
-        &quot;buildNumber&quot;: &quot;125&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T21:45:17&quot;,
-        &quot;revisions&quot;: {
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;160609&quot;,
-                &quot;timestamp&quot;: clusterTime(12.1).toISOString()
-            },
-        },
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;Suite&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [16, 17, 18, 19, 20] }}
-                    }
-                }
-            },
-        }}];
-
-    var reportWithAncentRevision = [{
-        &quot;buildNumber&quot;: &quot;126&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T23:07:25&quot;,
-        &quot;revisions&quot;: {
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;137793&quot;,
-                &quot;timestamp&quot;: clusterTime(1.8).toISOString()
-            },
-        },
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;Suite&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [21, 22, 23, 24, 25] }}
-                    }
-                }
-            },
-        }}];
-
-    it(&quot;should reject when platform ID is missing&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    httpGet('/api/measurement-set/?metric=' + metricId, function (response) {
-                        assert.notEqual(JSON.parse(response.responseText)['status'], 'InvalidMetric');
-                        notifyDone();
-                    });
-                });
-
-            });
-        });
-    });
-
-    it(&quot;should reject when metric ID is missing&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId, function (response) {
-                        assert.notEqual(JSON.parse(response.responseText)['status'], 'InvalidPlatform');
-                        notifyDone();
-                    });
-                });
-
-            });
-        });
-    });
-
-    it(&quot;should reject an invalid platform name&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + 'a&amp;metric=' + metricId, function (response) {
-                        assert.equal(JSON.parse(response.responseText)['status'], 'InvalidPlatform');
-                        notifyDone();
-                    });
-                });
-
-            });
-        });
-    });
-
-    it(&quot;should reject an invalid metric name&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId + 'b', function (response) {
-                        assert.equal(JSON.parse(response.responseText)['status'], 'InvalidMetric');
-                        notifyDone();
-                    });
-                });
-
-            });
-        });
-    });
-
-    it(&quot;should be able to retrieve a reported value&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                        try {
-                            var paresdResult = JSON.parse(response.responseText);
-                        } catch (error) {
-                            assert.fail(error, null, response.responseText);
-                        }
-
-                        var buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
-
-                        assert.deepEqual(Object.keys(paresdResult).sort(),
-                            ['clusterCount', 'clusterSize', 'clusterStart',
-                              'configurations', 'elapsedTime', 'endTime', 'formatMap', 'lastModified', 'startTime', 'status']);
-                        assert.equal(paresdResult['status'], 'OK');
-                        assert.equal(paresdResult['clusterCount'], 1);
-                        assert.deepEqual(paresdResult['formatMap'], [
-                            'id', 'mean', 'iterationCount', 'sum', 'squareSum', 'markedOutlier',
-                            'revisions', 'commitTime', 'build', 'buildTime', 'buildNumber', 'builder']);
-
-                        assert.equal(paresdResult['startTime'], reportWithBuildTime.startTime);
-                        assert(typeof(paresdResult['lastModified']) == 'number', 'lastModified time should be a numeric');
-
-                        assert.deepEqual(Object.keys(paresdResult['configurations']), ['current']);
-
-                        var currentRows = paresdResult['configurations']['current'];
-                        assert.equal(currentRows.length, 1);
-                        assert.equal(currentRows[0].length, paresdResult['formatMap'].length);
-                        assert.deepEqual(format(paresdResult['formatMap'], currentRows[0]), {
-                            mean: 3,
-                            iterationCount: 5,
-                            sum: 15,
-                            squareSum: 55,
-                            markedOutlier: false,
-                            revisions: [],
-                            commitTime: buildTime,
-                            buildTime: buildTime,
-                            buildNumber: '123'});
-                        notifyDone();
-                    });
-                });
-
-            });
-        });
-    });
-
-    it(&quot;should return return the right IDs for measurement, build, and builder&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postJSON('/api/report/', reportWithBuildTime, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryPlatformAndMetric('Mountain Lion', 'Time', function (platformId, metricId) {
-                    queryAndFetchAll('SELECT * FROM test_runs', [], function (runs) {
-                        assert.equal(runs.length, 1);
-                        var measurementId = runs[0]['run_id'];
-                        queryAndFetchAll('SELECT * FROM builds', [], function (builds) {
-                            assert.equal(builds.length, 1);
-                            var buildId = builds[0]['build_id'];
-                            queryAndFetchAll('SELECT * FROM builders', [], function (builders) {
-                                assert.equal(builders.length, 1);
-                                var builderId = builders[0]['builder_id'];
-                                httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                                    var paresdResult = JSON.parse(response.responseText);
-                                    assert.equal(paresdResult['configurations']['current'].length, 1);
-                                    var measurement = paresdResult['configurations']['current'][0];
-                                    assert.equal(paresdResult['status'], 'OK');
-                                    assert.equal(measurement[paresdResult['formatMap'].indexOf('id')], measurementId);
-                                    assert.equal(measurement[paresdResult['formatMap'].indexOf('build')], buildId);
-                                    assert.equal(measurement[paresdResult['formatMap'].indexOf('builder')], builderId);
-                                    notifyDone();
-                                });
-                            });
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-    function postReports(reports, callback) {
-        if (!reports.length)
-            return callback();
-
-        postJSON('/api/report/', reports[0], function (response) {
-            assert.equal(response.statusCode, 200);
-            assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-            postReports(reports.slice(1), callback);
-        });
-    }
-
-    function queryPlatformAndMetricWithRepository(platformName, metricName, repositoryName, callback) {
-        queryPlatformAndMetric(platformName, metricName, function (platformId, metricId) {
-            queryAndFetchAll('SELECT * FROM repositories WHERE repository_name = $1', [repositoryName], function (rows) {
-                callback(platformId, metricId, rows[0]['repository_id']);
-            });
-        });
-    }
-
-    it(&quot;should order results by commit time&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postReports([reportWithBuildTime, reportWithRevision], function () {
-                queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit', function (platformId, metricId, repositoryId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                        var parsedResult = JSON.parse(response.responseText);
-                        assert.equal(parsedResult['status'], 'OK');
-
-                        var buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
-                        var revisionTime = +(new Date(reportWithRevision[0]['revisions']['WebKit']['timestamp']));
-                        var revisionBuildTime = +(new Date(reportWithRevision[0]['buildTime']));
-
-                        var currentRows = parsedResult['configurations']['current'];
-                        assert.equal(currentRows.length, 2);
-                        assert.deepEqual(format(parsedResult['formatMap'], currentRows[0]), {
-                           mean: 13,
-                           iterationCount: 5,
-                           sum: 65,
-                           squareSum: 855,
-                           markedOutlier: false,
-                           revisions: [[1, repositoryId, '144000', revisionTime]],
-                           commitTime: revisionTime,
-                           buildTime: revisionBuildTime,
-                           buildNumber: '124' });
-                        assert.deepEqual(format(parsedResult['formatMap'], currentRows[1]), {
-                            mean: 3,
-                            iterationCount: 5,
-                            sum: 15,
-                            squareSum: 55,
-                            markedOutlier: false,
-                            revisions: [],
-                            commitTime: buildTime,
-                            buildTime: buildTime,
-                            buildNumber: '123' });
-                        notifyDone();
-                    });
-                });                
-            });
-        });
-    });
-
-    function buildNumbers(parsedResult, config) {
-        return parsedResult['configurations'][config].map(function (row) {
-            return format(parsedResult['formatMap'], row)['buildNumber'];
-        });
-    }
-
-    it(&quot;should include one data point after the current time range&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postReports([reportWithAncentRevision, reportWithNewRevision], function () {
-                queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit', function (platformId, metricId, repositoryId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                        var parsedResult = JSON.parse(response.responseText);
-                        assert.equal(parsedResult['status'], 'OK');
-                        assert.equal(parsedResult['clusterCount'], 2, 'should have two clusters');
-                        assert.deepEqual(buildNumbers(parsedResult, 'current'),
-                            [reportWithAncentRevision[0]['buildNumber'], reportWithNewRevision[0]['buildNumber']]);
-                        notifyDone();
-                    });
-                });                
-            });
-        });
-    });
-
-    // FIXME: This test assumes a cluster step of 2-3 months
-    it(&quot;should always include one old data point before the current time range&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postReports([reportWithBuildTime, reportWithAncentRevision], function () {
-                queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit', function (platformId, metricId, repositoryId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                        var parsedResult = JSON.parse(response.responseText);
-                        assert.equal(parsedResult['status'], 'OK');
-                        assert.equal(parsedResult['clusterCount'], 2, 'should have two clusters');
-
-                        var currentRows = parsedResult['configurations']['current'];
-                        assert.equal(currentRows.length, 2, 'should contain at least two data points');
-                        assert.deepEqual(buildNumbers(parsedResult, 'current'),
-                            [reportWithAncentRevision[0]['buildNumber'], reportWithBuildTime[0]['buildNumber']]);
-                        notifyDone();
-                    });
-                });                
-            });
-        });
-    });
-
-    // FIXME: This test assumes a cluster step of 2-3 months
-    it(&quot;should create cache results&quot;, function () {
-        addBuilder(reportWithBuildTime, function () {
-            postReports([reportWithAncentRevision, reportWithRevision, reportWithNewRevision], function () {
-                queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit', function (platformId, metricId, repositoryId) {
-                    httpGet('/api/measurement-set/?platform=' + platformId + '&amp;metric=' + metricId, function (response) {
-                        var parsedResult = JSON.parse(response.responseText);
-                        assert.equal(parsedResult['status'], 'OK');
-                        var cachePrefix = '/data/measurement-set-' + platformId + '-' + metricId;
-                        httpGet(cachePrefix + '.json', function (response) {
-                            var parsedCachedResult = JSON.parse(response.responseText);
-                            assert.deepEqual(parsedResult, parsedCachedResult);
-
-                            httpGet(cachePrefix + '-' + parsedResult['startTime'] + '.json', function (response) {
-                                var parsedOldResult = JSON.parse(response.responseText);
-
-                                var oldBuildNumbers = buildNumbers(parsedOldResult, 'current');
-                                var newBuildNumbers = buildNumbers(parsedResult, 'current');
-                                assert(oldBuildNumbers.length &gt;= 2, 'The old cluster should contain at least two data points');
-                                assert(newBuildNumbers.length &gt;= 2, 'The new cluster should contain at least two data points');
-                                assert.deepEqual(oldBuildNumbers.slice(oldBuildNumbers.length - 2), newBuildNumbers.slice(0, 2),
-                                    'Two conseqcutive clusters should share two data points');
-
-                                notifyDone();
-                            });
-                        });
-                    });
-                });                
-            });
-        });
-    });
-
-});
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsapireportcommitsjs"></a>
<div class="delfile"><h4>Deleted: trunk/Websites/perf.webkit.org/tests/api-report-commits.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/api-report-commits.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tests/api-report-commits.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,237 +0,0 @@
</span><del>-describe(&quot;/api/report-commits/&quot;, function () {
-    var emptyReport = {
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;slavePassword&quot;: &quot;somePassword&quot;,
-    };
-    var subversionCommit = {
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;slavePassword&quot;: &quot;somePassword&quot;,
-        &quot;commits&quot;: [
-            {
-                &quot;repository&quot;: &quot;WebKit&quot;,
-                &quot;revision&quot;: &quot;141977&quot;,
-                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
-                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
-                &quot;message&quot;: &quot;some message&quot;,
-            }
-        ],
-    };
-    var subversionInvalidCommit = {
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;slavePassword&quot;: &quot;somePassword&quot;,
-        &quot;commits&quot;: [
-            {
-                &quot;repository&quot;: &quot;WebKit&quot;,
-                &quot;revision&quot;: &quot;_141977&quot;,
-                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
-                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
-                &quot;message&quot;: &quot;some message&quot;,
-            }
-        ],
-    };
-    var subversionTwoCommits = {
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;slavePassword&quot;: &quot;somePassword&quot;,
-        &quot;commits&quot;: [
-            {
-                &quot;repository&quot;: &quot;WebKit&quot;,
-                &quot;revision&quot;: &quot;141977&quot;,
-                &quot;time&quot;: &quot;2013-02-06T08:55:20.9Z&quot;,
-                &quot;author&quot;: {&quot;name&quot;: &quot;Commit Queue&quot;, &quot;account&quot;: &quot;commit-queue@webkit.org&quot;},
-                &quot;message&quot;: &quot;some message&quot;,
-            },
-            {
-                &quot;repository&quot;: &quot;WebKit&quot;,
-                &quot;parent&quot;: &quot;141977&quot;,
-                &quot;revision&quot;: &quot;141978&quot;,
-                &quot;time&quot;: &quot;2013-02-06T09:54:56.0Z&quot;,
-                &quot;author&quot;: {&quot;name&quot;: &quot;Mikhail Pozdnyakov&quot;, &quot;account&quot;: &quot;mikhail.pozdnyakov@intel.com&quot;},
-                &quot;message&quot;: &quot;another message&quot;,
-            }
-        ]
-    }
-
-    function addSlave(report, callback) {
-        queryAndFetchAll('INSERT INTO build_slaves (slave_name, slave_password_hash) values ($1, $2)',
-            [report.slaveName, sha256(report.slavePassword)], callback);
-    }
-
-    it(&quot;should reject error when slave name is missing&quot;, function () {
-        postJSON('/api/report-commits/', {}, function (response) {
-            assert.equal(response.statusCode, 200);
-            assert.equal(JSON.parse(response.responseText)['status'], 'MissingSlaveName');
-            notifyDone();
-        });
-    });
-
-    it(&quot;should reject when there are no slaves&quot;, function () {
-        postJSON('/api/report-commits/', emptyReport, function (response) {
-            assert.equal(response.statusCode, 200);
-            assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
-
-            queryAndFetchAll('SELECT COUNT(*) from commits', [], function (rows) {
-                assert.equal(rows[0].count, 0);
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should accept an empty report&quot;, function () {
-        addSlave(emptyReport, function () {
-            postJSON('/api/report-commits/', emptyReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should add a missing repository&quot;, function () {
-        addSlave(subversionCommit, function () {
-            postJSON('/api/report-commits/', subversionCommit, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM repositories', [], function (rows) {
-                    assert.equal(rows.length, 1);
-                    assert.equal(rows[0]['repository_name'], subversionCommit.commits[0]['repository']);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should store a commit from a valid slave&quot;, function () {
-        addSlave(subversionCommit, function () {
-            postJSON('/api/report-commits/', subversionCommit, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM commits JOIN committers ON commit_committer = committer_id', [], function (rows) {
-                    assert.equal(rows.length, 1);
-                    var reportedData = subversionCommit.commits[0];
-                    assert.equal(rows[0]['commit_revision'], reportedData['revision']);
-                    assert.equal(rows[0]['commit_time'].toString(), new Date('2013-02-06 08:55:20.9').toString());
-                    assert.equal(rows[0]['committer_name'], reportedData['author']['name']);
-                    assert.equal(rows[0]['committer_account'], reportedData['author']['account']);
-                    assert.equal(rows[0]['commit_message'], reportedData['message']);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should reject an invalid revision number&quot;, function () {
-        addSlave(subversionCommit, function () {
-            subversionCommit
-            postJSON('/api/report-commits/', subversionInvalidCommit, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM commits', [], function (rows) {
-                    assert.equal(rows.length, 0);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should store two commits from a valid slave&quot;, function () {
-        addSlave(subversionTwoCommits, function () {
-            postJSON('/api/report-commits/', subversionTwoCommits, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                queryAndFetchAll('SELECT * FROM commits JOIN committers ON commit_committer = committer_id ORDER BY commit_time', [], function (rows) {
-                    assert.equal(rows.length, 2);
-                    var reportedData = subversionTwoCommits.commits[0];
-                    assert.equal(rows[0]['commit_revision'], reportedData['revision']);
-                    assert.equal(rows[0]['commit_time'].toString(), new Date('2013-02-06 08:55:20.9').toString());
-                    assert.equal(rows[0]['committer_name'], reportedData['author']['name']);
-                    assert.equal(rows[0]['committer_account'], reportedData['author']['account']);
-                    assert.equal(rows[0]['commit_message'], reportedData['message']);
-                    var reportedData = subversionTwoCommits.commits[1];
-                    assert.equal(rows[1]['commit_revision'], reportedData['revision']);
-                    assert.equal(rows[1]['commit_time'].toString(), new Date('2013-02-06 09:54:56.0').toString());
-                    assert.equal(rows[1]['committer_name'], reportedData['author']['name']);
-                    assert.equal(rows[1]['committer_account'], reportedData['author']['account']);
-                    assert.equal(rows[1]['commit_message'], reportedData['message']);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should update an existing commit if there is one&quot;, function () {
-        queryAndFetchAll('INSERT INTO repositories (repository_name) VALUES ($1) RETURNING *', ['WebKit'], function (repositories) {
-            var repositoryId = repositories[0]['repository_id'];
-            var reportedData = subversionCommit.commits[0];
-            queryAndFetchAll('INSERT INTO commits (commit_repository, commit_revision, commit_time) VALUES ($1, $2, $3) RETURNING *',
-                [repositoryId, reportedData['revision'], reportedData['time']], function (existingCommits) {
-                var commitId = existingCommits[0]['commit_id'];
-                assert.equal(existingCommits[0]['commit_message'], null);
-                addSlave(subversionCommit, function () {
-                    postJSON('/api/report-commits/', subversionCommit, function (response) {
-                        assert.equal(response.statusCode, 200);
-                        assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                        queryAndFetchAll('SELECT * FROM commits JOIN committers ON commit_committer = committer_id', [], function (rows) {
-                            assert.equal(rows.length, 1);
-                            var reportedData = subversionCommit.commits[0];
-                            assert.equal(rows[0]['committer_name'], reportedData['author']['name']);
-                            assert.equal(rows[0]['committer_account'], reportedData['author']['account']);
-                            assert.equal(rows[0]['commit_message'], reportedData['message']);
-                            notifyDone();
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should not update an unrelated commit&quot;, function () {
-        queryAndFetchAll('INSERT INTO repositories (repository_name) VALUES ($1) RETURNING *', ['WebKit'], function (repositories) {
-            var repositoryId = repositories[0]['repository_id'];
-            var reportedData = subversionTwoCommits.commits[1];
-            queryAndFetchAll('INSERT INTO commits (commit_repository, commit_revision, commit_time) VALUES ($1, $2, $3) RETURNING *',
-                [repositoryId, reportedData['revision'], reportedData['time']], function (existingCommits) {
-                reportedData = subversionTwoCommits.commits[0];
-                queryAndFetchAll('INSERT INTO commits (commit_repository, commit_revision, commit_time) VALUES ($1, $2, $3) RETURNING *',
-                    [repositoryId, reportedData['revision'], reportedData['time']], function () {
-                        addSlave(subversionCommit, function () {
-                            postJSON('/api/report-commits/', subversionCommit, function (response) {
-                                assert.equal(response.statusCode, 200);
-                                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                                queryAndFetchAll('SELECT * FROM commits LEFT OUTER JOIN committers ON commit_committer = committer_id ORDER BY commit_time', [], function (rows) {
-                                    assert.equal(rows.length, 2);
-                                    assert.equal(rows[0]['committer_name'], reportedData['author']['name']);
-                                    assert.equal(rows[0]['committer_account'], reportedData['author']['account']);
-                                    assert.equal(rows[0]['commit_message'], reportedData['message']);
-                                    assert.equal(rows[1]['committer_name'], null);
-                                    assert.equal(rows[1]['committer_account'], null);
-                                    assert.equal(rows[1]['commit_message'], null);
-                                    notifyDone();
-                                });
-                            });
-                        });
-                });
-            });
-        });
-    });
-
-    it(&quot;should update an existing committer if there is one&quot;, function () {
-        queryAndFetchAll('INSERT INTO repositories (repository_id, repository_name) VALUES (1, \'WebKit\')', [], function () {
-            var author = subversionCommit.commits[0]['author'];
-            queryAndFetchAll('INSERT INTO committers (committer_repository, committer_account) VALUES (1, $1)', [author['account']], function () {
-                addSlave(subversionCommit, function () {
-                    postJSON('/api/report-commits/', subversionCommit, function (response) {
-                        assert.equal(response.statusCode, 200);
-                        assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                        queryAndFetchAll('SELECT * FROM committers', [], function (rows) {
-                            assert.equal(rows.length, 1);
-                            assert.equal(rows[0]['committer_name'], author['name']);
-                            assert.equal(rows[0]['committer_account'], author['account']);
-                            notifyDone();
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-});
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsapireportjs"></a>
<div class="delfile"><h4>Deleted: trunk/Websites/perf.webkit.org/tests/api-report.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/api-report.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tests/api-report.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,759 +0,0 @@
</span><del>-describe(&quot;/api/report&quot;, function () {
-    var emptyReport = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {},
-        &quot;revisions&quot;: {
-            &quot;OS X&quot;: {
-                &quot;revision&quot;: &quot;10.8.2 12C60&quot;
-            },
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;141977&quot;,
-                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
-            }
-        }}];
-
-    var emptySlaveReport = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;slaveName&quot;: &quot;someSlave&quot;,
-        &quot;slavePassword&quot;: &quot;otherPassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {},
-        &quot;revisions&quot;: {
-            &quot;OS X&quot;: {
-                &quot;revision&quot;: &quot;10.8.2 12C60&quot;
-            },
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;141977&quot;,
-                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
-            }
-        }}];
-
-    function addBuilder(report, callback) {
-        queryAndFetchAll('INSERT INTO builders (builder_name, builder_password_hash) values ($1, $2)',
-            [report[0].builderName, sha256(report[0].builderPassword)], callback);
-    }
-
-    function addSlave(report, callback) {
-        queryAndFetchAll('INSERT INTO build_slaves (slave_name, slave_password_hash) values ($1, $2)',
-            [report[0].slaveName, sha256(report[0].slavePassword)], callback);
-    }
-
-    it(&quot;should reject error when builder name is missing&quot;, function () {
-        postJSON('/api/report/', [{&quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;}], function (response) {
-            assert.equal(response.statusCode, 200);
-            assert.equal(JSON.parse(response.responseText)['status'], 'MissingBuilderName');
-            notifyDone();
-        });
-    });
-
-    it(&quot;should reject error when build time is missing&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', [{&quot;builderName&quot;: &quot;someBuilder&quot;, &quot;builderPassword&quot;: &quot;somePassword&quot;}], function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'MissingBuildTime');
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should reject when there are no builders&quot;, function () {
-        postJSON('/api/report/', emptyReport, function (response) {
-            assert.equal(response.statusCode, 200);
-            assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
-            assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-            assert.equal(JSON.parse(response.responseText)['processedRuns'], 0);
-
-            queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
-                assert.equal(rows[0].count, 0);
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should reject a report without a builder password&quot;, function () {
-        addBuilder(emptyReport, function () {
-            var report = [{
-                &quot;buildNumber&quot;: &quot;123&quot;,
-                &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-                &quot;builderName&quot;: &quot;someBuilder&quot;,
-                &quot;tests&quot;: {},
-                &quot;revisions&quot;: {}}];
-            postJSON('/api/report/', report, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                assert.equal(JSON.parse(response.responseText)['processedRuns'], 0);
-
-                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
-                    assert.equal(rows[0].count, 0);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should store a report from a valid builder&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                assert.equal(JSON.parse(response.responseText)['processedRuns'], 1);
-                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
-                    assert.equal(rows[0].count, 1);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should treat the slave password as the builder password if there is no matching slave&quot;, function () {
-        addBuilder(emptyReport, function () {
-            emptyReport[0]['slavePassword'] = emptyReport[0]['builderPassword'];
-            delete emptyReport[0]['builderPassword'];
-            postJSON('/api/report/', emptyReport, function (response) {
-                emptyReport[0]['builderPassword'] = emptyReport[0]['slavePassword'];
-                delete emptyReport[0]['slavePassword'];
-
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                assert.equal(JSON.parse(response.responseText)['processedRuns'], 1);
-                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
-                    assert.equal(rows[0].count, 1);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should store a report from a valid slave&quot;, function () {
-        addSlave(emptySlaveReport, function () {
-            postJSON('/api/report/', emptySlaveReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                assert.equal(JSON.parse(response.responseText)['processedRuns'], 1);
-                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
-                    assert.equal(rows[0].count, 1);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should store the builder name but not the builder password&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                queryAndFetchAll('SELECT report_content from reports', [], function (rows) {
-                    var storedContent = JSON.parse(rows[0].report_content);
-                    assert.equal(storedContent['builderName'], emptyReport[0]['builderName']);
-                    assert(!('builderPassword' in storedContent));
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add a slave if there isn't one and the report was authenticated by a builder&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                queryAndFetchAll('SELECT * from build_slaves', [], function (rows) {
-                    assert.strictEqual(rows[0].slave_name, emptyReport[0].slaveName);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add a builder if there isn't one and the report was authenticated by a slave&quot;, function () {
-        addSlave(emptySlaveReport, function () {
-            postJSON('/api/report/', emptySlaveReport, function (response) {
-                queryAndFetchAll('SELECT * from builders', [], function (rows) {
-                    assert.strictEqual(rows[0].builder_name, emptyReport[0].builderName);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add a build&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                queryAndFetchAll('SELECT * from builds', [], function (rows) {
-                    assert.strictEqual(rows[0].build_number, 123);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add the platform&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                queryAndFetchAll('SELECT * from platforms', [], function (rows) {
-                    assert.strictEqual(rows[0].platform_name, 'Mountain Lion');
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add repositories and build revisions&quot;, function () {
-        addBuilder(emptyReport, function () {
-            postJSON('/api/report/', emptyReport, function (response) {
-                queryAndFetchAll('SELECT * FROM repositories', [], function (rows) {
-                    assert.deepEqual(rows.map(function (row) { return row['repository_name']; }), ['OS X', 'WebKit']);
-
-                    var repositoryIdToName = {};
-                    rows.forEach(function (row) { repositoryIdToName[row['repository_id']] = row['repository_name']; });
-                    queryAndFetchAll('SELECT * FROM build_commits, commits WHERE build_commit = commit_id', [], function (rows) {
-                        var repositoryNameToRevisionRow = {};
-                        rows.forEach(function (row) {
-                            repositoryNameToRevisionRow[repositoryIdToName[row['commit_repository']]] = row;
-                        });
-                        assert.equal(repositoryNameToRevisionRow['OS X']['commit_revision'], '10.8.2 12C60');
-                        assert.equal(repositoryNameToRevisionRow['WebKit']['commit_revision'], '141977');
-                        assert.equal(repositoryNameToRevisionRow['WebKit']['commit_time'].toString(),
-                            new Date('2013-02-06 08:55:20.9').toString());
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should not create a duplicate build for the same build number if build times are close&quot;, function () {
-        addBuilder(emptyReport, function () {
-            emptyReport[0]['buildTime'] = '2013-02-28T10:12:04';
-            postJSON('/api/report/', emptyReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-                emptyReport[0]['buildTime'] = '2013-02-28T10:22:03';
-                postJSON('/api/report/', emptyReport, function (response) {
-                    assert.equal(response.statusCode, 200);
-                    assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-                    queryAndFetchAll('SELECT * FROM builds', [], function (rows) {
-                        assert.equal(rows.length, 1);
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should create distinct builds for the same build number if build times are far apart&quot;, function () {
-        addBuilder(emptyReport, function () {
-            emptyReport[0]['buildTime'] = '2013-02-28T10:12:03';
-            postJSON('/api/report/', emptyReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-                queryAndFetchAll('SELECT * FROM builds', [], function (rows) {
-                    assert.equal(rows.length, 1);
-
-                    emptyReport[0]['buildTime'] = '2014-01-20T22:23:34';
-                    postJSON('/api/report/', emptyReport, function (response) {
-                        assert.equal(response.statusCode, 200);
-                        assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-                        queryAndFetchAll('SELECT * FROM builds', [], function (rows) {
-                            assert.equal(rows.length, 2);
-                            notifyDone();
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should reject a report with mismatching revision info&quot;, function () {
-        addBuilder(emptyReport, function () {
-            emptyReport[0]['revisions'] = {
-                &quot;WebKit&quot;: {
-                    &quot;revision&quot;: &quot;141977&quot;,
-                    &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.96Z&quot;
-                }
-            }
-            postJSON('/api/report/', emptyReport, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-
-                emptyReport[0]['revisions'] = {
-                    &quot;WebKit&quot;: {
-                        &quot;revision&quot;: &quot;150000&quot;,
-                        &quot;timestamp&quot;: &quot;2013-05-13T10:50:29.6Z&quot;
-                    }
-                }
-                postJSON('/api/report/', emptyReport, function (response) {
-                    assert.equal(response.statusCode, 200);
-                    assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
-                    assert(response.responseText.indexOf('141977') &gt;= 0);
-                    assert(response.responseText.indexOf('150000') &gt;= 0);
-                    assert.equal(JSON.parse(response.responseText)['failureStored'], true);
-                    assert.equal(JSON.parse(response.responseText)['processedRuns'], 0);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    var reportWithTwoLevelsOfAggregations = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;DummyPageLoading&quot;: {
-                &quot;metrics&quot;: {&quot;Time&quot;: { &quot;aggregators&quot; : [&quot;Arithmetic&quot;], &quot;current&quot;: [300, 310, 320, 330] }},
-                &quot;tests&quot;: {
-                    &quot;apple.com&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [500, 510, 520, 530] }},
-                        &quot;url&quot;: &quot;http://www.apple.com&quot;
-                    },
-                    &quot;webkit.org&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [100, 110, 120, 130] }},
-                        &quot;url&quot;: &quot;http://www.webkit.org&quot;
-                    }
-                }
-            },
-            &quot;DummyBenchmark&quot;: {
-                &quot;metrics&quot;: {&quot;Time&quot;: [&quot;Arithmetic&quot;]},
-                &quot;tests&quot;: {
-                    &quot;DOM&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: [&quot;Geometric&quot;, &quot;Arithmetic&quot;]},
-                        &quot;tests&quot;: {
-                            &quot;ModifyNodes&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]] }}},
-                            &quot;TraverseNodes&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[31, 32, 33, 34, 35], [36, 37, 38, 39, 40], [41, 42, 43, 44, 45]] }}}
-                        }
-                    },
-                    &quot;CSS&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [[101, 102, 103, 104, 105], [106, 107, 108, 109, 110], [111, 112, 113, 114, 115]] }}}
-                }
-            }
-        },
-        &quot;revisions&quot;: {
-            &quot;OS X&quot;: {
-                &quot;revision&quot;: &quot;10.8.2 12C60&quot;
-            },
-            &quot;WebKit&quot;: {
-                &quot;revision&quot;: &quot;141977&quot;,
-                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
-            }
-        }}];
-
-    function addBuilderAndMeanAggregators(report, callback) {
-        addBuilder(report, function () {
-            queryAndFetchAll('INSERT INTO aggregators (aggregator_name, aggregator_definition) values ($1, $2)',
-                ['Arithmetic', 'values.reduce(function (a, b) { return a + b; }) / values.length'], function () {
-                queryAndFetchAll('INSERT INTO aggregators (aggregator_name, aggregator_definition) values ($1, $2)',
-                    ['Geometric', 'Math.pow(values.reduce(function (a, b) { return a * b; }), 1 / values.length)'], callback);
-            });
-        });
-    }
-
-    function fetchRunForMetric(testName, metricName,callback) {
-        queryAndFetchAll('SELECT * FROM test_runs WHERE run_config IN'
-            + '(SELECT config_id FROM test_configurations, test_metrics, tests WHERE config_metric = metric_id AND metric_test = test_id AND'
-            + 'test_name = $1 AND metric_name = $2)',
-            ['Arithmetic', 'values.reduce(function (a, b) { return a + b; }) / values.length'], function () {
-            queryAndFetchAll('INSERT INTO aggregators (aggregator_name, aggregator_definition) values ($1, $2)',
-                ['Geometric', 'Math.pow(values.reduce(function (a, b) { return a * b; }), 1 / values.length)'], callback);
-        });
-    }
-
-    it(&quot;should not reject when aggregators are missing&quot;, function () {
-        addBuilder(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should add tests&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                assert.equal(response.statusCode, 200);
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                queryAndFetchAll('SELECT * FROM tests', [], function (rows) {
-                    assert.deepEqual(rows.map(function (row) { return row['test_name']; }).sort(),
-                        ['CSS', 'DOM', 'DummyBenchmark', 'DummyPageLoading', 'ModifyNodes', 'TraverseNodes', 'apple.com', 'webkit.org']);
-                    emptyReport[0].tests = {};
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should add metrics&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                queryAndFetchAll('SELECT * FROM tests, test_metrics LEFT JOIN aggregators ON metric_aggregator = aggregator_id WHERE metric_test = test_id',
-                    [], function (rows) {
-                    var testNameToMetrics = {};
-                    rows.forEach(function (row) {
-                        if (!(row['test_name'] in testNameToMetrics))
-                            testNameToMetrics[row['test_name']] = new Array;
-                        testNameToMetrics[row['test_name']].push([row['metric_name'], row['aggregator_name']]);
-                    });
-                    assert.deepEqual(testNameToMetrics['CSS'], [['Time', null]]);
-                    assert.deepEqual(testNameToMetrics['DOM'].sort(), [['Time', 'Arithmetic'], ['Time', 'Geometric']]);
-                    assert.deepEqual(testNameToMetrics['DummyBenchmark'], [['Time', 'Arithmetic']]);
-                    assert.deepEqual(testNameToMetrics['DummyPageLoading'], [['Time', 'Arithmetic']]);
-                    assert.deepEqual(testNameToMetrics['ModifyNodes'], [['Time', null]]);
-                    assert.deepEqual(testNameToMetrics['TraverseNodes'], [['Time', null]]);
-                    assert.deepEqual(testNameToMetrics['apple.com'], [['Time', null]]);
-                    assert.deepEqual(testNameToMetrics['webkit.org'], [['Time', null]]);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    function fetchTestRunIterationsForMetric(testName, metricName, callback) {
-         queryAndFetchAll('SELECT * FROM tests, test_metrics, test_configurations, test_runs WHERE metric_test = test_id AND config_metric = metric_id'
-            + ' AND run_config = config_id AND test_name = $1 AND metric_name = $2', [testName, metricName], function (runRows) {
-                assert.equal(runRows.length, 1);
-                var run = runRows[0];
-                queryAndFetchAll('SELECT * FROM run_iterations WHERE iteration_run = $1 ORDER BY iteration_order', [run['run_id']], function (iterationRows) {
-                    callback(run, iterationRows);
-                });
-            });
-    }
-
-    it(&quot;should store run values&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                fetchTestRunIterationsForMetric('apple.com', 'Time', function (run, iterations) {
-                    var runId = run['run_id'];
-                    assert.deepEqual(iterations, [
-                        {iteration_run: runId, iteration_order: 0, iteration_group: null, iteration_value: 500, iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 1, iteration_group: null, iteration_value: 510, iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 2, iteration_group: null, iteration_value: 520, iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 3, iteration_group: null, iteration_value: 530, iteration_relative_time: null}]);
-                    var sum = 500 + 510 + 520 + 530;
-                    assert.equal(run['run_mean_cache'], sum / iterations.length);
-                    assert.equal(run['run_sum_cache'], sum);
-                    assert.equal(run['run_square_sum_cache'], 500 * 500 + 510 * 510 + 520 * 520 + 530 * 530);
-
-                    fetchTestRunIterationsForMetric('CSS', 'Time', function (run, iterations) {
-                        var runId = run['run_id'];
-                        assert.deepEqual(iterations, [
-                            {iteration_run: runId, iteration_order: 0, iteration_group: 0, iteration_value: 101, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 1, iteration_group: 0, iteration_value: 102, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 2, iteration_group: 0, iteration_value: 103, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 3, iteration_group: 0, iteration_value: 104, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 4, iteration_group: 0, iteration_value: 105, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 5, iteration_group: 1, iteration_value: 106, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 6, iteration_group: 1, iteration_value: 107, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 7, iteration_group: 1, iteration_value: 108, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 8, iteration_group: 1, iteration_value: 109, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 9, iteration_group: 1, iteration_value: 110, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 10, iteration_group: 2, iteration_value: 111, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 11, iteration_group: 2, iteration_value: 112, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 12, iteration_group: 2, iteration_value: 113, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 13, iteration_group: 2, iteration_value: 114, iteration_relative_time: null},
-                            {iteration_run: runId, iteration_order: 14, iteration_group: 2, iteration_value: 115, iteration_relative_time: null}]);
-                        var sum = 0;
-                        var squareSum = 0;
-                        for (var value = 101; value &lt;= 115; ++value) {
-                            sum += value;
-                            squareSum += value * value;
-                        }
-                        assert.equal(run['run_mean_cache'], sum / iterations.length);
-                        assert.equal(run['run_sum_cache'], sum);
-                        assert.equal(run['run_square_sum_cache'], squareSum);
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should store aggregated run values&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                fetchTestRunIterationsForMetric('DummyPageLoading', 'Time', function (run, iterations) {
-                    var runId = run['run_id'];
-                    var expectedValues = [(500 + 100) / 2, (510 + 110) / 2, (520 + 120) / 2, (530 + 130) / 2];
-                    assert.deepEqual(iterations, [
-                        {iteration_run: runId, iteration_order: 0, iteration_group: null, iteration_value: expectedValues[0], iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 1, iteration_group: null, iteration_value: expectedValues[1], iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 2, iteration_group: null, iteration_value: expectedValues[2], iteration_relative_time: null},
-                        {iteration_run: runId, iteration_order: 3, iteration_group: null, iteration_value: expectedValues[3], iteration_relative_time: null}]);
-                    var sum = expectedValues.reduce(function (sum, value) { return sum + value; }, 0);
-                    assert.equal(run['run_mean_cache'], sum / iterations.length);
-                    assert.equal(run['run_sum_cache'], sum);
-                    assert.equal(run['run_square_sum_cache'], expectedValues.reduce(function (sum, value) { return sum + value * value; }, 0));
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    it(&quot;should be able to compute the aggregation of aggregated values&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithTwoLevelsOfAggregations, function () {
-            postJSON('/api/report/', reportWithTwoLevelsOfAggregations, function (response) {
-                fetchTestRunIterationsForMetric('DummyBenchmark', 'Time', function (run, iterations) {
-                    var expectedIterations = [];
-                    var sum = 0;
-                    var squareSum = 0;
-                    for (var i = 0; i &lt; 15; ++i) {
-                        var value = i + 1;
-                        var DOMMean = ((10 + value) + (30 + value)) / 2;
-                        var expectedValue = (DOMMean + 100 + value) / 2;
-                        sum += expectedValue;
-                        squareSum += expectedValue * expectedValue;
-                        expectedIterations.push({iteration_run: run['run_id'],
-                            iteration_order: i,
-                            iteration_group: Math.floor(i / 5),
-                            iteration_value: expectedValue,
-                            iteration_relative_time: null});
-                    }
-                    assert.deepEqual(iterations, expectedIterations);
-                    assert.equal(run['run_mean_cache'], sum / iterations.length);
-                    assert.equal(run['run_sum_cache'], sum);
-                    assert.equal(run['run_square_sum_cache'], squareSum);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    var reportWithSameSubtestName = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;Suite1&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [1, 2, 3, 4, 5] }}
-                    },
-                    &quot;test2&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [6, 7, 8, 9, 10] }}
-                    }
-                }
-            },
-            &quot;Suite2&quot;: {
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [11, 12, 13, 14, 15] }}
-                    },
-                    &quot;test2&quot;: {
-                        &quot;metrics&quot;: {&quot;Time&quot;: { &quot;current&quot;: [16, 17, 18, 19, 20] }}
-                    }
-                }
-            }
-        }}];
-
-    it(&quot;should be able to add a report with same subtest name&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithSameSubtestName, function () {
-            postJSON('/api/report/', reportWithSameSubtestName, function (response) {
-                assert.equal(response.statusCode, 200);
-                try {
-                    JSON.parse(response.responseText);
-                } catch (error) {
-                    assert.fail(error, null, response.responseText);
-                }
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                notifyDone();
-            });
-        });
-    });
-
-    it(&quot;should be able to reuse the same test rows&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithSameSubtestName, function () {
-            postJSON('/api/report/', reportWithSameSubtestName, function (response) {
-                assert.equal(response.statusCode, 200);
-                queryAndFetchAll('SELECT * FROM tests', [], function (testRows) {
-                   assert.equal(testRows.length, 6);
-                   reportWithSameSubtestName.buildNumber = &quot;125&quot;;
-                   reportWithSameSubtestName.buildTime = &quot;2013-02-28T12:17:24.1&quot;;
-
-                   postJSON('/api/report/', reportWithSameSubtestName, function (response) {
-                       assert.equal(response.statusCode, 200);
-                       queryAndFetchAll('SELECT * FROM tests', [], function (testRows) {
-                              assert.equal(testRows.length, 6);
-                              notifyDone();
-                          });
-                      });
-                });
-            });
-        });
-    });
-
-    var reportWithSameSingleValue = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-            &quot;suite&quot;: {
-                &quot;metrics&quot;: {&quot;Combined&quot;: [&quot;Arithmetic&quot;]},
-                &quot;tests&quot;: {
-                    &quot;test1&quot;: {
-                        &quot;metrics&quot;: {&quot;Combined&quot;: { &quot;current&quot;: 3 }}
-                    },
-                    &quot;test2&quot;: {
-                        &quot;metrics&quot;: {&quot;Combined&quot;: { &quot;current&quot;: 7 }}
-                    }
-                }
-            },
-        }}];
-
-    it(&quot;should be able to add a report with single value results&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithSameSingleValue, function () {
-            postJSON('/api/report/', reportWithSameSingleValue, function (response) {
-                assert.equal(response.statusCode, 200);
-                try {
-                    JSON.parse(response.responseText);
-                } catch (error) {
-                    assert.fail(error, null, response.responseText);
-                }
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                fetchTestRunIterationsForMetric('test1', 'Combined', function (run, iterations) {
-                    assert.equal(run['run_iteration_count_cache'], 1);
-                    assert.equal(run['run_mean_cache'], 3);
-                    assert.equal(run['run_sum_cache'], 3);
-                    assert.equal(run['run_square_sum_cache'], 9);
-                    fetchTestRunIterationsForMetric('suite', 'Combined', function (run, iterations) {
-                        assert.equal(run['run_iteration_count_cache'], 1);
-                        assert.equal(run['run_mean_cache'], 5);
-                        assert.equal(run['run_sum_cache'], 5);
-                        assert.equal(run['run_square_sum_cache'], 25);
-                        notifyDone();
-                    });
-                });
-            });
-        });
-    });
-
-    var reportWithSameValuePairs = [{
-        &quot;buildNumber&quot;: &quot;123&quot;,
-        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
-        &quot;builderName&quot;: &quot;someBuilder&quot;,
-        &quot;builderPassword&quot;: &quot;somePassword&quot;,
-        &quot;platform&quot;: &quot;Mountain Lion&quot;,
-        &quot;tests&quot;: {
-                &quot;test&quot;: {
-                    &quot;metrics&quot;: {&quot;FrameRate&quot;: { &quot;current&quot;: [[[0, 4], [100, 5], [205, 3]]] }}
-                },
-            },
-        }];
-
-    it(&quot;should be able to add a report with (relative time, value) pairs&quot;, function () {
-        addBuilderAndMeanAggregators(reportWithSameValuePairs, function () {
-            postJSON('/api/report/', reportWithSameValuePairs, function (response) {
-                assert.equal(response.statusCode, 200);
-                try {
-                    JSON.parse(response.responseText);
-                } catch (error) {
-                    assert.fail(error, null, response.responseText);
-                }
-                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
-                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
-                fetchTestRunIterationsForMetric('test', 'FrameRate', function (run, iterations) {
-                    assert.equal(run['run_iteration_count_cache'], 3);
-                    assert.equal(run['run_mean_cache'], 4);
-                    assert.equal(run['run_sum_cache'], 12);
-                    assert.equal(run['run_square_sum_cache'], 16 + 25 + 9);
-                    var runId = run['run_id'];
-                    assert.deepEqual(iterations, [
-                        {iteration_run: runId, iteration_order: 0, iteration_group: null, iteration_value: 4, iteration_relative_time: 0},
-                        {iteration_run: runId, iteration_order: 1, iteration_group: null, iteration_value: 5, iteration_relative_time: 100},
-                        {iteration_run: runId, iteration_order: 2, iteration_group: null, iteration_value: 3, iteration_relative_time: 205}]);
-                    notifyDone();
-                });
-            });
-        });
-    });
-
-    var reportsUpdatingDifferentTests = [
-        [{
-            &quot;buildNumber&quot;: &quot;123&quot;,
-            &quot;buildTime&quot;: &quot;2013-02-28T10:12:03&quot;,
-            &quot;builderName&quot;: &quot;someBuilder&quot;,
-            &quot;builderPassword&quot;: &quot;somePassword&quot;,
-            &quot;platform&quot;: &quot;Mountain Lion&quot;,
-            &quot;tests&quot;: {&quot;test1&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
-        }],
-        [{
-            &quot;buildNumber&quot;: &quot;124&quot;,
-            &quot;buildTime&quot;: &quot;2013-02-28T11:31:21&quot;,
-            &quot;builderName&quot;: &quot;someBuilder&quot;,
-            &quot;builderPassword&quot;: &quot;somePassword&quot;,
-            &quot;platform&quot;: &quot;Mountain Lion&quot;,
-            &quot;tests&quot;: {&quot;test2&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
-        }],
-        [{
-            &quot;buildNumber&quot;: &quot;125&quot;,
-            &quot;buildTime&quot;: &quot;2013-02-28T12:45:34&quot;,
-            &quot;builderName&quot;: &quot;someBuilder&quot;,
-            &quot;builderPassword&quot;: &quot;somePassword&quot;,
-            &quot;platform&quot;: &quot;Mountain Lion&quot;,
-            &quot;tests&quot;: {&quot;test1&quot;: {&quot;metrics&quot;: {&quot;Time&quot;: {&quot;current&quot;: 3}}}}
-        }],
-    ];
-
-    function fetchTestConfig(testName, metricName, callback) {
-         queryAndFetchAll('SELECT * FROM tests, test_metrics, test_configurations WHERE test_id = metric_test AND metric_id = config_metric'
-            + ' AND test_name = $1 AND metric_name = $2', [testName, metricName], function (runRows) {
-                assert.equal(runRows.length, 1);
-                callback(runRows[0]);
-            });
-    }
-
-    it(&quot;should update the last modified date of test configurations with new runs&quot;, function () {
-        addBuilder(reportsUpdatingDifferentTests[0], function () {
-            postJSON('/api/report/', reportsUpdatingDifferentTests[0], function (response) {
-                assert.equal(response.statusCode, 200);
-                fetchTestConfig('test1', 'Time', function (originalConfig) {
-                    postJSON('/api/report/', reportsUpdatingDifferentTests[2], function (response) {
-                        assert.equal(response.statusCode, 200);
-                        fetchTestConfig('test1', 'Time', function (config) {
-                            assert.notEqual(+originalConfig['config_runs_last_modified'], +config['config_runs_last_modified']);
-                            notifyDone();
-                        });
-                    });
-                });
-            });
-        });
-    });
-
-    it(&quot;should update the last modified date of unrelated test configurations&quot;, function () {
-        addBuilder(reportsUpdatingDifferentTests[0], function () {
-            postJSON('/api/report/', reportsUpdatingDifferentTests[0], function (response) {
-                assert.equal(response.statusCode, 200);
-                fetchTestConfig('test1', 'Time', function (originalConfig) {
-                    postJSON('/api/report/', reportsUpdatingDifferentTests[1], function (response) {
-                        assert.equal(response.statusCode, 200);
-                        fetchTestConfig('test1', 'Time', function (config) {
-                            assert.equal(+originalConfig['config_runs_last_modified'], +config['config_runs_last_modified']);
-                            notifyDone();
-                        });
-                    });
-                });
-            });
-        });
-    });
-});
</del></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtoolsjsdatabasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tools/js/database.js (199190 => 199191)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tools/js/database.js        2016-04-07 22:02:30 UTC (rev 199190)
+++ trunk/Websites/perf.webkit.org/tools/js/database.js        2016-04-07 22:10:50 UTC (rev 199191)
</span><span class="lines">@@ -1,7 +1,7 @@
</span><span class="cx"> &quot;use strict&quot;;
</span><span class="cx"> 
</span><del>-var pg = require('pg');
-var config = require('./config.js');
</del><ins>+const pg = require('pg');
+const config = require('./config.js');
</ins><span class="cx"> 
</span><span class="cx"> class Database {
</span><span class="cx">     constructor(databaseName)
</span><span class="lines">@@ -23,11 +23,8 @@
</span><span class="cx">         let connectionString = `tcp://${username}:${password}@${host}:${port}/${this._databaseName}`;
</span><span class="cx"> 
</span><span class="cx">         let client = new pg.Client(connectionString);
</span><del>-        if (!options || !options.keepAlive) {
-            client.on('drain', function () {
-                client.end();
-            });
-        }
</del><ins>+        if (!options || !options.keepAlive)
+            client.on('drain', this.disconnect.bind(this));
</ins><span class="cx"> 
</span><span class="cx">         this._client = client;
</span><span class="cx"> 
</span><span class="lines">@@ -62,6 +59,56 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    selectAll(table, columnToSortBy)
+    {
+        return this.selectRows(table, {}, {sortBy: columnToSortBy});
+    }
+
+    selectFirstRow(table, params, columnToSortBy)
+    {
+        return this.selectRows(table, params, {sortBy: columnToSortBy, limit: 1}).then(function (rows) {
+            return rows[0];
+        });
+    }
+
+    selectRows(table, params, options)
+    {
+        let prefix = '';
+        if (table in tableToPrefixMap)
+            prefix = tableToPrefixMap[table] + '_';
+
+        options = options || {};
+
+        let columnNames = [];
+        let placeholders = [];
+        let values = [];
+        for (let name in params) {
+            columnNames.push(prefix + name);
+            placeholders.push('$' + columnNames.length);
+            values.push(params[name]);
+        }
+
+        let qualifier = '';
+        if (columnNames.length)
+            qualifier += ` WHERE (${columnNames.join(',')}) = (${placeholders.join(',')})`;
+        qualifier += ` ORDER BY ${prefix}${options.sortBy || 'id'}`;
+        if (options &amp;&amp; options.limit)
+            qualifier += ` LIMIT ${options.limit}`;
+
+        return this.query(`SELECT * FROM ${table}${qualifier}`, values).then(function (result) {
+            return result.rows.map(function (row) {
+                let formattedResult = {};
+                for (let columnName in row) {
+                    let formattedName = columnName;
+                    if (formattedName.startsWith(prefix))
+                        formattedName = formattedName.substring(prefix.length);
+                    formattedResult[formattedName] = row[columnName];
+                }
+                return formattedResult;
+            });
+        });
+    }
+
</ins><span class="cx">     insert(table, parameters)
</span><span class="cx">     {
</span><span class="cx">         let columnNames = [];
</span><span class="lines">@@ -78,7 +125,7 @@
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-let tableToPrefixMap = {
</del><ins>+const tableToPrefixMap = {
</ins><span class="cx">     'aggregators': 'aggregator',
</span><span class="cx">     'analysis_tasks': 'task',
</span><span class="cx">     'analysis_test_groups': 'testgroup',
</span><span class="lines">@@ -86,16 +133,21 @@
</span><span class="cx">     'build_triggerables': 'triggerable',
</span><span class="cx">     'build_requests': 'request',
</span><span class="cx">     'build_slaves': 'slave',
</span><ins>+    'builds': 'build',
</ins><span class="cx">     'builders': 'builder',
</span><span class="cx">     'commits': 'commit',
</span><ins>+    'committers': 'committer',
</ins><span class="cx">     'test_configurations': 'config',
</span><span class="cx">     'test_metrics': 'metric',
</span><ins>+    'test_runs': 'run',
</ins><span class="cx">     'tests': 'test',
</span><span class="cx">     'tracker_repositories': 'tracrepo',
</span><span class="cx">     'platforms': 'platform',
</span><ins>+    'reports': 'report',
</ins><span class="cx">     'repositories': 'repository',
</span><span class="cx">     'root_sets': 'rootset',
</span><span class="cx">     'roots': 'root',
</span><ins>+    'run_iterations': 'iteration',
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> if (typeof module != 'undefined')
</span></span></pre>
</div>
</div>

</body>
</html>