|
| 1 | +// Flags: --experimental-vfs |
| 2 | +'use strict'; |
| 3 | + |
| 4 | +// A recursive readdir() of a ZipProvider lists every member below the |
| 5 | +// directory, together with every directory their paths pass through, whether |
| 6 | +// or not the archive holds an entry for it. |
| 7 | + |
| 8 | +const common = require('../common'); |
| 9 | +const assert = require('assert'); |
| 10 | +const fs = require('fs'); |
| 11 | +const path = require('path'); |
| 12 | +const zlib = require('zlib'); |
| 13 | +const vfs = require('node:vfs'); |
| 14 | + |
| 15 | +async function buildArchive(entries) { |
| 16 | + const chunks = []; |
| 17 | + for await (const chunk of zlib.createZipArchive(entries)) chunks.push(chunk); |
| 18 | + return Buffer.concat(chunks); |
| 19 | +} |
| 20 | + |
| 21 | +(async () => { |
| 22 | + const archive = await buildArchive([ |
| 23 | + await zlib.ZipEntry.create('top.txt', Buffer.from('top')), |
| 24 | + // Directories only implied by a member's path, several levels deep. |
| 25 | + await zlib.ZipEntry.create('a/b/c/deep.txt', Buffer.from('deep')), |
| 26 | + // An explicit directory entry, before and after members inside it. |
| 27 | + await zlib.ZipEntry.create('a/b/', Buffer.alloc(0)), |
| 28 | + await zlib.ZipEntry.create('a/b/sibling.txt', Buffer.from('sibling')), |
| 29 | + await zlib.ZipEntry.create('empty/', Buffer.alloc(0)), |
| 30 | + ]); |
| 31 | + const provider = new vfs.ZipProvider(new zlib.ZipBuffer(archive)); |
| 32 | + |
| 33 | + const all = [ |
| 34 | + 'a', 'a/b', 'a/b/c', 'a/b/c/deep.txt', 'a/b/sibling.txt', 'empty', 'top.txt', |
| 35 | + ]; |
| 36 | + const dirs = new Set(['a', 'a/b', 'a/b/c', 'empty']); |
| 37 | + |
| 38 | + // Each directory is listed once, whether implied, explicit, or both. |
| 39 | + assert.deepStrictEqual(provider.readdirSync('/', { recursive: true }).sort(), all); |
| 40 | + assert.deepStrictEqual((await provider.readdir('/', { recursive: true })).sort(), all); |
| 41 | + |
| 42 | + const dirents = provider.readdirSync('/', { recursive: true, withFileTypes: true }); |
| 43 | + assert.deepStrictEqual(dirents.map((d) => d.name).sort(), all); |
| 44 | + for (const dirent of dirents) { |
| 45 | + assert.strictEqual(dirent.isDirectory(), dirs.has(dirent.name), dirent.name); |
| 46 | + assert.strictEqual(dirent.isFile(), !dirs.has(dirent.name), dirent.name); |
| 47 | + } |
| 48 | + |
| 49 | + // Listing a subdirectory yields paths relative to it. |
| 50 | + assert.deepStrictEqual(provider.readdirSync('/a/b', { recursive: true }).sort(), |
| 51 | + ['c', 'c/deep.txt', 'sibling.txt']); |
| 52 | + assert.deepStrictEqual(provider.readdirSync('/empty', { recursive: true }), []); |
| 53 | + assert.throws(() => provider.readdirSync('/top.txt', { recursive: true }), |
| 54 | + { code: 'ENOTDIR' }); |
| 55 | + assert.throws(() => provider.readdirSync('/missing', { recursive: true }), |
| 56 | + { code: 'ENOENT' }); |
| 57 | + |
| 58 | + // A non-recursive listing is unchanged. |
| 59 | + assert.deepStrictEqual(provider.readdirSync('/').sort(), ['a', 'empty', 'top.txt']); |
| 60 | + assert.deepStrictEqual(provider.readdirSync('/a/b').sort(), ['c', 'sibling.txt']); |
| 61 | + |
| 62 | + // A name that is both a member and a directory is listed as a directory. |
| 63 | + { |
| 64 | + const clash = new vfs.ZipProvider(new zlib.ZipBuffer(await buildArchive([ |
| 65 | + await zlib.ZipEntry.create('x', Buffer.from('file')), |
| 66 | + await zlib.ZipEntry.create('x/y.txt', Buffer.from('nested')), |
| 67 | + ]))); |
| 68 | + const entries = clash.readdirSync('/', { recursive: true, withFileTypes: true }); |
| 69 | + assert.deepStrictEqual(entries.map((d) => [d.name, d.isDirectory()]).sort(), |
| 70 | + [['x', true], ['x/y.txt', false]]); |
| 71 | + } |
| 72 | + |
| 73 | + // Through node:fs, each Dirent reports its own parent directory. |
| 74 | + { |
| 75 | + const archiveVfs = vfs.create(provider); |
| 76 | + const mountPoint = archiveVfs.mount(); |
| 77 | + const listed = fs.readdirSync(mountPoint, { recursive: true, withFileTypes: true }) |
| 78 | + .map((d) => path.join(d.parentPath, d.name)).sort(); |
| 79 | + assert.deepStrictEqual(listed, all.map((p) => path.join(mountPoint, p)).sort()); |
| 80 | + assert.deepStrictEqual(fs.readdirSync(mountPoint, { recursive: true }).sort(), all); |
| 81 | + archiveVfs.unmount(); |
| 82 | + } |
| 83 | +})().then(common.mustCall()); |
0 commit comments