1 | const crypto = require('crypto')
|
2 | const test = require('tape')
|
3 |
|
4 | const create = require('./helpers/create')
|
5 | const { runAll } = require('./helpers/util')
|
6 |
|
7 | test('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 |
|
36 | test('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 |
|
66 | test('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 |
|
96 | test.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 |
|
126 | test('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 |
|
156 | test('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 |
|
192 | test('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 |
|
227 | test('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 |
|
263 | test('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 |
|
309 | test('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 |
|
341 | function 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 |
|
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 |
|
362 | function 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 |
|
372 | function 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 |
|
382 | function 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 | }
|