atomically 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nyc_output/09ad542c-5435-4970-ae9c-d3aca17cc8e0.json +1 -0
- package/.nyc_output/5c74a299-649e-4bcb-894e-d3d7bc61ba96.json +1 -0
- package/.nyc_output/aec5ef7b-c224-4fa5-a830-b12bd1873072.json +1 -0
- package/.nyc_output/e5566b61-47b7-4c05-8e8b-36c7f809df3e.json +1 -0
- package/.nyc_output/f6bca65a-d147-4d4d-9fbf-a307181ea52f.json +1 -0
- package/.nyc_output/processinfo/09ad542c-5435-4970-ae9c-d3aca17cc8e0.json +1 -0
- package/.nyc_output/processinfo/5c74a299-649e-4bcb-894e-d3d7bc61ba96.json +1 -0
- package/.nyc_output/processinfo/aec5ef7b-c224-4fa5-a830-b12bd1873072.json +1 -0
- package/.nyc_output/processinfo/e5566b61-47b7-4c05-8e8b-36c7f809df3e.json +1 -0
- package/.nyc_output/processinfo/f6bca65a-d147-4d4d-9fbf-a307181ea52f.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -21
- package/dist/types.d.ts +0 -1
- package/dist/utils/lang.d.ts +2 -2
- package/dist/utils/scheduler.d.ts +1 -1
- package/dist/utils/temp.d.ts +1 -1
- package/dist/utils/temp.js +1 -1
- package/package.json +10 -9
- package/.editorconfig +0 -10
- package/src/constants.ts +0 -39
- package/src/index.ts +0 -330
- package/src/types.ts +0 -37
- package/src/utils/lang.ts +0 -34
- package/src/utils/scheduler.ts +0 -62
- package/src/utils/temp.ts +0 -102
- package/tasks/benchmark.js +0 -76
- package/test/basic.cjs +0 -508
- package/test/concurrency.cjs +0 -151
- package/test/integration.cjs +0 -289
- package/tsconfig.json +0 -3
package/test/basic.cjs
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
process.setMaxListeners(1000000);
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const os = require('os')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const {test} = require('tap')
|
|
7
|
-
const requireInject = require('require-inject')
|
|
8
|
-
|
|
9
|
-
let expectClose = 0
|
|
10
|
-
let closeCalled = 0
|
|
11
|
-
let expectCloseSync = 0
|
|
12
|
-
let closeSyncCalled = 0
|
|
13
|
-
const createErr = code => Object.assign(new Error(code), { code })
|
|
14
|
-
|
|
15
|
-
let unlinked = []
|
|
16
|
-
|
|
17
|
-
const fsMock = Object.assign ( {}, fs, {
|
|
18
|
-
/* ASYNC */
|
|
19
|
-
mkdir (filename, opts, cb) {
|
|
20
|
-
return cb(null);
|
|
21
|
-
},
|
|
22
|
-
realpath (filename, cb) {
|
|
23
|
-
return cb(null, filename)
|
|
24
|
-
},
|
|
25
|
-
open (tmpfile, options, mode, cb) {
|
|
26
|
-
if (/noopen/.test(tmpfile)) return cb(createErr('ENOOPEN'))
|
|
27
|
-
expectClose++
|
|
28
|
-
cb(null, tmpfile)
|
|
29
|
-
},
|
|
30
|
-
write (fd) {
|
|
31
|
-
const cb = arguments[arguments.length - 1]
|
|
32
|
-
if (/nowrite/.test(fd)) return cb(createErr('ENOWRITE'))
|
|
33
|
-
cb()
|
|
34
|
-
},
|
|
35
|
-
fsync (fd, cb) {
|
|
36
|
-
if (/nofsync/.test(fd)) return cb(createErr('ENOFSYNC'))
|
|
37
|
-
cb()
|
|
38
|
-
},
|
|
39
|
-
close (fd, cb) {
|
|
40
|
-
closeCalled++
|
|
41
|
-
cb()
|
|
42
|
-
},
|
|
43
|
-
chown (tmpfile, uid, gid, cb) {
|
|
44
|
-
if (/nochown/.test(tmpfile)) return cb(createErr('ENOCHOWN'))
|
|
45
|
-
if (/enosys/.test(tmpfile)) return cb(createErr('ENOSYS'))
|
|
46
|
-
if (/einval/.test(tmpfile)) return cb(createErr('EINVAL'))
|
|
47
|
-
if (/eperm/.test(tmpfile)) return cb(createErr('EPERM'))
|
|
48
|
-
cb()
|
|
49
|
-
},
|
|
50
|
-
chmod (tmpfile, mode, cb) {
|
|
51
|
-
if (/nochmod/.test(tmpfile)) return cb(createErr('ENOCHMOD'))
|
|
52
|
-
if (/enosys/.test(tmpfile)) return cb(createErr('ENOSYS'))
|
|
53
|
-
if (/eperm/.test(tmpfile)) return cb(createErr('EPERM'))
|
|
54
|
-
if (/einval/.test(tmpfile)) return cb(createErr('EINVAL'))
|
|
55
|
-
cb()
|
|
56
|
-
},
|
|
57
|
-
rename (tmpfile, filename, cb) {
|
|
58
|
-
if (/norename/.test(tmpfile)) return cb(createErr('ENORENAME'))
|
|
59
|
-
cb()
|
|
60
|
-
},
|
|
61
|
-
unlink (tmpfile, cb) {
|
|
62
|
-
if (/nounlink/.test(tmpfile)) return cb(createErr('ENOUNLINK'))
|
|
63
|
-
cb()
|
|
64
|
-
},
|
|
65
|
-
stat (tmpfile, cb) {
|
|
66
|
-
if (/nostat/.test(tmpfile)) return cb(createErr('ENOSTAT'))
|
|
67
|
-
if (/statful/.test(tmpfile)) return cb(null, fs.statSync('/'));
|
|
68
|
-
cb()
|
|
69
|
-
},
|
|
70
|
-
/* SYNC */
|
|
71
|
-
mkdirSync (filename) {},
|
|
72
|
-
realpathSync (filename, cb) {
|
|
73
|
-
return filename
|
|
74
|
-
},
|
|
75
|
-
openSync (tmpfile, options) {
|
|
76
|
-
if (/noopen/.test(tmpfile)) throw createErr('ENOOPEN')
|
|
77
|
-
expectCloseSync++
|
|
78
|
-
return tmpfile
|
|
79
|
-
},
|
|
80
|
-
writeSync (fd) {
|
|
81
|
-
if (/nowrite/.test(fd)) throw createErr('ENOWRITE')
|
|
82
|
-
},
|
|
83
|
-
fsyncSync (fd) {
|
|
84
|
-
if (/nofsync/.test(fd)) throw createErr('ENOFSYNC')
|
|
85
|
-
},
|
|
86
|
-
closeSync (fd) {
|
|
87
|
-
closeSyncCalled++
|
|
88
|
-
},
|
|
89
|
-
chownSync (tmpfile, uid, gid) {
|
|
90
|
-
if (/nochown/.test(tmpfile)) throw createErr('ENOCHOWN')
|
|
91
|
-
if (/enosys/.test(tmpfile)) throw createErr('ENOSYS')
|
|
92
|
-
if (/einval/.test(tmpfile)) throw createErr('EINVAL')
|
|
93
|
-
if (/eperm/.test(tmpfile)) throw createErr('EPERM')
|
|
94
|
-
},
|
|
95
|
-
chmodSync (tmpfile, mode) {
|
|
96
|
-
if (/nochmod/.test(tmpfile)) throw createErr('ENOCHMOD')
|
|
97
|
-
if (/enosys/.test(tmpfile)) throw createErr('ENOSYS')
|
|
98
|
-
if (/einval/.test(tmpfile)) throw createErr('EINVAL')
|
|
99
|
-
if (/eperm/.test(tmpfile)) throw createErr('EPERM')
|
|
100
|
-
},
|
|
101
|
-
renameSync (tmpfile, filename) {
|
|
102
|
-
if (/norename/.test(tmpfile)) throw createErr('ENORENAME')
|
|
103
|
-
},
|
|
104
|
-
unlinkSync (tmpfile) {
|
|
105
|
-
if (/nounlink/.test(tmpfile)) throw createErr('ENOUNLINK')
|
|
106
|
-
unlinked.push(tmpfile)
|
|
107
|
-
},
|
|
108
|
-
statSync (tmpfile) {
|
|
109
|
-
if (/nostat/.test(tmpfile)) throw createErr('ENOSTAT')
|
|
110
|
-
if (/statful/.test(tmpfile)) return fs.statSync('/');
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const makeUnstableAsyncFn = function () {
|
|
115
|
-
return function () {
|
|
116
|
-
if ( Math.random () <= .75 ) {
|
|
117
|
-
const code = ['EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM'].sort ( () => Math.random () - .5 )[0];
|
|
118
|
-
throw createErr ( code );
|
|
119
|
-
}
|
|
120
|
-
return arguments[arguments.length -1](null, arguments[0]);
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const makeUnstableSyncFn = function ( fn ) {
|
|
125
|
-
return function () {
|
|
126
|
-
if ( Math.random () <= .75 ) {
|
|
127
|
-
const code = ['EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM'].sort ( () => Math.random () - .5 )[0];
|
|
128
|
-
throw createErr ( code );
|
|
129
|
-
}
|
|
130
|
-
return fn.apply(undefined, arguments)
|
|
131
|
-
};
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const fsMockUnstable = Object.assign ( {}, fsMock, {
|
|
135
|
-
open: makeUnstableAsyncFn (),
|
|
136
|
-
write: makeUnstableAsyncFn (),
|
|
137
|
-
fsync: makeUnstableAsyncFn (),
|
|
138
|
-
close: makeUnstableAsyncFn (),
|
|
139
|
-
rename: makeUnstableAsyncFn (),
|
|
140
|
-
openSync: makeUnstableSyncFn ( x => x ),
|
|
141
|
-
writeSync: makeUnstableSyncFn ( () => {} ),
|
|
142
|
-
fsyncSync: makeUnstableSyncFn ( () => {} ),
|
|
143
|
-
closeSync: makeUnstableSyncFn ( () => {} ),
|
|
144
|
-
renameSync: makeUnstableSyncFn ( () => {} )
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const {writeFile: writeFileAtomic, writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
148
|
-
|
|
149
|
-
test('async tests', t => {
|
|
150
|
-
t.plan(2)
|
|
151
|
-
|
|
152
|
-
expectClose = 0
|
|
153
|
-
closeCalled = 0
|
|
154
|
-
t.teardown(() => {
|
|
155
|
-
t.parent.equal(closeCalled, expectClose, 'async tests closed all files')
|
|
156
|
-
expectClose = 0
|
|
157
|
-
closeCalled = 0
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
t.test('non-root tests', t => {
|
|
161
|
-
t.plan(28)
|
|
162
|
-
|
|
163
|
-
writeFileAtomic('good', 'test', { mode: '0777' }, err => {
|
|
164
|
-
t.notOk(err, 'No errors occur when passing in options')
|
|
165
|
-
})
|
|
166
|
-
writeFileAtomic('good', 'test', 'utf8', err => {
|
|
167
|
-
t.notOk(err, 'No errors occur when passing in options as string')
|
|
168
|
-
})
|
|
169
|
-
writeFileAtomic('good', 'test', undefined, err => {
|
|
170
|
-
t.notOk(err, 'No errors occur when NOT passing in options')
|
|
171
|
-
})
|
|
172
|
-
writeFileAtomic('good', 'test', err => {
|
|
173
|
-
t.notOk(err)
|
|
174
|
-
})
|
|
175
|
-
writeFileAtomic('noopen', 'test', err => {
|
|
176
|
-
t.equal(err.message, 'ENOOPEN', 'fs.open failures propagate')
|
|
177
|
-
})
|
|
178
|
-
writeFileAtomic('nowrite', 'test', err => {
|
|
179
|
-
t.equal(err.message, 'ENOWRITE', 'fs.writewrite failures propagate')
|
|
180
|
-
})
|
|
181
|
-
writeFileAtomic('nowrite', Buffer.from('test', 'utf8'), err => {
|
|
182
|
-
t.equal(err.message, 'ENOWRITE', 'fs.writewrite failures propagate for buffers')
|
|
183
|
-
})
|
|
184
|
-
writeFileAtomic('nochown', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
185
|
-
t.equal(err.message, 'ENOCHOWN', 'Chown failures propagate')
|
|
186
|
-
})
|
|
187
|
-
writeFileAtomic('nochown', 'test', err => {
|
|
188
|
-
t.notOk(err, 'No attempt to chown when no uid/gid passed in')
|
|
189
|
-
})
|
|
190
|
-
writeFileAtomic('nochmod', 'test', { mode: parseInt('741', 8) }, err => {
|
|
191
|
-
t.equal(err.message, 'ENOCHMOD', 'Chmod failures propagate')
|
|
192
|
-
})
|
|
193
|
-
writeFileAtomic('nofsyncopt', 'test', { fsync: false }, err => {
|
|
194
|
-
t.notOk(err, 'fsync skipped if options.fsync is false')
|
|
195
|
-
})
|
|
196
|
-
writeFileAtomic('norename', 'test', err => {
|
|
197
|
-
t.equal(err.message, 'ENORENAME', 'Rename errors propagate')
|
|
198
|
-
})
|
|
199
|
-
writeFileAtomic('norename nounlink', 'test', err => {
|
|
200
|
-
t.equal(err.message, 'ENORENAME', 'Failure to unlink the temp file does not clobber the original error')
|
|
201
|
-
})
|
|
202
|
-
writeFileAtomic('nofsync', 'test', err => {
|
|
203
|
-
t.equal(err.message, 'ENOFSYNC', 'Fsync failures propagate')
|
|
204
|
-
})
|
|
205
|
-
writeFileAtomic('enosys', 'test', err => {
|
|
206
|
-
t.notOk(err, 'No errors on ENOSYS')
|
|
207
|
-
})
|
|
208
|
-
writeFileAtomic('einval', 'test', { mode: 0o741 }, err => {
|
|
209
|
-
t.notOk(err, 'No errors on EINVAL for non root')
|
|
210
|
-
})
|
|
211
|
-
writeFileAtomic('eperm', 'test', { mode: 0o741 }, err => {
|
|
212
|
-
t.notOk(err, 'No errors on EPERM for non root')
|
|
213
|
-
})
|
|
214
|
-
writeFileAtomic('einval', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
215
|
-
t.notOk(err, 'No errors on EINVAL for non root')
|
|
216
|
-
})
|
|
217
|
-
writeFileAtomic('eperm', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
218
|
-
t.notOk(err, 'No errors on EPERM for non root')
|
|
219
|
-
})
|
|
220
|
-
const optionsImmutable = {};
|
|
221
|
-
writeFileAtomic('statful', 'test', optionsImmutable, err => {
|
|
222
|
-
t.notOk(err);
|
|
223
|
-
t.same(optionsImmutable, {});
|
|
224
|
-
});
|
|
225
|
-
const schedule = filePath => {
|
|
226
|
-
t.equal(filePath, 'good');
|
|
227
|
-
return new Promise ( resolve => {
|
|
228
|
-
resolve ( () => {
|
|
229
|
-
t.equal(true,true);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
};
|
|
233
|
-
writeFileAtomic('good','test', {schedule}, err => {
|
|
234
|
-
t.notOk(err);
|
|
235
|
-
});
|
|
236
|
-
const tmpCreate = filePath => `.${filePath}.custom`;
|
|
237
|
-
const tmpCreated = filePath => t.equal(filePath, '.good.custom' );
|
|
238
|
-
writeFileAtomic('good','test', {tmpCreate, tmpCreated}, err => {
|
|
239
|
-
t.notOk(err)
|
|
240
|
-
})
|
|
241
|
-
const longPath = path.join(os.tmpdir(),'.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt');
|
|
242
|
-
const {writeFile: writeFileAtomicNative} = requireInject('./atomically.cjs', { fs });
|
|
243
|
-
writeFileAtomicNative(longPath,'test', err => {
|
|
244
|
-
t.notOk(err)
|
|
245
|
-
})
|
|
246
|
-
const pathMissingFolders = path.join(os.tmpdir(),String(Math.random()),String(Math.random()),String(Math.random()),'foo.txt');
|
|
247
|
-
writeFileAtomicNative(pathMissingFolders,'test', err => {
|
|
248
|
-
t.notOk(err)
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
t.test('errors for root', t => {
|
|
253
|
-
const { getuid } = process
|
|
254
|
-
process.getuid = () => 0
|
|
255
|
-
t.teardown(() => {
|
|
256
|
-
process.getuid = getuid
|
|
257
|
-
})
|
|
258
|
-
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
259
|
-
t.plan(2)
|
|
260
|
-
writeFileAtomic('einval', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
261
|
-
t.match(err, { code: 'EINVAL' })
|
|
262
|
-
})
|
|
263
|
-
writeFileAtomic('einval', 'test', { mode: 0o741 }, err => {
|
|
264
|
-
t.match(err, { code: 'EINVAL' })
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
test('unstable async tests', t => {
|
|
270
|
-
t.plan(2);
|
|
271
|
-
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
272
|
-
writeFileAtomic('good', 'test', err => {
|
|
273
|
-
t.notOk(err, 'No errors occur when retryable errors are thrown')
|
|
274
|
-
})
|
|
275
|
-
writeFileAtomic('good', 'test', { timeout: 0 }, err => {
|
|
276
|
-
t.equal(!!err.code, true, 'Retrying can be disabled')
|
|
277
|
-
})
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('sync tests', t => {
|
|
281
|
-
t.plan(2)
|
|
282
|
-
closeSyncCalled = 0
|
|
283
|
-
expectCloseSync = 0
|
|
284
|
-
t.teardown(() => {
|
|
285
|
-
t.parent.equal(closeSyncCalled, expectCloseSync, 'sync closed all files')
|
|
286
|
-
expectCloseSync = 0
|
|
287
|
-
closeSyncCalled = 0
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
const throws = function (t, shouldthrow, msg, todo) {
|
|
291
|
-
let err
|
|
292
|
-
try { todo() } catch (e) { err = e }
|
|
293
|
-
t.equal(shouldthrow, err.message, msg)
|
|
294
|
-
}
|
|
295
|
-
const noexception = function (t, msg, todo) {
|
|
296
|
-
let err
|
|
297
|
-
try { todo() } catch (e) { err = e }
|
|
298
|
-
t.error(err, msg)
|
|
299
|
-
}
|
|
300
|
-
let tmpfile
|
|
301
|
-
|
|
302
|
-
t.test('non-root', t => {
|
|
303
|
-
t.plan(38)
|
|
304
|
-
noexception(t, 'No errors occur when passing in options', () => {
|
|
305
|
-
writeFileAtomicSync('good', 'test', { mode: '0777' })
|
|
306
|
-
})
|
|
307
|
-
noexception(t, 'No errors occur when passing in options as string', () => {
|
|
308
|
-
writeFileAtomicSync('good', 'test', 'utf8')
|
|
309
|
-
})
|
|
310
|
-
noexception(t, 'No errors occur when NOT passing in options', () => {
|
|
311
|
-
writeFileAtomicSync('good', 'test')
|
|
312
|
-
})
|
|
313
|
-
noexception(t, 'fsync never called if options.fsync is falsy', () => {
|
|
314
|
-
writeFileAtomicSync('good', 'test', { fsync: false })
|
|
315
|
-
})
|
|
316
|
-
noexception(t, 'tmpCreated is called on success', () => {
|
|
317
|
-
writeFileAtomicSync('good', 'test', {
|
|
318
|
-
tmpCreated (gottmpfile) {
|
|
319
|
-
tmpfile = gottmpfile
|
|
320
|
-
}
|
|
321
|
-
})
|
|
322
|
-
t.match(tmpfile, /^good\.tmp-\w+$/, 'tmpCreated called for success')
|
|
323
|
-
t.match(tmpfile, /^good\.tmp-\d{10}[a-f0-9]{6}$/, 'tmpCreated format')
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
tmpfile = undefined
|
|
327
|
-
throws(t, 'ENOOPEN', 'fs.openSync failures propagate', () => {
|
|
328
|
-
writeFileAtomicSync('noopen', 'test', {
|
|
329
|
-
tmpCreated (gottmpfile) {
|
|
330
|
-
tmpfile = gottmpfile
|
|
331
|
-
}
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
t.equal(tmpfile, undefined, 'tmpCreated not called for open failure')
|
|
335
|
-
|
|
336
|
-
throws(t, 'ENOWRITE', 'fs.writeSync failures propagate', () => {
|
|
337
|
-
writeFileAtomicSync('nowrite', 'test', {
|
|
338
|
-
tmpCreated (gottmpfile) {
|
|
339
|
-
tmpfile = gottmpfile
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
})
|
|
343
|
-
t.match(tmpfile, /^nowrite\.tmp-\w+$/, 'tmpCreated called for failure after open')
|
|
344
|
-
|
|
345
|
-
throws(t, 'ENOCHOWN', 'Chown failures propagate', () => {
|
|
346
|
-
writeFileAtomicSync('nochown', 'test', { chown: { uid: 100, gid: 100 } })
|
|
347
|
-
})
|
|
348
|
-
noexception(t, 'No attempt to chown when false passed in', () => {
|
|
349
|
-
writeFileAtomicSync('nochown', 'test', { chown: false })
|
|
350
|
-
})
|
|
351
|
-
noexception(t, 'No errors occured when chown is undefined and original file owner used', () => {
|
|
352
|
-
writeFileAtomicSync('chowncopy', 'test', { chown: undefined })
|
|
353
|
-
})
|
|
354
|
-
throws(t, 'ENORENAME', 'Rename errors propagate', () => {
|
|
355
|
-
writeFileAtomicSync('norename', 'test')
|
|
356
|
-
})
|
|
357
|
-
throws(t, 'ENORENAME', 'Failure to unlink the temp file does not clobber the original error', () => {
|
|
358
|
-
writeFileAtomicSync('norename nounlink', 'test')
|
|
359
|
-
})
|
|
360
|
-
throws(t, 'ENOFSYNC', 'Fsync errors propagate', () => {
|
|
361
|
-
writeFileAtomicSync('nofsync', 'test')
|
|
362
|
-
})
|
|
363
|
-
noexception(t, 'No errors on ENOSYS', () => {
|
|
364
|
-
writeFileAtomicSync('enosys', 'test', { chown: { uid: 100, gid: 100 } })
|
|
365
|
-
})
|
|
366
|
-
noexception(t, 'No errors on EINVAL for non root', () => {
|
|
367
|
-
writeFileAtomicSync('einval', 'test', { chown: { uid: 100, gid: 100 } })
|
|
368
|
-
})
|
|
369
|
-
noexception(t, 'No errors on EPERM for non root', () => {
|
|
370
|
-
writeFileAtomicSync('eperm', 'test', { chown: { uid: 100, gid: 100 } })
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
throws(t, 'ENOCHMOD', 'Chmod failures propagate', () => {
|
|
374
|
-
writeFileAtomicSync('nochmod', 'test', { mode: 0o741 })
|
|
375
|
-
})
|
|
376
|
-
noexception(t, 'No errors on EPERM for non root', () => {
|
|
377
|
-
writeFileAtomicSync('eperm', 'test', { mode: 0o741 })
|
|
378
|
-
})
|
|
379
|
-
noexception(t, 'No attempt to chmod when no mode provided', () => {
|
|
380
|
-
writeFileAtomicSync('nochmod', 'test', { mode: false })
|
|
381
|
-
})
|
|
382
|
-
const optionsImmutable = {};
|
|
383
|
-
noexception(t, 'options are immutable', () => {
|
|
384
|
-
writeFileAtomicSync('statful', 'test', optionsImmutable)
|
|
385
|
-
})
|
|
386
|
-
t.same(optionsImmutable, {});
|
|
387
|
-
const tmpCreate = filePath => `.${filePath}.custom`;
|
|
388
|
-
const tmpCreated = filePath => t.equal(filePath, '.good.custom' );
|
|
389
|
-
noexception(t, 'custom temp creator', () => {
|
|
390
|
-
writeFileAtomicSync('good', 'test', {tmpCreate, tmpCreated})
|
|
391
|
-
})
|
|
392
|
-
const path0 = path.join(os.tmpdir(),'atomically-test-0');
|
|
393
|
-
const tmpPath0 = path0 + '.temp';
|
|
394
|
-
noexception(t, 'temp files are purged on success', () => {
|
|
395
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
396
|
-
writeFileAtomicSync(path0, 'test', {tmpCreate: () => tmpPath0})
|
|
397
|
-
})
|
|
398
|
-
t.equal(true,fs.existsSync(path0));
|
|
399
|
-
t.equal(false,fs.existsSync(tmpPath0));
|
|
400
|
-
const path1 = path.join(os.tmpdir(),'atomically-test-norename-1');
|
|
401
|
-
const tmpPath1 = path1 + '.temp';
|
|
402
|
-
throws(t, 'ENORENAME', 'temp files are purged on error', () => {
|
|
403
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: Object.assign ( {}, fs, { renameSync: fsMock.renameSync })});
|
|
404
|
-
writeFileAtomicSync(path1, 'test', {tmpCreate: () => tmpPath1})
|
|
405
|
-
})
|
|
406
|
-
t.equal(false,fs.existsSync(path1));
|
|
407
|
-
t.equal(false,fs.existsSync(tmpPath1));
|
|
408
|
-
const path2 = path.join(os.tmpdir(),'atomically-test-norename-2');
|
|
409
|
-
const tmpPath2 = path2 + '.temp';
|
|
410
|
-
throws(t, 'ENORENAME', 'temp files can also not be purged on error', () => {
|
|
411
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: Object.assign ( {}, fs, { renameSync: fsMock.renameSync })});
|
|
412
|
-
writeFileAtomicSync(path2, 'test', {tmpCreate: () => tmpPath2,tmpPurge: false})
|
|
413
|
-
})
|
|
414
|
-
t.equal(false,fs.existsSync(path2));
|
|
415
|
-
t.equal(true,fs.existsSync(tmpPath2));
|
|
416
|
-
const longPath = path.join(os.tmpdir(),'.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt');
|
|
417
|
-
noexception(t, 'temp files are truncated', () => {
|
|
418
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
419
|
-
writeFileAtomicSync(longPath, 'test')
|
|
420
|
-
})
|
|
421
|
-
const pathMissingFolders = path.join(os.tmpdir(),String(Math.random()),String(Math.random()),String(Math.random()),'foo.txt');
|
|
422
|
-
noexception(t, 'parent folders are created', () => {
|
|
423
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
424
|
-
writeFileAtomicSync(pathMissingFolders, 'test')
|
|
425
|
-
})
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
t.test('errors for root', t => {
|
|
429
|
-
const { getuid } = process
|
|
430
|
-
process.getuid = () => 0
|
|
431
|
-
t.teardown(() => {
|
|
432
|
-
process.getuid = getuid
|
|
433
|
-
})
|
|
434
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
435
|
-
t.plan(2)
|
|
436
|
-
throws(t, 'EINVAL', 'Chown error as root user', () => {
|
|
437
|
-
writeFileAtomicSync('einval', 'test', { chown: { uid: 100, gid: 100 } })
|
|
438
|
-
})
|
|
439
|
-
throws(t, 'EINVAL', 'Chmod error as root user', () => {
|
|
440
|
-
writeFileAtomicSync('einval', 'test', { mode: 0o741 })
|
|
441
|
-
})
|
|
442
|
-
})
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
test('unstable sync tests', t => {
|
|
446
|
-
t.plan(2);
|
|
447
|
-
|
|
448
|
-
const throws = function (t, msg, todo) {
|
|
449
|
-
let err
|
|
450
|
-
try { todo() } catch (e) { err = e }
|
|
451
|
-
t.equal(!!err.code, true, msg)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const noexception = function (t, msg, todo) {
|
|
455
|
-
let err
|
|
456
|
-
try { todo() } catch (e) { err = e }
|
|
457
|
-
t.error(err, msg)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
noexception(t, 'No errors occur when retryable errors are thrown', () => {
|
|
461
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
462
|
-
writeFileAtomicSync('good', 'test')
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
throws(t, 'retrying can be disabled', () => {
|
|
466
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
467
|
-
writeFileAtomicSync('good', 'test', { timeout: 0 })
|
|
468
|
-
})
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
test('promises', async t => {
|
|
472
|
-
let tmpfile
|
|
473
|
-
closeCalled = 0
|
|
474
|
-
expectClose = 0
|
|
475
|
-
t.teardown(() => {
|
|
476
|
-
t.parent.equal(closeCalled, expectClose, 'promises closed all files')
|
|
477
|
-
closeCalled = 0
|
|
478
|
-
expectClose = 0
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
await writeFileAtomic('good', 'test', {
|
|
482
|
-
tmpCreated (gottmpfile) {
|
|
483
|
-
tmpfile = gottmpfile
|
|
484
|
-
}
|
|
485
|
-
})
|
|
486
|
-
t.match(tmpfile, /^good\.tmp-\w+$/, 'tmpCreated is called for success')
|
|
487
|
-
|
|
488
|
-
await writeFileAtomic('good', 'test', {
|
|
489
|
-
tmpCreated (gottmpfile) {
|
|
490
|
-
return Promise.resolve()
|
|
491
|
-
}
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
tmpfile = undefined
|
|
495
|
-
await t.rejects(writeFileAtomic('noopen', 'test', {
|
|
496
|
-
tmpCreated (gottmpfile) {
|
|
497
|
-
tmpfile = gottmpfile
|
|
498
|
-
}
|
|
499
|
-
}))
|
|
500
|
-
t.equal(tmpfile, undefined, 'tmpCreated is not called on open failure')
|
|
501
|
-
|
|
502
|
-
await t.rejects(writeFileAtomic('nowrite', 'test', {
|
|
503
|
-
tmpCreated (gottmpfile) {
|
|
504
|
-
tmpfile = gottmpfile
|
|
505
|
-
}
|
|
506
|
-
}))
|
|
507
|
-
t.match(tmpfile, /^nowrite\.tmp-\w+$/, 'tmpCreated is called if failure is after open')
|
|
508
|
-
})
|
package/test/concurrency.cjs
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
process.setMaxListeners(1000000);
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const {test} = require('tap')
|
|
5
|
-
const requireInject = require('require-inject')
|
|
6
|
-
|
|
7
|
-
// defining mock for fs so its functions can be modified
|
|
8
|
-
const fsMock = Object.assign ( {}, fs, {
|
|
9
|
-
/* ASYNC */
|
|
10
|
-
mkdir (filename, opts, cb) {
|
|
11
|
-
return cb(null);
|
|
12
|
-
},
|
|
13
|
-
realpath (filename, cb) {
|
|
14
|
-
return cb(null, filename)
|
|
15
|
-
},
|
|
16
|
-
open (tmpfile, options, mode, cb) {
|
|
17
|
-
if (/noopen/.test(tmpfile)) return cb(new Error('ENOOPEN'))
|
|
18
|
-
cb(null, tmpfile)
|
|
19
|
-
},
|
|
20
|
-
write (fd) {
|
|
21
|
-
const cb = arguments[arguments.length - 1]
|
|
22
|
-
if (/nowrite/.test(fd)) return cb(new Error('ENOWRITE'))
|
|
23
|
-
cb()
|
|
24
|
-
},
|
|
25
|
-
fsync (fd, cb) {
|
|
26
|
-
if (/nofsync/.test(fd)) return cb(new Error('ENOFSYNC'))
|
|
27
|
-
cb()
|
|
28
|
-
},
|
|
29
|
-
close (fd, cb) {
|
|
30
|
-
cb()
|
|
31
|
-
},
|
|
32
|
-
chown (tmpfile, uid, gid, cb) {
|
|
33
|
-
if (/nochown/.test(tmpfile)) return cb(new Error('ENOCHOWN'))
|
|
34
|
-
cb()
|
|
35
|
-
},
|
|
36
|
-
chmod (tmpfile, mode, cb) {
|
|
37
|
-
if (/nochmod/.test(tmpfile)) return cb(new Error('ENOCHMOD'))
|
|
38
|
-
cb()
|
|
39
|
-
},
|
|
40
|
-
rename (tmpfile, filename, cb) {
|
|
41
|
-
if (/norename/.test(tmpfile)) return cb(new Error('ENORENAME'))
|
|
42
|
-
cb()
|
|
43
|
-
},
|
|
44
|
-
unlink (tmpfile, cb) {
|
|
45
|
-
if (/nounlink/.test(tmpfile)) return cb(new Error('ENOUNLINK'))
|
|
46
|
-
cb()
|
|
47
|
-
},
|
|
48
|
-
stat (tmpfile, cb) {
|
|
49
|
-
if (/nostat/.test(tmpfile)) return cb(new Error('ENOSTAT'))
|
|
50
|
-
cb()
|
|
51
|
-
},
|
|
52
|
-
/* SYNC */
|
|
53
|
-
mkdirSync (filename) {},
|
|
54
|
-
realpathSync (filename, cb) {
|
|
55
|
-
return filename
|
|
56
|
-
},
|
|
57
|
-
openSync (tmpfile, options) {
|
|
58
|
-
if (/noopen/.test(tmpfile)) throw new Error('ENOOPEN')
|
|
59
|
-
return tmpfile
|
|
60
|
-
},
|
|
61
|
-
writeSync (fd) {
|
|
62
|
-
if (/nowrite/.test(fd)) throw new Error('ENOWRITE')
|
|
63
|
-
},
|
|
64
|
-
fsyncSync (fd) {
|
|
65
|
-
if (/nofsync/.test(fd)) throw new Error('ENOFSYNC')
|
|
66
|
-
},
|
|
67
|
-
closeSync () {},
|
|
68
|
-
chownSync (tmpfile, uid, gid) {
|
|
69
|
-
if (/nochown/.test(tmpfile)) throw new Error('ENOCHOWN')
|
|
70
|
-
},
|
|
71
|
-
chmodSync (tmpfile, mode) {
|
|
72
|
-
if (/nochmod/.test(tmpfile)) throw new Error('ENOCHMOD')
|
|
73
|
-
},
|
|
74
|
-
renameSync (tmpfile, filename) {
|
|
75
|
-
if (/norename/.test(tmpfile)) throw new Error('ENORENAME')
|
|
76
|
-
},
|
|
77
|
-
unlinkSync (tmpfile) {
|
|
78
|
-
if (/nounlink/.test(tmpfile)) throw new Error('ENOUNLINK')
|
|
79
|
-
},
|
|
80
|
-
statSync (tmpfile) {
|
|
81
|
-
if (/nostat/.test(tmpfile)) throw new Error('ENOSTAT')
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
86
|
-
|
|
87
|
-
// preserve original functions
|
|
88
|
-
const oldRealPath = fsMock.realpath
|
|
89
|
-
const oldRename = fsMock.rename
|
|
90
|
-
|
|
91
|
-
test('ensure writes to the same file are serial', t => {
|
|
92
|
-
let fileInUse = false
|
|
93
|
-
const ops = 5 // count for how many concurrent write ops to request
|
|
94
|
-
t.plan(ops * 3 + 3)
|
|
95
|
-
fsMock.realpath = (...args) => {
|
|
96
|
-
t.notOk(fileInUse, 'file not in use')
|
|
97
|
-
fileInUse = true
|
|
98
|
-
oldRealPath(...args)
|
|
99
|
-
}
|
|
100
|
-
fsMock.rename = (...args) => {
|
|
101
|
-
t.ok(fileInUse, 'file in use')
|
|
102
|
-
fileInUse = false
|
|
103
|
-
oldRename(...args)
|
|
104
|
-
}
|
|
105
|
-
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
106
|
-
for (let i = 0; i < ops; i++) {
|
|
107
|
-
writeFileAtomic('test', 'test', err => {
|
|
108
|
-
if (err) t.fail(err)
|
|
109
|
-
else t.pass('wrote without error')
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
writeFileAtomic('test', 'test', err => {
|
|
114
|
-
if (err) t.fail(err)
|
|
115
|
-
else t.pass('successive writes after delay')
|
|
116
|
-
})
|
|
117
|
-
}, 500)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
test('allow write to multiple files in parallel, but same file writes are serial', t => {
|
|
121
|
-
const filesInUse = []
|
|
122
|
-
const ops = 5
|
|
123
|
-
let wasParallel = false
|
|
124
|
-
fsMock.realpath = (filename, ...args) => {
|
|
125
|
-
filesInUse.push(filename)
|
|
126
|
-
const firstOccurence = filesInUse.indexOf(filename)
|
|
127
|
-
t.equal(filesInUse.indexOf(filename, firstOccurence + 1), -1, 'serial writes') // check for another occurence after the first
|
|
128
|
-
if (filesInUse.length > 1) wasParallel = true // remember that a parallel operation took place
|
|
129
|
-
oldRealPath(filename, ...args)
|
|
130
|
-
}
|
|
131
|
-
fsMock.rename = (filename, ...args) => {
|
|
132
|
-
filesInUse.splice(filesInUse.indexOf(filename), 1)
|
|
133
|
-
oldRename(filename, ...args)
|
|
134
|
-
}
|
|
135
|
-
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
136
|
-
t.plan(ops * 2 * 2 + 1)
|
|
137
|
-
let opCount = 0
|
|
138
|
-
for (let i = 0; i < ops; i++) {
|
|
139
|
-
writeFileAtomic('test', 'test', err => {
|
|
140
|
-
if (err) t.fail(err, 'wrote without error')
|
|
141
|
-
else t.pass('wrote without error')
|
|
142
|
-
})
|
|
143
|
-
writeFileAtomic('test2', 'test', err => {
|
|
144
|
-
opCount++
|
|
145
|
-
if (opCount === ops) t.ok(wasParallel, 'parallel writes')
|
|
146
|
-
|
|
147
|
-
if (err) t.fail(err, 'wrote without error')
|
|
148
|
-
else t.pass('wrote without error')
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
})
|