| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- // Unit test framework. The one API that can be used by unit tests.
- // Usage:
- // How to import UTF -- add the following to the beginning of your test:
- // /// <reference path="../UnitTestFramework/UnitTestFramework.js" />
- // if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
- // this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
- // }
- // How to define and run tests:
- // var tests = [ { name: "test name 1", body: function () {} }, ...];
- // testRunner.run(tests);
- // How to output "pass" only if run passes so that we can skip baseline:
- // testRunner.run(tests, {verbose: false});
- // Or do this only when "summary" is given on command line:
- // jshost -args summary -endargs
- // testRunner.run(tests, { verbose: WScript.Arguments[0] != "summary" });
- // How to use assert:
- // assert.areEqual(expected, actual, "those two should be equal");
- // assert.areNotEqual(expected, actual, "those two should NOT be equal");
- // assert.fail("error");
- // assert.throws(function, SyntaxError);
- // assert.doesNotThrow(function, "this function should not throw anything");
- // Some useful helpers:
- // helpers.writeln("works in both", "console", "and", "browser);
- // helpers.printObject(WScript);
- // var isInBrowser = helpers.isInBrowser();
- var helpers = function helpers() {
- //private
- var undefinedAsString = "undefined";
- var isWScriptAvailable = this.WScript;
- if (isWScriptAvailable && !this.WScript.LoadModuleFile) {
- WScript.LoadModuleFile = function (fileName) {
- WScript.LoadScriptFile(fileName, "module");
- }
- }
- return {
- isInBrowser: function isInBrowser() {
- return typeof (document) !== undefinedAsString;
- },
- // TODO: instead of this hack consider exposing this/ScriptConfiguration through WScript.
- get isCompatVersion9() {
- return (typeof (ArrayBuffer) == undefinedAsString);
- },
- get isVersion10OrLater() {
- return !this.isCompatVersion9;
- },
- // If propName is provided, thedummy object would have 1 property with name = propName, value = 0.
- getDummyObject: function getDummyObject(propName) {
- var dummy = this.isInBrowser() ? document : {};
- //var dummy = {};
- if (propName != undefined) {
- dummy[propName] = 0;
- }
- return dummy;
- },
- writeln: function writeln() {
- var line = "", i;
- for (i = 0; i < arguments.length; i += 1) {
- line = line.concat(arguments[i])
- }
- if (!this.isInBrowser() || isWScriptAvailable) {
- WScript.Echo(line);
- } else {
- document.writeln(line);
- document.writeln("<br/>");
- }
- },
- printObject: function printObject(o) {
- var name;
- for (name in o) {
- this.writeln(name, o.hasOwnProperty(name) ? "" : " (inherited)", ": ", o[name]);
- }
- },
- withPropertyDeleted: function withPropertyDeleted(object, propertyName, callback) {
- var descriptor = Object.getOwnPropertyDescriptor(object, propertyName);
- try {
- delete object[propertyName];
- callback();
- } finally {
- Object.defineProperty(object, propertyName, descriptor);
- }
- },
- }
- }(); // helpers module.
- var testRunner = function testRunner() {
- var executedTestCount = 0;
- var passedTestCount = 0;
- var objectType = "object";
- var _verbose = true; // If false, try to output "pass" only for passed run so we can skip baseline.
- return {
- runTests: function runTests(testsToRun, options) {
- ///<summary>Runs provided tests.<br/>
- /// The 'testsToRun' is an object that has enumerable properties,<br/>
- /// each property is an object that has 'name' and 'body' properties.
- ///</summary>
- if (options && options.hasOwnProperty("verbose")) {
- _verbose = options.verbose;
- }
- for (var i in testsToRun) {
- var isRunnable = typeof testsToRun[i] === objectType;
- if (isRunnable) {
- this.runTest(i, testsToRun[i].name, testsToRun[i].body);
- }
- }
- if (_verbose || executedTestCount != passedTestCount) {
- helpers.writeln("Summary of tests: total executed: ", executedTestCount,
- "; passed: ", passedTestCount, "; failed: ", executedTestCount - passedTestCount);
- } else {
- helpers.writeln("pass");
- }
- },
- run: function run(tests, options) {
- this.runTests(tests, options);
- },
- runTest: function runTest(testIndex, testName, testBody) {
- ///<summary>Runs test body catching all exceptions.<br/>
- /// Result: prints PASSED/FAILED to the output.
- ///</summary>
- function logTestNameIf(b) {
- if (b) {
- helpers.writeln("*** Running test #", executedTestCount + 1, " (", testIndex, "): ", testName);
- }
- }
- logTestNameIf(_verbose);
- var isSuccess = true;
- try {
- testBody();
- } catch (ex) {
- var message = ex.stack || ex.message || ex;
- logTestNameIf(!_verbose);
- helpers.writeln("Test threw exception: ", message);
- isSuccess = false;
- }
- if (isSuccess) {
- if (_verbose) {
- helpers.writeln("PASSED");
- }
- ++passedTestCount;
- } else {
- helpers.writeln("FAILED");
- }
- ++executedTestCount;
- }
- }
- }(); // testRunner.
- var assert = function assert() {
- // private
- var isObject = function isObject(x) {
- return x instanceof Object && typeof x !== "function";
- };
- var isNaN = function isNaN(x) {
- return x !== x;
- };
- // returns true on success, and error message on failure
- var compare = function compare(expected, actual) {
- if (expected === actual) {
- return true;
- } else if (isObject(expected)) {
- if (!isObject(actual)) return "actual is not an object";
- var expectedFieldCount = 0, actualFieldCount = 0;
- for (var i in expected) {
- var compareResult = compare(expected[i], actual[i]);
- if (compareResult !== true) return compareResult;
- ++expectedFieldCount;
- }
- for (var i in actual) {
- ++actualFieldCount;
- }
- if (expectedFieldCount !== actualFieldCount) {
- return "actual has different number of fields than expected";
- }
- return true;
- } else {
- if (isObject(actual)) return "actual is an object";
- if (isNaN(expected) && isNaN(actual)) return true;
- return "expected: " + expected + " actual: " + actual;
- }
- };
- var epsilon = (function () {
- var next, result;
- for (next = 1; 1 + next !== 1; next = next / 2) {
- result = next;
- }
- return result;
- }());
- var compareAlmostEqualRelative = function compareAlmostEqualRelative(expected, actual) {
- if (typeof expected !== "number" || typeof actual !== "number") {
- return compare(expected, actual);
- } else {
- var diff = Math.abs(expected - actual);
- var absExpected = Math.abs(expected);
- var absActual = Math.abs(actual);
- var largest = (absExpected > absActual) ? absExpected : absActual;
- var maxRelDiff = epsilon * 5;
- if (diff <= largest * maxRelDiff) {
- return true;
- } else {
- return "expected almost: " + expected + " actual: " + actual + " difference: " + diff;
- }
- }
- }
- var validate = function validate(result, assertType, message) {
- if (result !== true) {
- var exMessage = addMessage("assert." + assertType + " failed: " + result);
- exMessage = addMessage(exMessage, message);
- throw exMessage;
- }
- }
- var addMessage = function addMessage(baseMessage, message) {
- if (message !== undefined) {
- baseMessage += ": " + message;
- }
- return baseMessage;
- }
- return {
- areEqual: function areEqual(expected, actual, message) {
- /// <summary>
- /// IMPORTANT: NaN compares equal.<br/>
- /// IMPORTANT: for objects, assert.AreEqual checks the fields.<br/>
- /// So, for 'var obj1={}, obj2={}' areEqual would be success, although in Javascript obj1 != obj2.<br/><br/>
- /// Performs deep comparison of arguments.<br/>
- /// This works for objects and simple types.<br/><br/>
- ///
- /// TODO: account for other types?<br/>
- /// TODO: account for missing vs undefined fields.
- /// </summary>
- validate(compare(expected, actual), "areEqual", message);
- },
- areNotEqual: function areNotEqual(expected, actual, message) {
- validate(compare(expected, actual) !== true, "areNotEqual", message);
- },
- areAlmostEqual: function areAlmostEqual(expected, actual, message) {
- /// <summary>
- /// Compares numbers with an almost equal relative epsilon comparison.
- /// Useful for verifying math functions.
- /// </summary>
- validate(compareAlmostEqualRelative(expected, actual), "areAlmostEqual", message);
- },
- isTrue: function isTrue(actual, message) {
- validate(compare(true, actual), "isTrue", message);
- },
- isFalse: function isFalse(actual, message) {
- validate(compare(false, actual), "isFalse", message);
- },
- isUndefined: function isUndefined(actual, message) {
- validate(compare(undefined, actual), "isUndefined", message);
- },
- isNotUndefined: function isUndefined(actual, message) {
- validate(compare(undefined, actual) !== true, "isNotUndefined", message);
- },
- throws: function throws(testFunction, expectedException, message, expectedErrorMessage) {
- /// <summary>
- /// Makes sure that the function specified by the 'testFunction' parameter
- /// throws the exception specified by the 'expectedException' parameter.<br/><br/>
- ///
- /// expectedException: A specific exception type, e.g. TypeError.
- /// if undefined, this will pass if testFunction throws any exception.<br/>
- /// message: Test-provided explanation of why this particular exception should be thrown.<br/>
- /// expectedErrorMessage: If specified, verifies the thrown Error has expected message.<br/>
- /// You only need to specify this if you are looking for a specific error message.<br/>
- /// Does not require the prefix of e.g. "TypeError:" that would be printed by the host.<br/><br/>
- ///
- /// Example:<br/>
- /// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError")<br/>
- /// assert.throws(function() { eval("{"); }) // -- use this when you don't care which exception is thrown.<br/>
- /// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError with message about expected semicolon.", "Expected ';'")
- /// </summary>
- var noException = {}; // Some unique object which will not be equal to anything else.
- var exception = noException; // Set default value.
- try {
- testFunction();
- } catch (ex) {
- exception = ex;
- }
- if (expectedException === undefined && exception !== noException) {
- return; // Any exception thrown is OK.
- }
- var validationPart = exception;
- if (exception !== noException && exception instanceof Object) {
- validationPart = exception.constructor;
- }
- var validationErrorMessage = exception instanceof Error ? exception.message : undefined;
- if (validationPart !== expectedException || (expectedErrorMessage && validationErrorMessage !== expectedErrorMessage)) {
- var expectedString = expectedException !== undefined ?
- expectedException.toString().replace(/\n/g, "").replace(/.*function (.*)\(.*/g, "$1") :
- "<any exception>";
- if (expectedErrorMessage) {
- expectedString += " " + expectedErrorMessage;
- }
- var actual = exception !== noException ? exception : "<no exception>";
- throw addMessage("assert.throws failed: expected: " + expectedString + ", actual: " + actual, message);
- }
- },
- doesNotThrow: function doesNotThrow(testFunction, message) {
- var noException = {};
- var exception = noException;
- try {
- testFunction();
- } catch (ex) {
- exception = ex;
- }
- if (exception === noException) {
- return;
- }
- throw addMessage("assert.doesNotThrow failed: expected: <no exception>, actual: " + exception, message);
- },
- fail: function fail(message) {
- ///<summary>Can be used to fail the test.</summary>
- throw message;
- }
- }
- }(); // assert.
|