index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. 'use strict';
  2. var util = require('../');
  3. var fs = require('fs');
  4. var path = require('path');
  5. var Tempfile = require('temporary/lib/file');
  6. exports['util.callbackify'] = {
  7. 'return': function(test) {
  8. test.expect(1);
  9. // This function returns a value.
  10. function add(a, b) {
  11. return a + b;
  12. }
  13. util.callbackify(add)(1, 2, function(result) {
  14. test.equal(result, 3, 'should be the correct result.');
  15. test.done();
  16. });
  17. },
  18. 'callback (sync)': function(test) {
  19. test.expect(1);
  20. // This function accepts a callback which it calls synchronously.
  21. function add(a, b, done) {
  22. done(a + b);
  23. }
  24. util.callbackify(add)(1, 2, function(result) {
  25. test.equal(result, 3, 'should be the correct result.');
  26. test.done();
  27. });
  28. },
  29. 'callback (async)': function(test) {
  30. test.expect(1);
  31. // This function accepts a callback which it calls asynchronously.
  32. function add(a, b, done) {
  33. setTimeout(done.bind(null, a + b), 0);
  34. }
  35. util.callbackify(add)(1, 2, function(result) {
  36. test.equal(result, 3, 'should be the correct result.');
  37. test.done();
  38. });
  39. }
  40. };
  41. exports['util'] = {
  42. 'error': function(test) {
  43. test.expect(9);
  44. var origError = new Error('Original error.');
  45. var err = util.error('Test message.');
  46. test.ok(err instanceof Error, 'Should be an Error.');
  47. test.equal(err.name, 'Error', 'Should be an Error.');
  48. test.equal(err.message, 'Test message.', 'Should have the correct message.');
  49. err = util.error('Test message.', origError);
  50. test.ok(err instanceof Error, 'Should be an Error.');
  51. test.equal(err.name, 'Error', 'Should be an Error.');
  52. test.equal(err.message, 'Test message.', 'Should have the correct message.');
  53. test.equal(err.origError, origError, 'Should reflect the original error.');
  54. var newError = new Error('Test message.');
  55. err = util.error(newError, origError);
  56. test.equal(err, newError, 'Should be the passed-in Error.');
  57. test.equal(err.origError, origError, 'Should reflect the original error.');
  58. test.done();
  59. },
  60. 'linefeed': function(test) {
  61. test.expect(1);
  62. if (process.platform === 'win32') {
  63. test.equal(util.linefeed, '\r\n', 'linefeed should be operating-system appropriate.');
  64. } else {
  65. test.equal(util.linefeed, '\n', 'linefeed should be operating-system appropriate.');
  66. }
  67. test.done();
  68. },
  69. 'normalizelf': function(test) {
  70. test.expect(1);
  71. if (process.platform === 'win32') {
  72. test.equal(util.normalizelf('foo\nbar\r\nbaz\r\n\r\nqux\n\nquux'), 'foo\r\nbar\r\nbaz\r\n\r\nqux\r\n\r\nquux', 'linefeeds should be normalized');
  73. } else {
  74. test.equal(util.normalizelf('foo\nbar\r\nbaz\r\n\r\nqux\n\nquux'), 'foo\nbar\nbaz\n\nqux\n\nquux', 'linefeeds should be normalized');
  75. }
  76. test.done();
  77. }
  78. };
  79. exports['util.spawn'] = {
  80. setUp: function(done) {
  81. this.script = path.resolve('test/fixtures/spawn.js');
  82. done();
  83. },
  84. 'exit code 0': function(test) {
  85. test.expect(6);
  86. util.spawn({
  87. cmd: process.execPath,
  88. args: [ this.script, 0 ],
  89. }, function(err, result, code) {
  90. test.equals(err, null);
  91. test.equals(code, 0);
  92. test.equals(result.stdout, 'stdout');
  93. test.equals(result.stderr, 'stderr');
  94. test.equals(result.code, 0);
  95. test.equals(String(result), 'stdout');
  96. test.done();
  97. });
  98. },
  99. 'exit code 0, fallback': function(test) {
  100. test.expect(6);
  101. util.spawn({
  102. cmd: process.execPath,
  103. args: [ this.script, 0 ],
  104. fallback: 'ignored if exit code is 0'
  105. }, function(err, result, code) {
  106. test.equals(err, null);
  107. test.equals(code, 0);
  108. test.equals(result.stdout, 'stdout');
  109. test.equals(result.stderr, 'stderr');
  110. test.equals(result.code, 0);
  111. test.equals(String(result), 'stdout');
  112. test.done();
  113. });
  114. },
  115. 'non-zero exit code': function(test) {
  116. test.expect(7);
  117. util.spawn({
  118. cmd: process.execPath,
  119. args: [ this.script, 123 ],
  120. }, function(err, result, code) {
  121. test.ok(err instanceof Error);
  122. test.equals(err.message, 'stderr');
  123. test.equals(code, 123);
  124. test.equals(result.stdout, 'stdout');
  125. test.equals(result.stderr, 'stderr');
  126. test.equals(result.code, 123);
  127. test.equals(String(result), 'stderr');
  128. test.done();
  129. });
  130. },
  131. 'non-zero exit code, fallback': function(test) {
  132. test.expect(6);
  133. util.spawn({
  134. cmd: process.execPath,
  135. args: [ this.script, 123 ],
  136. fallback: 'custom fallback'
  137. }, function(err, result, code) {
  138. test.equals(err, null);
  139. test.equals(code, 123);
  140. test.equals(result.stdout, 'stdout');
  141. test.equals(result.stderr, 'stderr');
  142. test.equals(result.code, 123);
  143. test.equals(String(result), 'custom fallback');
  144. test.done();
  145. });
  146. },
  147. 'cmd not found': function(test) {
  148. test.expect(3);
  149. util.spawn({
  150. cmd: 'nodewtfmisspelled',
  151. }, function(err, result, code) {
  152. test.ok(err instanceof Error);
  153. test.equals(code, 127);
  154. test.equals(result.code, 127);
  155. test.done();
  156. });
  157. },
  158. 'cmd not found, fallback': function(test) {
  159. test.expect(4);
  160. util.spawn({
  161. cmd: 'nodewtfmisspelled',
  162. fallback: 'use a fallback or good luck'
  163. }, function(err, result, code) {
  164. test.equals(err, null);
  165. test.equals(code, 127);
  166. test.equals(result.code, 127);
  167. test.equals(String(result), 'use a fallback or good luck');
  168. test.done();
  169. });
  170. },
  171. 'cmd not in path': function(test) {
  172. test.expect(6);
  173. var win32 = process.platform === 'win32';
  174. util.spawn({
  175. cmd: 'test\\fixtures\\exec' + (win32 ? '.cmd' : '.sh'),
  176. }, function(err, result, code) {
  177. test.equals(err, null);
  178. test.equals(code, 0);
  179. test.equals(result.stdout, 'done');
  180. test.equals(result.stderr, '');
  181. test.equals(result.code, 0);
  182. test.equals(String(result), 'done');
  183. test.done();
  184. });
  185. },
  186. 'cmd not in path (with cwd)': function(test) {
  187. test.expect(6);
  188. var win32 = process.platform === 'win32';
  189. util.spawn({
  190. cmd: './exec' + (win32 ? '.cmd' : '.sh'),
  191. opts: {cwd: 'test/fixtures'},
  192. }, function(err, result, code) {
  193. test.equals(err, null);
  194. test.equals(code, 0);
  195. test.equals(result.stdout, 'done');
  196. test.equals(result.stderr, '');
  197. test.equals(result.code, 0);
  198. test.equals(String(result), 'done');
  199. test.done();
  200. });
  201. },
  202. 'grunt': function(test) {
  203. test.expect(3);
  204. util.spawn({
  205. grunt: true,
  206. args: [ '--gruntfile', 'test/fixtures/Gruntfile-print-text.js', 'print:foo' ],
  207. }, function(err, result, code) {
  208. test.equals(err, null);
  209. test.equals(code, 0);
  210. test.ok(/^OUTPUT: foo/m.test(result.stdout), 'stdout should contain output indicating the grunt task was run.');
  211. test.done();
  212. });
  213. },
  214. 'grunt (with cwd)': function(test) {
  215. test.expect(3);
  216. util.spawn({
  217. grunt: true,
  218. args: [ '--gruntfile', 'Gruntfile-print-text.js', 'print:foo' ],
  219. opts: {cwd: 'test/fixtures'},
  220. }, function(err, result, code) {
  221. test.equals(err, null);
  222. test.equals(code, 0);
  223. test.ok(/^OUTPUT: foo/m.test(result.stdout), 'stdout should contain output indicating the grunt task was run.');
  224. test.done();
  225. });
  226. },
  227. 'grunt passes execArgv': function(test) {
  228. test.expect(3);
  229. util.spawn({
  230. cmd: process.execPath,
  231. args: [ '--harmony', process.argv[1], '--gruntfile', 'test/fixtures/Gruntfile-execArgv.js'],
  232. }, function(err, result, code) {
  233. test.equals(err, null);
  234. test.equals(code, 0);
  235. test.ok(/^OUTPUT: --harmony/m.test(result.stdout), 'stdout should contain passed-through process.execArgv.');
  236. test.done();
  237. });
  238. },
  239. 'grunt result.toString() with error': function(test) {
  240. // grunt.log.error uses standard out, to be fixed in 0.5.
  241. test.expect(4);
  242. util.spawn({
  243. grunt: true,
  244. args: [ 'nonexistentTask' ]
  245. }, function(err, result, code) {
  246. test.ok(err instanceof Error, 'Should be an Error.');
  247. test.equal(err.name, 'Error', 'Should be an Error.');
  248. test.equals(code, 3);
  249. test.ok(/Warning: Task "nonexistentTask" not found./m.test(result.toString()), 'stdout should contain output indicating the grunt task was (attempted to be) run.');
  250. test.done();
  251. });
  252. },
  253. 'custom stdio stream(s)': function(test) {
  254. test.expect(6);
  255. var stdoutFile = new Tempfile();
  256. var stderrFile = new Tempfile();
  257. var stdout = fs.openSync(stdoutFile.path, 'a');
  258. var stderr = fs.openSync(stderrFile.path, 'a');
  259. var child = util.spawn({
  260. cmd: process.execPath,
  261. args: [ this.script, 0 ],
  262. opts: {stdio: [null, stdout, stderr]},
  263. }, function(err, result, code) {
  264. test.equals(code, 0);
  265. test.equals(String(fs.readFileSync(stdoutFile.path)), 'stdout\n', 'Child process stdout should have been captured via custom stream.');
  266. test.equals(String(fs.readFileSync(stderrFile.path)), 'stderr\n', 'Child process stderr should have been captured via custom stream.');
  267. stdoutFile.unlinkSync();
  268. stderrFile.unlinkSync();
  269. test.equals(result.stdout, '', 'Nothing will be passed to the stdout string when spawn stdio is a custom stream.');
  270. test.done();
  271. });
  272. test.ok(!child.stdout, 'child should not have a stdout property.');
  273. test.ok(!child.stderr, 'child should not have a stderr property.');
  274. },
  275. };
  276. exports['util.spawn.multibyte'] = {
  277. setUp: function(done) {
  278. this.script = path.resolve('test/fixtures/spawn-multibyte.js');
  279. done();
  280. },
  281. 'partial stdout': function(test) {
  282. test.expect(4);
  283. util.spawn({
  284. cmd: process.execPath,
  285. args: [ this.script ],
  286. }, function(err, result, code) {
  287. test.equals(err, null);
  288. test.equals(code, 0);
  289. test.equals(result.stdout, 'こんにちは');
  290. test.equals(result.stderr, 'こんにちは');
  291. test.done();
  292. });
  293. }
  294. };
  295. exports['util.underscore.string'] = function(test) {
  296. test.expect(4);
  297. test.equals(util._.trim(' foo '), 'foo', 'Should have trimmed the string.');
  298. test.equals(util._.capitalize('foo'), 'Foo', 'Should have capitalized the first letter.');
  299. test.equals(util._.words('one two three').length, 3, 'Should have counted three words.');
  300. test.ok(util._.isBlank(' '), 'Should be blank.');
  301. test.done();
  302. };
  303. function getType(val) {
  304. if (Buffer.isBuffer(val)) { return 'buffer'; }
  305. return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
  306. }
  307. exports['util.recurse'] = {
  308. setUp: function(done) {
  309. this.typeValue = function(value) {
  310. return {
  311. value: value,
  312. type: getType(value),
  313. };
  314. };
  315. done();
  316. },
  317. 'primitives': function(test) {
  318. test.expect(1);
  319. var actual = util.recurse({
  320. bool: true,
  321. num: 1,
  322. str: 'foo',
  323. nul: null,
  324. undef: undefined,
  325. }, this.typeValue);
  326. var expected = {
  327. bool: {type: 'boolean', value: true},
  328. num: {type: 'number', value: 1},
  329. str: {type: 'string', value: 'foo'},
  330. nul: {type: 'null', value: null},
  331. undef: {type: 'undefined', value: undefined},
  332. };
  333. test.deepEqual(actual, expected, 'Should process primitive values.');
  334. test.done();
  335. },
  336. 'array': function(test) {
  337. test.expect(1);
  338. var actual = util.recurse({
  339. arr: [
  340. true,
  341. 1,
  342. 'foo',
  343. null,
  344. undefined,
  345. [
  346. true,
  347. 1,
  348. 'foo',
  349. null,
  350. undefined,
  351. ],
  352. ],
  353. }, this.typeValue);
  354. var expected = {
  355. arr: [
  356. {type: 'boolean', value: true},
  357. {type: 'number', value: 1},
  358. {type: 'string', value: 'foo'},
  359. {type: 'null', value: null},
  360. {type: 'undefined', value: undefined},
  361. [
  362. {type: 'boolean', value: true},
  363. {type: 'number', value: 1},
  364. {type: 'string', value: 'foo'},
  365. {type: 'null', value: null},
  366. {type: 'undefined', value: undefined},
  367. ],
  368. ],
  369. };
  370. test.deepEqual(actual, expected, 'Should recurse over arrays.');
  371. test.done();
  372. },
  373. 'object': function(test) {
  374. test.expect(1);
  375. var actual = util.recurse({
  376. obj: {
  377. bool: true,
  378. num: 1,
  379. str: 'foo',
  380. nul: null,
  381. undef: undefined,
  382. obj: {
  383. bool: true,
  384. num: 1,
  385. str: 'foo',
  386. nul: null,
  387. undef: undefined,
  388. },
  389. },
  390. }, this.typeValue);
  391. var expected = {
  392. obj: {
  393. bool: {type: 'boolean', value: true},
  394. num: {type: 'number', value: 1},
  395. str: {type: 'string', value: 'foo'},
  396. nul: {type: 'null', value: null},
  397. undef: {type: 'undefined', value: undefined},
  398. obj: {
  399. bool: {type: 'boolean', value: true},
  400. num: {type: 'number', value: 1},
  401. str: {type: 'string', value: 'foo'},
  402. nul: {type: 'null', value: null},
  403. undef: {type: 'undefined', value: undefined},
  404. },
  405. },
  406. };
  407. test.deepEqual(actual, expected, 'Should recurse over objects.');
  408. test.done();
  409. },
  410. 'array in object': function(test) {
  411. test.expect(1);
  412. var actual = util.recurse({
  413. obj: {
  414. arr: [
  415. true,
  416. 1,
  417. 'foo',
  418. null,
  419. undefined,
  420. ],
  421. },
  422. }, this.typeValue);
  423. var expected = {
  424. obj: {
  425. arr: [
  426. {type: 'boolean', value: true},
  427. {type: 'number', value: 1},
  428. {type: 'string', value: 'foo'},
  429. {type: 'null', value: null},
  430. {type: 'undefined', value: undefined},
  431. ],
  432. },
  433. };
  434. test.deepEqual(actual, expected, 'Should recurse over arrays in objects.');
  435. test.done();
  436. },
  437. 'object in array': function(test) {
  438. test.expect(1);
  439. var actual = util.recurse({
  440. arr: [
  441. true,
  442. {
  443. num: 1,
  444. str: 'foo',
  445. },
  446. null,
  447. undefined,
  448. ],
  449. }, this.typeValue);
  450. var expected = {
  451. arr: [
  452. {type: 'boolean', value: true},
  453. {
  454. num: {type: 'number', value: 1},
  455. str: {type: 'string', value: 'foo'},
  456. },
  457. {type: 'null', value: null},
  458. {type: 'undefined', value: undefined},
  459. ],
  460. };
  461. test.deepEqual(actual, expected, 'Should recurse over objects in arrays.');
  462. test.done();
  463. },
  464. 'buffer': function(test) {
  465. test.expect(1);
  466. var actual = util.recurse({
  467. buf: new Buffer('buf'),
  468. }, this.typeValue);
  469. var expected = {
  470. buf: {type: 'buffer', value: new Buffer('buf')},
  471. };
  472. test.deepEqual(actual, expected, 'Should not mangle Buffer instances.');
  473. test.done();
  474. },
  475. 'inherited properties': function(test) {
  476. test.expect(1);
  477. var actual = util.recurse({
  478. obj: Object.create({num: 1}, {
  479. str: {value: 'foo', enumerable: true},
  480. ignored: {value: 'ignored', enumerable: false},
  481. }),
  482. }, this.typeValue);
  483. var expected = {
  484. obj: {
  485. num: {type: 'number', value: 1},
  486. str: {type: 'string', value: 'foo'},
  487. }
  488. };
  489. test.deepEqual(actual, expected, 'Should enumerate inherited object properties.');
  490. test.done();
  491. },
  492. 'circular references': function(test) {
  493. test.expect(6);
  494. function assertErrorWithPath(expectedPath) {
  495. return function(actual) {
  496. return actual.path === expectedPath &&
  497. actual.message === 'Circular reference detected (' + expectedPath + ')';
  498. };
  499. }
  500. test.doesNotThrow(function() {
  501. var obj = {
  502. // wat
  503. a:[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
  504. // does
  505. b:[[[[],[[[],[[[[],[[[],[[[],[[[],[[[],[[[[],[[]]]]]]]]]]]]]]]]]]]]],
  506. // it
  507. c:{d:{e:{f:{g:{h:{i:{j:{k:{l:{m:{n:{o:{p:{q:{r:{s:{}}}}}}}}}}}}}}}}},
  508. // mean
  509. t:[{u:[{v:[[[[],[[[],[[[{w:[{x:[[[],[[[{y:[[1]]}]]]]]}]}]]]]]]]]}]}],
  510. };
  511. util.recurse(obj, function(v) { return v; });
  512. }, 'Should not throw when no circular reference is detected.');
  513. test.throws(function() {
  514. var obj = {a: 1, b: 2};
  515. obj.obj = obj;
  516. util.recurse(obj, function(v) { return v; });
  517. }, assertErrorWithPath('.obj'), 'Should throw when a circular reference is detected.');
  518. test.throws(function() {
  519. var obj = {a:{'b b':{'c-c':{d_d:{e:{f:{g:{h:{i:{j:{k:{l:{}}}}}}}}}}}}};
  520. obj.a['b b']['c-c'].d_d.e.f.g.h.i.j.k.l.obj = obj;
  521. util.recurse(obj, function(v) { return v; });
  522. }, assertErrorWithPath('.a["b b"]["c-c"].d_d.e.f.g.h.i.j.k.l.obj'), 'Should throw when a circular reference is detected.');
  523. test.throws(function() {
  524. var obj = {a: 1, b: 2};
  525. obj.arr = [1, 2, obj, 3, 4];
  526. util.recurse(obj, function(v) { return v; });
  527. }, assertErrorWithPath('.arr[2]'), 'Should throw when a circular reference is detected.');
  528. test.throws(function() {
  529. var obj = {a: 1, b: 2};
  530. obj.arr = [{a:[1,{b:[2,{c:[3,obj,4]},5]},6]},7];
  531. util.recurse(obj, function(v) { return v; });
  532. }, assertErrorWithPath('.arr[0].a[1].b[1].c[1]'), 'Should throw when a circular reference is detected.');
  533. test.throws(function() {
  534. var obj = {a: 1, b: 2};
  535. obj.arr = [];
  536. obj.arr.push(0,{a:[1,{b:[2,{c:[3,obj.arr,4]},5]},6]},7);
  537. util.recurse(obj, function(v) { return v; });
  538. }, assertErrorWithPath('.arr[1].a[1].b[1].c[1]'), 'Should throw when a circular reference is detected.');
  539. test.done();
  540. },
  541. };