3 const assert = require('assert');
4 const crypto = require('crypto');
6 const TestServer = require('./resources/test-server.js');
7 const addBuilderForReport = require('./resources/common-operations.js').addBuilderForReport;
8 const connectToDatabaseInEveryTest = require('./resources/common-operations.js').connectToDatabaseInEveryTest;
10 describe("/api/measurement-set", function () {
13 connectToDatabaseInEveryTest();
15 function queryPlatformAndMetric(platformName, metricName)
17 const db = TestServer.database();
19 db.selectFirstRow('platforms', {name: 'Mountain Lion'}),
20 db.selectFirstRow('test_metrics', {name: 'Time'}),
21 ]).then(function (result) {
22 return {platformId: result[0]['id'], metricId: result[1]['id']};
26 function format(formatMap, row)
29 for (var i = 0; i < formatMap.length; i++) {
30 var key = formatMap[i];
31 if (key == 'id' || key == 'build' || key == 'builder')
38 let clusterStart = TestServer.testConfig().clusterStart;
39 clusterStart = +Date.UTC(clusterStart[0], clusterStart[1] - 1, clusterStart[2], clusterStart[3], clusterStart[4]);
41 let clusterSize = TestServer.testConfig().clusterSize;
42 const DAY = 24 * 3600 * 1000;
43 const YEAR = 365.24 * DAY;
44 const MONTH = 30 * DAY;
45 clusterSize = clusterSize[0] * YEAR + clusterSize[1] * MONTH + clusterSize[2] * DAY;
47 function clusterTime(index) { return new Date(clusterStart + clusterSize * index); }
49 const reportWithBuildTime = [{
51 "buildTime": clusterTime(7.8).toISOString(),
52 "builderName": "someBuilder",
53 "builderPassword": "somePassword",
54 "platform": "Mountain Lion",
59 "metrics": {"Time": { "current": [1, 2, 3, 4, 5] }}
64 reportWithBuildTime.startTime = +clusterTime(7);
66 const reportWithRevision = [{
68 "buildTime": "2013-02-28T15:34:51",
72 "timestamp": clusterTime(10.3).toISOString(),
75 "builderName": "someBuilder",
76 "builderPassword": "somePassword",
77 "platform": "Mountain Lion",
82 "metrics": {"Time": { "current": [11, 12, 13, 14, 15] }}
88 const reportWithNewRevision = [{
90 "buildTime": "2013-02-28T21:45:17",
94 "timestamp": clusterTime(12.1).toISOString()
97 "builderName": "someBuilder",
98 "builderPassword": "somePassword",
99 "platform": "Mountain Lion",
104 "metrics": {"Time": { "current": [16, 17, 18, 19, 20] }}
110 const reportWithAncentRevision = [{
111 "buildNumber": "126",
112 "buildTime": "2013-02-28T23:07:25",
115 "revision": "137793",
116 "timestamp": clusterTime(1.8).toISOString()
119 "builderName": "someBuilder",
120 "builderPassword": "somePassword",
121 "platform": "Mountain Lion",
126 "metrics": {"Time": { "current": [21, 22, 23, 24, 25] }}
132 it("should reject when platform ID is missing", function (done) {
133 addBuilderForReport(reportWithBuildTime[0]).then(function () {
134 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
135 }).then(function (response) {
136 assert.equal(response['status'], 'OK');
137 return queryPlatformAndMetric('Mountain Lion', 'Time');
138 }).then(function (result) {
139 return TestServer.remoteAPI().getJSON(`/api/measurement-set/?metric=${result.metricId}`);
140 }).then(function (response) {
141 assert.equal(response['status'], 'AmbiguousRequest');
146 it("should reject when metric ID is missing", function (done) {
147 addBuilderForReport(reportWithBuildTime[0]).then(function () {
148 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
149 }).then(function (response) {
150 assert.equal(response['status'], 'OK');
151 return queryPlatformAndMetric('Mountain Lion', 'Time');
152 }).then(function (result) {
153 return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}`);
154 }).then(function (response) {
155 assert.equal(response['status'], 'AmbiguousRequest');
160 it("should reject an invalid platform name", function (done) {
161 addBuilderForReport(reportWithBuildTime[0]).then(function () {
162 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
163 }).then(function (response) {
164 assert.equal(response['status'], 'OK');
165 return queryPlatformAndMetric('Mountain Lion', 'Time');
166 }).then(function (result) {
167 return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}a&metric=${result.metricId}`);
168 }).then(function (response) {
169 assert.equal(response['status'], 'InvalidPlatform');
174 it("should reject an invalid metric name", function (done) {
175 addBuilderForReport(reportWithBuildTime[0]).then(function () {
176 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
177 }).then(function (response) {
178 assert.equal(response['status'], 'OK');
179 return queryPlatformAndMetric('Mountain Lion', 'Time');
180 }).then(function (result) {
181 return TestServer.remoteAPI().getJSON(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}b`);
182 }).then(function (response) {
183 assert.equal(response['status'], 'InvalidMetric');
188 it("should be able to retrieve a reported value", function (done) {
189 addBuilderForReport(reportWithBuildTime[0]).then(function () {
190 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
191 }).then(function (response) {
192 assert.equal(response['status'], 'OK');
193 return queryPlatformAndMetric('Mountain Lion', 'Time');
194 }).then(function (result) {
195 return TestServer.remoteAPI().getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
196 }).then(function (response) {
197 const buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
199 assert.deepEqual(Object.keys(response).sort(),
200 ['clusterCount', 'clusterSize', 'clusterStart',
201 'configurations', 'elapsedTime', 'endTime', 'formatMap', 'lastModified', 'startTime', 'status']);
202 assert.equal(response['status'], 'OK');
203 assert.equal(response['clusterCount'], 1);
204 assert.deepEqual(response['formatMap'], [
205 'id', 'mean', 'iterationCount', 'sum', 'squareSum', 'markedOutlier',
206 'revisions', 'commitTime', 'build', 'buildTime', 'buildNumber', 'builder']);
208 assert.equal(response['startTime'], reportWithBuildTime.startTime);
209 assert(typeof(response['lastModified']) == 'number', 'lastModified time should be a numeric');
211 assert.deepEqual(Object.keys(response['configurations']), ['current']);
213 var currentRows = response['configurations']['current'];
214 assert.equal(currentRows.length, 1);
215 assert.equal(currentRows[0].length, response['formatMap'].length);
216 assert.deepEqual(format(response['formatMap'], currentRows[0]), {
221 markedOutlier: false,
223 commitTime: buildTime,
224 buildTime: buildTime,
225 buildNumber: '123'});
230 it("should return return the right IDs for measurement, build, and builder", function (done) {
231 addBuilderForReport(reportWithBuildTime[0]).then(function () {
232 return TestServer.remoteAPI().postJSON('/api/report/', reportWithBuildTime);
233 }).then(function (response) {
234 assert.equal(response['status'], 'OK');
235 return queryPlatformAndMetric('Mountain Lion', 'Time');
236 }).then(function (result) {
237 const db = TestServer.database();
239 db.selectAll('test_runs'),
240 db.selectAll('builds'),
241 db.selectAll('builders'),
242 TestServer.remoteAPI().getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`),
244 }).then(function (result) {
245 const runs = result[0];
246 const builds = result[1];
247 const builders = result[2];
248 const response = result[3];
250 assert.equal(runs.length, 1);
251 assert.equal(builds.length, 1);
252 assert.equal(builders.length, 1);
253 const measurementId = runs[0]['id'];
254 const buildId = builds[0]['id'];
255 const builderId = builders[0]['id'];
257 assert.equal(response['configurations']['current'].length, 1);
258 const measurement = response['configurations']['current'][0];
259 assert.equal(response['status'], 'OK');
261 assert.equal(measurement[response['formatMap'].indexOf('id')], measurementId);
262 assert.equal(measurement[response['formatMap'].indexOf('build')], buildId);
263 assert.equal(measurement[response['formatMap'].indexOf('builder')], builderId);
269 function postReports(reports, callback)
274 postJSON('/api/report/', reports[0], function (response) {
275 assert.equal(response.statusCode, 200);
276 assert.equal(JSON.parse(response.responseText)['status'], 'OK');
278 postReports(reports.slice(1), callback);
282 function queryPlatformAndMetricWithRepository(platformName, metricName, repositoryName)
284 const db = TestServer.database();
286 db.selectFirstRow('platforms', {name: platformName}),
287 db.selectFirstRow('test_metrics', {name: metricName}),
288 db.selectFirstRow('repositories', {name: repositoryName}),
289 ]).then(function (result) {
290 return {platformId: result[0]['id'], metricId: result[1]['id'], repositoryId: result[2]['id']};
294 it("should order results by commit time", function (done) {
295 const remote = TestServer.remoteAPI();
297 addBuilderForReport(reportWithBuildTime[0]).then(function () {
298 return remote.postJSON('/api/report/', reportWithBuildTime);
299 }).then(function () {
300 return remote.postJSON('/api/report/', reportWithRevision);
301 }).then(function () {
302 return queryPlatformAndMetricWithRepository('Mountain Lion', 'Time', 'WebKit');
303 }).then(function (result) {
304 repositoryId = result.repositoryId;
305 return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
306 }).then(function (response) {
307 const currentRows = response['configurations']['current'];
308 const buildTime = +(new Date(reportWithBuildTime[0]['buildTime']));
309 const revisionTime = +(new Date(reportWithRevision[0]['revisions']['WebKit']['timestamp']));
310 const revisionBuildTime = +(new Date(reportWithRevision[0]['buildTime']));
312 assert.equal(currentRows.length, 2);
313 assert.deepEqual(format(response['formatMap'], currentRows[0]), {
318 markedOutlier: false,
319 revisions: [[1, repositoryId, '144000', revisionTime]],
320 commitTime: revisionTime,
321 buildTime: revisionBuildTime,
322 buildNumber: '124' });
323 assert.deepEqual(format(response['formatMap'], currentRows[1]), {
328 markedOutlier: false,
330 commitTime: buildTime,
331 buildTime: buildTime,
332 buildNumber: '123' });
337 function buildNumbers(parsedResult, config)
339 return parsedResult['configurations'][config].map(function (row) {
340 return format(parsedResult['formatMap'], row)['buildNumber'];
344 it("should include one data point after the current time range", function (done) {
345 const remote = TestServer.remoteAPI();
346 addBuilderForReport(reportWithBuildTime[0]).then(function () {
347 return remote.postJSON('/api/report/', reportWithAncentRevision);
348 }).then(function () {
349 return remote.postJSON('/api/report/', reportWithNewRevision);
350 }).then(function () {
351 return queryPlatformAndMetric('Mountain Lion', 'Time');
352 }).then(function (result) {
353 return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
354 }).then(function (response) {
355 assert.equal(response['status'], 'OK');
356 assert.equal(response['clusterCount'], 2, 'should have two clusters');
357 assert.deepEqual(buildNumbers(response, 'current'),
358 [reportWithAncentRevision[0]['buildNumber'], reportWithNewRevision[0]['buildNumber']]);
363 it("should always include one old data point before the current time range", function (done) {
364 const remote = TestServer.remoteAPI();
365 addBuilderForReport(reportWithBuildTime[0]).then(function () {
366 return remote.postJSON('/api/report/', reportWithBuildTime);
367 }).then(function () {
368 return remote.postJSON('/api/report/', reportWithAncentRevision);
369 }).then(function () {
370 return queryPlatformAndMetric('Mountain Lion', 'Time');
371 }).then(function (result) {
372 return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
373 }).then(function (response) {
374 assert.equal(response['clusterCount'], 2, 'should have two clusters');
375 let currentRows = response['configurations']['current'];
376 assert.equal(currentRows.length, 2, 'should contain two data points');
377 assert.deepEqual(buildNumbers(response, 'current'), [reportWithAncentRevision[0]['buildNumber'], reportWithBuildTime[0]['buildNumber']]);
383 it("should create cache results", function (done) {
384 const remote = TestServer.remoteAPI();
386 addBuilderForReport(reportWithBuildTime[0]).then(function () {
387 return remote.postJSON('/api/report/', reportWithAncentRevision);
388 }).then(function () {
389 return remote.postJSON('/api/report/', reportWithRevision);
390 }).then(function () {
391 return remote.postJSON('/api/report/', reportWithNewRevision);
392 }).then(function () {
393 return queryPlatformAndMetric('Mountain Lion', 'Time');
394 }).then(function (result) {
395 cachePrefix = '/data/measurement-set-' + result.platformId + '-' + result.metricId;
396 return remote.getJSONWithStatus(`/api/measurement-set/?platform=${result.platformId}&metric=${result.metricId}`);
397 }).then(function (newResult) {
398 return remote.getJSONWithStatus(`${cachePrefix}.json`).then(function (cachedResult) {
399 assert.deepEqual(newResult, cachedResult);
400 return remote.getJSONWithStatus(`${cachePrefix}-${cachedResult['startTime']}.json`);
401 }).then(function (oldResult) {
402 var oldBuildNumbers = buildNumbers(oldResult, 'current');
403 var newBuildNumbers = buildNumbers(newResult, 'current');
404 assert(oldBuildNumbers.length >= 2, 'The old cluster should contain at least two data points');
405 assert(newBuildNumbers.length >= 2, 'The new cluster should contain at least two data points');
406 assert.deepEqual(oldBuildNumbers.slice(oldBuildNumbers.length - 2), newBuildNumbers.slice(0, 2),
407 'Two conseqcutive clusters should share two data points');