blob: 14c7834ef8d18c44ba4cea979a75c53d95d9180b [file] [log] [blame]
/*
* Copyright (C) 2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase
{
constructor(name, tests, options = {})
{
console.assert(Array.isArray(tests));
// Set disabled once `_tests` is set so that it propagates.
let disabled = options.disabled;
options.disabled = false;
super(name, options);
this._tests = tests;
this._preventDisabledPropagation = false;
if (disabled || !this.supported)
this.disabled = true;
let hasSupportedTest = false;
for (let test of this._tests) {
if (!this.supported)
test.supported = false;
else if (test.supported)
hasSupportedTest = true;
test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
}
if (!hasSupportedTest)
this.supported = false;
}
// Static
static async fromPayload(payload)
{
if (typeof payload !== "object" || payload === null)
return null;
if (payload.type !== WI.AuditTestGroup.TypeIdentifier)
return null;
if (typeof payload.name !== "string") {
WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name")));
return null;
}
if (!Array.isArray(payload.tests)) {
WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-array \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("tests")));
return null;
}
let tests = await Promise.all(payload.tests.map(async (test) => {
let testCase = await WI.AuditTestCase.fromPayload(test);
if (testCase)
return testCase;
let testGroup = await WI.AuditTestGroup.fromPayload(test);
if (testGroup)
return testGroup;
return null;
}));
tests = tests.filter((test) => !!test);
if (!tests.length)
return null;
let options = {};
if (typeof payload.description === "string")
options.description = payload.description;
else if ("description" in payload)
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description")));
if (typeof payload.supports === "number")
options.supports = payload.supports;
else if ("supports" in payload)
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-number \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("supports")));
if (typeof payload.setup === "string")
options.setup = payload.setup;
else if ("setup" in payload)
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("setup")));
if (typeof payload.disabled === "boolean")
options.disabled = payload.disabled;
return new WI.AuditTestGroup(payload.name, tests, options);
}
// Public
get tests() { return this._tests; }
get supported()
{
return super.supported;
}
set supported(supported)
{
for (let test of this._tests)
test.supported = supported;
super.supported = supported;
}
get disabled()
{
return super.disabled;
}
set disabled(disabled)
{
if (!this._preventDisabledPropagation) {
for (let test of this._tests)
test.disabled = disabled;
}
super.disabled = disabled;
}
stop()
{
// Called from WI.AuditManager.
for (let test of this._tests)
test.stop();
super.stop();
}
clearResult(options = {})
{
let cleared = !!this._result;
for (let test of this._tests) {
if (test.clearResult(options))
cleared = true;
}
return super.clearResult({
...options,
suppressResultChangedEvent: !cleared,
});
}
toJSON(key)
{
let json = super.toJSON(key);
json.tests = this._tests.map((testCase) => testCase.toJSON(key));
return json;
}
// Protected
async run()
{
let count = this._tests.length;
for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
let test = this._tests[index];
if (test.disabled)
continue;
await test.start();
if (test instanceof WI.AuditTestCase)
this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count});
}
this._updateResult();
}
// Private
_updateResult()
{
let results = this._tests.map((test) => test.result).filter((result) => !!result);
if (!results.length)
return;
this._result = new WI.AuditTestGroupResult(this.name, results, {
description: this.description,
});
this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
}
_handleTestCompleted(event)
{
if (this._runningState === WI.AuditManager.RunningState.Active)
return;
this._updateResult();
this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
}
_handleTestDisabledChanged(event)
{
let enabledTestCount = this._tests.filter((test) => !test.disabled).length;
if (event.target.disabled && !enabledTestCount)
this.disabled = true;
else if (!event.target.disabled && enabledTestCount === 1) {
this._preventDisabledPropagation = true;
this.disabled = false;
this._preventDisabledPropagation = false;
} else {
// Don't change `disabled`, as we're currently in an "indeterminate" state.
this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
}
}
_handleTestProgress(event)
{
if (this._runningState !== WI.AuditManager.RunningState.Active)
return;
let walk = (tests) => {
let count = 0;
for (let test of tests) {
if (test.disabled)
continue;
if (test instanceof WI.AuditTestCase)
++count;
else if (test instanceof WI.AuditTestGroup)
count += walk(test.tests);
}
return count;
};
this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {
index: event.data.index + walk(this._tests.slice(0, this._tests.indexOf(event.target))),
count: walk(this._tests),
});
}
};
WI.AuditTestGroup.TypeIdentifier = "test-group";