Add a model for parsing buildbot JSON with unit tests
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Mar 2016 03:25:10 +0000 (03:25 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Mar 2016 03:25:10 +0000 (03:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155814

Reviewed by Joseph Pecoraro.

Added BuildbotSyncer and BuildbotBuildEntry classes to parse buildbot JSON files with unit tests.
They will be used in the new syncing scripts to improve A/B testing.

* public/v3/models/build-request.js:
(BuildRequest):
* tools/js/buildbot-syncer.js: Added.
(BuildbotBuildEntry): Added.
(BuildbotBuildEntry.prototype.slaveName): Added.
(BuildbotBuildEntry.prototype.buildRequestId): Added.
(BuildbotBuildEntry.prototype.isInProgress): Added.
(BuildbotSyncer): Added.
(BuildbotSyncer.prototype.testPath): Added.
(BuildbotSyncer.prototype.builderName): Added.
(BuildbotSyncer.prototype.platformName): Added.
(BuildbotSyncer.prototype.fetchPendingRequests): Added.
(BuildbotSyncer.prototype._propertiesForBuildRequest): Added.
(BuildbotSyncer.prototype._revisionSetFromRootSetWithExclusionList): Added.
(BuildbotSyncer._loadConfig): Added.
(BuildbotSyncer._validateAndMergeConfig): Added.
(BuildbotSyncer._validateAndMergeProperties): Added.
* tools/js/v3-models.js: Copied from unit-tests/resources/v3-models.js.
(beforeEach): Deleted since this only defined inside mocha.
* unit-tests/analysis-task-tests.js:
* unit-tests/buildbot-syncer-tests.js: Added.
(sampleiOSConfig):
(createSampleBuildRequest):
(.smallConfiguration):
* unit-tests/measurement-adaptor-tests.js:
* unit-tests/measurement-set-tests.js:
* unit-tests/resources/mock-v3-models.js: Renamed from unit-tests/resources/v3-models.js.
(beforeEach):
* unit-tests/test-groups-tests.js:
(sampleTestGroup):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@198614 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Websites/perf.webkit.org/ChangeLog
Websites/perf.webkit.org/public/v3/models/build-request.js
Websites/perf.webkit.org/tools/js/buildbot-syncer.js [new file with mode: 0644]
Websites/perf.webkit.org/tools/js/v3-models.js [new file with mode: 0644]
Websites/perf.webkit.org/unit-tests/analysis-task-tests.js
Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js [new file with mode: 0644]
Websites/perf.webkit.org/unit-tests/measurement-adaptor-tests.js
Websites/perf.webkit.org/unit-tests/measurement-set-tests.js
Websites/perf.webkit.org/unit-tests/resources/mock-v3-models.js [new file with mode: 0644]
Websites/perf.webkit.org/unit-tests/test-groups-tests.js

index bb6ebc2..eb6b4c4 100644 (file)
@@ -1,3 +1,44 @@
+2016-03-23  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add a model for parsing buildbot JSON with unit tests
+        https://bugs.webkit.org/show_bug.cgi?id=155814
+
+        Reviewed by Joseph Pecoraro.
+
+        Added BuildbotSyncer and BuildbotBuildEntry classes to parse buildbot JSON files with unit tests.
+        They will be used in the new syncing scripts to improve A/B testing.
+
+        * public/v3/models/build-request.js:
+        (BuildRequest):
+        * tools/js/buildbot-syncer.js: Added.
+        (BuildbotBuildEntry): Added.
+        (BuildbotBuildEntry.prototype.slaveName): Added.
+        (BuildbotBuildEntry.prototype.buildRequestId): Added.
+        (BuildbotBuildEntry.prototype.isInProgress): Added.
+        (BuildbotSyncer): Added.
+        (BuildbotSyncer.prototype.testPath): Added.
+        (BuildbotSyncer.prototype.builderName): Added.
+        (BuildbotSyncer.prototype.platformName): Added.
+        (BuildbotSyncer.prototype.fetchPendingRequests): Added.
+        (BuildbotSyncer.prototype._propertiesForBuildRequest): Added.
+        (BuildbotSyncer.prototype._revisionSetFromRootSetWithExclusionList): Added.
+        (BuildbotSyncer._loadConfig): Added.
+        (BuildbotSyncer._validateAndMergeConfig): Added.
+        (BuildbotSyncer._validateAndMergeProperties): Added.
+        * tools/js/v3-models.js: Copied from unit-tests/resources/v3-models.js.
+        (beforeEach): Deleted since this only defined inside mocha.
+        * unit-tests/analysis-task-tests.js:
+        * unit-tests/buildbot-syncer-tests.js: Added.
+        (sampleiOSConfig):
+        (createSampleBuildRequest):
+        (.smallConfiguration):
+        * unit-tests/measurement-adaptor-tests.js:
+        * unit-tests/measurement-set-tests.js:
+        * unit-tests/resources/mock-v3-models.js: Renamed from unit-tests/resources/v3-models.js.
+        (beforeEach):
+        * unit-tests/test-groups-tests.js:
+        (sampleTestGroup):
+
 2016-03-22  Ryosuke Niwa  <rniwa@webkit.org>
 
         Add unit tests for test-group.js
index 0ac39d5..7077e52 100644 (file)
@@ -5,9 +5,10 @@ class BuildRequest extends DataModelObject {
     constructor(id, object)
     {
         super(id, object);
-        console.assert(object.testGroup instanceof TestGroup);
+        console.assert(!object.testGroup || object.testGroup instanceof TestGroup);
         this._testGroup = object.testGroup;
-        this._testGroup.addBuildRequest(this);
+        if (this._testGroup)
+            this._testGroup.addBuildRequest(this);
         this._order = object.order;
         console.assert(object.rootSet instanceof RootSet);
         this._rootSet = object.rootSet;
diff --git a/Websites/perf.webkit.org/tools/js/buildbot-syncer.js b/Websites/perf.webkit.org/tools/js/buildbot-syncer.js
new file mode 100644 (file)
index 0000000..7b7b62d
--- /dev/null
@@ -0,0 +1,216 @@
+'use strict';
+
+let assert = require('assert');
+
+require('./v3-models.js');
+
+class BuildbotBuildEntry {
+    constructor(syncer, type, rawData)
+    {
+        assert.equal(syncer.builderName(), rawData['builderName']);
+
+        this._slaveName = null;
+        this._buildRequestId = null;
+        this._isInProgress = 'currentStep' in rawData;
+        this._buildNumber = rawData['number'];
+
+        for (var propertyTuple of (rawData['properties'] || [])) {
+            // e.g. ['build_request_id', '16733', 'Force Build Form']
+            var name = propertyTuple[0];
+            var value = propertyTuple[1];
+            if (name == syncer._slavePropertyName)
+                this._slaveName = value;
+            else if (name == syncer._buildRequestPropertyName)
+                this._buildRequestId = value;
+        }
+    }
+
+    slaveName() { return this._slaveName; }
+    buildRequestId() { return this._buildRequestId; }
+    isInProgress() { return this._isInProgress; }
+}
+
+class BuildbotSyncer {
+
+    constructor(url, object)
+    {
+        this._url = url;
+        this._builderName = object.builder;
+        this._platformName = object.platform;
+        this._testPath = object.test;
+        this._propertiesTemplate = object.properties;
+        this._slavePropertyName = object.slaveArgument;
+        this._buildRequestPropertyName = object.buildRequestArgument;
+    }
+
+    testPath() { return this._testPath }
+    builderName() { return this._builderName; }
+    platformName() { return this._platformName; }
+
+    fetchPendingRequests()
+    {
+        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]);
+                }
+            }
+            return requests;
+        });
+    }
+
+    _propertiesForBuildRequest(buildRequest)
+    {
+        console.assert(buildRequest instanceof BuildRequest);
+
+        let rootSet = buildRequest.rootSet();
+        console.assert(rootSet instanceof RootSet);
+
+        let repositoryByName = {};
+        for (let repository of rootSet.repositories())
+            repositoryByName[repository.name()] = repository;
+
+        let properties = {};
+        for (let key in this._propertiesTemplate) {
+            let value = this._propertiesTemplate[key];
+            if (typeof(value) != 'object')
+                properties[key] = value;
+            else if ('root' in value) {
+                let repositoryName = value['root'];
+                let repository = repositoryByName[repositoryName];
+                assert(repository, '"${repositoryName}" must be specified');
+                properties[key] = rootSet.revisionForRepository(repository);
+            } else if ('rootsExcluding' in value) {
+                let revisionSet = this._revisionSetFromRootSetWithExclusionList(rootSet, value['rootsExcluding']);
+                properties[key] = JSON.stringify(revisionSet);
+            }
+        }
+
+        properties[this._buildRequestPropertyName] = buildRequest.id();
+
+        return properties;
+    }
+
+    _revisionSetFromRootSetWithExclusionList(rootSet, exclusionList)
+    {
+        let revisionSet = {};
+        for (let repository of rootSet.repositories()) {
+            if (exclusionList.indexOf(repository.name()) >= 0)
+                continue;
+            let commit = rootSet.commitForRepository(repository);
+            revisionSet[repository.name()] = {
+                id: commit.id(),
+                time: +commit.time(),
+                repository: repository.name(),
+                revision: commit.revision(),
+            };
+        }
+        return revisionSet;
+    }
+
+    static _loadConfig(url, config)
+    {
+        let shared = config['shared'] || {};
+        let types = config['types'] || {};
+        let builders = config['builders'] || {};
+
+        let syncers = [];
+        for (let entry of config['configurations']) {
+            let newConfig = {};
+            this._validateAndMergeConfig(newConfig, shared);
+
+            this._validateAndMergeConfig(newConfig, entry);
+
+            let type = entry['type'];
+            if (type) {
+                assert(types[type]);
+                this._validateAndMergeConfig(newConfig, types[type]);
+            }
+
+            let builder = entry['builder'];
+            if (builders[builder])
+                this._validateAndMergeConfig(newConfig, builders[builder]);
+
+            assert('platform' in newConfig, 'configuration must specify a platform');
+            assert('test' in newConfig, 'configuration must specify a test');
+            assert('builder' in newConfig, 'configuration must specify a builder');
+            assert('properties' in newConfig, 'configuration must specify arguments to post on a builder');
+            assert('buildRequestArgument' in newConfig, 'configuration must specify buildRequestArgument');
+            syncers.push(new BuildbotSyncer(url, newConfig));
+        }
+
+        return syncers;
+    }
+
+    static _validateAndMergeConfig(config, valuesToMerge)
+    {
+        for (let name in valuesToMerge) {
+            let value = valuesToMerge[name];
+            switch (name) {
+            case 'arguments':
+                assert.equal(typeof(value), 'object', 'arguments should be a dictionary');
+                if (!config['properties'])
+                    config['properties'] = {};
+                this._validateAndMergeProperties(config['properties'], value);
+                break;
+            case 'test':
+                assert(value instanceof Array, 'test should be an array');
+                assert(value.every(function (part) { return typeof part == 'string'; }), 'test should be an array of strings');
+                config[name] = value.slice();
+                break;
+            case 'type': // fallthrough
+            case 'builder': // fallthrough
+            case 'platform': // fallthrough
+            case 'slaveArgument': // fallthrough
+            case 'buildRequestArgument':
+                assert.equal(typeof(value), 'string', `${name} should be of string type`);
+                config[name] = value;
+                break;
+            default:
+                assert(false, `Unrecognized parameter ${name}`);
+            }
+        }
+    }
+
+    static _validateAndMergeProperties(properties, configArguments)
+    {
+        for (let name in configArguments) {
+            let value = configArguments[name];
+            if (typeof(value) == 'string') {
+                properties[name] = value;
+                continue;
+            }
+            assert.equal(typeof(value), 'object', 'A argument value must be either a string or a dictionary');
+                
+            let keys = Object.keys(value);
+            assert.equal(keys.length, 1, 'arguments value cannot contain more than one key');
+            let namedValue = value[keys[0]];
+            switch (keys[0]) {
+            case 'root':
+                assert.equal(typeof(namedValue), 'string', 'root name must be a string');
+                break;
+            case 'rootsExcluding':
+                assert(namedValue instanceof Array, 'rootsExcluding must specify an array');
+                for (let excludedRootName of namedValue)
+                    assert.equal(typeof(excludedRootName), 'string', 'rootsExcluding must specify an array of strings');
+                namedValue = namedValue.slice();
+                break;
+            default:
+                assert(false, `Unrecognized named argument ${keys[0]}`);
+            }
+            properties[name] = {[keys[0]]: namedValue};
+        }
+    }
+
+}
+
+if (typeof module != 'undefined') {
+    module.exports.BuildbotSyncer = BuildbotSyncer;
+    module.exports.BuildbotBuildEntry = BuildbotBuildEntry;
+}
diff --git a/Websites/perf.webkit.org/tools/js/v3-models.js b/Websites/perf.webkit.org/tools/js/v3-models.js
new file mode 100644 (file)
index 0000000..4781cc9
--- /dev/null
@@ -0,0 +1,30 @@
+'use strict';
+
+function importFromV3(file, name) {
+    const modelsDirectory = '../../public/v3/';
+
+    global[name] = require(modelsDirectory + file)[name];
+}
+
+importFromV3('models/data-model.js', 'DataModelObject');
+importFromV3('models/data-model.js', 'LabeledObject');
+
+importFromV3('models/analysis-task.js', 'AnalysisTask');
+importFromV3('models/build-request.js', 'BuildRequest');
+importFromV3('models/builder.js', 'Build');
+importFromV3('models/builder.js', 'Builder');
+importFromV3('models/commit-log.js', 'CommitLog');
+importFromV3('models/measurement-adaptor.js', 'MeasurementAdaptor');
+importFromV3('models/measurement-cluster.js', 'MeasurementCluster');
+importFromV3('models/measurement-set.js', 'MeasurementSet');
+importFromV3('models/metric.js', 'Metric');
+importFromV3('models/platform.js', 'Platform');
+importFromV3('models/repository.js', 'Repository');
+importFromV3('models/root-set.js', 'MeasurementRootSet');
+importFromV3('models/root-set.js', 'RootSet');
+importFromV3('models/test.js', 'Test');
+importFromV3('models/test-group.js', 'TestGroup');
+
+importFromV3('instrumentation.js', 'Instrumentation');
+
+global.Statistics = require('../../public/shared/statistics.js');
index 8c76db9..7a0f35b 100644 (file)
@@ -2,7 +2,8 @@
 
 var assert = require('assert');
 
-require('./resources/v3-models.js');
+require('../tools/js/v3-models.js');
+require('./resources/mock-v3-models.js');
 require('./resources/mock-remote-api.js');
 
 function sampleAnalysisTask()
diff --git a/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js b/Websites/perf.webkit.org/unit-tests/buildbot-syncer-tests.js
new file mode 100644 (file)
index 0000000..1b9bb12
--- /dev/null
@@ -0,0 +1,295 @@
+'use strict';
+
+let assert = require('assert');
+
+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;
+let BuildbotSyncer = require('../tools/js/buildbot-syncer.js').BuildbotSyncer;
+
+function sampleiOSConfig()
+{
+    return {
+        'shared':
+            {
+                'arguments': {
+                    'desired_image': {'root': 'iOS'},
+                    'roots_dict': {'rootsExcluding': ['iOS']}
+                },
+                'slaveArgument': 'slavename',
+                'buildRequestArgument': 'build_request_id'
+            },
+        'types': {
+            'speedometer': {
+                'test': ['Speedometer'],
+                'arguments': {'test_name': 'speedometer'}
+            },
+            'jetstream': {
+                'test': ['JetStream'],
+                'arguments': {'test_name': 'jetstream'}
+            },
+            "dromaeo-dom": {
+                "test": ["Dromaeo", "DOM Core Tests"],
+                "arguments": {"tests": "dromaeo-dom"}
+            },
+        },
+        'builders': {
+            'iPhone-bench': {
+                'builder': 'ABTest-iPhone-RunBenchmark-Tests',
+                'arguments': { 'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler' }
+            },
+            'iPad-bench': {
+                'builder': 'ABTest-iPad-RunBenchmark-Tests',
+                'arguments': { 'forcescheduler': 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler' }
+            }
+        },
+        'configurations': [
+            {'type': 'speedometer', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
+            {'type': 'jetstream', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
+            {'type': 'dromaeo-dom', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
+
+            {'type': 'speedometer', 'builder': 'iPad-bench', 'platform': 'iPad'},
+            {'type': 'jetstream', 'builder': 'iPad-bench', 'platform': 'iPad'},
+        ]
+    };
+}
+
+let sampleRootSetData = {
+    'WebKit': {
+        'id': '111127',
+        'time': 1456955807334,
+        'repository': 'WebKit',
+        'revision': '197463',
+    },
+    'Shared': {
+        'id': '111237',
+        'time': 1456931874000,
+        'repository': 'Shared',
+        'revision': '80229',
+    }
+};
+
+function createSampleBuildRequest()
+{
+    let rootSet = RootSet.ensureSingleton('4197', {roots: [
+        {'id': '111127', 'time': 1456955807334, 'repository': webkit, 'revision': '197463'},
+        {'id': '111237', 'time': 1456931874000, 'repository': sharedRepository, 'revision': '80229'},
+        {'id': '88930', 'time': 0, 'repository': ios, 'revision': '13A452'},
+    ]});
+
+    let request = BuildRequest.ensureSingleton('16733', {'rootSet': rootSet, 'status': 'pending'});
+    return request;
+}
+
+let samplePendingBuilds = [
+    {
+        'builderName': 'ABTest-iPad-RunBenchmark-Tests',
+        'builds': [],
+        'properties': [
+            ['build_request_id', '16733', 'Force Build Form'],
+            ['desired_image', '13A452', 'Force Build Form'],
+            ['owner', '<unknown>', 'Force Build Form'],
+            ['test_name', 'speedometer', 'Force Build Form'],
+            ['reason', 'force build','Force Build Form'],
+            [
+                'roots_dict',
+                JSON.stringify(sampleRootSetData),
+                'Force Build Form'
+            ],
+            ['scheduler', 'ABTest-iPad-Performance-Tests-ForceScheduler', 'Scheduler']
+        ],
+        'source': {
+            'branch': '',
+            'changes': [],
+            'codebase': 'compiler-rt',
+            'hasPatch': false,
+            'project': '',
+            'repository': '',
+            'revision': ''
+        },
+        'submittedAt': 1458704983
+    }
+];
+
+describe('BuildbotSyncer', function () {
+    describe('fetchPendingBuilds', function () {
+        BuildbotSyncer.fetchPendingBuilds
+    });
+
+    describe('_loadConfig', function () {
+
+        function smallConfiguration()
+        {
+            return {
+                'builder': 'some builder',
+                'platform': 'some platform',
+                'test': ['some test'],
+                'arguments': {},
+                'buildRequestArgument': 'id'};
+        }
+
+        it('should create BuildbotSyncer objects for a configuration that specify all required options', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [smallConfiguration()]});
+            assert.equal(syncers.length, 1);
+        });
+
+        it('should throw when some required options are missing', function () {
+            assert.throws(function () {
+                let config = smallConfiguration();
+                delete config['builder'];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            }, 'builder should be a required option');
+            assert.throws(function () {
+                let config = smallConfiguration();
+                delete config['platform'];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            }, 'platform should be a required option');
+            assert.throws(function () {
+                let config = smallConfiguration();
+                delete config['test'];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            }, 'test should be a required option');
+            assert.throws(function () {
+                let config = smallConfiguration();
+                delete config['arguments'];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                delete config['buildRequestArgument'];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+        });
+
+        it('should throw when a test name is not an array of strings', function () {
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.test = 'some test';
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.test = [1];
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+        });
+
+        it('should throw when arguments is not an object', function () {
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = 'hello';
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+        });
+
+        it('should throw when arguments\'s values are malformed', function () {
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'root': 'some root', 'rootsExcluding': ['other root']}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'otherKey': 'some root'}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'root': ['a', 'b']}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'root': 1}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'rootsExcluding': 'a'}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+            assert.throws(function () {
+                let config = smallConfiguration();
+                config.arguments = {'some': {'rootsExcluding': [1]}};
+                BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
+            });
+        });
+
+        it('should create BuildbotSyncer objects for valid configurations', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            assert.equal(syncers.length, 5);
+            assert.ok(syncers[0] instanceof BuildbotSyncer);
+            assert.ok(syncers[1] instanceof BuildbotSyncer);
+            assert.ok(syncers[2] instanceof BuildbotSyncer);
+            assert.ok(syncers[3] instanceof BuildbotSyncer);
+            assert.ok(syncers[4] instanceof BuildbotSyncer);
+        });
+
+        it('should parse builder names correctly', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            assert.equal(syncers[0].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
+            assert.equal(syncers[1].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
+            assert.equal(syncers[2].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
+            assert.equal(syncers[3].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
+            assert.equal(syncers[4].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
+        });
+
+        it('should parse platform names correctly', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            assert.equal(syncers[0].platformName(), 'iPhone');
+            assert.equal(syncers[1].platformName(), 'iPhone');
+            assert.equal(syncers[2].platformName(), 'iPhone');
+            assert.equal(syncers[3].platformName(), 'iPad');
+            assert.equal(syncers[4].platformName(), 'iPad');
+        });
+
+        it('should parse test names correctly', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            assert.deepEqual(syncers[0].testPath(), ['Speedometer']);
+            assert.deepEqual(syncers[1].testPath(), ['JetStream']);
+            assert.deepEqual(syncers[2].testPath(), ['Dromaeo', 'DOM Core Tests']);
+            assert.deepEqual(syncers[3].testPath(), ['Speedometer']);
+            assert.deepEqual(syncers[4].testPath(), ['JetStream']);
+        });
+    });
+
+    describe('_propertiesForBuildRequest', function () {
+        it('should include all properties specified in a given configuration', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.deepEqual(Object.keys(properties), ['desired_image', 'roots_dict', 'test_name', 'forcescheduler', 'build_request_id']);
+        });
+
+        it('should preserve non-parametric property values', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.equal(properties['test_name'], 'speedometer');
+            assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
+
+            properties = syncers[1]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.equal(properties['test_name'], 'jetstream');
+            assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
+        });
+
+        it('should resolve "root"', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.equal(properties['desired_image'], '13A452');
+        });
+
+        it('should resolve "rootsExcluding"', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.equal(properties['roots_dict'], JSON.stringify(sampleRootSetData));
+        });
+
+        it('should set the property for the build request id', function () {
+            let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
+            let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
+            assert.equal(properties['build_request_id'], createSampleBuildRequest().id());
+        });
+    });
+
+});
\ No newline at end of file
index 30a1a32..834a323 100644 (file)
@@ -2,7 +2,8 @@
 
 var assert = require('assert');
 
-require('./resources/v3-models');
+require('../tools/js/v3-models.js');
+require('./resources/mock-v3-models.js');
 
 var sampleCluster = {
     'clusterStart': 946684800000,
index 0af1087..efd696a 100644 (file)
@@ -3,7 +3,8 @@
 var assert = require('assert');
 
 require('./resources/mock-remote-api.js');
-require('./resources/v3-models.js');
+require('../tools/js/v3-models.js');
+require('./resources/mock-v3-models.js');
 
 describe('MeasurementSet', function () {
     beforeEach(function () {
diff --git a/Websites/perf.webkit.org/unit-tests/resources/mock-v3-models.js b/Websites/perf.webkit.org/unit-tests/resources/mock-v3-models.js
new file mode 100644 (file)
index 0000000..b814a9e
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+beforeEach(function () {
+    AnalysisTask._fetchAllPromise = null;
+    AnalysisTask.clearStaticMap();
+    CommitLog.clearStaticMap();
+    RootSet.clearStaticMap();
+    TestGroup.clearStaticMap();
+    BuildRequest.clearStaticMap();
+
+    global.osx = Repository.ensureSingleton(9, {name: 'OS X'});
+    global.ios = Repository.ensureSingleton(22, {name: 'iOS'});
+    global.webkit = Repository.ensureSingleton(11, {name: 'WebKit', url: 'http://trac.webkit.org/changeset/$1'});
+    global.sharedRepository = Repository.ensureSingleton(16, {name: 'Shared'});
+    global.builder = new Builder(176, {name: 'WebKit Perf Builder', buildUrl: 'http://build.webkit.org/builders/$builderName/$buildNumber'});
+
+    global.someTest = Test.ensureSingleton(1928, {name: 'Some test'});
+    global.someMetric = Metric.ensureSingleton(2884, {name: 'Some metric', test: someTest});
+    global.somePlatform = Platform.ensureSingleton(65, {name: 'Some platform', metrics: [someMetric]});
+});
index bd67fbb..deff1a6 100644 (file)
@@ -2,7 +2,8 @@
 
 var assert = require('assert');
 
-require('./resources/v3-models.js');
+require('../tools/js/v3-models.js');
+require('./resources/mock-v3-models.js');
 
 function sampleTestGroup() {
     return {