blob: 97e8bc8ee3cda9a6f641623534e67fb80039982b [file] [log] [blame]
'use strict';
class TestGroup extends LabeledObject {
constructor(id, object)
{
super(id, object);
this._taskId = object.task;
this._authorName = object.author;
this._createdAt = new Date(object.createdAt);
this._isHidden = object.hidden;
this._needsNotification = object.needsNotification;
this._mayNeedMoreRequests = object.mayNeedMoreRequests;
this._initialRepetitionCount = object.initialRepetitionCount;
this._buildRequests = [];
this._orderBuildRequestsLazily = new LazilyEvaluatedFunction((...buildRequests) => {
return buildRequests.sort((a, b) => a.order() - b.order());
});
this._repositories = null;
this._computeRequestedCommitSetsLazily = new LazilyEvaluatedFunction(TestGroup._computeRequestedCommitSets);
this._requestedCommitSets = null;
this._commitSetToLabel = new Map;
console.assert(!object.platform || object.platform instanceof Platform);
this._platform = object.platform;
}
updateSingleton(object)
{
super.updateSingleton(object);
console.assert(this._taskId == object.task);
console.assert(+this._createdAt == +object.createdAt);
console.assert(this._platform == object.platform);
this._isHidden = object.hidden;
this._needsNotification = object.needsNotification;
this._notificationSentAt = object.notificationSentAt ? new Date(object.notificationSentAt) : null;
this._mayNeedMoreRequests = object.mayNeedMoreRequests;
this._initialRepetitionCount = object.initialRepetitionCount;
}
task() { return AnalysisTask.findById(this._taskId); }
createdAt() { return this._createdAt; }
isHidden() { return this._isHidden; }
buildRequests() { return this._buildRequests; }
needsNotification() { return this._needsNotification; }
mayNeedMoreRequests() { return this._mayNeedMoreRequests; }
initialRepetitionCount() { return this._initialRepetitionCount; }
notificationSentAt() { return this._notificationSentAt; }
author() { return this._authorName; }
addBuildRequest(request)
{
this._buildRequests.push(request);
this._requestedCommitSets = null;
this._commitSetToLabel.clear();
}
test()
{
const request = this._lastRequest();
return request ? request.test() : null;
}
async fetchTask()
{
if (this.task())
return this.task();
return await AnalysisTask.fetchById(this._taskId);
}
platform() { return this._platform; }
_lastRequest()
{
const requests = this._orderedBuildRequests();
return requests.length ? requests[requests.length - 1] : null;
}
_orderedBuildRequests()
{
return this._orderBuildRequestsLazily.evaluate(...this._buildRequests);
}
repetitionCount()
{
if (!this._buildRequests.length)
return 0;
const commitSet = this._buildRequests[0].commitSet();
let count = 0;
for (const request of this._buildRequests) {
if (request.isTest() && request.commitSet() == commitSet)
count++;
}
return count;
}
requestedCommitSets()
{
return this._computeRequestedCommitSetsLazily.evaluate(...this._orderedBuildRequests());
}
static _computeRequestedCommitSets(...orderedBuildRequests)
{
const requestedCommitSets = [];
const commitSetLabelMap = new Map;
for (const request of orderedBuildRequests) {
const set = request.commitSet();
if (!requestedCommitSets.includes(set))
requestedCommitSets.push(set);
}
return requestedCommitSets;
}
requestsForCommitSet(commitSet)
{
return this._orderedBuildRequests().filter((request) => request.commitSet() == commitSet);
}
labelForCommitSet(commitSet)
{
const requestedSets = this.requestedCommitSets();
const setIndex = requestedSets.indexOf(commitSet);
if (setIndex < 0)
return null;
return String.fromCharCode('A'.charCodeAt(0) + setIndex);
}
hasFinished()
{
return this._buildRequests.every(function (request) { return request.hasFinished(); });
}
hasStarted()
{
return this._buildRequests.some(function (request) { return request.hasStarted(); });
}
hasPending()
{
return this._buildRequests.some(function (request) { return request.isPending(); });
}
compareTestResults(metric, beforeMeasurements, afterMeasurements)
{
console.assert(metric);
const beforeValues = beforeMeasurements.map((measurment) => measurment.value);
const afterValues = afterMeasurements.map((measurement) => measurement.value);
const beforeMean = Statistics.sum(beforeValues) / beforeValues.length;
const afterMean = Statistics.sum(afterValues) / afterValues.length;
const result = {changeType: null, status: 'failed', label: 'Failed', fullLabelForMean: 'Failed',
isStatisticallySignificantForMean: false, fullLabelForIndividual: 'Failed', isStatisticallySignificantForIndividual: false,
probabilityRangeForMean: [null, null], probabilityRangeForIndividual: [null, null]};
const hasCompleted = this.hasFinished();
if (!hasCompleted) {
if (this.hasStarted()) {
result.status = 'running';
result.label = 'Running';
result.fullLabelForMean = 'Running';
result.fullLabelForIndividual = 'Running';
} else {
console.assert(result.changeType === null);
result.status = 'pending';
result.label = 'Pending';
result.fullLabelForMean = 'Pending';
result.fullLabelForIndividual = 'Pending';
}
}
if (beforeValues.length && afterValues.length) {
const summary = metric.labelForDifference(beforeMean, afterMean, 'better', 'worse');
result.changeType = summary.changeType;
result.label = summary.changeLabel;
const constructSignificanceLabel = (probabilityRange) => !!probabilityRange.range[0] ? `significant with ${(probabilityRange.range[0] * 100).toFixed()}% probability` : 'insignificant';
const probabilityRangeForMean = Statistics.probabilityRangeForWelchsT(beforeValues, afterValues);
const significanceLabelForMean = constructSignificanceLabel(probabilityRangeForMean);
result.fullLabelForMean = `${result.label} (${significanceLabelForMean})`;
result.isStatisticallySignificantForMean = !!probabilityRangeForMean.range[0];
result.probabilityRangeForMean = probabilityRangeForMean.range;
const adaptMeasurementToSamples = (measurement) => ({sum: measurement.sum, squareSum: measurement.squareSum, sampleSize: measurement.iterationCount});
const probabilityRangeForIndividual = Statistics.probabilityRangeForWelchsTForMultipleSamples(beforeMeasurements.map(adaptMeasurementToSamples), afterMeasurements.map(adaptMeasurementToSamples));
const significanceLabelForIndividual = constructSignificanceLabel(probabilityRangeForIndividual);
result.fullLabelForIndividual = `${result.label} (${significanceLabelForIndividual})`;
result.isStatisticallySignificantForIndividual = !!probabilityRangeForIndividual.range[0];
result.probabilityRangeForIndividual = probabilityRangeForIndividual.range;
if (hasCompleted)
result.status = result.isStatisticallySignificantForMean ? result.changeType : 'unchanged';
}
return result;
}
async _updateBuildRequest(content, endPoint='update-test-group')
{
await PrivilegedAPI.sendRequest(endPoint, content);
const data = await TestGroup.cachedFetch(`/api/test-groups/${this.id()}`, {}, true);
return TestGroup._createModelsFromFetchedTestGroups(data);
}
updateName(newName)
{
return this._updateBuildRequest({
group: this.id(),
name: newName,
});
}
updateHiddenFlag(hidden)
{
return this._updateBuildRequest({
group: this.id(),
hidden: !!hidden,
});
}
async didSendNotification()
{
return await this._updateBuildRequest({
group: this.id(),
needsNotification: false,
notificationSentAt: (new Date).toISOString()
});
}
async addMoreBuildRequests(addCount)
{
return await this._updateBuildRequest({
group: this.id(),
addCount,
}, 'add-build-requests');
}
async clearMayNeedMoreBuildRequests()
{
return await this._updateBuildRequest({
group: this.id(),
mayNeedMoreRequests: false
});
}
static createWithTask(taskName, platform, test, groupName, repetitionCount, commitSets, notifyOnCompletion)
{
console.assert(commitSets.length == 2);
const revisionSets = CommitSet.revisionSetsFromCommitSets(commitSets);
const params = {taskName, name: groupName, platform: platform.id(), test: test.id(), repetitionCount, revisionSets, needsNotification: !!notifyOnCompletion};
return PrivilegedAPI.sendRequest('create-test-group', params).then((data) => {
return AnalysisTask.fetchById(data['taskId'], true);
}).then((task) => {
return this.fetchForTask(task.id()).then(() => task);
});
}
static createWithCustomConfiguration(task, platform, test, groupName, repetitionCount, commitSets, notifyOnCompletion)
{
console.assert(commitSets.length == 2);
const revisionSets = CommitSet.revisionSetsFromCommitSets(commitSets);
const params = {task: task.id(), name: groupName, platform: platform.id(), test: test.id(), repetitionCount, revisionSets, needsNotification: !!notifyOnCompletion};
return PrivilegedAPI.sendRequest('create-test-group', params).then((data) => {
return this.fetchForTask(data['taskId'], true);
});
}
static createAndRefetchTestGroups(task, name, repetitionCount, commitSets, notifyOnCompletion)
{
console.assert(commitSets.length == 2);
const revisionSets = CommitSet.revisionSetsFromCommitSets(commitSets);
return PrivilegedAPI.sendRequest('create-test-group', {
task: task.id(),
name: name,
repetitionCount: repetitionCount,
revisionSets: revisionSets,
needsNotification: !!notifyOnCompletion,
}).then((data) => this.fetchForTask(data['taskId'], true));
}
static findAllByTask(taskId)
{
return TestGroup.all().filter((testGroup) => testGroup._taskId == taskId);
}
static fetchForTask(taskId, ignoreCache = false)
{
return this.cachedFetch('/api/test-groups', {task: taskId}, ignoreCache).then(this._createModelsFromFetchedTestGroups.bind(this));
}
static fetchAllWithNotificationReady()
{
return this.cachedFetch('/api/test-groups/ready-for-notification', null, true).then(this._createModelsFromFetchedTestGroups.bind(this));
}
static fetchAllThatMayNeedMoreRequests()
{
return this.cachedFetch('/api/test-groups/need-more-requests', null, true).then(this._createModelsFromFetchedTestGroups.bind(this));
}
static _createModelsFromFetchedTestGroups(data)
{
var testGroups = data['testGroups'].map(function (row) {
row.platform = Platform.findById(row.platform);
return TestGroup.ensureSingleton(row.id, row);
});
BuildRequest.constructBuildRequestsFromData(data);
return testGroups;
}
}
if (typeof module != 'undefined')
module.exports.TestGroup = TestGroup;