test_task.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*
  2. * Jake JavaScript build tool
  3. * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. let path = require('path');
  19. let currDir = process.cwd();
  20. /**
  21. @name jake
  22. @namespace jake
  23. */
  24. /**
  25. @name jake.TestTask
  26. @constructor
  27. @description Instantiating a TestTask creates a number of Jake
  28. Tasks that make running tests for your software easy.
  29. @param {String} name The name of the project
  30. @param {Function} definition Defines the list of files containing the tests,
  31. and the name of the namespace/task for running them. Will be executed on the
  32. instantiated TestTask (i.e., 'this', will be the TestTask instance), to set
  33. the various instance-propertiess.
  34. @example
  35. let t = new jake.TestTask('bij-js', function () {
  36. this.testName = 'testSpecial';
  37. this.testFiles.include('test/**');
  38. });
  39. */
  40. let TestTask = function () {
  41. let self = this;
  42. let args = Array.prototype.slice.call(arguments);
  43. let name = args.shift();
  44. let definition = args.pop();
  45. let prereqs = args.pop() || [];
  46. /**
  47. @name jake.TestTask#testNam
  48. @public
  49. @type {String}
  50. @description The name of the namespace to place the tests in, and
  51. the top-level task for running tests. Defaults to "test"
  52. */
  53. this.testName = 'test';
  54. /**
  55. @name jake.TestTask#testFiles
  56. @public
  57. @type {jake.FileList}
  58. @description The list of files containing tests to load
  59. */
  60. this.testFiles = new jake.FileList();
  61. /**
  62. @name jake.TestTask#showDescription
  63. @public
  64. @type {Boolean}
  65. @description Show the created task when doing Jake -T
  66. */
  67. this.showDescription = true;
  68. /*
  69. @name jake.TestTask#totalTests
  70. @public
  71. @type {Number}
  72. @description The total number of tests to run
  73. */
  74. this.totalTests = 0;
  75. /*
  76. @name jake.TestTask#executedTests
  77. @public
  78. @type {Number}
  79. @description The number of tests successfully run
  80. */
  81. this.executedTests = 0;
  82. if (typeof definition == 'function') {
  83. definition.call(this);
  84. }
  85. if (this.showDescription) {
  86. desc('Run the tests for ' + name);
  87. }
  88. task(this.testName, prereqs, {async: true}, function () {
  89. let t = jake.Task[this.fullName + ':run'];
  90. t.on('complete', function () {
  91. complete();
  92. });
  93. // Pass args to the namespaced test
  94. t.invoke.apply(t, arguments);
  95. });
  96. namespace(self.testName, function () {
  97. let runTask = task('run', {async: true}, function (pat) {
  98. let re;
  99. let testFiles;
  100. // Don't nest; make a top-level namespace. Don't want
  101. // re-calling from inside to nest infinitely
  102. jake.currentNamespace = jake.defaultNamespace;
  103. re = new RegExp(pat);
  104. // Get test files that match the passed-in pattern
  105. testFiles = self.testFiles.toArray()
  106. .filter(function (f) {
  107. return (re).test(f);
  108. }) // Don't load the same file multiple times -- should this be in FileList?
  109. .reduce(function (p, c) {
  110. if (p.indexOf(c) < 0) {
  111. p.push(c);
  112. }
  113. return p;
  114. }, []);
  115. // Create a namespace for all the testing tasks to live in
  116. namespace(self.testName + 'Exec', function () {
  117. // Each test will be a prereq for the dummy top-level task
  118. let prereqs = [];
  119. // Continuation to pass to the async tests, wrapping `continune`
  120. let next = function () {
  121. complete();
  122. };
  123. // Create the task for this test-function
  124. let createTask = function (name, action) {
  125. // If the test-function is defined with a continuation
  126. // param, flag the task as async
  127. let t;
  128. let isAsync = !!action.length;
  129. // Define the actual namespaced task with the name, the
  130. // wrapped action, and the correc async-flag
  131. t = task(name, createAction(name, action), {
  132. async: isAsync
  133. });
  134. t.once('complete', function () {
  135. self.executedTests++;
  136. });
  137. t._internal = true;
  138. return t;
  139. };
  140. // Used as the action for the defined task for each test.
  141. let createAction = function (n, a) {
  142. // A wrapped function that passes in the `next` function
  143. // for any tasks that run asynchronously
  144. return function () {
  145. let cb;
  146. if (a.length) {
  147. cb = next;
  148. }
  149. if (!(n == 'before' || n == 'after' ||
  150. /_beforeEach$/.test(n) || /_afterEach$/.test(n))) {
  151. jake.logger.log(n);
  152. }
  153. // 'this' will be the task when action is run
  154. return a.call(this, cb);
  155. };
  156. };
  157. // Dummy top-level task for everything to be prereqs for
  158. let topLevel;
  159. // Pull in each test-file, and iterate over any exported
  160. // test-functions. Register each test-function as a prereq task
  161. testFiles.forEach(function (file) {
  162. let exp = require(path.join(currDir, file));
  163. // Create a namespace for each filename, so test-name collisions
  164. // won't be a problem
  165. namespace(file, function () {
  166. let testPrefix = self.testName + 'Exec:' + file + ':';
  167. let testName;
  168. // Dummy task for displaying file banner
  169. testName = '*** Running ' + file + ' ***';
  170. prereqs.push(testPrefix + testName);
  171. createTask(testName, function () {});
  172. // 'before' setup
  173. if (typeof exp.before == 'function') {
  174. prereqs.push(testPrefix + 'before');
  175. // Create the task
  176. createTask('before', exp.before);
  177. }
  178. // Walk each exported function, and create a task for each
  179. for (let p in exp) {
  180. if (p == 'before' || p == 'after' ||
  181. p == 'beforeEach' || p == 'afterEach') {
  182. continue;
  183. }
  184. if (typeof exp.beforeEach == 'function') {
  185. prereqs.push(testPrefix + p + '_beforeEach');
  186. // Create the task
  187. createTask(p + '_beforeEach', exp.beforeEach);
  188. }
  189. // Add the namespace:name of this test to the list of prereqs
  190. // for the dummy top-level task
  191. prereqs.push(testPrefix + p);
  192. // Create the task
  193. createTask(p, exp[p]);
  194. if (typeof exp.afterEach == 'function') {
  195. prereqs.push(testPrefix + p + '_afterEach');
  196. // Create the task
  197. createTask(p + '_afterEach', exp.afterEach);
  198. }
  199. }
  200. // 'after' teardown
  201. if (typeof exp.after == 'function') {
  202. prereqs.push(testPrefix + 'after');
  203. // Create the task
  204. let afterTask = createTask('after', exp.after);
  205. afterTask._internal = true;
  206. }
  207. });
  208. });
  209. self.totalTests = prereqs.length;
  210. process.on('exit', function () {
  211. // Throw in the case where the process exits without
  212. // finishing tests, but no error was thrown
  213. if (!jake.errorCode && (self.totalTests > self.executedTests)) {
  214. throw new Error('Process exited without all tests completing.');
  215. }
  216. });
  217. // Create the dummy top-level task. When calling a task internally
  218. // with `invoke` that is async (or has async prereqs), have to listen
  219. // for the 'complete' event to know when it's done
  220. topLevel = task('__top__', prereqs);
  221. topLevel._internal = true;
  222. topLevel.addListener('complete', function () {
  223. jake.logger.log('All tests ran successfully');
  224. complete();
  225. });
  226. topLevel.invoke(); // Do the thing!
  227. });
  228. });
  229. runTask._internal = true;
  230. });
  231. };
  232. jake.TestTask = TestTask;
  233. exports.TestTask = TestTask;