<!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>[198824] 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/198824">198824</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-03-29 20:16:29 -0700 (Tue, 29 Mar 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>BuildbotSyncer should be able to fetch JSON from buildbot
https://bugs.webkit.org/show_bug.cgi?id=155921

Reviewed by Joseph Pecoraro.

Added BuildbotSyncer.pullBuildbot which fetches pending, in-progress, and finished builds from buildbot
with lots of unit tests as this has historically been a source of subtle bugs in the old script.

New implementation fixes a subtle bug in the old pythons script which overlooked the possibility that
the state of some builds may change between each HTTP request. In the old script, we fetched the list
of the pending builds, and requested -1, -2, etc... builds for N times. But between each request,
a pending build may start running or an in-progress build finish and shift the offset by one. The new
script avoids this problem by first requesting all pending builds, then all in-progress and finished
builds in a single HTTP request. The results are then merged so that entries for in-progress and
finished builds would override the entries for pending builds if they overlap.

Also renamed RemoteAPI.fetchJSON to RemoteAPI.getJSON to match v3 UI's RemoteAPI. This change makes
the class interchangeable between frontend (public/v3/remote.js) and backend (tools/js/remote.js).

* server-tests/api-build-requests-tests.js:
* server-tests/api-manifest.js:
* tools/js/buildbot-syncer.js:
(BuildbotBuildEntry): Removed the unused argument &quot;type&quot;. Store the syncer as an instance variable as
we'd need to query for the buildbot URL. Also fixed a bug that _isInProgress was true for finished
builds as 'currentStep' is always defined but null in those builds.
(BuildbotBuildEntry.prototype.buildNumber): Added.
(BuildbotBuildEntry.prototype.isPending): Added.
(BuildbotBuildEntry.prototype.hasFinished): Added.
(BuildbotSyncer.prototype.pullBuildbot): Added. Fetches pending builds first and then finished builds.
(BuildbotSyncer.prototype._pullRecentBuilds): Added. Fetches in-progress and finished builds.
(BuildbotSyncer.prototype.urlForPendingBuildsJSON): Added.
(BuildbotSyncer.prototype.urlForBuildJSON): Added.
(BuildbotSyncer.prototype.url): Added.
(BuildbotSyncer.prototype.urlForBuildNumber): Added.
* tools/js/remote.js:
(RemoteAPI.prototype.getJSON): Renamed from fetchJSON.
(RemoteAPI.prototype.getJSONWithStatus): Renamed from fetchJSONWithStatus.
* tools/js/v3-models.js: Load tools/js/remote.js instead of public/v3/remote.js inside node.
* unit-tests/buildbot-syncer-tests.js: Added a lot of unit tests for BuildbotSyncer.pullBuildbot
(samplePendingBuild):
(sampleInProgressBuild): Added.
(sampleFinishedBuild): Added.
* unit-tests/resources/mock-remote-api.js:
(global.RemoteAPI.getJSON): Use the same mock as getJSONWithStatus.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</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="#trunkWebsitesperfwebkitorgtoolsjsbuildbotsyncerjs">trunk/Websites/perf.webkit.org/tools/js/buildbot-syncer.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtoolsjsremotejs">trunk/Websites/perf.webkit.org/tools/js/remote.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtoolsjsv3modelsjs">trunk/Websites/perf.webkit.org/tools/js/v3-models.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgunittestsbuildbotsyncertestsjs">trunk/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgunittestsresourcesmockremoteapijs">trunk/Websites/perf.webkit.org/unit-tests/resources/mock-remote-api.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 (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -1,3 +1,50 @@
</span><ins>+2016-03-29  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        BuildbotSyncer should be able to fetch JSON from buildbot
+        https://bugs.webkit.org/show_bug.cgi?id=155921
+
+        Reviewed by Joseph Pecoraro.
+
+        Added BuildbotSyncer.pullBuildbot which fetches pending, in-progress, and finished builds from buildbot
+        with lots of unit tests as this has historically been a source of subtle bugs in the old script.
+
+        New implementation fixes a subtle bug in the old pythons script which overlooked the possibility that
+        the state of some builds may change between each HTTP request. In the old script, we fetched the list
+        of the pending builds, and requested -1, -2, etc... builds for N times. But between each request,
+        a pending build may start running or an in-progress build finish and shift the offset by one. The new
+        script avoids this problem by first requesting all pending builds, then all in-progress and finished
+        builds in a single HTTP request. The results are then merged so that entries for in-progress and
+        finished builds would override the entries for pending builds if they overlap.
+
+        Also renamed RemoteAPI.fetchJSON to RemoteAPI.getJSON to match v3 UI's RemoteAPI. This change makes
+        the class interchangeable between frontend (public/v3/remote.js) and backend (tools/js/remote.js).
+
+        * server-tests/api-build-requests-tests.js:
+        * server-tests/api-manifest.js:
+        * tools/js/buildbot-syncer.js:
+        (BuildbotBuildEntry): Removed the unused argument &quot;type&quot;. Store the syncer as an instance variable as
+        we'd need to query for the buildbot URL. Also fixed a bug that _isInProgress was true for finished
+        builds as 'currentStep' is always defined but null in those builds.
+        (BuildbotBuildEntry.prototype.buildNumber): Added.
+        (BuildbotBuildEntry.prototype.isPending): Added.
+        (BuildbotBuildEntry.prototype.hasFinished): Added.
+        (BuildbotSyncer.prototype.pullBuildbot): Added. Fetches pending builds first and then finished builds.
+        (BuildbotSyncer.prototype._pullRecentBuilds): Added. Fetches in-progress and finished builds.
+        (BuildbotSyncer.prototype.urlForPendingBuildsJSON): Added.
+        (BuildbotSyncer.prototype.urlForBuildJSON): Added.
+        (BuildbotSyncer.prototype.url): Added.
+        (BuildbotSyncer.prototype.urlForBuildNumber): Added.
+        * tools/js/remote.js:
+        (RemoteAPI.prototype.getJSON): Renamed from fetchJSON.
+        (RemoteAPI.prototype.getJSONWithStatus): Renamed from fetchJSONWithStatus.
+        * tools/js/v3-models.js: Load tools/js/remote.js instead of public/v3/remote.js inside node.
+        * unit-tests/buildbot-syncer-tests.js: Added a lot of unit tests for BuildbotSyncer.pullBuildbot
+        (samplePendingBuild):
+        (sampleInProgressBuild): Added.
+        (sampleFinishedBuild): Added.
+        * unit-tests/resources/mock-remote-api.js:
+        (global.RemoteAPI.getJSON): Use the same mock as getJSONWithStatus.
+
</ins><span class="cx"> 2016-03-24  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Migrate admin-regenerate-manifest.js to mocha.js and test v3 UI code
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapibuildrequeststestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -8,7 +8,7 @@
</span><span class="cx">     TestServer.inject();
</span><span class="cx"> 
</span><span class="cx">     it('should return &quot;TriggerableNotFound&quot; when the database is empty', function (done) {
</span><del>-        TestServer.remoteAPI().fetchJSON('/api/build-requests/build-webkit').then(function (content) {
</del><ins>+        TestServer.remoteAPI().getJSON('/api/build-requests/build-webkit').then(function (content) {
</ins><span class="cx">             assert.equal(content['status'], 'TriggerableNotFound');
</span><span class="cx">             done();
</span><span class="cx">         }).catch(done);
</span><span class="lines">@@ -18,7 +18,7 @@
</span><span class="cx">         TestServer.database().connect().then(function () {
</span><span class="cx">             return TestServer.database().insert('build_triggerables', {name: 'build-webkit'});
</span><span class="cx">         }).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSON('/api/build-requests/build-webkit');
</del><ins>+            return TestServer.remoteAPI().getJSON('/api/build-requests/build-webkit');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.equal(content['status'], 'OK');
</span><span class="cx">             assert.deepEqual(content['buildRequests'], []);
</span><span class="lines">@@ -61,7 +61,7 @@
</span><span class="cx">         db.connect().then(function () {
</span><span class="cx">             return addMockData(db);
</span><span class="cx">         }).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSONWithStatus('/api/build-requests/build-webkit');
</del><ins>+            return TestServer.remoteAPI().getJSONWithStatus('/api/build-requests/build-webkit');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.deepEqual(Object.keys(content).sort(), ['buildRequests', 'rootSets', 'roots', 'status']);
</span><span class="cx"> 
</span><span class="lines">@@ -119,7 +119,7 @@
</span><span class="cx">         db.connect().then(function () {
</span><span class="cx">             return addMockData(db);
</span><span class="cx">         }).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSONWithStatus('/api/build-requests/build-webkit?useLegacyIdResolution=true');
</del><ins>+            return TestServer.remoteAPI().getJSONWithStatus('/api/build-requests/build-webkit?useLegacyIdResolution=true');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.deepEqual(Object.keys(content).sort(), ['buildRequests', 'rootSets', 'roots', 'status']);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapimanifestjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><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><del>-        TestServer.remoteAPI().fetchJSON('/api/manifest').then(function (manifest) {
</del><ins>+        TestServer.remoteAPI().getJSON('/api/manifest').then(function (manifest) {
</ins><span class="cx">             assert.deepEqual(Object.keys(manifest).sort(), ['all', 'bugTrackers', 'builders', 'dashboard', 'dashboards',
</span><span class="cx">                 'elapsedTime', 'metrics', 'repositories', 'siteTitle', 'status', 'tests']);
</span><span class="cx"> 
</span><span class="lines">@@ -49,7 +49,7 @@
</span><span class="cx">     it(&quot;should generate manifest with bug trackers without repositories&quot;, function (done) {
</span><span class="cx">         TestServer.database().connect();
</span><span class="cx">         TestServer.database().insert('bug_trackers', bugzillaData).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
</del><ins>+            return TestServer.remoteAPI().getJSON('/api/manifest');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.deepEqual(content.bugTrackers, {1: {name: 'Bugzilla', bugUrl: 'https://webkit.org/b/$number',
</span><span class="cx">                 newBugUrl: 'https://bugs.webkit.org/', repositories: null}});
</span><span class="lines">@@ -78,7 +78,7 @@
</span><span class="cx">             db.insert('tracker_repositories', {tracker: radarData.id, repository: 9}),
</span><span class="cx">             db.insert('tracker_repositories', {tracker: radarData.id, repository: 22}),
</span><span class="cx">         ]).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
</del><ins>+            return TestServer.remoteAPI().getJSON('/api/manifest');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             let manifest = Manifest._didFetchManifest(content);
</span><span class="cx"> 
</span><span class="lines">@@ -119,7 +119,7 @@
</span><span class="cx">                 build_url: 'https://build.webkit.org/builders/$builderName/build/$buildNumber'}),
</span><span class="cx">             db.insert('builders', {id: 2, name: 'SomeOtherBuilder', password_hash: 'b'})
</span><span class="cx">         ]).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
</del><ins>+            return TestServer.remoteAPI().getJSON('/api/manifest');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.deepEqual(content.builders, {
</span><span class="cx">                 '1': {name: 'SomeBuilder', buildUrl: 'https://build.webkit.org/builders/$builderName/build/$buildNumber'},
</span><span class="lines">@@ -166,7 +166,7 @@
</span><span class="cx">             db.insert('test_configurations', {id: 106, metric: 5, platform: 23, type: 'current'}),
</span><span class="cx">             db.insert('test_configurations', {id: 107, metric: 5, platform: 23, type: 'baseline'}),
</span><span class="cx">         ]).then(function () {
</span><del>-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
</del><ins>+            return TestServer.remoteAPI().getJSON('/api/manifest');
</ins><span class="cx">         }).then(function (content) {
</span><span class="cx">             assert.deepEqual(content.tests, {
</span><span class="cx">                 &quot;1&quot;: {&quot;name&quot;: &quot;SomeTest&quot;, &quot;parentId&quot;: null, &quot;url&quot;: null},
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtoolsjsbuildbotsyncerjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tools/js/buildbot-syncer.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tools/js/buildbot-syncer.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/tools/js/buildbot-syncer.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -5,19 +5,20 @@
</span><span class="cx"> require('./v3-models.js');
</span><span class="cx"> 
</span><span class="cx"> class BuildbotBuildEntry {
</span><del>-    constructor(syncer, type, rawData)
</del><ins>+    constructor(syncer, rawData)
</ins><span class="cx">     {
</span><span class="cx">         assert.equal(syncer.builderName(), rawData['builderName']);
</span><span class="cx"> 
</span><ins>+        this._syncer = syncer;
</ins><span class="cx">         this._slaveName = null;
</span><span class="cx">         this._buildRequestId = null;
</span><del>-        this._isInProgress = 'currentStep' in rawData;
</del><ins>+        this._isInProgress = rawData['currentStep'] || (rawData['times'] &amp;&amp; !rawData['times'][1]);
</ins><span class="cx">         this._buildNumber = rawData['number'];
</span><span class="cx"> 
</span><del>-        for (var propertyTuple of (rawData['properties'] || [])) {
</del><ins>+        for (let propertyTuple of (rawData['properties'] || [])) {
</ins><span class="cx">             // e.g. ['build_request_id', '16733', 'Force Build Form']
</span><del>-            var name = propertyTuple[0];
-            var value = propertyTuple[1];
</del><ins>+            let name = propertyTuple[0];
+            let value = propertyTuple[1];
</ins><span class="cx">             if (name == syncer._slavePropertyName)
</span><span class="cx">                 this._slaveName = value;
</span><span class="cx">             else if (name == syncer._buildRequestPropertyName)
</span><span class="lines">@@ -25,9 +26,13 @@
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    buildNumber() { return this._buildNumber; }
</ins><span class="cx">     slaveName() { return this._slaveName; }
</span><span class="cx">     buildRequestId() { return this._buildRequestId; }
</span><ins>+    isPending() { return !this._buildNumber; }
</ins><span class="cx">     isInProgress() { return this._isInProgress; }
</span><ins>+    hasFinished() { return !this.isPending() &amp;&amp; !this.isInProgress(); }
+    url() { return this.isPending() ? this._syncer.url() : this._syncer.urlForBuildNumber(this._buildNumber); }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> class BuildbotSyncer {
</span><span class="lines">@@ -47,24 +52,57 @@
</span><span class="cx">     builderName() { return this._builderName; }
</span><span class="cx">     platformName() { return this._platformName; }
</span><span class="cx"> 
</span><del>-    fetchPendingRequests()
</del><ins>+    pullBuildbot(count)
</ins><span class="cx">     {
</span><del>-        return RemoteAPI.fetchJSON(`${this._url}/json/builders/${this._name}/pendingBuilds`).then(function (content) {
-            var requests = [];
-            for (var entry of content) {
-                var properties = entry['properties'];
-                if (!properties)
-                    continue;
-                for (var propertyTuple of properties) {
-                    // e.g. ['build_request_id', '16733', 'Force Build Form']
-                    if (propertyTuple[0] == this._buildRequestPropertyName)
-                        requests.push(propertyTuple[1]);
-                }
</del><ins>+        let self = this;
+        return RemoteAPI.getJSON(this.urlForPendingBuildsJSON()).then(function (content) {
+            let pendingEntries = content.map(function (entry) { return new BuildbotBuildEntry(self, entry); });
+
+            return self._pullRecentBuilds(count).then(function (entries) {
+                let entryByRequest = {};
+
+                for (let entry of pendingEntries)
+                    entryByRequest[entry.buildRequestId()] = entry;
+
+                for (let entry of entries)
+                    entryByRequest[entry.buildRequestId()] = entry;
+
+                return entryByRequest;
+            });
+        });
+    }
+
+    _pullRecentBuilds(count)
+    {
+        if (!count)
+            return Promise.resolve([]);
+
+        let selectedBuilds = new Array(count);
+        for (let i = 0; i &lt; count; i++)
+            selectedBuilds[i] = -i - 1;
+
+        let self = this;
+        return RemoteAPI.getJSON(this.urlForBuildJSON(selectedBuilds)).then(function (content) {
+            let entries = [];
+            for (let index of selectedBuilds) {
+                let entry = content[index];
+                if (entry &amp;&amp; !entry['error'])
+                    entries.push(new BuildbotBuildEntry(self, entry));
</ins><span class="cx">             }
</span><del>-            return requests;
</del><ins>+            return entries;
</ins><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    urlForPendingBuildsJSON() { return `${this._url}/json/builders/${this._builderName}/pendingBuilds`; }
+    urlForBuildJSON(selectedBuilds)
+    {
+        return `${this._url}/json/builders/${this._builderName}/builds/?`
+            + selectedBuilds.map(function (number) { return 'select=' + number; }).join('&amp;');
+    }
+
+    url() { return `${this._url}/builders/${this._builderName}/`; }
+    urlForBuildNumber(number) { return `${this._url}/builders/${this._builderName}/builds/${number}`; }
+
</ins><span class="cx">     _propertiesForBuildRequest(buildRequest)
</span><span class="cx">     {
</span><span class="cx">         console.assert(buildRequest instanceof BuildRequest);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtoolsjsremotejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tools/js/remote.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tools/js/remote.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/tools/js/remote.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx">         this._server = server;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    fetchJSON(path, data)
</del><ins>+    getJSON(path, data)
</ins><span class="cx">     {
</span><span class="cx">         let contentType = null;
</span><span class="cx">         if (data) {
</span><span class="lines">@@ -34,9 +34,9 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    fetchJSONWithStatus(path, data)
</del><ins>+    getJSONWithStatus(path, data)
</ins><span class="cx">     {
</span><del>-        return this.fetchJSON(path, data).then(function (result) {
</del><ins>+        return this.getJSON(path, data).then(function (result) {
</ins><span class="cx">             if (result['status'] != 'OK')
</span><span class="cx">                 return Promise.reject(result);
</span><span class="cx">             return result;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtoolsjsv3modelsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tools/js/v3-models.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tools/js/v3-models.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/tools/js/v3-models.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -29,4 +29,7 @@
</span><span class="cx"> 
</span><span class="cx"> importFromV3('instrumentation.js', 'Instrumentation');
</span><span class="cx"> 
</span><ins>+// RemoteAPI has a different implementation in node since XHR isn't available.
+global.RemoteAPI = require('./remote.js').RemoteAPI;
+
</ins><span class="cx"> global.Statistics = require('../../public/shared/statistics.js');
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgunittestsbuildbotsyncertestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -2,8 +2,8 @@
</span><span class="cx"> 
</span><span class="cx"> let assert = require('assert');
</span><span class="cx"> 
</span><ins>+require('../tools/js/v3-models.js');
</ins><span class="cx"> require('./resources/mock-remote-api.js');
</span><del>-require('../tools/js/v3-models.js');
</del><span class="cx"> require('./resources/mock-v3-models.js');
</span><span class="cx"> 
</span><span class="cx"> let BuildbotBuildEntry = require('../tools/js/buildbot-syncer.js').BuildbotBuildEntry;
</span><span class="lines">@@ -83,12 +83,13 @@
</span><span class="cx">     return request;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-let samplePendingBuilds = [
-    {
</del><ins>+function samplePendingBuild(buildRequestId)
+{
+    return {
</ins><span class="cx">         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
</span><span class="cx">         'builds': [],
</span><span class="cx">         'properties': [
</span><del>-            ['build_request_id', '16733', 'Force Build Form'],
</del><ins>+            ['build_request_id', buildRequestId || '16733', 'Force Build Form'],
</ins><span class="cx">             ['desired_image', '13A452', 'Force Build Form'],
</span><span class="cx">             ['owner', '&lt;unknown&gt;', 'Force Build Form'],
</span><span class="cx">             ['test_name', 'speedometer', 'Force Build Form'],
</span><span class="lines">@@ -98,7 +99,7 @@
</span><span class="cx">                 JSON.stringify(sampleRootSetData),
</span><span class="cx">                 'Force Build Form'
</span><span class="cx">             ],
</span><del>-            ['scheduler', 'ABTest-iPad-Performance-Tests-ForceScheduler', 'Scheduler']
</del><ins>+            ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler']
</ins><span class="cx">         ],
</span><span class="cx">         'source': {
</span><span class="cx">             'branch': '',
</span><span class="lines">@@ -110,14 +111,176 @@
</span><span class="cx">             'revision': ''
</span><span class="cx">         },
</span><span class="cx">         'submittedAt': 1458704983
</span><del>-    }
-];
</del><ins>+    };
+}
</ins><span class="cx"> 
</span><ins>+function sampleInProgressBuild()
+{
+    return {
+        'blame': [],
+        'builderName': 'ABTest-iPad-RunBenchmark-Tests',
+        'currentStep': {
+            'eta': 0.26548067698460565,
+            'expectations': [['output', 845, 1315.0]],
+            'hidden': false,
+            'isFinished': false,
+            'isStarted': true,
+            'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
+            'name': 'Some step',
+            'results': [null,[]],
+            'statistics': {},
+            'step_number': 1,
+            'text': [''],
+            'times': [1458718657.581628, null],
+            'urls': {}
+        },
+        'eta': 6497.991612434387,
+        'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
+        'number': 614,
+        'properties': [
+            ['build_request_id', '16733', 'Force Build Form'],
+            ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
+            ['buildnumber', 614, 'Build'],
+            ['desired_image', '13A452', 'Force Build Form'],
+            ['owner', '&lt;unknown&gt;', 'Force Build Form'],
+            ['reason', 'force build', 'Force Build Form'],
+            ['roots_dict', JSON.stringify(sampleRootSetData), 'Force Build Form'],
+            ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
+            ['slavename', 'ABTest-iPad-0', 'BuildSlave'],
+        ],
+        'reason': 'A build was forced by \'&lt;unknown&gt;\': force build',
+        'results': null,
+        'slave': 'ABTest-iPad-0',
+        'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
+        'steps': [
+            {
+                'eta': null,
+                'expectations': [['output',2309,2309.0]],
+                'hidden': false,
+                'isFinished': true,
+                'isStarted': true,
+                'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
+                'name': 'Finished step',
+                'results': [0, []],
+                'statistics': {},
+                'step_number': 0,
+                'text': [''],
+                'times': [1458718655.419865, 1458718655.453633],
+                'urls': {}
+            },
+            {
+                'eta': 0.26548067698460565,
+                'expectations': [['output', 845, 1315.0]],
+                'hidden': false,
+                'isFinished': false,
+                'isStarted': true,
+                'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
+                'name': 'Some step',
+                'results': [null,[]],
+                'statistics': {},
+                'step_number': 1,
+                'text': [''],
+                'times': [1458718657.581628, null],
+                'urls': {}
+            },
+            {
+                'eta': null,
+                'expectations': [['output', null, null]],
+                'hidden': false,
+                'isFinished': false,
+                'isStarted': false,
+                'logs': [],
+                'name': 'Some other step',
+                'results': [null, []],
+                'statistics': {},
+                'step_number': 2,
+                'text': [],
+                'times': [null, null],
+                'urls': {}
+            },
+        ],
+        'text': [],
+        'times': [1458718655.415821, null]
+    };
+}
+
+function sampleFinishedBuild(buildRequestId)
+{
+    return {
+        'blame': [],
+        'builderName': 'ABTest-iPad-RunBenchmark-Tests',
+        'currentStep': null,
+        'eta': null,
+        'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755/steps/shell/logs/stdio']],
+        'number': 1755,
+        'properties': [
+            ['build_request_id', buildRequestId || '18935', 'Force Build Form'],
+            ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
+            ['buildnumber', 1755, 'Build'],
+            ['desired_image', '13A452', 'Force Build Form'],
+            ['owner', '&lt;unknown&gt;', 'Force Build Form'],
+            ['reason', 'force build', 'Force Build Form'],
+            ['roots_dict', JSON.stringify(sampleRootSetData), 'Force Build Form'],
+            ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
+            ['slavename', 'ABTest-iPad-0', 'BuildSlave'],
+        ],
+        'reason': 'A build was forced by \'&lt;unknown&gt;\': force build',
+        'results': 2,
+        'slave': 'ABTest-iPad-0',
+        'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
+        'steps': [
+            {
+                'eta': null,
+                'expectations': [['output',2309,2309.0]],
+                'hidden': false,
+                'isFinished': true,
+                'isStarted': true,
+                'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
+                'name': 'Finished step',
+                'results': [0, []],
+                'statistics': {},
+                'step_number': 0,
+                'text': [''],
+                'times': [1458718655.419865, 1458718655.453633],
+                'urls': {}
+            },
+            {
+                'eta': null,
+                'expectations': [['output', 845, 1315.0]],
+                'hidden': false,
+                'isFinished': true,
+                'isStarted': true,
+                'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
+                'name': 'Some step',
+                'results': [null,[]],
+                'statistics': {},
+                'step_number': 1,
+                'text': [''],
+                'times': [1458718657.581628, null],
+                'urls': {}
+            },
+            {
+                'eta': null,
+                'expectations': [['output', null, null]],
+                'hidden': false,
+                'isFinished': true,
+                'isStarted': true,
+                'logs': [],
+                'name': 'Some other step',
+                'results': [null, []],
+                'statistics': {},
+                'step_number': 2,
+                'text': [],
+                'times': [null, null],
+                'urls': {}
+            },
+        ],
+        'text': [],
+        'times': [1458937478.25837, 1458946147.173785]
+    };
+}
+
</ins><span class="cx"> describe('BuildbotSyncer', function () {
</span><del>-    describe('fetchPendingBuilds', function () {
-        BuildbotSyncer.fetchPendingBuilds
-    });
-
</del><span class="cx">     describe('_loadConfig', function () {
</span><span class="cx"> 
</span><span class="cx">         function smallConfiguration()
</span><span class="lines">@@ -292,4 +455,226 @@
</span><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> 
</span><del>-});
</del><span class="cx">\ No newline at end of file
</span><ins>+    describe('pullBuildbot', function () {
+        it('should fetch pending builds from the right URL', function () {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+            assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
+            let expectedURL = 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds';
+            assert.equal(syncer.urlForPendingBuildsJSON(), expectedURL);
+            syncer.pullBuildbot();
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, expectedURL);
+        });
+
+        it('should fetch recent builds once pending builds have been fetched', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+            assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
+
+            syncer.pullBuildbot(1);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
+            requests[0].resolve([]);
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                assert.equal(requests[1].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1');
+                done();
+            }).catch(done);
+        });
+
+        it('should fetch the right number of recent builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            syncer.pullBuildbot(3);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
+            requests[0].resolve([]);
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                assert.equal(requests[1].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1&amp;select=-2&amp;select=-3');
+                done();
+            }).catch(done);
+        });
+
+        it('should create BuildbotBuildEntry for pending builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+            let promise = syncer.pullBuildbot();
+            requests[0].resolve([samplePendingBuild()]);
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['16733']);
+                let entry = entries['16733'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.ok(!entry.buildNumber());
+                assert.ok(!entry.slaveName());
+                assert.equal(entry.buildRequestId(), 16733);
+                assert.ok(entry.isPending());
+                assert.ok(!entry.isInProgress());
+                assert.ok(!entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
+                done();
+            }).catch(done);
+        });
+
+        it('should create BuildbotBuildEntry for in-progress builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            let promise = syncer.pullBuildbot(1);
+            assert.equal(requests.length, 1);
+            requests[0].resolve([]);
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                requests[1].resolve({[-1]: sampleInProgressBuild()});
+            }).catch(done);
+
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['16733']);
+                let entry = entries['16733'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 614);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 16733);
+                assert.ok(!entry.isPending());
+                assert.ok(entry.isInProgress());
+                assert.ok(!entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
+                done();
+            }).catch(done);
+        });
+
+        it('should create BuildbotBuildEntry for finished builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            let promise = syncer.pullBuildbot(1);
+            assert.equal(requests.length, 1);
+            requests[0].resolve([]);
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                requests[1].resolve({[-1]: sampleFinishedBuild()});
+            }).catch(done);
+
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['18935']);
+                let entry = entries['18935'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 1755);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 18935);
+                assert.ok(!entry.isPending());
+                assert.ok(!entry.isInProgress());
+                assert.ok(entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
+                done();
+            }).catch(done);
+        });
+
+        it('should create BuildbotBuildEntry for mixed pending, in-progress, finished, and missing builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            let promise = syncer.pullBuildbot(5);
+            assert.equal(requests.length, 1);
+
+            requests[0].resolve([samplePendingBuild(123, 456)]);
+
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                requests[1].resolve({[-1]: sampleFinishedBuild(), [-2]: {'error': 'Not available'}, [-4]: sampleInProgressBuild()});
+            }).catch(done);
+
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['123', '16733', '18935']);
+
+                let entry = entries['123'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), null);
+                assert.equal(entry.slaveName(), null);
+                assert.equal(entry.buildRequestId(), 123);
+                assert.ok(entry.isPending());
+                assert.ok(!entry.isInProgress());
+                assert.ok(!entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
+
+                entry = entries['16733'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 614);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 16733);
+                assert.ok(!entry.isPending());
+                assert.ok(entry.isInProgress());
+                assert.ok(!entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
+
+                entry = entries['18935'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 1755);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 18935);
+                assert.ok(!entry.isPending());
+                assert.ok(!entry.isInProgress());
+                assert.ok(entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
+
+                done();
+            }).catch(done);
+        });
+
+        it('should override BuildbotBuildEntry for pending builds by in-progress builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            let promise = syncer.pullBuildbot(5);
+            assert.equal(requests.length, 1);
+
+            requests[0].resolve([samplePendingBuild()]);
+
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                requests[1].resolve({[-1]: sampleInProgressBuild()});
+            }).catch(done);
+
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['16733']);
+
+                let entry = entries['16733'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 614);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 16733);
+                assert.ok(!entry.isPending());
+                assert.ok(entry.isInProgress());
+                assert.ok(!entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
+
+                done();
+            }).catch(done);
+        });
+
+        it('should override BuildbotBuildEntry for pending builds by finished builds', function (done) {
+            let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
+
+            let promise = syncer.pullBuildbot(5);
+            assert.equal(requests.length, 1);
+
+            requests[0].resolve([samplePendingBuild()]);
+
+            Promise.resolve().then(function () {
+                assert.equal(requests.length, 2);
+                requests[1].resolve({[-1]: sampleFinishedBuild(16733)});
+            }).catch(done);
+
+            promise.then(function (entries) {
+                assert.deepEqual(Object.keys(entries), ['16733']);
+
+                let entry = entries['16733'];
+                assert.ok(entry instanceof BuildbotBuildEntry);
+                assert.equal(entry.buildNumber(), 1755);
+                assert.equal(entry.slaveName(), 'ABTest-iPad-0');
+                assert.equal(entry.buildRequestId(), 16733);
+                assert.ok(!entry.isPending());
+                assert.ok(!entry.isInProgress());
+                assert.ok(entry.hasFinished());
+                assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
+
+                done();
+            }).catch(done);
+        });
+
+    });
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgunittestsresourcesmockremoteapijs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/unit-tests/resources/mock-remote-api.js (198823 => 198824)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/unit-tests/resources/mock-remote-api.js        2016-03-30 02:50:12 UTC (rev 198823)
+++ trunk/Websites/perf.webkit.org/unit-tests/resources/mock-remote-api.js        2016-03-30 03:16:29 UTC (rev 198824)
</span><span class="lines">@@ -5,9 +5,9 @@
</span><span class="cx"> 
</span><span class="cx"> global.requests = [];
</span><span class="cx"> global.RemoteAPI = {
</span><del>-    getJSON: function ()
</del><ins>+    getJSON: function (url)
</ins><span class="cx">     {
</span><del>-        assert.notReached();
</del><ins>+        return this.getJSONWithStatus(url);
</ins><span class="cx">     },
</span><span class="cx">     getJSONWithStatus: function (url)
</span><span class="cx">     {
</span></span></pre>
</div>
</div>

</body>
</html>