UnitTestFramework.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. // Unit test framework. The one API that can be used by unit tests.
  6. // Usage:
  7. // How to import UTF -- add the following to the beginning of your test:
  8. // /// <reference path="../UnitTestFramework/UnitTestFramework.js" />
  9. // if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
  10. // this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  11. // }
  12. // How to define and run tests:
  13. // var tests = [ { name: "test name 1", body: function () {} }, ...];
  14. // testRunner.run(tests);
  15. // How to output "pass" only if run passes so that we can skip baseline:
  16. // testRunner.run(tests, {verbose: false});
  17. // Or do this only when "summary" is given on command line:
  18. // jshost -args summary -endargs
  19. // testRunner.run(tests, { verbose: WScript.Arguments[0] != "summary" });
  20. // How to use assert:
  21. // assert.areEqual(expected, actual, "those two should be equal");
  22. // assert.areNotEqual(expected, actual, "those two should NOT be equal");
  23. // assert.fail("error");
  24. // assert.throws(function, SyntaxError);
  25. // assert.doesNotThrow(function, "this function should not throw anything");
  26. // Some useful helpers:
  27. // helpers.writeln("works in both", "console", "and", "browser);
  28. // helpers.printObject(WScript);
  29. // var isInBrowser = helpers.isInBrowser();
  30. var helpers = function helpers() {
  31. //private
  32. var undefinedAsString = "undefined";
  33. var isWScriptAvailable = this.WScript;
  34. if (isWScriptAvailable && !this.WScript.LoadModuleFile) {
  35. WScript.LoadModuleFile = function (fileName) {
  36. WScript.LoadScriptFile(fileName, "module");
  37. }
  38. }
  39. return {
  40. isInBrowser: function isInBrowser() {
  41. return typeof (document) !== undefinedAsString;
  42. },
  43. // TODO: instead of this hack consider exposing this/ScriptConfiguration through WScript.
  44. get isCompatVersion9() {
  45. return (typeof (ArrayBuffer) == undefinedAsString);
  46. },
  47. get isVersion10OrLater() {
  48. return !this.isCompatVersion9;
  49. },
  50. // If propName is provided, thedummy object would have 1 property with name = propName, value = 0.
  51. getDummyObject: function getDummyObject(propName) {
  52. var dummy = this.isInBrowser() ? document : {};
  53. //var dummy = {};
  54. if (propName != undefined) {
  55. dummy[propName] = 0;
  56. }
  57. return dummy;
  58. },
  59. writeln: function writeln() {
  60. var line = "", i;
  61. for (i = 0; i < arguments.length; i += 1) {
  62. line = line.concat(arguments[i])
  63. }
  64. if (!this.isInBrowser() || isWScriptAvailable) {
  65. WScript.Echo(line);
  66. } else {
  67. document.writeln(line);
  68. document.writeln("<br/>");
  69. }
  70. },
  71. printObject: function printObject(o) {
  72. var name;
  73. for (name in o) {
  74. this.writeln(name, o.hasOwnProperty(name) ? "" : " (inherited)", ": ", o[name]);
  75. }
  76. },
  77. withPropertyDeleted: function withPropertyDeleted(object, propertyName, callback) {
  78. var descriptor = Object.getOwnPropertyDescriptor(object, propertyName);
  79. try {
  80. delete object[propertyName];
  81. callback();
  82. } finally {
  83. Object.defineProperty(object, propertyName, descriptor);
  84. }
  85. },
  86. }
  87. }(); // helpers module.
  88. var testRunner = function testRunner() {
  89. var executedTestCount = 0;
  90. var passedTestCount = 0;
  91. var objectType = "object";
  92. var _verbose = true; // If false, try to output "pass" only for passed run so we can skip baseline.
  93. return {
  94. runTests: function runTests(testsToRun, options) {
  95. ///<summary>Runs provided tests.<br/>
  96. /// The 'testsToRun' is an object that has enumerable properties,<br/>
  97. /// each property is an object that has 'name' and 'body' properties.
  98. ///</summary>
  99. if (options && options.hasOwnProperty("verbose")) {
  100. _verbose = options.verbose;
  101. }
  102. for (var i in testsToRun) {
  103. var isRunnable = typeof testsToRun[i] === objectType;
  104. if (isRunnable) {
  105. this.runTest(i, testsToRun[i].name, testsToRun[i].body);
  106. }
  107. }
  108. if (_verbose || executedTestCount != passedTestCount) {
  109. helpers.writeln("Summary of tests: total executed: ", executedTestCount,
  110. "; passed: ", passedTestCount, "; failed: ", executedTestCount - passedTestCount);
  111. } else {
  112. helpers.writeln("pass");
  113. }
  114. },
  115. run: function run(tests, options) {
  116. this.runTests(tests, options);
  117. },
  118. runTest: function runTest(testIndex, testName, testBody) {
  119. ///<summary>Runs test body catching all exceptions.<br/>
  120. /// Result: prints PASSED/FAILED to the output.
  121. ///</summary>
  122. function logTestNameIf(b) {
  123. if (b) {
  124. helpers.writeln("*** Running test #", executedTestCount + 1, " (", testIndex, "): ", testName);
  125. }
  126. }
  127. logTestNameIf(_verbose);
  128. var isSuccess = true;
  129. try {
  130. testBody();
  131. } catch (ex) {
  132. var message = ex.stack || ex.message || ex;
  133. logTestNameIf(!_verbose);
  134. helpers.writeln("Test threw exception: ", message);
  135. isSuccess = false;
  136. }
  137. if (isSuccess) {
  138. if (_verbose) {
  139. helpers.writeln("PASSED");
  140. }
  141. ++passedTestCount;
  142. } else {
  143. helpers.writeln("FAILED");
  144. }
  145. ++executedTestCount;
  146. }
  147. }
  148. }(); // testRunner.
  149. var assert = function assert() {
  150. // private
  151. var isObject = function isObject(x) {
  152. return x instanceof Object && typeof x !== "function";
  153. };
  154. var isNaN = function isNaN(x) {
  155. return x !== x;
  156. };
  157. // returns true on success, and error message on failure
  158. var compare = function compare(expected, actual) {
  159. if (expected === actual) {
  160. return true;
  161. } else if (isObject(expected)) {
  162. if (!isObject(actual)) return "actual is not an object";
  163. var expectedFieldCount = 0, actualFieldCount = 0;
  164. for (var i in expected) {
  165. var compareResult = compare(expected[i], actual[i]);
  166. if (compareResult !== true) return compareResult;
  167. ++expectedFieldCount;
  168. }
  169. for (var i in actual) {
  170. ++actualFieldCount;
  171. }
  172. if (expectedFieldCount !== actualFieldCount) {
  173. return "actual has different number of fields than expected";
  174. }
  175. return true;
  176. } else {
  177. if (isObject(actual)) return "actual is an object";
  178. if (isNaN(expected) && isNaN(actual)) return true;
  179. return "expected: " + expected + " actual: " + actual;
  180. }
  181. };
  182. var epsilon = (function () {
  183. var next, result;
  184. for (next = 1; 1 + next !== 1; next = next / 2) {
  185. result = next;
  186. }
  187. return result;
  188. }());
  189. var compareAlmostEqualRelative = function compareAlmostEqualRelative(expected, actual) {
  190. if (typeof expected !== "number" || typeof actual !== "number") {
  191. return compare(expected, actual);
  192. } else {
  193. var diff = Math.abs(expected - actual);
  194. var absExpected = Math.abs(expected);
  195. var absActual = Math.abs(actual);
  196. var largest = (absExpected > absActual) ? absExpected : absActual;
  197. var maxRelDiff = epsilon * 5;
  198. if (diff <= largest * maxRelDiff) {
  199. return true;
  200. } else {
  201. return "expected almost: " + expected + " actual: " + actual + " difference: " + diff;
  202. }
  203. }
  204. }
  205. var validate = function validate(result, assertType, message) {
  206. if (result !== true) {
  207. var exMessage = addMessage("assert." + assertType + " failed: " + result);
  208. exMessage = addMessage(exMessage, message);
  209. throw exMessage;
  210. }
  211. }
  212. var addMessage = function addMessage(baseMessage, message) {
  213. if (message !== undefined) {
  214. baseMessage += ": " + message;
  215. }
  216. return baseMessage;
  217. }
  218. return {
  219. areEqual: function areEqual(expected, actual, message) {
  220. /// <summary>
  221. /// IMPORTANT: NaN compares equal.<br/>
  222. /// IMPORTANT: for objects, assert.AreEqual checks the fields.<br/>
  223. /// So, for 'var obj1={}, obj2={}' areEqual would be success, although in Javascript obj1 != obj2.<br/><br/>
  224. /// Performs deep comparison of arguments.<br/>
  225. /// This works for objects and simple types.<br/><br/>
  226. ///
  227. /// TODO: account for other types?<br/>
  228. /// TODO: account for missing vs undefined fields.
  229. /// </summary>
  230. validate(compare(expected, actual), "areEqual", message);
  231. },
  232. areNotEqual: function areNotEqual(expected, actual, message) {
  233. validate(compare(expected, actual) !== true, "areNotEqual", message);
  234. },
  235. areAlmostEqual: function areAlmostEqual(expected, actual, message) {
  236. /// <summary>
  237. /// Compares numbers with an almost equal relative epsilon comparison.
  238. /// Useful for verifying math functions.
  239. /// </summary>
  240. validate(compareAlmostEqualRelative(expected, actual), "areAlmostEqual", message);
  241. },
  242. isTrue: function isTrue(actual, message) {
  243. validate(compare(true, actual), "isTrue", message);
  244. },
  245. isFalse: function isFalse(actual, message) {
  246. validate(compare(false, actual), "isFalse", message);
  247. },
  248. isUndefined: function isUndefined(actual, message) {
  249. validate(compare(undefined, actual), "isUndefined", message);
  250. },
  251. isNotUndefined: function isUndefined(actual, message) {
  252. validate(compare(undefined, actual) !== true, "isNotUndefined", message);
  253. },
  254. throws: function throws(testFunction, expectedException, message, expectedErrorMessage) {
  255. /// <summary>
  256. /// Makes sure that the function specified by the 'testFunction' parameter
  257. /// throws the exception specified by the 'expectedException' parameter.<br/><br/>
  258. ///
  259. /// expectedException: A specific exception type, e.g. TypeError.
  260. /// if undefined, this will pass if testFunction throws any exception.<br/>
  261. /// message: Test-provided explanation of why this particular exception should be thrown.<br/>
  262. /// expectedErrorMessage: If specified, verifies the thrown Error has expected message.<br/>
  263. /// You only need to specify this if you are looking for a specific error message.<br/>
  264. /// Does not require the prefix of e.g. "TypeError:" that would be printed by the host.<br/><br/>
  265. ///
  266. /// Example:<br/>
  267. /// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError")<br/>
  268. /// assert.throws(function() { eval("{"); }) // -- use this when you don't care which exception is thrown.<br/>
  269. /// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError with message about expected semicolon.", "Expected ';'")
  270. /// </summary>
  271. var noException = {}; // Some unique object which will not be equal to anything else.
  272. var exception = noException; // Set default value.
  273. try {
  274. testFunction();
  275. } catch (ex) {
  276. exception = ex;
  277. }
  278. if (expectedException === undefined && exception !== noException) {
  279. return; // Any exception thrown is OK.
  280. }
  281. var validationPart = exception;
  282. if (exception !== noException && exception instanceof Object) {
  283. validationPart = exception.constructor;
  284. }
  285. var validationErrorMessage = exception instanceof Error ? exception.message : undefined;
  286. if (validationPart !== expectedException || (expectedErrorMessage && validationErrorMessage !== expectedErrorMessage)) {
  287. var expectedString = expectedException !== undefined ?
  288. expectedException.toString().replace(/\n/g, "").replace(/.*function (.*)\(.*/g, "$1") :
  289. "<any exception>";
  290. if (expectedErrorMessage) {
  291. expectedString += " " + expectedErrorMessage;
  292. }
  293. var actual = exception !== noException ? exception : "<no exception>";
  294. throw addMessage("assert.throws failed: expected: " + expectedString + ", actual: " + actual, message);
  295. }
  296. },
  297. doesNotThrow: function doesNotThrow(testFunction, message) {
  298. var noException = {};
  299. var exception = noException;
  300. try {
  301. testFunction();
  302. } catch (ex) {
  303. exception = ex;
  304. }
  305. if (exception === noException) {
  306. return;
  307. }
  308. throw addMessage("assert.doesNotThrow failed: expected: <no exception>, actual: " + exception, message);
  309. },
  310. fail: function fail(message) {
  311. ///<summary>Can be used to fail the test.</summary>
  312. throw message;
  313. }
  314. }
  315. }(); // assert.