Web Inspector: Improve output of TestHarness.expect* failures
authormattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Sep 2016 21:40:22 +0000 (21:40 +0000)
committermattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Sep 2016 21:40:22 +0000 (21:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162177
<rdar://problem/28367186>

Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

This patch adds specific expectation functions to TestHarness, to better
express intent when writing tests, and to allow more details to be logged
in the event of a failure.

For functions taking both `actual` and `expected` parameters, the `actual`
parameter comes first. This convention simplifies the implementation of
TestHarness, improves the readability of tests involving inequalities,
and is consistent with XCTest assertions.

* UserInterface/Test/TestHarness.js:
(TestHarness):
(TestHarness.prototype.expectThat):
(TestHarness.prototype.expectFalse):
(TestHarness.prototype.expectNull):
(TestHarness.prototype.expectNotNull):
(TestHarness.prototype.expectEqual):
(TestHarness.prototype.expectNotEqual):
(TestHarness.prototype.expectShallowEqual):
(TestHarness.prototype.expectNotShallowEqual):
(TestHarness.prototype.expectEqualWithAccuracy):
(TestHarness.prototype.expectLessThan):
(TestHarness.prototype.expectLessThanOrEqual):
(TestHarness.prototype.expectGreaterThan):
(TestHarness.prototype.expectGreaterThanOrEqual):
New expectation functions, all of which call _expect under the hood.

(TestHarness.prototype._expect):
Helper method which calls pass or fail. Creates a message when no user
message is provided, and logs expected and actual values in the event
of a failure.

(TestHarness.prototype._expectationValueAsString):
(TestHarness.prototype._expectationMessageFormat):
Get a message format string for the expectation type. Used to create
pass/fail message when no user message is provided.

(TestHarness.prototype._expectedValueFormat):
Get a format string for displaying the expected value. Used to create
the "Expected: " failure message line.

LayoutTests:

Extend coverage to more TestHarness messages by omitting the optional
`message` parameter when calling expect* functions.

Other improvements:
- expectEqual/expectNotEqual should test WebInspector object instances,
  to cover more TestHarness message formatting cases.
- expectEqual/expectNotEqual should test shallow equal arrays, since the
  test is for strict equality.

* inspector/indexeddb/requestData-expected.txt:
* inspector/unit-tests/number-utilities-expected.txt:
Updated expectations for new TestHarness output.

* inspector/unit-tests/test-harness-expect-functions-expected.txt:
* inspector/unit-tests/test-harness-expect-functions.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@206237 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/inspector/indexeddb/requestData-expected.txt
LayoutTests/inspector/unit-tests/number-utilities-expected.txt
LayoutTests/inspector/unit-tests/test-harness-expect-functions-expected.txt
LayoutTests/inspector/unit-tests/test-harness-expect-functions.html
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Test/TestHarness.js

index 870a487a141d830b5375e52f692aebc21ffeaccd..38714027efb26daaa9ba4c13ac8a082828285e0f 100644 (file)
@@ -1,3 +1,27 @@
+2016-09-21  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: Improve output of TestHarness.expect* failures
+        https://bugs.webkit.org/show_bug.cgi?id=162177
+        <rdar://problem/28367186>
+
+        Reviewed by Joseph Pecoraro.
+
+        Extend coverage to more TestHarness messages by omitting the optional
+        `message` parameter when calling expect* functions.
+
+        Other improvements:
+        - expectEqual/expectNotEqual should test WebInspector object instances,
+          to cover more TestHarness message formatting cases.
+        - expectEqual/expectNotEqual should test shallow equal arrays, since the
+          test is for strict equality.
+
+        * inspector/indexeddb/requestData-expected.txt:
+        * inspector/unit-tests/number-utilities-expected.txt:
+        Updated expectations for new TestHarness output.
+
+        * inspector/unit-tests/test-harness-expect-functions-expected.txt:
+        * inspector/unit-tests/test-harness-expect-functions.html:
+
 2016-09-21  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking inspector/css/manager-preferredInspectorStyleSheetForFrame.html as flaky on mac.
index cacedfa55479dd6c534ceb1562682c43dae14048..ec745a28ea01827af952278152d43fdfd9ffe731 100644 (file)
@@ -41,9 +41,13 @@ PASS: Primary key should be ordered by email: 4
 PASS: Key should be ordered by email: 'beaver@webkit.org'
 PASS: Value should be a remote object for: 'Monstrous Beaver'
 FAIL: Primary key should be ordered by email: 2
+    Expected: truthy
+    Actual: false
 PASS: Key should be ordered by email: 'hampster@webkit.org'
 PASS: Value should be a remote object for: 'Thirsty Hampster'
 FAIL: Primary key should be ordered by email: 1
+    Expected: truthy
+    Actual: false
 PASS: Key should be ordered by email: 'peacock@webkit.org'
 PASS: Value should be a remote object for: 'Jamming Peacock'
 
index e00af38b32f5e89a26c9f85e261bdbf287900766..9a394261de8a70f2280562e58de14c1719925c4b 100644 (file)
@@ -12,6 +12,8 @@ PASS: constrain of a value above max becomes max
 PASS: constrain of a value above max becomes max
 PASS: constrain of a value above max becomes max
 FAIL: constrain of NaN becomes min
+    Expected: truthy
+    Actual: false
 
 -- Running test case: Number.secondsToString
 PASS: normal resolution of 0ms should be ms with no decimals
index 8cb87472329a9a02b4ba1b58f4e7154d5f96c070..d7ff35efe4d0267aebb4d1ae0608984d35016f1a 100644 (file)
@@ -11,11 +11,23 @@ PASS: expectThat({})
 PASS: expectThat([])
 Expected to FAIL
 FAIL: expectThat(false)
+    Expected: truthy
+    Actual: false
 FAIL: expectThat(0)
+    Expected: truthy
+    Actual: 0
 FAIL: expectThat("")
+    Expected: truthy
+    Actual: ""
 FAIL: expectThat(null)
+    Expected: truthy
+    Actual: null
 FAIL: expectThat(undefined)
+    Expected: truthy
+    Actual: undefined
 FAIL: expectThat(NaN)
+    Expected: truthy
+    Actual: NaN
 
 -- Running test case: InspectorTest.expectFalse
 Expected to PASS
@@ -27,22 +39,46 @@ PASS: expectFalse(undefined)
 PASS: expectFalse(NaN)
 Expected to FAIL
 FAIL: expectFalse(true)
+    Expected: falsey
+    Actual: true
 FAIL: expectFalse(1)
+    Expected: falsey
+    Actual: 1
 FAIL: expectFalse("abc")
+    Expected: falsey
+    Actual: "abc"
 FAIL: expectFalse({})
+    Expected: falsey
+    Actual: {}
 FAIL: expectFalse([])
+    Expected: falsey
+    Actual: []
 
 -- Running test case: InspectorTest.expectNull
 Expected to PASS
 PASS: expectNull(null)
 Expected to FAIL
 FAIL: expectNull(true)
+    Expected: null
+    Actual: true
 FAIL: expectNull(false)
+    Expected: null
+    Actual: false
 FAIL: expectNull(1)
+    Expected: null
+    Actual: 1
 FAIL: expectNull("")
+    Expected: null
+    Actual: ""
 FAIL: expectNull(undefined)
+    Expected: null
+    Actual: undefined
 FAIL: expectNull({})
+    Expected: null
+    Actual: {}
 FAIL: expectNull([])
+    Expected: null
+    Actual: []
 
 -- Running test case: InspectorTest.expectNotNull
 Expected to PASS
@@ -55,29 +91,52 @@ PASS: expectNotNull({})
 PASS: expectNotNull([])
 Expected to FAIL
 FAIL: expectNotNull(null)
+    Expected: not null
+    Actual: null
 
 -- Running test case: InspectorTest.expectEqual
 Expected to PASS
 PASS: expectEqual(true, true)
 PASS: expectEqual({"a":1,"b":2}, {"a":1,"b":2})
+PASS: expectEqual(CSSStyleDeclaration instance #1, CSSStyleDeclaration instance #1)
 PASS: expectEqual(1.23, 1.23)
 PASS: expectEqual("abc", "abc")
 PASS: expectEqual(null, null)
 PASS: expectEqual(undefined, undefined)
 Expected to FAIL
 FAIL: expectEqual(true, false)
-FAIL: expectEqual({"a":1,"b":2}, {"c":3,"d":4})
+    Expected: false
+    Actual: true
+FAIL: expectEqual({"a":1,"b":2}, {"a":1,"b":2})
+    Expected: {"a":1,"b":2}
+    Actual: {"a":1,"b":2}
+FAIL: expectEqual(CSSStyleDeclaration instance #1, CSSRule instance #2)
+    Expected: CSSRule instance #2
+    Actual: CSSStyleDeclaration instance #1
 FAIL: expectEqual(1.23, 4.56)
+    Expected: 4.56
+    Actual: 1.23
 FAIL: expectEqual("abc", "def")
+    Expected: "def"
+    Actual: "abc"
 FAIL: expectEqual(null, undefined)
+    Expected: undefined
+    Actual: null
 FAIL: expectEqual(NaN, NaN)
+    Expected: NaN
+    Actual: NaN
 FAIL: expectEqual({}, {})
+    Expected: {}
+    Actual: {}
 FAIL: expectEqual([], [])
+    Expected: []
+    Actual: []
 
 -- Running test case: InspectorTest.expectNotEqual
 Expected to PASS
 PASS: expectNotEqual(true, false)
-PASS: expectNotEqual({"a":1,"b":2}, {"c":3,"d":4})
+PASS: expectNotEqual({"a":1,"b":2}, {"a":1,"b":2})
+PASS: expectNotEqual(CSSStyleDeclaration instance #1, CSSRule instance #2)
 PASS: expectNotEqual(1.23, 4.56)
 PASS: expectNotEqual("abc", "def")
 PASS: expectNotEqual(null, undefined)
@@ -86,11 +145,26 @@ PASS: expectNotEqual({}, {})
 PASS: expectNotEqual([], [])
 Expected to FAIL
 FAIL: expectNotEqual(true, true)
+    Expected: not true
+    Actual: true
 FAIL: expectNotEqual({"a":1,"b":2}, {"a":1,"b":2})
+    Expected: not {"a":1,"b":2}
+    Actual: {"a":1,"b":2}
+FAIL: expectNotEqual(CSSStyleDeclaration instance #1, CSSStyleDeclaration instance #1)
+    Expected: not CSSStyleDeclaration instance #1
+    Actual: CSSStyleDeclaration instance #1
 FAIL: expectNotEqual(1.23, 1.23)
+    Expected: not 1.23
+    Actual: 1.23
 FAIL: expectNotEqual("abc", "abc")
+    Expected: not "abc"
+    Actual: "abc"
 FAIL: expectNotEqual(null, null)
+    Expected: not null
+    Actual: null
 FAIL: expectNotEqual(undefined, undefined)
+    Expected: not undefined
+    Actual: undefined
 
 -- Running test case: InspectorTest.expectShallowEqual
 Expected to PASS
@@ -99,7 +173,11 @@ PASS: expectShallowEqual({}, {})
 PASS: expectShallowEqual([], [])
 Expected to FAIL
 FAIL: expectShallowEqual({"a":1,"b":2}, {"a":3,"b":4})
+    Expected: {"a":3,"b":4}
+    Actual: {"a":1,"b":2}
 FAIL: expectShallowEqual({}, [])
+    Expected: []
+    Actual: {}
 
 -- Running test case: InspectorTest.expectNotShallowEqual
 Expected to PASS
@@ -107,8 +185,14 @@ PASS: expectNotShallowEqual({"a":1,"b":2}, {"a":3,"b":4})
 PASS: expectNotShallowEqual({}, [])
 Expected to FAIL
 FAIL: expectNotShallowEqual({"a":1,"b":2}, {"a":1,"b":2})
+    Expected: not {"a":1,"b":2}
+    Actual: {"a":1,"b":2}
 FAIL: expectNotShallowEqual({}, {})
+    Expected: not {}
+    Actual: {}
 FAIL: expectNotShallowEqual([], [])
+    Expected: not []
+    Actual: []
 
 -- Running test case: InspectorTest.expectEqualWithAccuracy
 Expected to PASS
@@ -118,7 +202,11 @@ PASS: expectEqualWithAccuracy(0, 1, 1)
 PASS: expectEqualWithAccuracy(1, 0, 1)
 Expected to FAIL
 FAIL: expectEqualWithAccuracy(0, 2, 1)
+    Expected: 2 +/- 1
+    Actual: 0
 FAIL: expectEqualWithAccuracy(2, 0, 1)
+    Expected: 0 +/- 1
+    Actual: 2
 
 -- Running test case: InspectorTest.expectLessThan
 Expected to PASS
@@ -126,9 +214,17 @@ PASS: expectLessThan(0, 1)
 PASS: expectLessThan("abc", "def")
 Expected to FAIL
 FAIL: expectLessThan(0, 0)
+    Expected: less than 0
+    Actual: 0
 FAIL: expectLessThan(1, 0)
+    Expected: less than 0
+    Actual: 1
 FAIL: expectLessThan("abc", "abc")
+    Expected: less than "abc"
+    Actual: "abc"
 FAIL: expectLessThan("def", "abc")
+    Expected: less than "abc"
+    Actual: "def"
 
 -- Running test case: InspectorTest.expectLessThanOrEqual
 Expected to PASS
@@ -138,7 +234,11 @@ PASS: expectLessThanOrEqual("abc", "def")
 PASS: expectLessThanOrEqual("abc", "abc")
 Expected to FAIL
 FAIL: expectLessThanOrEqual(1, 0)
+    Expected: less than or equal to 0
+    Actual: 1
 FAIL: expectLessThanOrEqual("def", "abc")
+    Expected: less than or equal to "abc"
+    Actual: "def"
 
 -- Running test case: InspectorTest.expectGreaterThan
 Expected to PASS
@@ -146,9 +246,17 @@ PASS: expectGreaterThan(1, 0)
 PASS: expectGreaterThan("def", "abc")
 Expected to FAIL
 FAIL: expectGreaterThan(0, 0)
+    Expected: greater than 0
+    Actual: 0
 FAIL: expectGreaterThan(0, 1)
+    Expected: greater than 1
+    Actual: 0
 FAIL: expectGreaterThan("abc", "abc")
+    Expected: greater than "abc"
+    Actual: "abc"
 FAIL: expectGreaterThan("abc", "def")
+    Expected: greater than "def"
+    Actual: "abc"
 
 -- Running test case: InspectorTest.expectGreaterThanOrEqual
 Expected to PASS
@@ -158,5 +266,9 @@ PASS: expectGreaterThanOrEqual("def", "abc")
 PASS: expectGreaterThanOrEqual("abc", "abc")
 Expected to FAIL
 FAIL: expectGreaterThanOrEqual(0, 1)
+    Expected: greater than or equal to 1
+    Actual: 0
 FAIL: expectGreaterThanOrEqual("abc", "def")
+    Expected: greater than or equal to "def"
+    Actual: "abc"
 
index c60ff69df49e77c7e51b3973f559162fc4d06f8e..c19341bf1f3c2731d697c8c101642ecc0a6770a4 100644 (file)
@@ -5,23 +5,12 @@
 <script>
 function test()
 {
-    console.assert(false, "FAIL ASSERTION!!!!!!!");
-
     let suite = InspectorTest.createSyncSuite("InspectorTestExpectFunctions");
 
     function toArray(a) {
         return a instanceof Array && a.length ? a : [a];
     }
 
-    function stringifyArguments(args) {
-        return args.map((a) => {
-            if (typeof a === "number")
-                return a;
-            // Append empty string so `undefined` is displayed correctly.
-            return JSON.stringify(a) + "";
-        }).join(", ");
-    }
-
     function addTestCase({functionName, passingInputs, failingInputs}) {
         let functionUnderTest = InspectorTest[functionName];
         InspectorTest.assert(typeof functionUnderTest === "function", "Unknown InspectorTest function: " + functionName);
@@ -36,10 +25,8 @@ function test()
                     }
 
                     InspectorTest.log("Expected to " + (shouldPass ? "PASS" : "FAIL"));
-                    for (let input of inputs.map(toArray)) {
-                        let argumentsString = stringifyArguments(input);
-                        functionUnderTest.call(InspectorTest, ...input, `${functionName}(${argumentsString})`);
-                    }
+                    for (let input of inputs.map(toArray))
+                        functionUnderTest.call(InspectorTest, ...input);
                 }
 
                 exerciseFunction(passingInputs, true);
@@ -70,12 +57,15 @@ function test()
     addInverseTestCase("expectNotNull", expectNullTestCase);
 
     let object1 = {a: 1, b: 2};
-    let object2 = {c: 3, d: 4};
+    let object2 = {a: 1, b: 2};
+    let customObject1 = new WebInspector.CSSStyleDeclaration;
+    let customObject2 = new WebInspector.CSSRule;
     let expectEqualTestCase = {
         functionName: "expectEqual",
         passingInputs: [
             [true, true],
             [object1, object1],
+            [customObject1, customObject1],
             [1.23, 1.23],
             ["abc", "abc"],
             [null, null],
@@ -84,6 +74,7 @@ function test()
         failingInputs: [
             [true, false],
             [object1, object2],
+            [customObject1, customObject2],
             [1.23, 4.56],
             ["abc", "def"],
             [null, undefined],
index e45fe91c4954494ba1b6973ea40363c96519157f..b440db55314a3d8b0238d80f272991a4ef50fecc 100644 (file)
@@ -1,3 +1,51 @@
+2016-09-21  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: Improve output of TestHarness.expect* failures
+        https://bugs.webkit.org/show_bug.cgi?id=162177
+        <rdar://problem/28367186>
+
+        Reviewed by Joseph Pecoraro.
+
+        This patch adds specific expectation functions to TestHarness, to better
+        express intent when writing tests, and to allow more details to be logged
+        in the event of a failure.
+
+        For functions taking both `actual` and `expected` parameters, the `actual`
+        parameter comes first. This convention simplifies the implementation of
+        TestHarness, improves the readability of tests involving inequalities,
+        and is consistent with XCTest assertions.
+
+        * UserInterface/Test/TestHarness.js:
+        (TestHarness):
+        (TestHarness.prototype.expectThat):
+        (TestHarness.prototype.expectFalse):
+        (TestHarness.prototype.expectNull):
+        (TestHarness.prototype.expectNotNull):
+        (TestHarness.prototype.expectEqual):
+        (TestHarness.prototype.expectNotEqual):
+        (TestHarness.prototype.expectShallowEqual):
+        (TestHarness.prototype.expectNotShallowEqual):
+        (TestHarness.prototype.expectEqualWithAccuracy):
+        (TestHarness.prototype.expectLessThan):
+        (TestHarness.prototype.expectLessThanOrEqual):
+        (TestHarness.prototype.expectGreaterThan):
+        (TestHarness.prototype.expectGreaterThanOrEqual):
+        New expectation functions, all of which call _expect under the hood.
+
+        (TestHarness.prototype._expect):
+        Helper method which calls pass or fail. Creates a message when no user
+        message is provided, and logs expected and actual values in the event
+        of a failure.
+
+        (TestHarness.prototype._expectationValueAsString):
+        (TestHarness.prototype._expectationMessageFormat):
+        Get a message format string for the expectation type. Used to create
+        pass/fail message when no user message is provided.
+
+        (TestHarness.prototype._expectedValueFormat):
+        Get a format string for displaying the expected value. Used to create
+        the "Expected: " failure message line.
+
 2016-09-20  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Reload unexpectedly switches to Storage Tab
index d4ff40d69b7dd980ee99870676cf2ba2f6f83dc3..3d51f24513541e107fdc18042befe974da7d8270 100644 (file)
@@ -30,6 +30,8 @@ TestHarness = class TestHarness extends WebInspector.Object
         super();
 
         this._logCount = 0;
+        this._failureObjects = new Map;
+        this._failureObjectIdentifier = 1;
     }
 
     completeTest()
@@ -91,74 +93,72 @@ TestHarness = class TestHarness extends WebInspector.Object
         this.log("ASSERT: " + stringifiedMessage);
     }
 
-    expectThat(condition, message)
+    expectThat(actual, message)
     {
-        if (condition)
-            this.pass(message);
-        else
-            this.fail(message);
+        this._expect(TestHarness.ExpectationType.True, !!actual, message, actual);
     }
 
-    expectFalse(expression, message)
+    expectFalse(actual, message)
     {
-        this.expectThat(!expression, message);
+        this._expect(TestHarness.ExpectationType.False, !actual, message, actual);
     }
 
-    expectNull(expression, message)
+    expectNull(actual, message)
     {
-        this.expectThat(expression === null, message);
+        this._expect(TestHarness.ExpectationType.Null, actual === null, message, actual, null);
     }
 
-    expectNotNull(expression, message)
+    expectNotNull(actual, message)
     {
-        this.expectThat(expression !== null, message);
+        this._expect(TestHarness.ExpectationType.NotNull, actual !== null, message, actual);
     }
 
-    expectEqual(expression1, expression2, message)
+    expectEqual(actual, expected, message)
     {
-        this.expectThat(expression1 === expression2, message);
+        this._expect(TestHarness.ExpectationType.Equal, expected === actual, message, actual, expected);
     }
 
-    expectNotEqual(expression1, expression2, message)
+    expectNotEqual(actual, expected, message)
     {
-        this.expectThat(expression1 !== expression2, message);
+        this._expect(TestHarness.ExpectationType.NotEqual, expected !== actual, message, actual, expected);
     }
 
-    expectShallowEqual(expression1, expression2, message)
+    expectShallowEqual(actual, expected, message)
     {
-        this.expectThat(Object.shallowEqual(expression1, expression2), message);
+        this._expect(TestHarness.ExpectationType.ShallowEqual, Object.shallowEqual(actual, expected), message, actual, expected);
     }
 
-    expectNotShallowEqual(expression1, expression2, message)
+    expectNotShallowEqual(actual, expected, message)
     {
-        this.expectThat(!Object.shallowEqual(expression1, expression2), message);
+        this._expect(TestHarness.ExpectationType.NotShallowEqual, !Object.shallowEqual(actual, expected), message, actual, expected);
     }
 
-    expectEqualWithAccuracy(expression1, expression2, accuracy, message)
+    expectEqualWithAccuracy(actual, expected, accuracy, message)
     {
-        console.assert(typeof expression1 === "number");
-        console.assert(typeof expression2 === "number");
-        this.expectThat(Math.abs(expression1 - expression2) <= 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(expression1, expression2, message)
+    expectLessThan(actual, expected, message)
     {
-        this.expectThat(expression1 < expression2, message);
+        this._expect(TestHarness.ExpectationType.LessThan, actual < expected, message, actual, expected);
     }
 
-    expectLessThanOrEqual(expression1, expression2, message)
+    expectLessThanOrEqual(actual, expected, message)
     {
-        this.expectThat(expression1 <= expression2, message);
+        this._expect(TestHarness.ExpectationType.LessThanOrEqual, actual <= expected, message, actual, expected);
     }
 
-    expectGreaterThan(expression1, expression2, message)
+    expectGreaterThan(actual, expected, message)
     {
-        this.expectThat(expression1 > expression2, message);
+        this._expect(TestHarness.ExpectationType.GreaterThan, actual > expected, message, actual, expected);
     }
 
-    expectGreaterThanOrEqual(expression1, expression2, message)
+    expectGreaterThanOrEqual(actual, expected, message)
     {
-        this.expectThat(expression1 >= expression2, message);
+        this._expect(TestHarness.ExpectationType.GreaterThanOrEqual, actual >= expected, message, actual, expected);
     }
 
     pass(message)
@@ -182,4 +182,138 @@ TestHarness = class TestHarness extends WebInspector.Object
 
         return (typeof message !== "string") ? JSON.stringify(message) : message;
     }
+
+    // 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"),
 };