Title: [198824] trunk/Websites/perf.webkit.org
Revision
198824
Author
rn...@webkit.org
Date
2016-03-29 20:16:29 -0700 (Tue, 29 Mar 2016)

Log Message

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 "type". 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.

Modified Paths

Diff

Modified: trunk/Websites/perf.webkit.org/ChangeLog (198823 => 198824)


--- 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)
@@ -1,3 +1,50 @@
+2016-03-29  Ryosuke Niwa  <rn...@webkit.org>
+
+        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 "type". 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.
+
 2016-03-24  Ryosuke Niwa  <rn...@webkit.org>
 
         Migrate admin-regenerate-manifest.js to mocha.js and test v3 UI code

Modified: trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js (198823 => 198824)


--- 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)
@@ -8,7 +8,7 @@
     TestServer.inject();
 
     it('should return "TriggerableNotFound" when the database is empty', function (done) {
-        TestServer.remoteAPI().fetchJSON('/api/build-requests/build-webkit').then(function (content) {
+        TestServer.remoteAPI().getJSON('/api/build-requests/build-webkit').then(function (content) {
             assert.equal(content['status'], 'TriggerableNotFound');
             done();
         }).catch(done);
@@ -18,7 +18,7 @@
         TestServer.database().connect().then(function () {
             return TestServer.database().insert('build_triggerables', {name: 'build-webkit'});
         }).then(function () {
-            return TestServer.remoteAPI().fetchJSON('/api/build-requests/build-webkit');
+            return TestServer.remoteAPI().getJSON('/api/build-requests/build-webkit');
         }).then(function (content) {
             assert.equal(content['status'], 'OK');
             assert.deepEqual(content['buildRequests'], []);
@@ -61,7 +61,7 @@
         db.connect().then(function () {
             return addMockData(db);
         }).then(function () {
-            return TestServer.remoteAPI().fetchJSONWithStatus('/api/build-requests/build-webkit');
+            return TestServer.remoteAPI().getJSONWithStatus('/api/build-requests/build-webkit');
         }).then(function (content) {
             assert.deepEqual(Object.keys(content).sort(), ['buildRequests', 'rootSets', 'roots', 'status']);
 
@@ -119,7 +119,7 @@
         db.connect().then(function () {
             return addMockData(db);
         }).then(function () {
-            return TestServer.remoteAPI().fetchJSONWithStatus('/api/build-requests/build-webkit?useLegacyIdResolution=true');
+            return TestServer.remoteAPI().getJSONWithStatus('/api/build-requests/build-webkit?useLegacyIdResolution=true');
         }).then(function (content) {
             assert.deepEqual(Object.keys(content).sort(), ['buildRequests', 'rootSets', 'roots', 'status']);
 

Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest.js (198823 => 198824)


--- 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)
@@ -20,7 +20,7 @@
     });
 
     it("should generate an empty manifest when database is empty", function (done) {
-        TestServer.remoteAPI().fetchJSON('/api/manifest').then(function (manifest) {
+        TestServer.remoteAPI().getJSON('/api/manifest').then(function (manifest) {
             assert.deepEqual(Object.keys(manifest).sort(), ['all', 'bugTrackers', 'builders', 'dashboard', 'dashboards',
                 'elapsedTime', 'metrics', 'repositories', 'siteTitle', 'status', 'tests']);
 
@@ -49,7 +49,7 @@
     it("should generate manifest with bug trackers without repositories", function (done) {
         TestServer.database().connect();
         TestServer.database().insert('bug_trackers', bugzillaData).then(function () {
-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
+            return TestServer.remoteAPI().getJSON('/api/manifest');
         }).then(function (content) {
             assert.deepEqual(content.bugTrackers, {1: {name: 'Bugzilla', bugUrl: 'https://webkit.org/b/$number',
                 newBugUrl: 'https://bugs.webkit.org/', repositories: null}});
@@ -78,7 +78,7 @@
             db.insert('tracker_repositories', {tracker: radarData.id, repository: 9}),
             db.insert('tracker_repositories', {tracker: radarData.id, repository: 22}),
         ]).then(function () {
-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
+            return TestServer.remoteAPI().getJSON('/api/manifest');
         }).then(function (content) {
             let manifest = Manifest._didFetchManifest(content);
 
@@ -119,7 +119,7 @@
                 build_url: 'https://build.webkit.org/builders/$builderName/build/$buildNumber'}),
             db.insert('builders', {id: 2, name: 'SomeOtherBuilder', password_hash: 'b'})
         ]).then(function () {
-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
+            return TestServer.remoteAPI().getJSON('/api/manifest');
         }).then(function (content) {
             assert.deepEqual(content.builders, {
                 '1': {name: 'SomeBuilder', buildUrl: 'https://build.webkit.org/builders/$builderName/build/$buildNumber'},
@@ -166,7 +166,7 @@
             db.insert('test_configurations', {id: 106, metric: 5, platform: 23, type: 'current'}),
             db.insert('test_configurations', {id: 107, metric: 5, platform: 23, type: 'baseline'}),
         ]).then(function () {
-            return TestServer.remoteAPI().fetchJSON('/api/manifest');
+            return TestServer.remoteAPI().getJSON('/api/manifest');
         }).then(function (content) {
             assert.deepEqual(content.tests, {
                 "1": {"name": "SomeTest", "parentId": null, "url": null},

Modified: trunk/Websites/perf.webkit.org/tools/js/buildbot-syncer.js (198823 => 198824)


--- 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)
@@ -5,19 +5,20 @@
 require('./v3-models.js');
 
 class BuildbotBuildEntry {
-    constructor(syncer, type, rawData)
+    constructor(syncer, rawData)
     {
         assert.equal(syncer.builderName(), rawData['builderName']);
 
+        this._syncer = syncer;
         this._slaveName = null;
         this._buildRequestId = null;
-        this._isInProgress = 'currentStep' in rawData;
+        this._isInProgress = rawData['currentStep'] || (rawData['times'] && !rawData['times'][1]);
         this._buildNumber = rawData['number'];
 
-        for (var propertyTuple of (rawData['properties'] || [])) {
+        for (let propertyTuple of (rawData['properties'] || [])) {
             // e.g. ['build_request_id', '16733', 'Force Build Form']
-            var name = propertyTuple[0];
-            var value = propertyTuple[1];
+            let name = propertyTuple[0];
+            let value = propertyTuple[1];
             if (name == syncer._slavePropertyName)
                 this._slaveName = value;
             else if (name == syncer._buildRequestPropertyName)
@@ -25,9 +26,13 @@
         }
     }
 
+    buildNumber() { return this._buildNumber; }
     slaveName() { return this._slaveName; }
     buildRequestId() { return this._buildRequestId; }
+    isPending() { return !this._buildNumber; }
     isInProgress() { return this._isInProgress; }
+    hasFinished() { return !this.isPending() && !this.isInProgress(); }
+    url() { return this.isPending() ? this._syncer.url() : this._syncer.urlForBuildNumber(this._buildNumber); }
 }
 
 class BuildbotSyncer {
@@ -47,24 +52,57 @@
     builderName() { return this._builderName; }
     platformName() { return this._platformName; }
 
-    fetchPendingRequests()
+    pullBuildbot(count)
     {
-        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]);
-                }
+        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 < 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 && !entry['error'])
+                    entries.push(new BuildbotBuildEntry(self, entry));
             }
-            return requests;
+            return entries;
         });
     }
 
+    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('&');
+    }
+
+    url() { return `${this._url}/builders/${this._builderName}/`; }
+    urlForBuildNumber(number) { return `${this._url}/builders/${this._builderName}/builds/${number}`; }
+
     _propertiesForBuildRequest(buildRequest)
     {
         console.assert(buildRequest instanceof BuildRequest);

Modified: trunk/Websites/perf.webkit.org/tools/js/remote.js (198823 => 198824)


--- 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)
@@ -22,7 +22,7 @@
         this._server = server;
     }
 
-    fetchJSON(path, data)
+    getJSON(path, data)
     {
         let contentType = null;
         if (data) {
@@ -34,9 +34,9 @@
         });
     }
 
-    fetchJSONWithStatus(path, data)
+    getJSONWithStatus(path, data)
     {
-        return this.fetchJSON(path, data).then(function (result) {
+        return this.getJSON(path, data).then(function (result) {
             if (result['status'] != 'OK')
                 return Promise.reject(result);
             return result;

Modified: trunk/Websites/perf.webkit.org/tools/js/v3-models.js (198823 => 198824)


--- 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)
@@ -29,4 +29,7 @@
 
 importFromV3('instrumentation.js', 'Instrumentation');
 
+// RemoteAPI has a different implementation in node since XHR isn't available.
+global.RemoteAPI = require('./remote.js').RemoteAPI;
+
 global.Statistics = require('../../public/shared/statistics.js');

Modified: trunk/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js (198823 => 198824)


--- 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)
@@ -2,8 +2,8 @@
 
 let assert = require('assert');
 
+require('../tools/js/v3-models.js');
 require('./resources/mock-remote-api.js');
-require('../tools/js/v3-models.js');
 require('./resources/mock-v3-models.js');
 
 let BuildbotBuildEntry = require('../tools/js/buildbot-syncer.js').BuildbotBuildEntry;
@@ -83,12 +83,13 @@
     return request;
 }
 
-let samplePendingBuilds = [
-    {
+function samplePendingBuild(buildRequestId)
+{
+    return {
         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
         'builds': [],
         'properties': [
-            ['build_request_id', '16733', 'Force Build Form'],
+            ['build_request_id', buildRequestId || '16733', 'Force Build Form'],
             ['desired_image', '13A452', 'Force Build Form'],
             ['owner', '<unknown>', 'Force Build Form'],
             ['test_name', 'speedometer', 'Force Build Form'],
@@ -98,7 +99,7 @@
                 JSON.stringify(sampleRootSetData),
                 'Force Build Form'
             ],
-            ['scheduler', 'ABTest-iPad-Performance-Tests-ForceScheduler', 'Scheduler']
+            ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler']
         ],
         'source': {
             'branch': '',
@@ -110,14 +111,176 @@
             'revision': ''
         },
         'submittedAt': 1458704983
-    }
-];
+    };
+}
 
+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', '<unknown>', '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 \'<unknown>\': 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', '<unknown>', '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 \'<unknown>\': 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]
+    };
+}
+
 describe('BuildbotSyncer', function () {
-    describe('fetchPendingBuilds', function () {
-        BuildbotSyncer.fetchPendingBuilds
-    });
-
     describe('_loadConfig', function () {
 
         function smallConfiguration()
@@ -292,4 +455,226 @@
         });
     });
 
-});
\ No newline at end of file
+    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&select=-2&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);
+        });
+
+    });
+});

Modified: trunk/Websites/perf.webkit.org/unit-tests/resources/mock-remote-api.js (198823 => 198824)


--- 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)
@@ -5,9 +5,9 @@
 
 global.requests = [];
 global.RemoteAPI = {
-    getJSON: function ()
+    getJSON: function (url)
     {
-        assert.notReached();
+        return this.getJSONWithStatus(url);
     },
     getJSONWithStatus: function (url)
     {
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to