summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Test
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Test')
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js256
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/InspectorProtocol.js182
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/ProtocolTestHarness.js78
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/Test.js120
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/TestHarness.js374
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/TestStub.js40
-rw-r--r--Source/WebInspectorUI/UserInterface/Test/TestSuite.js251
7 files changed, 1301 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js b/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js
new file mode 100644
index 000000000..5d9bb202e
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2013-2016 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 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 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.
+ */
+
+FrontendTestHarness = class FrontendTestHarness extends TestHarness
+{
+ constructor()
+ {
+ super();
+
+ this._results = [];
+ this._shouldResendResults = true;
+
+ // Options that are set per-test for debugging purposes.
+ this.dumpActivityToSystemConsole = false;
+ }
+
+ // TestHarness Overrides
+
+ completeTest()
+ {
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog("completeTest()");
+
+ // Wait for results to be resent before requesting completeTest(). Otherwise, messages will be
+ // queued after pending dispatches run to zero and the test page will quit before processing them.
+ if (this._testPageIsReloading) {
+ this._completeTestAfterReload = true;
+ return;
+ }
+
+ InspectorBackend.runAfterPendingDispatches(this.evaluateInPage.bind(this, "TestPage.completeTest()"));
+ }
+
+ addResult(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+
+ // Save the stringified message, since message may be a DOM element that won't survive reload.
+ this._results.push(stringifiedMessage);
+
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog(stringifiedMessage);
+
+ if (!this._testPageIsReloading)
+ this.evaluateInPage(`TestPage.addResult(unescape("${escape(stringifiedMessage)}"))`);
+ }
+
+ debugLog(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog(stringifiedMessage);
+
+ this.evaluateInPage(`TestPage.debugLog(unescape("${escape(stringifiedMessage)}"));`);
+ }
+
+ evaluateInPage(expression, callback)
+ {
+ // If we load this page outside of the inspector, or hit an early error when loading
+ // the test frontend, then defer evaluating the commands (indefinitely in the former case).
+ if (this._originalConsole && !window.RuntimeAgent) {
+ this._originalConsole["error"]("Tried to evaluate in test page, but connection not yet established:", expression);
+ return;
+ }
+
+ RuntimeAgent.evaluate.invoke({expression, objectGroup: "test", includeCommandLineAPI: false}, callback);
+ }
+
+ debug()
+ {
+ this.dumpActivityToSystemConsole = true;
+ InspectorBackend.dumpInspectorProtocolMessages = true;
+ }
+
+ // Frontend test-specific methods.
+
+ expectNoError(error)
+ {
+ if (error) {
+ InspectorTest.log("PROTOCOL ERROR: " + error);
+ InspectorTest.completeTest();
+ throw "PROTOCOL ERROR";
+ }
+ }
+
+ testPageDidLoad()
+ {
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog("testPageDidLoad()");
+
+ this._testPageIsReloading = false;
+ this._resendResults();
+
+ this.dispatchEventToListeners(FrontendTestHarness.Event.TestPageDidLoad);
+
+ if (this._completeTestAfterReload)
+ this.completeTest();
+ }
+
+ reloadPage(shouldIgnoreCache)
+ {
+ console.assert(!this._testPageIsReloading);
+ console.assert(!this._testPageReloadedOnce);
+
+ this._testPageIsReloading = true;
+
+ return PageAgent.reload(!!shouldIgnoreCache)
+ .then(() => {
+ this._shouldResendResults = true;
+ this._testPageReloadedOnce = true;
+
+ return Promise.resolve(null);
+ });
+ }
+
+ redirectConsoleToTestOutput()
+ {
+ // We can't use arrow functions here because of 'arguments'. It might
+ // be okay once rest parameters work.
+ let self = this;
+ function createProxyConsoleHandler(type) {
+ return function() {
+ self.addResult(`${type}: ` + Array.from(arguments).join(" "));
+ };
+ }
+
+ function createProxyConsoleTraceHandler(){
+ return function() {
+ try {
+ throw new Exception();
+ } catch (e) {
+ // Skip the first frame which is added by this function.
+ let frames = e.stack.split("\n").slice(1);
+ let sanitizedFrames = frames.map(TestHarness.sanitizeStackFrame);
+ self.addResult("TRACE: " + Array.from(arguments).join(" "));
+ self.addResult(sanitizedFrames.join("\n"));
+ }
+ };
+ }
+
+ let redirectedMethods = {};
+ for (let key in window.console)
+ redirectedMethods[key] = window.console[key];
+
+ for (let type of ["log", "error", "info", "warn"])
+ redirectedMethods[type] = createProxyConsoleHandler(type.toUpperCase());
+
+ redirectedMethods["trace"] = createProxyConsoleTraceHandler();
+
+ this._originalConsole = window.console;
+ window.console = redirectedMethods;
+ }
+
+ reportUnhandledRejection(error)
+ {
+ let message = error.message;
+ let stack = error.stack;
+ let result = `Unhandled promise rejection in inspector page: ${message}\n`;
+ if (stack) {
+ let sanitizedStack = this.sanitizeStack(stack);
+ result += `\nStack Trace: ${sanitizedStack}\n`;
+ }
+
+ // If the connection to the test page is not set up, then just dump to console and give up.
+ // Errors encountered this early can be debugged by loading Test.html in a normal browser page.
+ if (this._originalConsole && !this._testPageHasLoaded())
+ this._originalConsole["error"](result);
+
+ this.addResult(result);
+ this.completeTest();
+
+ // Stop default handler so we can empty InspectorBackend's message queue.
+ return true;
+ }
+
+ reportUncaughtExceptionFromEvent(message, url, lineNumber, columnNumber)
+ {
+ // An exception thrown from a timer callback does not report a URL.
+ if (url === "undefined")
+ url = "global";
+
+ return this.reportUncaughtException({message, url, lineNumber, columnNumber});
+ }
+
+ reportUncaughtException({message, url, lineNumber, columnNumber, stack, code})
+ {
+ let result;
+ let sanitizedURL = TestHarness.sanitizeURL(url);
+ let sanitizedStack = this.sanitizeStack(stack);
+ if (url || lineNumber || columnNumber)
+ result = `Uncaught exception in Inspector page: ${message} [${sanitizedURL}:${lineNumber}:${columnNumber}]\n`;
+ else
+ result = `Uncaught exception in Inspector page: ${message}\n`;
+
+ if (stack)
+ result += `\nStack Trace:\n${sanitizedStack}\n`;
+ if (code)
+ result += `\nEvaluated Code:\n${code}`;
+
+ // If the connection to the test page is not set up, then just dump to console and give up.
+ // Errors encountered this early can be debugged by loading Test.html in a normal browser page.
+ if (this._originalConsole && !this._testPageHasLoaded())
+ this._originalConsole["error"](result);
+
+ this.addResult(result);
+ this.completeTest();
+ // Stop default handler so we can empty InspectorBackend's message queue.
+ return true;
+ }
+
+ // Private
+
+ _testPageHasLoaded()
+ {
+ return self._shouldResendResults;
+ }
+
+ _resendResults()
+ {
+ console.assert(this._shouldResendResults);
+ this._shouldResendResults = false;
+
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog("_resendResults()");
+
+ for (let result of this._results)
+ this.evaluateInPage(`TestPage.addResult(unescape("${escape(result)}"))`);
+ }
+};
+
+FrontendTestHarness.Event = {
+ TestPageDidLoad: "frontend-test-test-page-did-load"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Test/InspectorProtocol.js b/Source/WebInspectorUI/UserInterface/Test/InspectorProtocol.js
new file mode 100644
index 000000000..60ec624f1
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/InspectorProtocol.js
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ * Copyright (C) 2014, 2015 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 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 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.
+ */
+
+InspectorProtocol = {};
+InspectorProtocol._dispatchTable = [];
+InspectorProtocol._placeholderRequestIds = [];
+InspectorProtocol._requestId = -1;
+InspectorProtocol.eventHandler = {};
+
+InspectorProtocol.sendCommand = function(methodOrObject, params, handler)
+{
+ // Allow new-style arguments object, as in awaitCommand.
+ let method = methodOrObject;
+ if (typeof methodOrObject === "object")
+ ({method, params, handler} = methodOrObject);
+ else if (!params)
+ params = {};
+
+ this._dispatchTable[++this._requestId] = handler;
+ let messageObject = {method, params, id: this._requestId};
+ this._sendMessage(messageObject);
+
+ return this._requestId;
+};
+
+InspectorProtocol.awaitCommand = function(args)
+{
+ let {method, params} = args;
+ let messageObject = {method, params, id: ++this._requestId};
+
+ return this.awaitMessage(messageObject);
+};
+
+InspectorProtocol.awaitMessage = function(messageObject)
+{
+ // Send a raw message to the backend. Mostly used to test the backend's error handling.
+ return new Promise((resolve, reject) => {
+ let requestId = messageObject.id;
+
+ // If the caller did not provide an id, then make one up so that the response
+ // can be used to settle a promise.
+ if (typeof requestId !== "number") {
+ requestId = ++this._requestId;
+ this._placeholderRequestIds.push(requestId);
+ }
+
+ this._dispatchTable[requestId] = {resolve, reject};
+ this._sendMessage(messageObject);
+ });
+};
+
+InspectorProtocol.awaitEvent = function(args)
+{
+ let event = args.event;
+ if (typeof event !== "string")
+ throw new Error("Event must be a string.");
+
+ return new Promise((resolve, reject) => {
+ InspectorProtocol.eventHandler[event] = function(message) {
+ InspectorProtocol.eventHandler[event] = undefined;
+ resolve(message);
+ };
+ });
+};
+
+InspectorProtocol._sendMessage = function(messageObject)
+{
+ let messageString = typeof messageObject !== "string" ? JSON.stringify(messageObject) : messageObject;
+
+ if (ProtocolTest.dumpInspectorProtocolMessages)
+ InspectorFrontendHost.unbufferedLog(`frontend: ${messageString}`);
+
+ InspectorFrontendHost.sendMessageToBackend(messageString);
+};
+
+InspectorProtocol.addEventListener = function(eventTypeOrObject, listener)
+{
+ let event = eventTypeOrObject;
+ if (typeof eventTypeOrObject === "object")
+ ({event, listener} = eventTypeOrObject);
+
+ if (typeof event !== "string")
+ throw new Error("Event name must be a string.");
+
+ if (typeof listener !== "function")
+ throw new Error("Event listener must be callable.");
+
+ // Convert to an array of listeners.
+ let listeners = InspectorProtocol.eventHandler[event];
+ if (!listeners)
+ listeners = InspectorProtocol.eventHandler[event] = [];
+ else if (typeof listeners === "function")
+ listeners = InspectorProtocol.eventHandler[event] = [listeners];
+
+ // Prevent registering multiple times.
+ if (listeners.includes(listener))
+ throw new Error("Cannot register the same listener more than once.");
+
+ listeners.push(listener);
+};
+
+InspectorProtocol.checkForError = function(responseObject)
+{
+ if (responseObject.error) {
+ ProtocolTest.log("PROTOCOL ERROR: " + JSON.stringify(responseObject.error));
+ ProtocolTest.completeTest();
+ throw "PROTOCOL ERROR";
+ }
+};
+
+InspectorProtocol.dispatchMessageFromBackend = function(messageObject)
+{
+ // This matches the debug dumping in InspectorBackend, which is bypassed
+ // by InspectorProtocol. Return messages should be dumped by InspectorBackend.
+ if (ProtocolTest.dumpInspectorProtocolMessages)
+ InspectorFrontendHost.unbufferedLog("backend: " + JSON.stringify(messageObject));
+
+ // If the message has an id, then it is a reply to a command.
+ let messageId = messageObject.id;
+
+ // If the id is 'null', then it may be an error response.
+ if (messageId === null)
+ messageId = InspectorProtocol._placeholderRequestIds.shift();
+
+ // If we could figure out a requestId, then dispatch the message.
+ if (typeof messageId === "number") {
+ let handler = InspectorProtocol._dispatchTable[messageId];
+ if (!handler)
+ return;
+
+ if (typeof handler === "function")
+ handler(messageObject);
+ else if (typeof handler === "object") {
+ let {resolve, reject} = handler;
+ if ("error" in messageObject)
+ reject(messageObject.error);
+ else
+ resolve(messageObject.result);
+ }
+ } else {
+ // Otherwise, it is an event.
+ let eventName = messageObject["method"];
+ let handler = InspectorProtocol.eventHandler[eventName];
+ if (!handler)
+ return;
+
+ if (typeof handler === "function")
+ handler(messageObject);
+ else if (handler instanceof Array) {
+ handler.map((listener) => { listener.call(null, messageObject); });
+ } else if (typeof handler === "object") {
+ let {resolve, reject} = handler;
+ if ("error" in messageObject)
+ reject(messageObject.error);
+ else
+ resolve(messageObject.result);
+ }
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Test/ProtocolTestHarness.js b/Source/WebInspectorUI/UserInterface/Test/ProtocolTestHarness.js
new file mode 100644
index 000000000..d0ef614b6
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/ProtocolTestHarness.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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 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 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.
+ */
+
+ProtocolTestHarness = class ProtocolTestHarness extends TestHarness
+{
+ // TestHarness Overrides
+
+ completeTest()
+ {
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog("completeTest()");
+
+ this.evaluateInPage("TestPage.closeTest();");
+ }
+
+ addResult(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog(stringifiedMessage);
+
+ // Unfortunately, every string argument must be escaped because tests are not consistent
+ // with respect to escaping with single or double quotes. Some exceptions use single quotes.
+ this.evaluateInPage(`TestPage.log(unescape("${escape(stringifiedMessage)}"));`);
+ }
+
+ debugLog(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+
+ if (this.dumpActivityToSystemConsole)
+ InspectorFrontendHost.unbufferedLog(stringifiedMessage);
+
+ this.evaluateInPage(`TestPage.debugLog(unescape("${escape(stringifiedMessage)}"));`);
+ }
+
+ evaluateInPage(expression, callback)
+ {
+ let args = {
+ method: "Runtime.evaluate",
+ params: {expression}
+ };
+
+ if (typeof callback === "function")
+ InspectorProtocol.sendCommand(args, callback);
+ else
+ return InspectorProtocol.awaitCommand(args);
+ }
+
+ debug()
+ {
+ this.dumpActivityToSystemConsole = true;
+ this.dumpInspectorProtocolMessages = true;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Test/Test.js b/Source/WebInspectorUI/UserInterface/Test/Test.js
new file mode 100644
index 000000000..f59bf7304
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/Test.js
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013-2015 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * HOLDER OR 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.
+ */
+
+WebInspector.DebuggableType = {
+ Web: "web",
+ JavaScript: "javascript"
+};
+
+WebInspector.loaded = function()
+{
+ this.debuggableType = WebInspector.DebuggableType.Web;
+ this.hasExtraDomains = false;
+
+ // Register observers for events from the InspectorBackend.
+ // The initialization order should match the same in Main.js.
+ InspectorBackend.registerInspectorDispatcher(new WebInspector.InspectorObserver);
+ InspectorBackend.registerPageDispatcher(new WebInspector.PageObserver);
+ InspectorBackend.registerConsoleDispatcher(new WebInspector.ConsoleObserver);
+ InspectorBackend.registerDOMDispatcher(new WebInspector.DOMObserver);
+ InspectorBackend.registerNetworkDispatcher(new WebInspector.NetworkObserver);
+ InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerObserver);
+ InspectorBackend.registerHeapDispatcher(new WebInspector.HeapObserver);
+ InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageObserver);
+ InspectorBackend.registerTimelineDispatcher(new WebInspector.TimelineObserver);
+ InspectorBackend.registerCSSDispatcher(new WebInspector.CSSObserver);
+ InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeObserver);
+ InspectorBackend.registerWorkerDispatcher(new WebInspector.WorkerObserver);
+ if (InspectorBackend.registerReplayDispatcher)
+ InspectorBackend.registerReplayDispatcher(new WebInspector.ReplayObserver);
+
+ WebInspector.mainTarget = new WebInspector.MainTarget;
+
+ // Instantiate controllers used by tests.
+ this.targetManager = new WebInspector.TargetManager;
+ this.frameResourceManager = new WebInspector.FrameResourceManager;
+ this.storageManager = new WebInspector.StorageManager;
+ this.domTreeManager = new WebInspector.DOMTreeManager;
+ this.cssStyleManager = new WebInspector.CSSStyleManager;
+ this.logManager = new WebInspector.LogManager;
+ this.issueManager = new WebInspector.IssueManager;
+ this.runtimeManager = new WebInspector.RuntimeManager;
+ this.heapManager = new WebInspector.HeapManager;
+ this.memoryManager = new WebInspector.MemoryManager;
+ this.timelineManager = new WebInspector.TimelineManager;
+ this.debuggerManager = new WebInspector.DebuggerManager;
+ this.probeManager = new WebInspector.ProbeManager;
+ this.workerManager = new WebInspector.WorkerManager;
+ this.replayManager = new WebInspector.ReplayManager;
+
+ document.addEventListener("DOMContentLoaded", this.contentLoaded);
+
+ // Enable agents.
+ InspectorAgent.enable();
+ ConsoleAgent.enable();
+
+ // Perform one-time tasks.
+ WebInspector.CSSCompletions.requestCSSCompletions();
+
+ // Global settings.
+ this.showShadowDOMSetting = new WebInspector.Setting("show-shadow-dom", true);
+};
+
+WebInspector.contentLoaded = function()
+{
+ // Signal that the frontend is now ready to receive messages.
+ InspectorFrontendAPI.loadCompleted();
+
+ // Tell the InspectorFrontendHost we loaded, which causes the window to display
+ // and pending InspectorFrontendAPI commands to be sent.
+ InspectorFrontendHost.loaded();
+};
+
+Object.defineProperty(WebInspector, "targets",
+{
+ get() { return this.targetManager.targets; }
+});
+
+WebInspector.assumingMainTarget = () => WebInspector.mainTarget;
+
+WebInspector.isDebugUIEnabled = () => false;
+
+WebInspector.UIString = (string) => string;
+
+WebInspector.indentString = () => " ";
+
+// Add stubs that are called by the frontend API.
+WebInspector.updateDockedState = () => {};
+WebInspector.updateDockingAvailability = () => {};
+WebInspector.updateVisibilityState = () => {};
+
+window.InspectorTest = new FrontendTestHarness();
+
+InspectorTest.redirectConsoleToTestOutput();
+
+WebInspector.reportInternalError = (e) => { console.error(e); };
+
+window.reportUnhandledRejection = InspectorTest.reportUnhandledRejection.bind(InspectorTest);
+window.onerror = InspectorTest.reportUncaughtExceptionFromEvent.bind(InspectorTest);
diff --git a/Source/WebInspectorUI/UserInterface/Test/TestHarness.js b/Source/WebInspectorUI/UserInterface/Test/TestHarness.js
new file mode 100644
index 000000000..53bee4a4b
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/TestHarness.js
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2015, 2016 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 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 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.
+ */
+
+TestHarness = class TestHarness extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._logCount = 0;
+ this._failureObjects = new Map;
+ this._failureObjectIdentifier = 1;
+
+ // Options that are set per-test for debugging purposes.
+ this.forceDebugLogging = false;
+
+ // Options that are set per-test to ensure deterministic output.
+ this.suppressStackTraces = false;
+ }
+
+ completeTest()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ addResult()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ debugLog()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ evaluateInPage(string, callback)
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ debug()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ createAsyncSuite(name)
+ {
+ return new AsyncTestSuite(this, name);
+ }
+
+ createSyncSuite(name)
+ {
+ return new SyncTestSuite(this, name);
+ }
+
+ get logCount()
+ {
+ return this._logCount;
+ }
+
+ log(message)
+ {
+ ++this._logCount;
+
+ if (this.forceDebugLogging)
+ this.debugLog(message);
+ else
+ this.addResult(message);
+ }
+
+ assert(condition, message)
+ {
+ if (condition)
+ return;
+
+ let stringifiedMessage = TestHarness.messageAsString(message);
+ this.log("ASSERT: " + stringifiedMessage);
+ }
+
+ expectThat(actual, message)
+ {
+ this._expect(TestHarness.ExpectationType.True, !!actual, message, actual);
+ }
+
+ expectFalse(actual, message)
+ {
+ this._expect(TestHarness.ExpectationType.False, !actual, message, actual);
+ }
+
+ expectNull(actual, message)
+ {
+ this._expect(TestHarness.ExpectationType.Null, actual === null, message, actual, null);
+ }
+
+ expectNotNull(actual, message)
+ {
+ this._expect(TestHarness.ExpectationType.NotNull, actual !== null, message, actual);
+ }
+
+ expectEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.Equal, expected === actual, message, actual, expected);
+ }
+
+ expectNotEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.NotEqual, expected !== actual, message, actual, expected);
+ }
+
+ expectShallowEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.ShallowEqual, Object.shallowEqual(actual, expected), message, actual, expected);
+ }
+
+ expectNotShallowEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.NotShallowEqual, !Object.shallowEqual(actual, expected), message, actual, expected);
+ }
+
+ expectEqualWithAccuracy(actual, expected, accuracy, message)
+ {
+ console.assert(typeof expected === "number");
+ console.assert(typeof actual === "number");
+
+ this._expect(TestHarness.ExpectationType.EqualWithAccuracy, Math.abs(expected - actual) <= accuracy, message, actual, expected, accuracy);
+ }
+
+ expectLessThan(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.LessThan, actual < expected, message, actual, expected);
+ }
+
+ expectLessThanOrEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.LessThanOrEqual, actual <= expected, message, actual, expected);
+ }
+
+ expectGreaterThan(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.GreaterThan, actual > expected, message, actual, expected);
+ }
+
+ expectGreaterThanOrEqual(actual, expected, message)
+ {
+ this._expect(TestHarness.ExpectationType.GreaterThanOrEqual, actual >= expected, message, actual, expected);
+ }
+
+ pass(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+ this.log("PASS: " + stringifiedMessage);
+ }
+
+ fail(message)
+ {
+ let stringifiedMessage = TestHarness.messageAsString(message);
+ this.log("FAIL: " + stringifiedMessage);
+ }
+
+ // Protected
+
+ static messageAsString(message)
+ {
+ if (message instanceof Element)
+ return message.textContent;
+
+ return (typeof message !== "string") ? JSON.stringify(message) : message;
+ }
+
+ static sanitizeURL(url)
+ {
+ if (!url)
+ return "(unknown)";
+
+ let lastPathSeparator = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
+ let location = (lastPathSeparator > 0) ? url.substr(lastPathSeparator + 1) : url;
+ if (!location.length)
+ location = "(unknown)";
+
+ // Clean up the location so it is bracketed or in parenthesis.
+ if (url.indexOf("[native code]") !== -1)
+ location = "[native code]";
+
+ return location;
+ }
+
+ static sanitizeStackFrame(frame, i)
+ {
+ // Most frames are of the form "functionName@file:///foo/bar/File.js:345".
+ // But, some frames do not have a functionName. Get rid of the file path.
+ let nameAndURLSeparator = frame.indexOf("@");
+ let frameName = (nameAndURLSeparator > 0) ? frame.substr(0, nameAndURLSeparator) : "(anonymous)";
+
+ let lastPathSeparator = Math.max(frame.lastIndexOf("/"), frame.lastIndexOf("\\"));
+ let frameLocation = (lastPathSeparator > 0) ? frame.substr(lastPathSeparator + 1) : frame;
+ if (!frameLocation.length)
+ frameLocation = "unknown";
+
+ // Clean up the location so it is bracketed or in parenthesis.
+ if (frame.indexOf("[native code]") !== -1)
+ frameLocation = "[native code]";
+ else
+ frameLocation = "(" + frameLocation + ")";
+
+ return `#${i}: ${frameName} ${frameLocation}`;
+ }
+
+ sanitizeStack(stack)
+ {
+ if (this.suppressStackTraces)
+ return "(suppressed)";
+
+ if (!stack || typeof stack !== "string")
+ return "(unknown)";
+
+ return stack.split("\n").map(TestHarness.sanitizeStackFrame).join("\n");
+ }
+
+ // Private
+
+ _expect(type, condition, message, ...values)
+ {
+ console.assert(values.length > 0, "Should have an 'actual' value.");
+
+ if (!message || !condition) {
+ values = values.map(this._expectationValueAsString.bind(this));
+ message = message || this._expectationMessageFormat(type).format(...values);
+ }
+
+ if (condition) {
+ this.pass(message);
+ return;
+ }
+
+ message += "\n Expected: " + this._expectedValueFormat(type).format(...values.slice(1));
+ message += "\n Actual: " + values[0];
+
+ this.fail(message);
+ }
+
+ _expectationValueAsString(value)
+ {
+ let instanceIdentifier = (object) => {
+ let id = this._failureObjects.get(object);
+ if (!id) {
+ id = this._failureObjectIdentifier++;
+ this._failureObjects.set(object, id);
+ }
+ return "#" + id;
+ };
+
+ const maximumValueStringLength = 200;
+ const defaultValueString = String(new Object); // [object Object]
+
+ // Special case for numbers, since JSON.stringify converts Infinity and NaN to null.
+ if (typeof value === "number")
+ return value;
+
+ try {
+ let valueString = JSON.stringify(value);
+ if (valueString.length <= maximumValueStringLength)
+ return valueString;
+ } catch (e) {}
+
+ try {
+ let valueString = String(value);
+ if (valueString === defaultValueString && value.constructor && value.constructor.name !== "Object")
+ return value.constructor.name + " instance " + instanceIdentifier(value);
+ return valueString;
+ } catch (e) {
+ return defaultValueString;
+ }
+ }
+
+ _expectationMessageFormat(type)
+ {
+ switch (type) {
+ case TestHarness.ExpectationType.True:
+ return "expectThat(%s)";
+ case TestHarness.ExpectationType.False:
+ return "expectFalse(%s)";
+ case TestHarness.ExpectationType.Null:
+ return "expectNull(%s)";
+ case TestHarness.ExpectationType.NotNull:
+ return "expectNotNull(%s)";
+ case TestHarness.ExpectationType.Equal:
+ return "expectEqual(%s, %s)";
+ case TestHarness.ExpectationType.NotEqual:
+ return "expectNotEqual(%s, %s)";
+ case TestHarness.ExpectationType.ShallowEqual:
+ return "expectShallowEqual(%s, %s)";
+ case TestHarness.ExpectationType.NotShallowEqual:
+ return "expectNotShallowEqual(%s, %s)";
+ case TestHarness.ExpectationType.EqualWithAccuracy:
+ return "expectEqualWithAccuracy(%s, %s, %s)";
+ case TestHarness.ExpectationType.LessThan:
+ return "expectLessThan(%s, %s)";
+ case TestHarness.ExpectationType.LessThanOrEqual:
+ return "expectLessThanOrEqual(%s, %s)";
+ case TestHarness.ExpectationType.GreaterThan:
+ return "expectGreaterThan(%s, %s)";
+ case TestHarness.ExpectationType.GreaterThanOrEqual:
+ return "expectGreaterThanOrEqual(%s, %s)";
+ default:
+ console.error("Unknown TestHarness.ExpectationType type: " + type);
+ return null;
+ }
+ }
+
+ _expectedValueFormat(type)
+ {
+ switch (type) {
+ case TestHarness.ExpectationType.True:
+ return "truthy";
+ case TestHarness.ExpectationType.False:
+ return "falsey";
+ case TestHarness.ExpectationType.NotNull:
+ return "not null";
+ case TestHarness.ExpectationType.NotEqual:
+ case TestHarness.ExpectationType.NotShallowEqual:
+ return "not %s";
+ case TestHarness.ExpectationType.EqualWithAccuracy:
+ return "%s +/- %s";
+ case TestHarness.ExpectationType.LessThan:
+ return "less than %s";
+ case TestHarness.ExpectationType.LessThanOrEqual:
+ return "less than or equal to %s";
+ case TestHarness.ExpectationType.GreaterThan:
+ return "greater than %s";
+ case TestHarness.ExpectationType.GreaterThanOrEqual:
+ return "greater than or equal to %s";
+ default:
+ return "%s";
+ }
+ }
+};
+
+TestHarness.ExpectationType = {
+ True: Symbol("expect-true"),
+ False: Symbol("expect-false"),
+ Null: Symbol("expect-null"),
+ NotNull: Symbol("expect-not-null"),
+ Equal: Symbol("expect-equal"),
+ NotEqual: Symbol("expect-not-equal"),
+ ShallowEqual: Symbol("expect-shallow-equal"),
+ NotShallowEqual: Symbol("expect-not-shallow-equal"),
+ EqualWithAccuracy: Symbol("expect-equal-with-accuracy"),
+ LessThan: Symbol("expect-less-than"),
+ LessThanOrEqual: Symbol("expect-less-than-or-equal"),
+ GreaterThan: Symbol("expect-greater-than"),
+ GreaterThanOrEqual: Symbol("expect-greater-than-or-equal"),
+};
diff --git a/Source/WebInspectorUI/UserInterface/Test/TestStub.js b/Source/WebInspectorUI/UserInterface/Test/TestStub.js
new file mode 100644
index 000000000..cda7c7ac6
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/TestStub.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ * Copyright (C) 2014, 2015 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 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 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.
+ */
+
+InspectorFrontendAPI = {};
+InspectorFrontendAPI.dispatchMessageAsync = InspectorProtocol.dispatchMessageFromBackend;
+
+window.ProtocolTest = new ProtocolTestHarness();
+
+window.addEventListener("message", (event) => {
+ try {
+ eval(event.data);
+ } catch (e) {
+ alert(e.stack);
+ ProtocolTest.completeTest();
+ throw e;
+ }
+});
diff --git a/Source/WebInspectorUI/UserInterface/Test/TestSuite.js b/Source/WebInspectorUI/UserInterface/Test/TestSuite.js
new file mode 100644
index 000000000..aacd313b7
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Test/TestSuite.js
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2015 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 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 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.
+ */
+
+TestSuite = class TestSuite extends WebInspector.Object
+{
+ constructor(harness, name) {
+ if (!(harness instanceof TestHarness))
+ throw new Error("Must pass the test's harness as the first argument.");
+
+ if (typeof name !== "string" || !name.trim().length)
+ throw new Error("Tried to create TestSuite without string suite name.");
+
+ super();
+
+ this.name = name;
+ this._harness = harness;
+
+ this.testcases = [];
+ this.runCount = 0;
+ this.failCount = 0;
+ }
+
+ // Use this if the test file only has one suite, and no handling
+ // of the value returned by runTestCases() is needed.
+ runTestCasesAndFinish()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ runTestCases()
+ {
+ throw new Error("Must be implemented by subclasses.");
+ }
+
+ get passCount()
+ {
+ return this.runCount - this.failCount;
+ }
+
+ get skipCount()
+ {
+ if (this.failCount)
+ return this.testcases.length - this.runCount;
+ else
+ return 0;
+ }
+
+ addTestCase(testcase)
+ {
+ if (!testcase || !(testcase instanceof Object))
+ throw new Error("Tried to add non-object test case.");
+
+ if (typeof testcase.name !== "string" || !testcase.name.trim().length)
+ throw new Error("Tried to add test case without a name.");
+
+ if (typeof testcase.test !== "function")
+ throw new Error("Tried to add test case without `test` function.");
+
+ if (testcase.setup && typeof testcase.setup !== "function")
+ throw new Error("Tried to add test case with invalid `setup` parameter (must be a function).");
+
+ if (testcase.teardown && typeof testcase.teardown !== "function")
+ throw new Error("Tried to add test case with invalid `teardown` parameter (must be a function).");
+
+ this.testcases.push(testcase);
+ }
+
+ // Protected
+
+ logThrownObject(e)
+ {
+ let message = e;
+ let stack = "(unknown)";
+ if (e instanceof Error) {
+ message = e.message;
+ if (e.stack)
+ stack = e.stack;
+ }
+
+ if (typeof message !== "string")
+ message = JSON.stringify(message);
+
+ let sanitizedStack = this._harness.sanitizeStack(stack);
+
+ let result = `!! EXCEPTION: ${message}`;
+ if (stack)
+ result += `\nStack Trace: ${sanitizedStack}`;
+
+ this._harness.log(result);
+ }
+};
+
+AsyncTestSuite = class AsyncTestSuite extends TestSuite
+{
+ runTestCasesAndFinish()
+ {
+ let finish = () => { this._harness.completeTest(); };
+
+ this.runTestCases()
+ .then(finish)
+ .catch(finish);
+ }
+
+ runTestCases()
+ {
+ if (!this.testcases.length)
+ throw new Error("Tried to call runTestCases() for suite with no test cases");
+ if (this._startedRunning)
+ throw new Error("Tried to call runTestCases() more than once.");
+
+ this._startedRunning = true;
+
+ this._harness.log("");
+ this._harness.log(`== Running test suite: ${this.name}`);
+
+ // Avoid adding newlines if nothing was logged.
+ let priorLogCount = this._harness.logCount;
+ let result = this.testcases.reduce((chain, testcase, i) => {
+ if (testcase.setup) {
+ chain = chain.then(() => {
+ this._harness.log("-- Running test setup.");
+ return new Promise(testcase.setup);
+ });
+ }
+
+ chain = chain.then(() => {
+ if (i > 0 && priorLogCount + 1 < this._harness.logCount)
+ this._harness.log("");
+
+ priorLogCount = this._harness.logCount;
+ this._harness.log(`-- Running test case: ${testcase.name}`);
+ this.runCount++;
+ return new Promise(testcase.test);
+ });
+
+ if (testcase.teardown) {
+ chain = chain.then(() => {
+ this._harness.log("-- Running test teardown.");
+ return new Promise(testcase.teardown);
+ });
+ }
+ return chain;
+ }, Promise.resolve());
+
+ return result.catch((e) => {
+ this.failCount++;
+ this.logThrownObject(e);
+
+ throw e; // Reject this promise by re-throwing the error.
+ });
+ }
+};
+
+SyncTestSuite = class SyncTestSuite extends TestSuite
+{
+ runTestCasesAndFinish()
+ {
+ this.runTestCases();
+ this._harness.completeTest();
+ }
+
+ runTestCases()
+ {
+ if (!this.testcases.length)
+ throw new Error("Tried to call runTestCases() for suite with no test cases");
+ if (this._startedRunning)
+ throw new Error("Tried to call runTestCases() more than once.");
+
+ this._startedRunning = true;
+
+ this._harness.log("");
+ this._harness.log(`== Running test suite: ${this.name}`);
+
+ let priorLogCount = this._harness.logCount;
+ for (let i = 0; i < this.testcases.length; i++) {
+ let testcase = this.testcases[i];
+ if (i > 0 && priorLogCount + 1 < this._harness.logCount)
+ this._harness.log("");
+
+ priorLogCount = this._harness.logCount;
+
+ // Run the setup action, if one was provided.
+ if (testcase.setup) {
+ this._harness.log("-- Running test setup.");
+ try {
+ let result = testcase.setup.call(null);
+ if (result === false) {
+ this._harness.log("!! SETUP FAILED");
+ return false;
+ }
+ } catch (e) {
+ this.logThrownObject(e);
+ return false;
+ }
+ }
+
+ this._harness.log("-- Running test case: " + testcase.name);
+ this.runCount++;
+ try {
+ let result = testcase.test.call(null);
+ if (result === false) {
+ this.failCount++;
+ return false;
+ }
+ } catch (e) {
+ this.failCount++;
+ this.logThrownObject(e);
+ return false;
+ }
+
+ // Run the teardown action, if one was provided.
+ if (testcase.teardown) {
+ this._harness.log("-- Running test teardown.");
+ try {
+ let result = testcase.teardown.call(null);
+ if (result === false) {
+ this._harness.log("!! TEARDOWN FAILED");
+ return false;
+ }
+ } catch (e) {
+ this.logThrownObject(e);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+};