readdirp-stream.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*jshint asi:true */
  2. var debug //= true;
  3. var test = debug ? function () {} : require('tap').test
  4. var test_ = !debug ? function () {} : require('tap').test
  5. , path = require('path')
  6. , fs = require('fs')
  7. , util = require('util')
  8. , TransformStream = require('readable-stream').Transform
  9. , through = require('through2')
  10. , proxyquire = require('proxyquire')
  11. , streamapi = require('../stream-api')
  12. , readdirp = require('..')
  13. , root = path.join(__dirname, 'bed')
  14. , totalDirs = 6
  15. , totalFiles = 12
  16. , ext1Files = 4
  17. , ext2Files = 3
  18. , ext3Files = 2
  19. ;
  20. // see test/readdirp.js for test bed layout
  21. function opts (extend) {
  22. var o = { root: root };
  23. if (extend) {
  24. for (var prop in extend) {
  25. o[prop] = extend[prop];
  26. }
  27. }
  28. return o;
  29. }
  30. function capture () {
  31. var result = { entries: [], errors: [], ended: false }
  32. , dst = new TransformStream({ objectMode: true });
  33. dst._transform = function (entry, _, cb) {
  34. result.entries.push(entry);
  35. cb();
  36. }
  37. dst._flush = function (cb) {
  38. result.ended = true;
  39. this.push(result);
  40. cb();
  41. }
  42. return dst;
  43. }
  44. test('\nintegrated', function (t) {
  45. t.test('\n# reading root without filter', function (t) {
  46. t.plan(2);
  47. readdirp(opts())
  48. .on('error', function (err) {
  49. t.fail('should not throw error', err);
  50. })
  51. .pipe(capture())
  52. .pipe(through.obj(
  53. function (result, _ , cb) {
  54. t.equals(result.entries.length, totalFiles, 'emits all files');
  55. t.ok(result.ended, 'ends stream');
  56. t.end();
  57. cb();
  58. }
  59. ));
  60. })
  61. t.test('\n# normal: ["*.ext1", "*.ext3"]', function (t) {
  62. t.plan(2);
  63. readdirp(opts( { fileFilter: [ '*.ext1', '*.ext3' ] } ))
  64. .on('error', function (err) {
  65. t.fail('should not throw error', err);
  66. })
  67. .pipe(capture())
  68. .pipe(through.obj(
  69. function (result, _ , cb) {
  70. t.equals(result.entries.length, ext1Files + ext3Files, 'all ext1 and ext3 files');
  71. t.ok(result.ended, 'ends stream');
  72. t.end();
  73. cb();
  74. }
  75. ))
  76. })
  77. t.test('\n# files only', function (t) {
  78. t.plan(2);
  79. readdirp(opts( { entryType: 'files' } ))
  80. .on('error', function (err) {
  81. t.fail('should not throw error', err);
  82. })
  83. .pipe(capture())
  84. .pipe(through.obj(
  85. function (result, _ , cb) {
  86. t.equals(result.entries.length, totalFiles, 'returned files');
  87. t.ok(result.ended, 'ends stream');
  88. t.end();
  89. cb();
  90. }
  91. ))
  92. })
  93. t.test('\n# directories only', function (t) {
  94. t.plan(2);
  95. readdirp(opts( { entryType: 'directories' } ))
  96. .on('error', function (err) {
  97. t.fail('should not throw error', err);
  98. })
  99. .pipe(capture())
  100. .pipe(through.obj(
  101. function (result, _ , cb) {
  102. t.equals(result.entries.length, totalDirs, 'returned directories');
  103. t.ok(result.ended, 'ends stream');
  104. t.end();
  105. cb();
  106. }
  107. ))
  108. })
  109. t.test('\n# both directories + files', function (t) {
  110. t.plan(2);
  111. readdirp(opts( { entryType: 'both' } ))
  112. .on('error', function (err) {
  113. t.fail('should not throw error', err);
  114. })
  115. .pipe(capture())
  116. .pipe(through.obj(
  117. function (result, _ , cb) {
  118. t.equals(result.entries.length, totalDirs + totalFiles, 'returned everything');
  119. t.ok(result.ended, 'ends stream');
  120. t.end();
  121. cb();
  122. }
  123. ))
  124. })
  125. t.test('\n# directory filter with directories only', function (t) {
  126. t.plan(2);
  127. readdirp(opts( { entryType: 'directories', directoryFilter: [ 'root_dir1', '*dir1_subdir1' ] } ))
  128. .on('error', function (err) {
  129. t.fail('should not throw error', err);
  130. })
  131. .pipe(capture())
  132. .pipe(through.obj(
  133. function (result, _ , cb) {
  134. t.equals(result.entries.length, 2, 'two directories');
  135. t.ok(result.ended, 'ends stream');
  136. t.end();
  137. cb();
  138. }
  139. ))
  140. })
  141. t.test('\n# directory and file filters with both entries', function (t) {
  142. t.plan(2);
  143. readdirp(opts( { entryType: 'both', directoryFilter: [ 'root_dir1', '*dir1_subdir1' ], fileFilter: [ '!*.ext1' ] } ))
  144. .on('error', function (err) {
  145. t.fail('should not throw error', err);
  146. })
  147. .pipe(capture())
  148. .pipe(through.obj(
  149. function (result, _ , cb) {
  150. t.equals(result.entries.length, 6, '2 directories and 4 files');
  151. t.ok(result.ended, 'ends stream');
  152. t.end();
  153. cb();
  154. }
  155. ))
  156. })
  157. t.test('\n# negated: ["!*.ext1", "!*.ext3"]', function (t) {
  158. t.plan(2);
  159. readdirp(opts( { fileFilter: [ '!*.ext1', '!*.ext3' ] } ))
  160. .on('error', function (err) {
  161. t.fail('should not throw error', err);
  162. })
  163. .pipe(capture())
  164. .pipe(through.obj(
  165. function (result, _ , cb) {
  166. t.equals(result.entries.length, totalFiles - ext1Files - ext3Files, 'all but ext1 and ext3 files');
  167. t.ok(result.ended, 'ends stream');
  168. t.end();
  169. }
  170. ))
  171. })
  172. t.test('\n# no options given', function (t) {
  173. t.plan(1);
  174. readdirp()
  175. .on('error', function (err) {
  176. t.similar(err.toString() , /Need to pass at least one argument/ , 'emits meaningful error');
  177. t.end();
  178. })
  179. })
  180. t.test('\n# mixed: ["*.ext1", "!*.ext3"]', function (t) {
  181. t.plan(1);
  182. readdirp(opts( { fileFilter: [ '*.ext1', '!*.ext3' ] } ))
  183. .on('error', function (err) {
  184. t.similar(err.toString() , /Cannot mix negated with non negated glob filters/ , 'emits meaningful error');
  185. t.end();
  186. })
  187. })
  188. })
  189. test('\napi separately', function (t) {
  190. t.test('\n# handleError', function (t) {
  191. t.plan(1);
  192. var api = streamapi()
  193. , warning = new Error('some file caused problems');
  194. api.stream
  195. .on('warn', function (err) {
  196. t.equals(err, warning, 'warns with the handled error');
  197. })
  198. api.handleError(warning);
  199. })
  200. t.test('\n# when stream is paused and then resumed', function (t) {
  201. t.plan(6);
  202. var api = streamapi()
  203. , resumed = false
  204. , fatalError = new Error('fatal!')
  205. , nonfatalError = new Error('nonfatal!')
  206. , processedData = 'some data'
  207. ;
  208. api.stream
  209. .on('warn', function (err) {
  210. t.equals(err, nonfatalError, 'emits the buffered warning');
  211. t.ok(resumed, 'emits warning only after it was resumed');
  212. })
  213. .on('error', function (err) {
  214. t.equals(err, fatalError, 'emits the buffered fatal error');
  215. t.ok(resumed, 'emits errors only after it was resumed');
  216. })
  217. .on('data', function (data) {
  218. t.equals(data, processedData, 'emits the buffered data');
  219. t.ok(resumed, 'emits data only after it was resumed');
  220. })
  221. .pause()
  222. api.processEntry(processedData);
  223. api.handleError(nonfatalError);
  224. api.handleFatalError(fatalError);
  225. setTimeout(function () {
  226. resumed = true;
  227. api.stream.resume();
  228. }, 1)
  229. })
  230. t.test('\n# when a stream is paused it stops walking the fs', function (t) {
  231. var resumed = false,
  232. mockedAPI = streamapi();
  233. mockedAPI.processEntry = function (entry) {
  234. if (!resumed) t.notOk(true, 'should not emit while paused')
  235. t.ok(entry, 'emitted while resumed')
  236. }.bind(mockedAPI.stream)
  237. function wrapper () {
  238. return mockedAPI
  239. }
  240. var readdirp = proxyquire('../readdirp', {'./stream-api': wrapper})
  241. , stream = readdirp(opts())
  242. .on('error', function (err) {
  243. t.fail('should not throw error', err);
  244. })
  245. .on('end', function () {
  246. t.end()
  247. })
  248. .pause();
  249. setTimeout(function () {
  250. resumed = true;
  251. stream.resume();
  252. }, 5)
  253. })
  254. t.test('\n# when a stream is destroyed, it emits "closed", but no longer emits "data", "warn" and "error"', function (t) {
  255. var api = streamapi()
  256. , fatalError = new Error('fatal!')
  257. , nonfatalError = new Error('nonfatal!')
  258. , processedData = 'some data'
  259. , plan = 0;
  260. t.plan(6)
  261. var stream = api.stream
  262. .on('warn', function (err) {
  263. t.ok(!stream._destroyed, 'emits warning until destroyed');
  264. })
  265. .on('error', function (err) {
  266. t.ok(!stream._destroyed, 'emits errors until destroyed');
  267. })
  268. .on('data', function (data) {
  269. t.ok(!stream._destroyed, 'emits data until destroyed');
  270. })
  271. .on('close', function () {
  272. t.ok(stream._destroyed, 'emits close when stream is destroyed');
  273. })
  274. api.processEntry(processedData);
  275. api.handleError(nonfatalError);
  276. api.handleFatalError(fatalError);
  277. setTimeout(function () {
  278. stream.destroy()
  279. t.notOk(stream.readable, 'stream is no longer readable after it is destroyed')
  280. api.processEntry(processedData);
  281. api.handleError(nonfatalError);
  282. api.handleFatalError(fatalError);
  283. process.nextTick(function () {
  284. t.pass('emits no more data, warn or error events after it was destroyed')
  285. t.end();
  286. })
  287. }, 10)
  288. })
  289. })