Title: [210367] trunk
Revision
210367
Author
[email protected]
Date
2017-01-05 12:07:44 -0800 (Thu, 05 Jan 2017)

Log Message

Web Inspector: Test.html should support globals reportInternalError, reportUnhandledRejection, reportUncaughtException
https://bugs.webkit.org/show_bug.cgi?id=161358
<rdar://problem/28066446>

Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

We have a hodgepodge of redundant code that reports uncaught exceptions in the inspector page.
There is better handling of uncaught exceptions in the inspected page, such as including stack traces.

This patch consolidates a lot of this code and makes it possible to report
unhandled promise rejections, top-level uncaught exceptions, and exceptions
caught in a try-catch block. The formatting and sanitization code for all of
these things is shared and consistent. Finally, some tests have been added to
catch regressions in unhandled rejection/uncaught exception reporting.

* UserInterface/Test/FrontendTestHarness.js:
(FrontendTestHarness): Explicitly set initial flag state here so it's easy to find all flags.

(FrontendTestHarness.prototype.redirectConsoleToTestOutput):
Extract this code to sanitize stack frames and put it in TestHarness. It is used
by other methods that need to print stack frames.

(FrontendTestHarness.prototype.reportUnhandledRejection):
(FrontendTestHarness.prototype.reportUncaughtException):
Added. Sanitize stack trace data so it is deterministic. Log the message to the
original window.console but don't exit early. Sometimes the test page is not
fully loaded if we throw an exception quite early in the test() method, and there's
no harm in not early returning. If we do early return in this case, then a test that
uses reportUncaughtException on purpose may not complete because the call to completeTest()
would be skipped by returning early.

(FrontendTestHarness.prototype.reportUncaughtExceptionFromEvent):
Renamed from reportUncaughtException since the signature of that method suggests
it should have a single exception argument rather than lots of data arguments.

* UserInterface/Test/Test.js: Add globals.

* UserInterface/Test/TestHarness.js:
(TestHarness): Document class flags.
(TestHarness.sanitizeURL):
(TestHarness.sanitizeStackFrame):
(TestHarness.prototype.sanitizeStack):
Extract this code from other parts of the test harness. Make sanitizeStack
an instance method so that there is only one place that needs to check the
'suppressStackTraces' flag.

* UserInterface/Test/TestSuite.js:
(TestSuite.prototype.logThrownObject):
(TestSuite):
(AsyncTestSuite.prototype.runTestCases):
(AsyncTestSuite):
(SyncTestSuite.prototype.runTestCases):
(SyncTestSuite):
(TestSuite.messageFromThrownObject): Deleted.
Inline some helpers with only one use-site and consolidate redundant code
for adding an exception and message to the test results.

LayoutTests:

Improve uncaught exception reporting and add some tests to document
new and existing behavior.

* http/tests/inspector/resources/inspector-test.js:
(runTest.runTestMethodInFrontend):
(runTest): Outsource reporting of an uncaught exception while injecting
a method into the frontend. By doing this, we can make the report using
the actual exception object since it doesn't go through window.onerror.

* inspector/unit-tests/async-test-suite-expected.txt:
* inspector/unit-tests/async-test-suite.html:
* inspector/unit-tests/sync-test-suite-expected.txt:
* inspector/unit-tests/sync-test-suite.html:
Rebaseline and force suppression of stack traces, which are not deterministic
across commits due to logging specific lines and columns in TestCombined.js.

* inspector/unit-tests/globals-uncaught-exception-from-timer-callback-expected.txt: Added.
* inspector/unit-tests/globals-uncaught-exception-from-timer-callback.html: Added.
* inspector/unit-tests/globals-uncaught-exception-in-test-function-expected.txt: Added.
* inspector/unit-tests/globals-uncaught-exception-in-test-function.html: Added.
* inspector/unit-tests/globals-uncaught-exception-in-test-suite-expected.txt: Added.
* inspector/unit-tests/globals-uncaught-exception-in-test-suite.html: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-test-function-expected.txt: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-test-function.html: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-test-suite-expected.txt: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-test-suite.html: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-timer-callback-expected.txt: Added.
* inspector/unit-tests/globals-unhandled-rejection-in-timer-callback.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (210366 => 210367)


--- trunk/LayoutTests/ChangeLog	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/ChangeLog	2017-01-05 20:07:44 UTC (rev 210367)
@@ -1,3 +1,40 @@
+2017-01-04  Brian Burg  <[email protected]>
+
+        Web Inspector: Test.html should support globals reportInternalError, reportUnhandledRejection, reportUncaughtException
+        https://bugs.webkit.org/show_bug.cgi?id=161358
+        <rdar://problem/28066446>
+
+        Reviewed by Joseph Pecoraro.
+
+        Improve uncaught exception reporting and add some tests to document
+        new and existing behavior.
+
+        * http/tests/inspector/resources/inspector-test.js:
+        (runTest.runTestMethodInFrontend):
+        (runTest): Outsource reporting of an uncaught exception while injecting
+        a method into the frontend. By doing this, we can make the report using
+        the actual exception object since it doesn't go through window.onerror.
+
+        * inspector/unit-tests/async-test-suite-expected.txt:
+        * inspector/unit-tests/async-test-suite.html:
+        * inspector/unit-tests/sync-test-suite-expected.txt:
+        * inspector/unit-tests/sync-test-suite.html:
+        Rebaseline and force suppression of stack traces, which are not deterministic
+        across commits due to logging specific lines and columns in TestCombined.js.
+
+        * inspector/unit-tests/globals-uncaught-exception-from-timer-callback-expected.txt: Added.
+        * inspector/unit-tests/globals-uncaught-exception-from-timer-callback.html: Added.
+        * inspector/unit-tests/globals-uncaught-exception-in-test-function-expected.txt: Added.
+        * inspector/unit-tests/globals-uncaught-exception-in-test-function.html: Added.
+        * inspector/unit-tests/globals-uncaught-exception-in-test-suite-expected.txt: Added.
+        * inspector/unit-tests/globals-uncaught-exception-in-test-suite.html: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-test-function-expected.txt: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-test-function.html: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-test-suite-expected.txt: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-test-suite.html: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-timer-callback-expected.txt: Added.
+        * inspector/unit-tests/globals-unhandled-rejection-in-timer-callback.html: Added.
+
 2017-01-05  Andreas Kling  <[email protected]>
 
         Skip fast/scrolling/page-cache-back-overflow-scroll-restore.html on iOS simulator.

Modified: trunk/LayoutTests/http/tests/inspector/resources/inspector-test.js (210366 => 210367)


--- trunk/LayoutTests/http/tests/inspector/resources/inspector-test.js	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/http/tests/inspector/resources/inspector-test.js	2017-01-05 20:07:44 UTC (rev 210367)
@@ -88,9 +88,9 @@
         try {
             testFunction();
         } catch (e) {
-            // FIXME: the redirected console methods do not forward additional arguments.
-            console.error("Exception during test execution: " + e, e.stack || "(no stack trace)");
-            InspectorTest.completeTest();
+            // Using this instead of window.onerror will preserve the stack trace.
+            e.code = testFunction.toString();
+            InspectorTest.reportUncaughtException(e);
         }
     }
 

Modified: trunk/LayoutTests/inspector/unit-tests/async-test-suite-expected.txt (210366 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/async-test-suite-expected.txt	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/inspector/unit-tests/async-test-suite-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -26,6 +26,7 @@
 -- Running test case: DummyTest3
 -- Running test case: FailingTest4
 !! EXCEPTION: [object Object]
+Stack Trace: (suppressed)
 PASS: Promise from sequentialExecutionSuite.runTestCases() should reject when a test case fails.
 PASS: Promise from sequentialExecutionSuite.runTestCases() should reject without altering its result value.
 PASS: sequentialExecutionSuite should have executed four tests.
@@ -37,6 +38,7 @@
 -- Running test case: PassingTest5
 -- Running test case: FailingTest6
 !! EXCEPTION: {"token":666}
+Stack Trace: (suppressed)
 PASS: Promise from abortOnFailureSuite.runTestCases() should reject when a test case fails.
 PASS: Promise from abortOnFailureSuite.runTestCases() should reject without altering its result value.
 PASS: abortOnFailureSuite should have executed two tests.
@@ -58,11 +60,13 @@
 == Running test suite: AsyncTestSuite.SetupException
 -- Running test setup.
 !! EXCEPTION: 
+Stack Trace: (suppressed)
 PASS: Promise from setupExceptionTestSuite.runTestCases() should reject.
 
 == Running test suite: AsyncTestSuite.SetupFailure
 -- Running test setup.
 !! EXCEPTION: undefined
+Stack Trace: (suppressed)
 PASS: Promise from setupFailureTestSuite.runTestCases() should reject.
 
 == Running test suite: AsyncTestSuite.TeardownException
@@ -69,6 +73,7 @@
 -- Running test case: TestWithExceptionDuringTeardown
 -- Running test teardown.
 !! EXCEPTION: 
+Stack Trace: (suppressed)
 PASS: Promise from teardownExceptionTestSuite.runTestCases() should reject.
 
 == Running test suite: AsyncTestSuite.TeardownFailure
@@ -75,5 +80,6 @@
 -- Running test case: TestWithExceptionDuringTeardown
 -- Running test teardown.
 !! EXCEPTION: undefined
+Stack Trace: (suppressed)
 PASS: Promise from teardownFailureTestSuite.runTestCases() should reject.
 

Modified: trunk/LayoutTests/inspector/unit-tests/async-test-suite.html (210366 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/async-test-suite.html	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/inspector/unit-tests/async-test-suite.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -5,6 +5,8 @@
 <script>
 function test()
 {
+    ProtocolTest.suppressStackTraces = true;
+
     try {
         let result = new AsyncTestSuite(this);
         ProtocolTest.log("FAIL: instantiating AsyncTestSuite requires name argument.");

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,5 @@
+Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.
+
+Uncaught exception in Inspector page: Error: This is an exception thrown in the inspector page. [global:20:24]
+
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-from-timer-callback.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    setTimeout(() => {
+        throw new Error("This is an exception thrown in the inspector page.");
+    });
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,15 @@
+Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.
+
+Uncaught exception in Inspector page: This is an exception thrown in the inspector page.
+
+Stack Trace:
+(suppressed)
+
+Evaluated Code:
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    throw new Error("This is an exception thrown in the inspector page.");
+}
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-function.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    throw new Error("This is an exception thrown in the inspector page.");
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,8 @@
+Test that the unhandled promise rejection hook will immediately terminate the test and print the associated exception and stack trace.
+
+
+== Running test suite: UncaughtExceptionInsideTestCase
+-- Running test case: CatchRejectedPromise
+!! EXCEPTION: This promise is rejected with an Error object.
+Stack Trace: (suppressed)
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-uncaught-exception-in-test-suite.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,26 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    let suite = InspectorTest.createAsyncSuite("UncaughtExceptionInsideTestCase");
+    suite.addTestCase({
+        name: "CatchRejectedPromise",
+        description: "window.reportUnhandledRejection should dump the unhandled exception and quit testing.",
+        test(resolve, reject) {
+            throw new Error("This promise is rejected with an Error object.");
+        }
+    })
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the unhandled promise rejection hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,7 @@
+Test that the unhandled hook will immediately terminate the test and print the associated exception and stack trace.
+
+Unhandled promise rejection in inspector page: This is an exception thrown in the inspector page.
+
+Stack Trace: (suppressed)
+
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-function.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    reportUnhandledRejection(new Error("This is an exception thrown in the inspector page."));
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the unhandled hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,10 @@
+Test that the unhandled promise rejection hook will immediately terminate the test and print the associated exception and stack trace.
+
+
+== Running test suite: ReportUnhandledRejection
+-- Running test case: CatchRejectedPromise
+Unhandled promise rejection in inspector page: This promise is rejected with an Error object.
+
+Stack Trace: (suppressed)
+
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-test-suite.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    let suite = InspectorTest.createAsyncSuite("ReportUnhandledRejection");
+    suite.addTestCase({
+        name: "CatchRejectedPromise",
+        description: "window.reportUnhandledRejection should dump the unhandled exception and quit testing.",
+        test(resolve, reject) {
+            let rejectedPromise = Promise.reject(new Error("This promise is rejected with an Error object."));
+            rejectedPromise.catch(reportUnhandledRejection);
+        }
+    })
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the unhandled promise rejection hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback-expected.txt (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,5 @@
+Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.
+
+Unhandled promise rejection in inspector page: This is an exception thrown in the inspector page.
+
+

Added: trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback.html (0 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/globals-unhandled-rejection-in-timer-callback.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.suppressStackTraces = true;
+
+    setTimeout(() => {
+        reportUnhandledRejection(new Error("This is an exception thrown in the inspector page."));
+    });
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test that the uncaught exception hook will immediately terminate the test and print the associated exception and stack trace.</p>
+</body>
+</html>

Modified: trunk/LayoutTests/inspector/unit-tests/sync-test-suite-expected.txt (210366 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/sync-test-suite-expected.txt	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/inspector/unit-tests/sync-test-suite-expected.txt	2017-01-05 20:07:44 UTC (rev 210367)
@@ -26,6 +26,7 @@
 -- Running test case: DummyTest3
 -- Running test case: FailingTest4
 !! EXCEPTION: [object Object]
+Stack Trace: (suppressed)
 PASS: Return value of sequentialExecutionSuite.runTestCases() should be false when a test case fails.
 PASS: sequentialExecutionSuite should have executed four tests.
 PASS: sequentialExecutionSuite should have passed three tests.
@@ -54,18 +55,20 @@
 == Running test suite: SyncTestSuite.SetupException
 -- Running test setup.
 !! EXCEPTION: 
+Stack Trace: (suppressed)
 
 == Running test suite: SyncTestSuite.SetupFailure
 -- Running test setup.
-!! EXCEPTION
+!! SETUP FAILED
 
 == Running test suite: SyncTestSuite.TeardownException
 -- Running test case: TestWithExceptionDuringTeardown
 -- Running test teardown.
 !! EXCEPTION: 
+Stack Trace: (suppressed)
 
 == Running test suite: SyncTestSuite.TeardownFailure
 -- Running test case: TestWithExceptionDuringTeardown
 -- Running test teardown.
-!! EXCEPTION:
+!! TEARDOWN FAILED
 

Modified: trunk/LayoutTests/inspector/unit-tests/sync-test-suite.html (210366 => 210367)


--- trunk/LayoutTests/inspector/unit-tests/sync-test-suite.html	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/LayoutTests/inspector/unit-tests/sync-test-suite.html	2017-01-05 20:07:44 UTC (rev 210367)
@@ -5,6 +5,8 @@
 <script>
 function test()
 {
+    ProtocolTest.suppressStackTraces = true;
+
     try {
         let result = new SyncTestSuite(this);
         ProtocolTest.log("FAIL: instantiating SyncTestSuite requires name argument.");

Modified: trunk/Source/WebInspectorUI/ChangeLog (210366 => 210367)


--- trunk/Source/WebInspectorUI/ChangeLog	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/Source/WebInspectorUI/ChangeLog	2017-01-05 20:07:44 UTC (rev 210367)
@@ -1,3 +1,62 @@
+2017-01-04  Brian Burg  <[email protected]>
+
+        Web Inspector: Test.html should support globals reportInternalError, reportUnhandledRejection, reportUncaughtException
+        https://bugs.webkit.org/show_bug.cgi?id=161358
+        <rdar://problem/28066446>
+
+        Reviewed by Joseph Pecoraro.
+
+        We have a hodgepodge of redundant code that reports uncaught exceptions in the inspector page.
+        There is better handling of uncaught exceptions in the inspected page, such as including stack traces.
+
+        This patch consolidates a lot of this code and makes it possible to report
+        unhandled promise rejections, top-level uncaught exceptions, and exceptions
+        caught in a try-catch block. The formatting and sanitization code for all of
+        these things is shared and consistent. Finally, some tests have been added to
+        catch regressions in unhandled rejection/uncaught exception reporting.
+
+        * UserInterface/Test/FrontendTestHarness.js:
+        (FrontendTestHarness): Explicitly set initial flag state here so it's easy to find all flags.
+
+        (FrontendTestHarness.prototype.redirectConsoleToTestOutput):
+        Extract this code to sanitize stack frames and put it in TestHarness. It is used
+        by other methods that need to print stack frames.
+
+        (FrontendTestHarness.prototype.reportUnhandledRejection):
+        (FrontendTestHarness.prototype.reportUncaughtException):
+        Added. Sanitize stack trace data so it is deterministic. Log the message to the
+        original window.console but don't exit early. Sometimes the test page is not
+        fully loaded if we throw an exception quite early in the test() method, and there's
+        no harm in not early returning. If we do early return in this case, then a test that
+        uses reportUncaughtException on purpose may not complete because the call to completeTest()
+        would be skipped by returning early.
+
+        (FrontendTestHarness.prototype.reportUncaughtExceptionFromEvent):
+        Renamed from reportUncaughtException since the signature of that method suggests
+        it should have a single exception argument rather than lots of data arguments.
+
+        * UserInterface/Test/Test.js: Add globals.
+
+        * UserInterface/Test/TestHarness.js:
+        (TestHarness): Document class flags.
+        (TestHarness.sanitizeURL):
+        (TestHarness.sanitizeStackFrame):
+        (TestHarness.prototype.sanitizeStack):
+        Extract this code from other parts of the test harness. Make sanitizeStack
+        an instance method so that there is only one place that needs to check the
+        'suppressStackTraces' flag.
+
+        * UserInterface/Test/TestSuite.js:
+        (TestSuite.prototype.logThrownObject):
+        (TestSuite):
+        (AsyncTestSuite.prototype.runTestCases):
+        (AsyncTestSuite):
+        (SyncTestSuite.prototype.runTestCases):
+        (SyncTestSuite):
+        (TestSuite.messageFromThrownObject): Deleted.
+        Inline some helpers with only one use-site and consolidate redundant code
+        for adding an exception and message to the test results.
+
 2017-01-04  Nikita Vasilyev  <[email protected]>
 
         Web Inspector: application cache details not shown in Storage Tab

Modified: trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js (210366 => 210367)


--- trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js	2017-01-05 20:07:44 UTC (rev 210367)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
+ * 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
@@ -31,6 +31,9 @@
 
         this._results = [];
         this._shouldResendResults = true;
+
+        // Options that are set per-test for debugging purposes.
+        this.dumpActivityToSystemConsole = false;
     }
 
     // TestHarness Overrides
@@ -151,25 +154,7 @@
                 } catch (e) {
                     // Skip the first frame which is added by this function.
                     let frames = e.stack.split("\n").slice(1);
-                    let sanitizedFrames = frames.map((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}`;
-                    });
+                    let sanitizedFrames = frames.map(TestHarness.sanitizeStackFrame);
                     self.addResult("TRACE: " + Array.from(arguments).join(" "));
                     self.addResult(sanitizedFrames.join("\n"));
                 }
@@ -189,23 +174,63 @@
         window.console = redirectedMethods;
     }
 
-    reportUncaughtException(message, url, lineNumber, columnNumber)
+    reportUnhandledRejection(error)
     {
-        let result = `Uncaught exception in inspector page: ${message} [${url}:${lineNumber}:${columnNumber}]`;
+        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()) {
+        if (this._originalConsole && !this._testPageHasLoaded())
             this._originalConsole["error"](result);
-            return false;
-        }
 
         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 = ""
+
+        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()

Modified: trunk/Source/WebInspectorUI/UserInterface/Test/Test.js (210366 => 210367)


--- trunk/Source/WebInspectorUI/UserInterface/Test/Test.js	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/Test.js	2017-01-05 20:07:44 UTC (rev 210367)
@@ -115,3 +115,6 @@
 InspectorTest.redirectConsoleToTestOutput();
 
 WebInspector.reportInternalError = (e) => { console.error(e); }
+
+window.reportUnhandledRejection = InspectorTest.reportUnhandledRejection.bind(InspectorTest);
+window._onerror_ = InspectorTest.reportUncaughtExceptionFromEvent.bind(InspectorTest);

Modified: trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js (210366 => 210367)


--- trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js	2017-01-05 20:07:44 UTC (rev 210367)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * 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
@@ -32,6 +32,12 @@
         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()
@@ -183,6 +189,55 @@
         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)

Modified: trunk/Source/WebInspectorUI/UserInterface/Test/TestSuite.js (210366 => 210367)


--- trunk/Source/WebInspectorUI/UserInterface/Test/TestSuite.js	2017-01-05 19:51:38 UTC (rev 210366)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/TestSuite.js	2017-01-05 20:07:44 UTC (rev 210367)
@@ -87,16 +87,28 @@
         this.testcases.push(testcase);
     }
 
-    static messageFromThrownObject(e)
+    // Protected
+
+    logThrownObject(e)
     {
         let message = e;
-        if (e instanceof Error)
+        let stack = "(unknown)";
+        if (e instanceof Error) {
             message = e.message;
+            if (e.stack)
+                stack = e.stack;
+        }
 
         if (typeof message !== "string")
             message = JSON.stringify(message);
 
-        return message;
+        let sanitizedStack = this._harness.sanitizeStack(stack);
+
+        let result = `!! EXCEPTION: ${message}`;
+        if (stack)
+            result += `\nStack Trace: ${sanitizedStack}`;
+
+        this._harness.log(result);
     }
 };
 
@@ -154,8 +166,7 @@
 
         return result.catch((e) => {
             this.failCount++;
-            let message = TestSuite.messageFromThrownObject(e);
-            this._harness.log(`!! EXCEPTION: ${message}`);
+            this.logThrownObject(e);
 
             throw e; // Reject this promise by re-throwing the error.
         });
@@ -196,12 +207,11 @@
                 try {
                     let result = testcase.setup.call(null);
                     if (result === false) {
-                        this._harness.log("!! EXCEPTION");
+                        this._harness.log("!! SETUP FAILED");
                         return false;
                     }
                 } catch (e) {
-                    let message = TestSuite.messageFromThrownObject(e);
-                    this._harness.log(`!! EXCEPTION: ${message}`);
+                    this.logThrownObject(e);
                     return false;
                 }
             }
@@ -216,8 +226,7 @@
                 }
             } catch (e) {
                 this.failCount++;
-                let message = TestSuite.messageFromThrownObject(e);
-                this._harness.log(`!! EXCEPTION: ${message}`);
+                this.logThrownObject(e);
                 return false;
             }
 
@@ -227,12 +236,11 @@
                 try {
                     let result = testcase.teardown.call(null);
                     if (result === false) {
-                        this._harness.log("!! EXCEPTION:");
+                        this._harness.log("!! TEARDOWN FAILED");
                         return false;
                     }
                 } catch (e) {
-                    let message = TestSuite.messageFromThrownObject(e);
-                    this._harness.log(`!! EXCEPTION: ${message}`);
+                    this.logThrownObject(e);
                     return false;
                 }
             }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to