forked from yarnpkg/berry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathZipOpenFS.test.ts
249 lines (186 loc) Β· 6.77 KB
/
ZipOpenFS.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import {ppath, npath, Filename, PortablePath} from '@yarnpkg/fslib';
import {ZipOpenFS, getArchivePart} from '@yarnpkg/libzip';
export const ZIP_DIR1 = ppath.join(
npath.toPortablePath(__dirname),
`fixtures/foo.zip` as Filename,
);
export const ZIP_DIR2 = ppath.join(
npath.toPortablePath(__dirname),
`fixtures/folder.zip/foo.zip` as Filename,
);
export const ZIP_DIR3 = ppath.join(
npath.toPortablePath(__dirname),
`fixtures/foo.hiddenzip` as Filename,
);
export const ZIP_DIR4 = ppath.join(
npath.toPortablePath(__dirname),
`fixtures/symlink.zip` as Filename,
);
export const ZIP_FILE1 = ppath.join(ZIP_DIR1, `foo.txt`);
export const ZIP_FILE2 = ppath.join(ZIP_DIR2, `foo.txt`);
export const ZIP_FILE3 = ppath.join(ZIP_DIR3, `foo.txt`);
export const ZIP_FILE4 = ppath.join(ZIP_DIR4, `foo.txt`);
afterEach(() => {
jest.useRealTimers();
});
describe(`getArchivePart`, () => {
const tests = [
[`.zip`, null],
[`foo`, null],
[`foo.zip`, `foo.zip`],
[`foo.zip/bar`, `foo.zip`],
[`foo.zip/bar/baz`, `foo.zip`],
[`/a/b/c/foo.zip`, `/a/b/c/foo.zip`],
[`./a/b/c/foo.zip`, `./a/b/c/foo.zip`],
[`./a/b/c/.zip`, null],
[`./a/b/c/foo.zipp`, null],
[`./a/b/c/foo.zip/bar/baz/qux.zip`, `./a/b/c/foo.zip`],
[`./a/b/c/foo.zip-bar.zip`, `./a/b/c/foo.zip-bar.zip`],
[`./a/b/c/foo.zip-bar.zip/bar/baz/qux.zip`, `./a/b/c/foo.zip-bar.zip`],
[`./a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip/d`, `./a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip`],
] as const;
for (const [path, result] of tests) {
test(`getArchivePart(${JSON.stringify(path)}) === ${JSON.stringify(result)}`, () => {
expect(getArchivePart(path, `.zip`)).toStrictEqual(result);
});
}
});
describe(`ZipOpenFS`, () => {
it(`can read from a zip file`, () => {
const fs = new ZipOpenFS();
expect(fs.readFileSync(ZIP_FILE1, `utf8`)).toEqual(`foo\n`);
fs.discardAndClose();
});
it(`can read from a zip file in a path containing .zip`, () => {
const fs = new ZipOpenFS();
expect(fs.readFileSync(ZIP_FILE2, `utf8`)).toEqual(`foo\n`);
fs.discardAndClose();
});
it(`can read from a zip file with an unusual extension if so configured`, () => {
const fs = new ZipOpenFS({fileExtensions: [`.hiddenzip`]});
expect(fs.readFileSync(ZIP_FILE3, `utf8`)).toEqual(`foo\n`);
fs.discardAndClose();
});
it(`throws when reading from a zip file with an unusual extension`, () => {
const fs = new ZipOpenFS();
expect(() => {
fs.readFileSync(ZIP_FILE3, `utf8`);
}).toThrowError();
fs.discardAndClose();
});
it(`can read from a zip file that's a symlink`, () => {
const fs = new ZipOpenFS();
expect(fs.readFileSync(ZIP_FILE4, `utf8`)).toEqual(`foo\n`);
fs.discardAndClose();
});
it(`doesn't close a ZipFS instance with open handles`, () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
const fileHandle = fs.openSync(ZIP_FILE1, ``);
expect(fs.readFileSync(ZIP_FILE2, `utf8`)).toEqual(`foo\n`);
const buff = Buffer.alloc(4);
fs.readSync(fileHandle, buff, 0, 4, 0);
fs.closeSync(fileHandle);
expect(buff.toString(`utf8`)).toEqual(`foo\n`);
fs.discardAndClose();
});
it(`sets the path property of the stream object returned by createReadStream to the normalized native version of the input path`, async () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
const unnormalizedPortablePath = ZIP_FILE1.replace(/\//g, `/./`) as PortablePath;
const normalizedNativePath = npath.fromPortablePath(ZIP_FILE1);
const stream = fs.createReadStream(unnormalizedPortablePath);
expect(stream.path).toMatch(normalizedNativePath);
stream.destroy();
fs.discardAndClose();
});
it(`treats createReadStream as an open file handle`, async () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
const chunks: Array<Buffer> = [];
await new Promise<void>(resolve => {
let done = 0;
fs.createReadStream(ZIP_FILE1)
.on(`data`, (chunk: Buffer) => {
chunks.push(chunk);
})
.on(`close`, () => {
if (++done === 2) {
resolve();
}
});
fs.createReadStream(ZIP_FILE2)
.on(`data`, (chunk: Buffer) => {
chunks.push(chunk);
})
.on(`close`, () => {
if (++done === 2) {
resolve();
}
});
});
expect(chunks[0].toString(`utf8`)).toMatch(`foo\n`);
expect(chunks[1].toString(`utf8`)).toMatch(`foo\n`);
fs.discardAndClose();
});
it(`treats createWriteStream as an open file handle`, async () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
const stream1 = fs.createWriteStream(ZIP_FILE1);
const stream2 = fs.createWriteStream(ZIP_FILE2);
await new Promise<void>(resolve => {
let done = 0;
stream1.end(`foo`, () => {
if (++done === 2) {
resolve();
}
});
stream2.end(`bar`, () => {
if (++done === 2) {
resolve();
}
});
});
fs.discardAndClose();
});
it(`closes ZipFS instances once they become stale`, async () => {
jest.useFakeTimers();
const fs = new ZipOpenFS({maxAge: 2000});
await fs.existsPromise(ZIP_FILE1);
// @ts-expect-error: mountInstances is private
expect(fs.mountInstances!.size).toEqual(1);
jest.advanceTimersByTime(1000);
fs.existsSync(ZIP_FILE2);
// @ts-expect-error: mountInstances is private
expect(fs.mountInstances!.size).toEqual(2);
jest.advanceTimersByTime(1000);
// @ts-expect-error: mountInstances is private
expect(fs.mountInstances!.size).toEqual(1);
jest.advanceTimersByTime(1000);
// @ts-expect-error: mountInstances is private
expect(fs.mountInstances!.size).toEqual(0);
fs.discardAndClose();
});
it(`doesn't close zip files while they are in use`, async () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
await Promise.all([
fs.readFilePromise(ZIP_FILE1),
fs.realpathPromise(ZIP_FILE1),
fs.readFilePromise(ZIP_FILE2),
fs.realpathPromise(ZIP_FILE2),
]);
fs.discardAndClose();
});
it(`doesn't crash when watching a file in a archive that gets closed`, async () => {
jest.useFakeTimers();
const fs = new ZipOpenFS({maxOpenFiles: 1});
fs.watchFile(ZIP_FILE1, (current, previous) => {});
fs.watchFile(ZIP_FILE2, (current, previous) => {});
jest.advanceTimersByTime(100);
fs.discardAndClose();
});
it(`treats Dir instances opened via opendir as open file handles`, () => {
const fs = new ZipOpenFS({maxOpenFiles: 1});
const dir1 = fs.opendirSync(ZIP_DIR1);
const dir2 = fs.opendirSync(ZIP_DIR2);
expect(dir1.readSync()!.name).toStrictEqual(`foo.txt`);
expect(dir2.readSync()!.name).toStrictEqual(`foo.txt`);
fs.discardAndClose();
});
});