test_task.js 8.6 KB

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