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.
Files changed (33) hide show
  1. package/.nyc_output/09ad542c-5435-4970-ae9c-d3aca17cc8e0.json +1 -0
  2. package/.nyc_output/5c74a299-649e-4bcb-894e-d3d7bc61ba96.json +1 -0
  3. package/.nyc_output/aec5ef7b-c224-4fa5-a830-b12bd1873072.json +1 -0
  4. package/.nyc_output/e5566b61-47b7-4c05-8e8b-36c7f809df3e.json +1 -0
  5. package/.nyc_output/f6bca65a-d147-4d4d-9fbf-a307181ea52f.json +1 -0
  6. package/.nyc_output/processinfo/09ad542c-5435-4970-ae9c-d3aca17cc8e0.json +1 -0
  7. package/.nyc_output/processinfo/5c74a299-649e-4bcb-894e-d3d7bc61ba96.json +1 -0
  8. package/.nyc_output/processinfo/aec5ef7b-c224-4fa5-a830-b12bd1873072.json +1 -0
  9. package/.nyc_output/processinfo/e5566b61-47b7-4c05-8e8b-36c7f809df3e.json +1 -0
  10. package/.nyc_output/processinfo/f6bca65a-d147-4d4d-9fbf-a307181ea52f.json +1 -0
  11. package/.nyc_output/processinfo/index.json +1 -0
  12. package/dist/constants.d.ts +2 -2
  13. package/dist/constants.js +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +25 -21
  16. package/dist/types.d.ts +0 -1
  17. package/dist/utils/lang.d.ts +2 -2
  18. package/dist/utils/scheduler.d.ts +1 -1
  19. package/dist/utils/temp.d.ts +1 -1
  20. package/dist/utils/temp.js +1 -1
  21. package/package.json +10 -9
  22. package/.editorconfig +0 -10
  23. package/src/constants.ts +0 -39
  24. package/src/index.ts +0 -330
  25. package/src/types.ts +0 -37
  26. package/src/utils/lang.ts +0 -34
  27. package/src/utils/scheduler.ts +0 -62
  28. package/src/utils/temp.ts +0 -102
  29. package/tasks/benchmark.js +0 -76
  30. package/test/basic.cjs +0 -508
  31. package/test/concurrency.cjs +0 -151
  32. package/test/integration.cjs +0 -289
  33. 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
- })
@@ -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
- })