UNPKG

8.84 kBJavaScriptView Raw
1const crypto = require('crypto')
2const test = require('tape')
3
4const create = require('./helpers/create')
5const { runAll } = require('./helpers/util')
6
7test('readdir on empty directory', async function (t) {
8 const drive = create()
9
10 const files = createFiles([
11 'a/a',
12 'a/b',
13 'a/c/d',
14 'a/c/e',
15 'a/e',
16 'b/e',
17 'b/f',
18 'b/d',
19 'e'
20 ])
21
22 try {
23 await runAll([
24 cb => drive.mkdir('l', cb),
25 cb => writeFiles(drive, files, cb),
26 cb => validateReaddir(t, drive, 'd', [], cb),
27 cb => validateReaddir(t, drive, 'l', [], cb)
28 ])
29 } catch (err) {
30 t.fail(err)
31 }
32
33 t.end()
34})
35
36test('can read a single directory', async function (t) {
37 const drive = create(null)
38
39 let files = ['a', 'b', 'c', 'd', 'e', 'f']
40 let fileSet = new Set(files)
41
42 for (let file of files) {
43 await insertFile(file, 'a small file')
44 }
45
46 drive.readdir('/', (err, files) => {
47 t.error(err, 'no error')
48 for (let file of files) {
49 t.true(fileSet.has(file), 'correct file was listed')
50 fileSet.delete(file)
51 }
52 t.same(fileSet.size, 0, 'all files were listed')
53 t.end()
54 })
55
56 function insertFile (name, content) {
57 return new Promise((resolve, reject) => {
58 drive.writeFile(name, content, err => {
59 if (err) return reject(err)
60 return resolve()
61 })
62 })
63 }
64})
65
66test('another single-directory readdir', async t => {
67 const drive = create()
68
69 const files = createFiles([
70 'a/a',
71 'a/b',
72 'a/c/d',
73 'a/c/e',
74 'a/e',
75 'b/e',
76 'b/f',
77 'b/d',
78 'e'
79 ])
80
81 try {
82 await runAll([
83 cb => writeFiles(drive, files, cb),
84 cb => validateReaddir(t, drive, 'a', ['a', 'b', 'c', 'e'], cb),
85 cb => validateReaddir(t, drive, 'a/c', ['d', 'e'], cb),
86 cb => validateReaddir(t, drive, 'b', ['e', 'f', 'd'], cb),
87 cb => validateReaddir(t, drive, '', ['a', 'b', 'e'], cb)
88 ])
89 } catch (err) {
90 t.fail(err)
91 }
92
93 t.end()
94})
95
96test.only('readdir can include stats/mounts', async t => {
97 const drive = create()
98
99 const files = createFiles([
100 'a/a',
101 'a/b',
102 'a/c/d',
103 'a/c/e',
104 'a/e',
105 'b/e',
106 'b/f',
107 'b/d',
108 'e'
109 ])
110
111 try {
112 await runAll([
113 cb => writeFiles(drive, files, cb),
114 cb => validateReaddir(t, drive, 'a', ['a', 'b', 'c', 'e'], { includeStats: true }, cb),
115 cb => validateReaddir(t, drive, 'a/c', ['d', 'e'], { includeStats: true }, cb),
116 cb => validateReaddir(t, drive, 'b', ['e', 'f', 'd'], { includeStats: true }, cb),
117 cb => validateReaddir(t, drive, '', ['a', 'b', 'e'], { includeStats: true }, cb)
118 ])
119 } catch (err) {
120 t.fail(err)
121 }
122
123 t.end()
124})
125
126test('recursive readdir', async t => {
127 const drive = create()
128
129 const files = createFiles([
130 'a/a',
131 'a/b',
132 'a/c/d',
133 'a/c/e',
134 'a/e',
135 'b/e',
136 'b/f',
137 'b/d',
138 'e'
139 ])
140
141 try {
142 await runAll([
143 cb => writeFiles(drive, files, cb),
144 cb => validateReaddir(t, drive, 'a', ['a', 'b', 'c/d', 'c/e', 'e'], { recursive: true }, cb),
145 cb => validateReaddir(t, drive, 'a/c', ['d', 'e'], { recursive: true }, cb),
146 cb => validateReaddir(t, drive, 'b', ['e', 'f', 'd'], { recursive: true }, cb),
147 cb => validateReaddir(t, drive, '', ['a/a', 'a/b', 'a/c/d', 'a/c/e', 'a/e', 'b/e', 'b/f', 'b/d', 'e'], { recursive: true }, cb)
148 ])
149 } catch (err) {
150 t.fail(err)
151 }
152
153 t.end()
154})
155
156test('readdir follows symlink', async t => {
157 const drive = create()
158
159 const files = createFiles([
160 'a/a',
161 'a/b',
162 'a/c/d',
163 'a/c/e',
164 'a/e',
165 'b/e',
166 'b/f',
167 'b/d',
168 'e'
169 ])
170 const links = new Map([
171 ['f', 'a'],
172 ['p', 'a/c'],
173 ['g', 'e']
174 ])
175
176 try {
177 await runAll([
178 cb => writeFiles(drive, files, cb),
179 cb => writeLinks(drive, links, cb),
180 cb => validateReaddir(t, drive, 'f', ['a', 'b', 'c', 'e'], cb),
181 cb => validateReaddir(t, drive, 'p', ['d', 'e'], cb),
182 cb => validateReaddir(t, drive, 'b', ['e', 'f', 'd'], cb),
183 cb => validateReaddir(t, drive, '', ['a', 'b', 'e', 'f', 'p', 'g'], cb)
184 ])
185 } catch (err) {
186 t.fail(err)
187 }
188
189 t.end()
190})
191
192test('readdir works with broken links', async t => {
193 const drive = create()
194
195 const files = createFiles([
196 'a/a',
197 'a/b',
198 'a/c/d',
199 'a/c/e',
200 'a/e',
201 'b/e',
202 'b/f',
203 'b/d',
204 'e'
205 ])
206 const links = new Map([
207 ['f', 'a'],
208 ['p', 'nothing_here'],
209 ['g', 'e']
210 ])
211
212 try {
213 await runAll([
214 cb => writeFiles(drive, files, cb),
215 cb => writeLinks(drive, links, cb),
216 cb => validateReaddir(t, drive, 'f', ['a', 'b', 'c', 'e'], cb),
217 cb => validateReaddir(t, drive, 'b', ['e', 'f', 'd'], cb),
218 cb => validateReaddir(t, drive, '', ['a', 'b', 'e', 'f', 'p', 'g'], cb)
219 ])
220 } catch (err) {
221 t.fail(err)
222 }
223
224 t.end()
225})
226
227test('readdir follows symlinks to symlinks', async t => {
228 const drive = create()
229
230 const files = createFiles([
231 'a/a',
232 'a/b',
233 'a/c/d',
234 'a/c/e',
235 'a/e',
236 'b/e',
237 'b/f',
238 'b/d',
239 'e'
240 ])
241 const links = new Map([
242 ['a/d', '../r'],
243 ['r', 'a/c/f'],
244 ['a/c/f', '../../b']
245 ])
246
247 try {
248 await runAll([
249 cb => writeFiles(drive, files, cb),
250 cb => writeLinks(drive, links, cb),
251 cb => validateReaddir(t, drive, 'a/d', ['e', 'f', 'd'], cb),
252 cb => validateReaddir(t, drive, 'r', ['e', 'f', 'd'], cb),
253 cb => validateReaddir(t, drive, 'a/c/f', ['e', 'f', 'd'], cb),
254 cb => validateReaddir(t, drive, '', ['a', 'b', 'e', 'r'], cb)
255 ])
256 } catch (err) {
257 t.fail(err)
258 }
259
260 t.end()
261})
262
263test('can read nested directories', async function (t) {
264 const drive = create(null)
265
266 let files = ['a', 'b/a/b', 'b/c', 'c/b', 'd/e/f/g/h', 'd/e/a', 'e/a', 'e/b', 'f', 'g']
267 let rootSet = new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
268 let bSet = new Set(['a', 'c'])
269 let dSet = new Set(['e'])
270 let eSet = new Set(['a', 'b'])
271 let deSet = new Set(['f', 'a'])
272
273 for (let file of files) {
274 await insertFile(file, 'a small file')
275 }
276
277 await checkDir('/', rootSet)
278 await checkDir('b', bSet)
279 await checkDir('d', dSet)
280 await checkDir('e', eSet)
281 await checkDir('d/e', deSet)
282
283 t.end()
284
285 function checkDir (dir, fileSet) {
286 return new Promise(resolve => {
287 drive.readdir(dir, (err, files) => {
288 t.error(err, 'no error')
289 for (let file of files) {
290 t.true(fileSet.has(file), 'correct file was listed')
291 fileSet.delete(file)
292 }
293 t.same(fileSet.size, 0, 'all files were listed')
294 return resolve()
295 })
296 })
297 }
298
299 function insertFile (name, content) {
300 return new Promise((resolve, reject) => {
301 drive.writeFile(name, content, err => {
302 if (err) return reject(err)
303 return resolve()
304 })
305 })
306 }
307})
308
309test('can stream a large directory', async function (t) {
310 const drive = create(null)
311
312 let files = new Array(1000).fill(0).map((_, idx) => '/' + idx)
313 let fileSet = new Set(files)
314
315 for (let file of files) {
316 await insertFile(file, 'a small file')
317 }
318
319 let stream = drive.createDirectoryStream('/')
320 stream.on('data', ({ path, stat }) => {
321 if (!fileSet.has(path)) {
322 return t.fail('an incorrect file was streamed')
323 }
324 fileSet.delete(path)
325 })
326 stream.on('end', () => {
327 t.same(fileSet.size, 0, 'all files were streamed')
328 t.end()
329 })
330
331 function insertFile (name, content) {
332 return new Promise((resolve, reject) => {
333 drive.writeFile(name, content, err => {
334 if (err) return reject(err)
335 return resolve()
336 })
337 })
338 }
339})
340
341function validateReaddir (t, drive, path, names, opts, cb) {
342 if (typeof opts === 'function') return validateReaddir(t, drive, path, names, {}, opts)
343 drive.readdir(path, opts, (err, list) => {
344 if (err) return cb(err)
345 t.same(list.length, names.length)
346 if (opts && opts.includeStats) {
347 for (const { name, stat, mount } of list) {
348 t.notEqual(names.indexOf(name), -1)
349 // TODO: Support more detailed validation of stat/mount here.
350 t.true(stat)
351 t.true(mount)
352 }
353 } else {
354 for (const name of list) {
355 t.notEqual(names.indexOf(name), -1)
356 }
357 }
358 return cb(null)
359 })
360}
361
362function writeFiles (drive, files, cb) {
363 var expected = files.size
364 for (const [name, contents] of files) {
365 drive.writeFile(name, contents, err => {
366 if (err) return cb(err)
367 if (!--expected) return cb(null)
368 })
369 }
370}
371
372function writeLinks (drive, links, cb) {
373 var expected = links.size
374 for (const [name, target] of links) {
375 drive.symlink(target, name, err => {
376 if (err) return cb(err)
377 if (!--expected) return cb(null)
378 })
379 }
380}
381
382function createFiles (names) {
383 const files = []
384 for (const name of names) {
385 files.push([name, crypto.randomBytes(32)])
386 }
387 return new Map(files)
388}