test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. 'use strict';
  2. var deepEqual = require('deep-equal');
  3. var defined = require('defined');
  4. var path = require('path');
  5. var inherits = require('inherits');
  6. var EventEmitter = require('events').EventEmitter;
  7. var has = require('has');
  8. var isRegExp = require('is-regex');
  9. var trim = require('string.prototype.trim');
  10. var callBound = require('call-bind/callBound');
  11. var forEach = require('for-each');
  12. var inspect = require('object-inspect');
  13. var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
  14. var toLowerCase = callBound('String.prototype.toLowerCase');
  15. var $exec = callBound('RegExp.prototype.exec');
  16. var objectToString = callBound('Object.prototype.toString');
  17. var nextTick = typeof setImmediate !== 'undefined'
  18. ? setImmediate
  19. : process.nextTick;
  20. var safeSetTimeout = setTimeout;
  21. var safeClearTimeout = clearTimeout;
  22. // eslint-disable-next-line no-unused-vars
  23. function getTestArgs(name_, opts_, cb_) {
  24. var name = '(anonymous)';
  25. var opts = {};
  26. var cb;
  27. for (var i = 0; i < arguments.length; i++) {
  28. var arg = arguments[i];
  29. var t = typeof arg;
  30. if (t === 'string') {
  31. name = arg;
  32. } else if (t === 'object') {
  33. opts = arg || opts;
  34. } else if (t === 'function') {
  35. cb = arg;
  36. }
  37. }
  38. return {
  39. name: name,
  40. opts: opts,
  41. cb: cb
  42. };
  43. }
  44. function Test(name_, opts_, cb_) {
  45. if (!(this instanceof Test)) {
  46. return new Test(name_, opts_, cb_);
  47. }
  48. var args = getTestArgs(name_, opts_, cb_);
  49. this.readable = true;
  50. this.name = args.name || '(anonymous)';
  51. this.assertCount = 0;
  52. this.pendingCount = 0;
  53. this._skip = args.opts.skip || false;
  54. this._todo = args.opts.todo || false;
  55. this._timeout = args.opts.timeout;
  56. this._plan = undefined;
  57. this._cb = args.cb;
  58. this._progeny = [];
  59. this._teardown = [];
  60. this._ok = true;
  61. var depthEnvVar = process.env.NODE_TAPE_OBJECT_PRINT_DEPTH;
  62. if (args.opts.objectPrintDepth) {
  63. this._objectPrintDepth = args.opts.objectPrintDepth;
  64. } else if (depthEnvVar) {
  65. if (toLowerCase(depthEnvVar) === 'infinity') {
  66. this._objectPrintDepth = Infinity;
  67. } else {
  68. this._objectPrintDepth = depthEnvVar;
  69. }
  70. } else {
  71. this._objectPrintDepth = 5;
  72. }
  73. for (var prop in this) {
  74. this[prop] = (function bind(self, val) {
  75. if (typeof val === 'function') {
  76. return function bound() {
  77. return val.apply(self, arguments);
  78. };
  79. }
  80. return val;
  81. }(this, this[prop]));
  82. }
  83. }
  84. inherits(Test, EventEmitter);
  85. Test.prototype.run = function run() {
  86. this.emit('prerun');
  87. if (!this._cb || this._skip) {
  88. this._end();
  89. return;
  90. }
  91. if (this._timeout != null) {
  92. this.timeoutAfter(this._timeout);
  93. }
  94. this._cb(this);
  95. this.emit('run');
  96. };
  97. Test.prototype.test = function (name, opts, cb) {
  98. var self = this;
  99. var t = new Test(name, opts, cb);
  100. this._progeny.push(t);
  101. this.pendingCount++;
  102. this.emit('test', t);
  103. t.on('prerun', function () {
  104. self.assertCount++;
  105. });
  106. if (!self._pendingAsserts()) {
  107. nextTick(function () {
  108. self._end();
  109. });
  110. }
  111. nextTick(function () {
  112. if (!self._plan && self.pendingCount == self._progeny.length) {
  113. self._end();
  114. }
  115. });
  116. };
  117. Test.prototype.comment = function (msg) {
  118. var that = this;
  119. forEach(trim(msg).split('\n'), function (aMsg) {
  120. that.emit('result', trim(aMsg).replace(/^#\s*/, ''));
  121. });
  122. };
  123. Test.prototype.plan = function (n) {
  124. this._plan = n;
  125. this.emit('plan', n);
  126. };
  127. Test.prototype.timeoutAfter = function (ms) {
  128. if (!ms) { throw new Error('timeoutAfter requires a timespan'); }
  129. var self = this;
  130. var timeout = safeSetTimeout(function () {
  131. self.fail(self.name + ' timed out after ' + ms + 'ms');
  132. self.end();
  133. }, ms);
  134. this.once('end', function () {
  135. safeClearTimeout(timeout);
  136. });
  137. };
  138. Test.prototype.end = function end(err) {
  139. if (arguments.length >= 1 && !!err) {
  140. this.ifError(err);
  141. }
  142. if (this.calledEnd) {
  143. this.fail('.end() already called');
  144. }
  145. this.calledEnd = true;
  146. this._end();
  147. };
  148. Test.prototype.teardown = function (fn) {
  149. if (typeof fn !== 'function') {
  150. this.fail('teardown: ' + inspect(fn) + ' is not a function');
  151. } else {
  152. this._teardown.push(fn);
  153. }
  154. };
  155. Test.prototype._end = function (err) {
  156. var self = this;
  157. if (this._progeny.length) {
  158. var t = this._progeny.shift();
  159. t.on('end', function () { self._end(); });
  160. t.run();
  161. return;
  162. }
  163. function completeEnd() {
  164. if (!self.ended) { self.emit('end'); }
  165. var pendingAsserts = self._pendingAsserts();
  166. if (!self._planError && self._plan !== undefined && pendingAsserts) {
  167. self._planError = true;
  168. self.fail('plan != count', {
  169. expected: self._plan,
  170. actual: self.assertCount
  171. });
  172. }
  173. self.ended = true;
  174. }
  175. function next(i) {
  176. if (i === self._teardown.length) {
  177. completeEnd();
  178. return;
  179. }
  180. var fn = self._teardown[i];
  181. var res;
  182. try {
  183. res = fn();
  184. } catch (e) {
  185. self.fail(e);
  186. }
  187. if (res && typeof res.then === 'function') {
  188. res.then(function () {
  189. next(++i);
  190. }, function (_err) {
  191. err = err || _err;
  192. });
  193. } else {
  194. next(++i);
  195. }
  196. }
  197. if (this._teardown.length > 0) {
  198. next(0);
  199. } else {
  200. completeEnd();
  201. }
  202. };
  203. Test.prototype._exit = function () {
  204. if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) {
  205. this._planError = true;
  206. this.fail('plan != count', {
  207. expected: this._plan,
  208. actual: this.assertCount,
  209. exiting: true
  210. });
  211. } else if (!this.ended) {
  212. this.fail('test exited without ending: ' + this.name, {
  213. exiting: true
  214. });
  215. }
  216. };
  217. Test.prototype._pendingAsserts = function () {
  218. if (this._plan === undefined) {
  219. return 1;
  220. }
  221. return this._plan - (this._progeny.length + this.assertCount);
  222. };
  223. Test.prototype._assert = function assert(ok, opts) {
  224. var self = this;
  225. var extra = opts.extra || {};
  226. ok = !!ok || !!extra.skip;
  227. var res = {
  228. id: self.assertCount++,
  229. ok: ok,
  230. skip: defined(extra.skip, opts.skip),
  231. todo: defined(extra.todo, opts.todo, self._todo),
  232. name: defined(extra.message, opts.message, '(unnamed assert)'),
  233. operator: defined(extra.operator, opts.operator),
  234. objectPrintDepth: self._objectPrintDepth
  235. };
  236. if (has(opts, 'actual') || has(extra, 'actual')) {
  237. res.actual = defined(extra.actual, opts.actual);
  238. }
  239. if (has(opts, 'expected') || has(extra, 'expected')) {
  240. res.expected = defined(extra.expected, opts.expected);
  241. }
  242. this._ok = !!(this._ok && ok);
  243. if (!ok && !res.todo) {
  244. res.error = defined(extra.error, opts.error, new Error(res.name));
  245. }
  246. if (!ok) {
  247. var e = new Error('exception');
  248. var err = (e.stack || '').split('\n');
  249. var dir = __dirname + path.sep;
  250. for (var i = 0; i < err.length; i++) {
  251. /*
  252. Stack trace lines may resemble one of the following. We need
  253. to correctly extract a function name (if any) and path / line
  254. number for each line.
  255. at myFunction (/path/to/file.js:123:45)
  256. at myFunction (/path/to/file.other-ext:123:45)
  257. at myFunction (/path to/file.js:123:45)
  258. at myFunction (C:\path\to\file.js:123:45)
  259. at myFunction (/path/to/file.js:123)
  260. at Test.<anonymous> (/path/to/file.js:123:45)
  261. at Test.bound [as run] (/path/to/file.js:123:45)
  262. at /path/to/file.js:123:45
  263. Regex has three parts. First is non-capturing group for 'at '
  264. (plus anything preceding it).
  265. /^(?:[^\s]*\s*\bat\s+)/
  266. Second captures function call description (optional). This is
  267. not necessarily a valid JS function name, but just what the
  268. stack trace is using to represent a function call. It may look
  269. like `<anonymous>` or 'Test.bound [as run]'.
  270. For our purposes, we assume that, if there is a function
  271. name, it's everything leading up to the first open
  272. parentheses (trimmed) before our pathname.
  273. /(?:(.*)\s+\()?/
  274. Last part captures file path plus line no (and optional
  275. column no).
  276. /((?:\/|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)\)?/
  277. */
  278. var re = /^(?:[^\s]*\s*\bat\s+)(?:(.*)\s+\()?((?:\/|[a-zA-Z]:\\)[^:)]+:(\d+)(?::(\d+))?)\)?$/;
  279. var lineWithTokens = err[i].replace(process.cwd(), '/$CWD').replace(__dirname, '/$TEST');
  280. var m = re.exec(lineWithTokens);
  281. if (!m) {
  282. continue;
  283. }
  284. var callDescription = m[1] || '<anonymous>';
  285. var filePath = m[2].replace('/$CWD', process.cwd()).replace('/$TEST', __dirname);
  286. if (filePath.slice(0, dir.length) === dir) {
  287. continue;
  288. }
  289. // Function call description may not (just) be a function name. Try to extract function name by looking at first "word" only.
  290. res.functionName = callDescription.split(/\s+/)[0];
  291. res.file = filePath;
  292. res.line = Number(m[3]);
  293. if (m[4]) { res.column = Number(m[4]); }
  294. res.at = callDescription + ' (' + filePath + ')';
  295. break;
  296. }
  297. }
  298. self.emit('result', res);
  299. var pendingAsserts = self._pendingAsserts();
  300. if (!pendingAsserts) {
  301. if (extra.exiting) {
  302. self._end();
  303. } else {
  304. nextTick(function () {
  305. self._end();
  306. });
  307. }
  308. }
  309. if (!self._planError && pendingAsserts < 0) {
  310. self._planError = true;
  311. self.fail('plan != count', {
  312. expected: self._plan,
  313. actual: self._plan - pendingAsserts
  314. });
  315. }
  316. };
  317. Test.prototype.fail = function (msg, extra) {
  318. this._assert(false, {
  319. message: msg,
  320. operator: 'fail',
  321. extra: extra
  322. });
  323. };
  324. Test.prototype.pass = function (msg, extra) {
  325. this._assert(true, {
  326. message: msg,
  327. operator: 'pass',
  328. extra: extra
  329. });
  330. };
  331. Test.prototype.skip = function (msg, extra) {
  332. this._assert(true, {
  333. message: msg,
  334. operator: 'skip',
  335. skip: true,
  336. extra: extra
  337. });
  338. };
  339. // eslint-disable-next-line func-style
  340. var tapeAssert = function assert(value, msg, extra) {
  341. this._assert(value, {
  342. message: defined(msg, 'should be truthy'),
  343. operator: 'ok',
  344. expected: true,
  345. actual: value,
  346. extra: extra
  347. });
  348. };
  349. Test.prototype.ok
  350. = Test.prototype['true']
  351. = Test.prototype.assert
  352. = tapeAssert;
  353. function notOK(value, msg, extra) {
  354. this._assert(!value, {
  355. message: defined(msg, 'should be falsy'),
  356. operator: 'notOk',
  357. expected: false,
  358. actual: value,
  359. extra: extra
  360. });
  361. }
  362. Test.prototype.notOk
  363. = Test.prototype['false']
  364. = Test.prototype.notok
  365. = notOK;
  366. function error(err, msg, extra) {
  367. this._assert(!err, {
  368. message: defined(msg, String(err)),
  369. operator: 'error',
  370. actual: err,
  371. extra: extra
  372. });
  373. }
  374. Test.prototype.error
  375. = Test.prototype.ifError
  376. = Test.prototype.ifErr
  377. = Test.prototype.iferror
  378. = error;
  379. function equal(a, b, msg, extra) {
  380. this._assert(a === b, {
  381. message: defined(msg, 'should be equal'),
  382. operator: 'equal',
  383. actual: a,
  384. expected: b,
  385. extra: extra
  386. });
  387. }
  388. Test.prototype.equal
  389. = Test.prototype.equals
  390. = Test.prototype.isEqual
  391. = Test.prototype.is
  392. = Test.prototype.strictEqual
  393. = Test.prototype.strictEquals
  394. = equal;
  395. function notEqual(a, b, msg, extra) {
  396. this._assert(a !== b, {
  397. message: defined(msg, 'should not be equal'),
  398. operator: 'notEqual',
  399. actual: a,
  400. expected: b,
  401. extra: extra
  402. });
  403. }
  404. Test.prototype.notEqual
  405. = Test.prototype.notEquals
  406. = Test.prototype.notStrictEqual
  407. = Test.prototype.notStrictEquals
  408. = Test.prototype.isNotEqual
  409. = Test.prototype.isNot
  410. = Test.prototype.not
  411. = Test.prototype.doesNotEqual
  412. = Test.prototype.isInequal
  413. = notEqual;
  414. function tapeDeepEqual(a, b, msg, extra) {
  415. this._assert(deepEqual(a, b, { strict: true }), {
  416. message: defined(msg, 'should be equivalent'),
  417. operator: 'deepEqual',
  418. actual: a,
  419. expected: b,
  420. extra: extra
  421. });
  422. }
  423. Test.prototype.deepEqual
  424. = Test.prototype.deepEquals
  425. = Test.prototype.isEquivalent
  426. = Test.prototype.same
  427. = tapeDeepEqual;
  428. function deepLooseEqual(a, b, msg, extra) {
  429. this._assert(deepEqual(a, b), {
  430. message: defined(msg, 'should be equivalent'),
  431. operator: 'deepLooseEqual',
  432. actual: a,
  433. expected: b,
  434. extra: extra
  435. });
  436. }
  437. Test.prototype.deepLooseEqual
  438. = Test.prototype.looseEqual
  439. = Test.prototype.looseEquals
  440. = deepLooseEqual;
  441. function notDeepEqual(a, b, msg, extra) {
  442. this._assert(!deepEqual(a, b, { strict: true }), {
  443. message: defined(msg, 'should not be equivalent'),
  444. operator: 'notDeepEqual',
  445. actual: a,
  446. expected: b,
  447. extra: extra
  448. });
  449. }
  450. Test.prototype.notDeepEqual
  451. = Test.prototype.notDeepEquals
  452. = Test.prototype.notEquivalent
  453. = Test.prototype.notDeeply
  454. = Test.prototype.notSame
  455. = Test.prototype.isNotDeepEqual
  456. = Test.prototype.isNotDeeply
  457. = Test.prototype.isNotEquivalent
  458. = Test.prototype.isInequivalent
  459. = notDeepEqual;
  460. function notDeepLooseEqual(a, b, msg, extra) {
  461. this._assert(!deepEqual(a, b), {
  462. message: defined(msg, 'should be equivalent'),
  463. operator: 'notDeepLooseEqual',
  464. actual: a,
  465. expected: b,
  466. extra: extra
  467. });
  468. }
  469. Test.prototype.notDeepLooseEqual
  470. = Test.prototype.notLooseEqual
  471. = Test.prototype.notLooseEquals
  472. = notDeepLooseEqual;
  473. Test.prototype['throws'] = function (fn, expected, msg, extra) {
  474. if (typeof expected === 'string') {
  475. msg = expected;
  476. expected = undefined;
  477. }
  478. var caught;
  479. try {
  480. fn();
  481. } catch (err) {
  482. caught = { error: err };
  483. if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !has(err, 'message'))) {
  484. try {
  485. var message = err.message;
  486. delete err.message;
  487. err.message = message;
  488. } catch (e) { /**/ }
  489. }
  490. }
  491. var passed = caught;
  492. if (isRegExp(expected)) {
  493. passed = $exec(expected, caught && caught.error) !== null;
  494. expected = String(expected);
  495. }
  496. if (typeof expected === 'function' && caught) {
  497. passed = caught.error instanceof expected;
  498. }
  499. this._assert(typeof fn === 'function' && passed, {
  500. message: defined(msg, 'should throw'),
  501. operator: 'throws',
  502. actual: caught && caught.error,
  503. expected: expected,
  504. error: !passed && caught && caught.error,
  505. extra: extra
  506. });
  507. };
  508. Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
  509. if (typeof expected === 'string') {
  510. msg = expected;
  511. expected = undefined;
  512. }
  513. var caught;
  514. try {
  515. fn();
  516. } catch (err) {
  517. caught = { error: err };
  518. }
  519. this._assert(!caught, {
  520. message: defined(msg, 'should not throw'),
  521. operator: 'throws',
  522. actual: caught && caught.error,
  523. expected: expected,
  524. error: caught && caught.error,
  525. extra: extra
  526. });
  527. };
  528. Test.prototype.match = function match(string, regexp, msg, extra) {
  529. if (!isRegExp(regexp)) {
  530. this._assert(false, {
  531. message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
  532. operator: 'match',
  533. actual: objectToString(regexp),
  534. expected: '[object RegExp]',
  535. extra: extra
  536. });
  537. } else if (typeof string !== 'string') {
  538. this._assert(false, {
  539. message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
  540. operator: 'match',
  541. actual: string === null ? null : typeof string,
  542. expected: 'string',
  543. extra: extra
  544. });
  545. } else {
  546. var matches = $exec(regexp, string) !== null;
  547. var message = defined(
  548. msg,
  549. 'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
  550. );
  551. this._assert(matches, {
  552. message: message,
  553. operator: 'match',
  554. actual: string,
  555. expected: regexp,
  556. extra: extra
  557. });
  558. }
  559. };
  560. Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) {
  561. if (!isRegExp(regexp)) {
  562. this._assert(false, {
  563. message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
  564. operator: 'doesNotMatch',
  565. actual: objectToString(regexp),
  566. expected: '[object RegExp]',
  567. extra: extra
  568. });
  569. } else if (typeof string !== 'string') {
  570. this._assert(false, {
  571. message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
  572. operator: 'doesNotMatch',
  573. actual: string === null ? null : typeof string,
  574. expected: 'string',
  575. extra: extra
  576. });
  577. } else {
  578. var matches = $exec(regexp, string) !== null;
  579. var message = defined(
  580. msg,
  581. 'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
  582. );
  583. this._assert(!matches, {
  584. message: message,
  585. operator: 'doesNotMatch',
  586. actual: string,
  587. expected: regexp,
  588. extra: extra
  589. });
  590. }
  591. };
  592. // eslint-disable-next-line no-unused-vars
  593. Test.skip = function (name_, _opts, _cb) {
  594. var args = getTestArgs.apply(null, arguments);
  595. args.opts.skip = true;
  596. return new Test(args.name, args.opts, args.cb);
  597. };
  598. module.exports = Test;
  599. // vim: set softtabstop=4 shiftwidth=4: