atomically 1.7.0 → 2.0.0
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/.editorconfig +0 -3
- package/README.md +3 -3
- package/dist/{consts.d.ts → constants.d.ts} +6 -4
- package/dist/constants.js +19 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +124 -105
- package/dist/types.d.ts +7 -7
- package/dist/types.js +2 -3
- package/dist/utils/lang.d.ts +6 -6
- package/dist/utils/lang.js +14 -14
- package/dist/utils/scheduler.d.ts +1 -1
- package/dist/utils/scheduler.js +4 -5
- package/dist/utils/temp.d.ts +1 -1
- package/dist/utils/temp.js +18 -15
- package/{LICENSE → license} +0 -0
- package/package.json +22 -32
- package/src/{consts.ts → constants.ts} +12 -4
- package/src/index.ts +153 -93
- package/src/types.ts +9 -9
- package/src/utils/lang.ts +18 -12
- package/src/utils/scheduler.ts +5 -3
- package/src/utils/temp.ts +18 -13
- package/tasks/benchmark.js +16 -12
- package/test/{basic.js → basic.cjs} +47 -49
- package/test/{concurrency.js → concurrency.cjs} +6 -8
- package/test/{integration.js → integration.cjs} +44 -46
- package/tsconfig.json +1 -26
- package/.nvmrc +0 -1
- package/dist/consts.js +0 -28
- package/dist/utils/attemptify.d.ts +0 -4
- package/dist/utils/attemptify.js +0 -25
- package/dist/utils/fs.d.ts +0 -34
- package/dist/utils/fs.js +0 -42
- package/dist/utils/fs_handlers.d.ts +0 -7
- package/dist/utils/fs_handlers.js +0 -28
- package/dist/utils/retryify.d.ts +0 -4
- package/dist/utils/retryify.js +0 -45
- package/dist/utils/retryify_queue.d.ts +0 -15
- package/dist/utils/retryify_queue.js +0 -58
- package/src/utils/attemptify.ts +0 -42
- package/src/utils/fs.ts +0 -51
- package/src/utils/fs_handlers.ts +0 -45
- package/src/utils/retryify.ts +0 -78
- package/src/utils/retryify_queue.ts +0 -95
package/src/utils/temp.ts
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'stubborn-fs';
|
|
6
|
+
import whenExit from 'when-exit';
|
|
7
|
+
import {LIMIT_BASENAME_LENGTH} from '../constants';
|
|
8
|
+
import type {Disposer} from '../types';
|
|
8
9
|
|
|
9
|
-
/*
|
|
10
|
+
/* MAIN */
|
|
10
11
|
|
|
11
12
|
//TODO: Maybe publish this as a standalone package
|
|
12
13
|
|
|
13
14
|
const Temp = {
|
|
14
15
|
|
|
16
|
+
/* VARIABLES */
|
|
17
|
+
|
|
15
18
|
store: <Record<string, boolean>> {}, // filePath => purge
|
|
16
19
|
|
|
20
|
+
/* API */
|
|
21
|
+
|
|
17
22
|
create: ( filePath: string ): string => {
|
|
18
23
|
|
|
19
|
-
const randomness = `000000${Math.floor ( Math.random () * 16777215 ).toString ( 16 )}`.slice ( -6 )
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const randomness = `000000${Math.floor ( Math.random () * 16777215 ).toString ( 16 )}`.slice ( -6 ); // 6 random-enough hex characters
|
|
25
|
+
const timestamp = Date.now ().toString ().slice ( -10 ); // 10 precise timestamp digits
|
|
26
|
+
const prefix = 'tmp-';
|
|
27
|
+
const suffix = `.${prefix}${timestamp}${randomness}`;
|
|
28
|
+
const tempPath = `${filePath}${suffix}`;
|
|
24
29
|
|
|
25
30
|
return tempPath;
|
|
26
31
|
|
|
@@ -46,7 +51,7 @@ const Temp = {
|
|
|
46
51
|
|
|
47
52
|
delete Temp.store[filePath];
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
fs.attempt.unlink ( filePath );
|
|
50
55
|
|
|
51
56
|
},
|
|
52
57
|
|
|
@@ -56,7 +61,7 @@ const Temp = {
|
|
|
56
61
|
|
|
57
62
|
delete Temp.store[filePath];
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
fs.attempt.unlinkSync ( filePath );
|
|
60
65
|
|
|
61
66
|
},
|
|
62
67
|
|
|
@@ -90,7 +95,7 @@ const Temp = {
|
|
|
90
95
|
|
|
91
96
|
/* INIT */
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
whenExit ( Temp.purgeSyncAll ); // Ensuring purgeable temp files are purged on exit
|
|
94
99
|
|
|
95
100
|
/* EXPORT */
|
|
96
101
|
|
package/tasks/benchmark.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import {randomUUID} from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {setTimeout as delay} from 'node:timers/promises';
|
|
9
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
10
|
+
import {writeFile, writeFileSync} from '../dist/index.js';
|
|
10
11
|
|
|
11
|
-
/*
|
|
12
|
+
/* MAIN */
|
|
12
13
|
|
|
13
|
-
const TEMP = os.tmpdir ()
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const TEMP = os.tmpdir ();
|
|
15
|
+
const UUID = randomUUID ();
|
|
16
|
+
const DST = i => path.join ( TEMP, `atomically-${UUID}-temp-${i}.txt` );
|
|
17
|
+
const ITERATIONS = 250;
|
|
16
18
|
|
|
17
19
|
const runSingleAsync = async ( name, fn, buffer, options ) => {
|
|
18
20
|
console.time ( name );
|
|
@@ -37,7 +39,8 @@ const runAllDummy = () => { // Preparation run
|
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
const runAllAsync = async ( name, buffer ) => {
|
|
40
|
-
await runSingleAsync ( `${name} -> async -> write-file-atomic`, writeFileAtomic, buffer );
|
|
42
|
+
await runSingleAsync ( `${name} -> async -> write-file-atomic`, writeFileAtomic, buffer, { mode: 0o666 } );
|
|
43
|
+
await runSingleAsync ( `${name} -> async -> write-file-atomic (faster)`, writeFileAtomic, buffer );
|
|
41
44
|
await runSingleAsync ( `${name} -> async -> write-file-atomic (fastest)`, writeFileAtomic, buffer, { fsync: false } );
|
|
42
45
|
await runSingleAsync ( `${name} -> async -> atomically`, writeFile, buffer );
|
|
43
46
|
await runSingleAsync ( `${name} -> async -> atomically (faster)`, writeFile, buffer, { mode: false, chown: false, fsyncWait: false } );
|
|
@@ -45,7 +48,8 @@ const runAllAsync = async ( name, buffer ) => {
|
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
const runAllSync = ( name, buffer ) => {
|
|
48
|
-
runSingleSync ( `${name} -> sync -> write-file-atomic`, writeFileAtomic.sync, buffer );
|
|
51
|
+
runSingleSync ( `${name} -> sync -> write-file-atomic`, writeFileAtomic.sync, buffer, { mode: 0o666 } );
|
|
52
|
+
runSingleSync ( `${name} -> sync -> write-file-atomic (faster)`, writeFileAtomic.sync, buffer );
|
|
49
53
|
runSingleSync ( `${name} -> sync -> write-file-atomic (fastest)`, writeFileAtomic.sync, buffer, { fsync: false } );
|
|
50
54
|
runSingleSync ( `${name} -> sync -> atomically`, writeFileSync, buffer );
|
|
51
55
|
runSingleSync ( `${name} -> sync -> atomically (faster)`, writeFileSync, buffer, { mode: false, chown: false, fsyncWait: false } );
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
process.setMaxListeners(1000000);
|
|
4
2
|
|
|
5
|
-
const _ = require('lodash')
|
|
6
3
|
const fs = require('fs')
|
|
7
4
|
const os = require('os')
|
|
8
5
|
const path = require('path')
|
|
@@ -117,7 +114,7 @@ const fsMock = Object.assign ( {}, fs, {
|
|
|
117
114
|
const makeUnstableAsyncFn = function () {
|
|
118
115
|
return function () {
|
|
119
116
|
if ( Math.random () <= .75 ) {
|
|
120
|
-
const code =
|
|
117
|
+
const code = ['EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM'].sort ( () => Math.random () - .5 )[0];
|
|
121
118
|
throw createErr ( code );
|
|
122
119
|
}
|
|
123
120
|
return arguments[arguments.length -1](null, arguments[0]);
|
|
@@ -127,7 +124,7 @@ const makeUnstableAsyncFn = function () {
|
|
|
127
124
|
const makeUnstableSyncFn = function ( fn ) {
|
|
128
125
|
return function () {
|
|
129
126
|
if ( Math.random () <= .75 ) {
|
|
130
|
-
const code =
|
|
127
|
+
const code = ['EMFILE', 'ENFILE', 'EAGAIN', 'EBUSY', 'EACCESS', 'EPERM'].sort ( () => Math.random () - .5 )[0];
|
|
131
128
|
throw createErr ( code );
|
|
132
129
|
}
|
|
133
130
|
return fn.apply(undefined, arguments)
|
|
@@ -140,14 +137,14 @@ const fsMockUnstable = Object.assign ( {}, fsMock, {
|
|
|
140
137
|
fsync: makeUnstableAsyncFn (),
|
|
141
138
|
close: makeUnstableAsyncFn (),
|
|
142
139
|
rename: makeUnstableAsyncFn (),
|
|
143
|
-
openSync: makeUnstableSyncFn (
|
|
144
|
-
writeSync: makeUnstableSyncFn (
|
|
145
|
-
fsyncSync: makeUnstableSyncFn (
|
|
146
|
-
closeSync: makeUnstableSyncFn (
|
|
147
|
-
renameSync: makeUnstableSyncFn (
|
|
140
|
+
openSync: makeUnstableSyncFn ( x => x ),
|
|
141
|
+
writeSync: makeUnstableSyncFn ( () => {} ),
|
|
142
|
+
fsyncSync: makeUnstableSyncFn ( () => {} ),
|
|
143
|
+
closeSync: makeUnstableSyncFn ( () => {} ),
|
|
144
|
+
renameSync: makeUnstableSyncFn ( () => {} )
|
|
148
145
|
});
|
|
149
146
|
|
|
150
|
-
const {writeFile: writeFileAtomic, writeFileSync: writeFileAtomicSync} = requireInject('
|
|
147
|
+
const {writeFile: writeFileAtomic, writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
151
148
|
|
|
152
149
|
test('async tests', t => {
|
|
153
150
|
t.plan(2)
|
|
@@ -176,34 +173,34 @@ test('async tests', t => {
|
|
|
176
173
|
t.notOk(err)
|
|
177
174
|
})
|
|
178
175
|
writeFileAtomic('noopen', 'test', err => {
|
|
179
|
-
t.
|
|
176
|
+
t.equal(err.message, 'ENOOPEN', 'fs.open failures propagate')
|
|
180
177
|
})
|
|
181
178
|
writeFileAtomic('nowrite', 'test', err => {
|
|
182
|
-
t.
|
|
179
|
+
t.equal(err.message, 'ENOWRITE', 'fs.writewrite failures propagate')
|
|
183
180
|
})
|
|
184
181
|
writeFileAtomic('nowrite', Buffer.from('test', 'utf8'), err => {
|
|
185
|
-
t.
|
|
182
|
+
t.equal(err.message, 'ENOWRITE', 'fs.writewrite failures propagate for buffers')
|
|
186
183
|
})
|
|
187
184
|
writeFileAtomic('nochown', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
188
|
-
t.
|
|
185
|
+
t.equal(err.message, 'ENOCHOWN', 'Chown failures propagate')
|
|
189
186
|
})
|
|
190
187
|
writeFileAtomic('nochown', 'test', err => {
|
|
191
188
|
t.notOk(err, 'No attempt to chown when no uid/gid passed in')
|
|
192
189
|
})
|
|
193
190
|
writeFileAtomic('nochmod', 'test', { mode: parseInt('741', 8) }, err => {
|
|
194
|
-
t.
|
|
191
|
+
t.equal(err.message, 'ENOCHMOD', 'Chmod failures propagate')
|
|
195
192
|
})
|
|
196
193
|
writeFileAtomic('nofsyncopt', 'test', { fsync: false }, err => {
|
|
197
194
|
t.notOk(err, 'fsync skipped if options.fsync is false')
|
|
198
195
|
})
|
|
199
196
|
writeFileAtomic('norename', 'test', err => {
|
|
200
|
-
t.
|
|
197
|
+
t.equal(err.message, 'ENORENAME', 'Rename errors propagate')
|
|
201
198
|
})
|
|
202
199
|
writeFileAtomic('norename nounlink', 'test', err => {
|
|
203
|
-
t.
|
|
200
|
+
t.equal(err.message, 'ENORENAME', 'Failure to unlink the temp file does not clobber the original error')
|
|
204
201
|
})
|
|
205
202
|
writeFileAtomic('nofsync', 'test', err => {
|
|
206
|
-
t.
|
|
203
|
+
t.equal(err.message, 'ENOFSYNC', 'Fsync failures propagate')
|
|
207
204
|
})
|
|
208
205
|
writeFileAtomic('enosys', 'test', err => {
|
|
209
206
|
t.notOk(err, 'No errors on ENOSYS')
|
|
@@ -223,13 +220,13 @@ test('async tests', t => {
|
|
|
223
220
|
const optionsImmutable = {};
|
|
224
221
|
writeFileAtomic('statful', 'test', optionsImmutable, err => {
|
|
225
222
|
t.notOk(err);
|
|
226
|
-
t.
|
|
223
|
+
t.same(optionsImmutable, {});
|
|
227
224
|
});
|
|
228
225
|
const schedule = filePath => {
|
|
229
|
-
t.
|
|
226
|
+
t.equal(filePath, 'good');
|
|
230
227
|
return new Promise ( resolve => {
|
|
231
228
|
resolve ( () => {
|
|
232
|
-
t.
|
|
229
|
+
t.equal(true,true);
|
|
233
230
|
});
|
|
234
231
|
});
|
|
235
232
|
};
|
|
@@ -237,12 +234,12 @@ test('async tests', t => {
|
|
|
237
234
|
t.notOk(err);
|
|
238
235
|
});
|
|
239
236
|
const tmpCreate = filePath => `.${filePath}.custom`;
|
|
240
|
-
const tmpCreated = filePath => t.
|
|
237
|
+
const tmpCreated = filePath => t.equal(filePath, '.good.custom' );
|
|
241
238
|
writeFileAtomic('good','test', {tmpCreate, tmpCreated}, err => {
|
|
242
239
|
t.notOk(err)
|
|
243
240
|
})
|
|
244
241
|
const longPath = path.join(os.tmpdir(),'.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt');
|
|
245
|
-
const {writeFile: writeFileAtomicNative} = requireInject('
|
|
242
|
+
const {writeFile: writeFileAtomicNative} = requireInject('./atomically.cjs', { fs });
|
|
246
243
|
writeFileAtomicNative(longPath,'test', err => {
|
|
247
244
|
t.notOk(err)
|
|
248
245
|
})
|
|
@@ -258,7 +255,7 @@ test('async tests', t => {
|
|
|
258
255
|
t.teardown(() => {
|
|
259
256
|
process.getuid = getuid
|
|
260
257
|
})
|
|
261
|
-
const {writeFile: writeFileAtomic} = requireInject('
|
|
258
|
+
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
262
259
|
t.plan(2)
|
|
263
260
|
writeFileAtomic('einval', 'test', { chown: { uid: 100, gid: 100 } }, err => {
|
|
264
261
|
t.match(err, { code: 'EINVAL' })
|
|
@@ -271,12 +268,12 @@ test('async tests', t => {
|
|
|
271
268
|
|
|
272
269
|
test('unstable async tests', t => {
|
|
273
270
|
t.plan(2);
|
|
274
|
-
const {writeFile: writeFileAtomic} = requireInject('
|
|
271
|
+
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
275
272
|
writeFileAtomic('good', 'test', err => {
|
|
276
273
|
t.notOk(err, 'No errors occur when retryable errors are thrown')
|
|
277
274
|
})
|
|
278
275
|
writeFileAtomic('good', 'test', { timeout: 0 }, err => {
|
|
279
|
-
t.
|
|
276
|
+
t.equal(!!err.code, true, 'Retrying can be disabled')
|
|
280
277
|
})
|
|
281
278
|
});
|
|
282
279
|
|
|
@@ -293,12 +290,12 @@ test('sync tests', t => {
|
|
|
293
290
|
const throws = function (t, shouldthrow, msg, todo) {
|
|
294
291
|
let err
|
|
295
292
|
try { todo() } catch (e) { err = e }
|
|
296
|
-
t.
|
|
293
|
+
t.equal(shouldthrow, err.message, msg)
|
|
297
294
|
}
|
|
298
295
|
const noexception = function (t, msg, todo) {
|
|
299
296
|
let err
|
|
300
297
|
try { todo() } catch (e) { err = e }
|
|
301
|
-
t.
|
|
298
|
+
t.error(err, msg)
|
|
302
299
|
}
|
|
303
300
|
let tmpfile
|
|
304
301
|
|
|
@@ -334,7 +331,7 @@ test('sync tests', t => {
|
|
|
334
331
|
}
|
|
335
332
|
})
|
|
336
333
|
})
|
|
337
|
-
t.
|
|
334
|
+
t.equal(tmpfile, undefined, 'tmpCreated not called for open failure')
|
|
338
335
|
|
|
339
336
|
throws(t, 'ENOWRITE', 'fs.writeSync failures propagate', () => {
|
|
340
337
|
writeFileAtomicSync('nowrite', 'test', {
|
|
@@ -386,44 +383,44 @@ test('sync tests', t => {
|
|
|
386
383
|
noexception(t, 'options are immutable', () => {
|
|
387
384
|
writeFileAtomicSync('statful', 'test', optionsImmutable)
|
|
388
385
|
})
|
|
389
|
-
t.
|
|
386
|
+
t.same(optionsImmutable, {});
|
|
390
387
|
const tmpCreate = filePath => `.${filePath}.custom`;
|
|
391
|
-
const tmpCreated = filePath => t.
|
|
388
|
+
const tmpCreated = filePath => t.equal(filePath, '.good.custom' );
|
|
392
389
|
noexception(t, 'custom temp creator', () => {
|
|
393
390
|
writeFileAtomicSync('good', 'test', {tmpCreate, tmpCreated})
|
|
394
391
|
})
|
|
395
392
|
const path0 = path.join(os.tmpdir(),'atomically-test-0');
|
|
396
393
|
const tmpPath0 = path0 + '.temp';
|
|
397
394
|
noexception(t, 'temp files are purged on success', () => {
|
|
398
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
395
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
399
396
|
writeFileAtomicSync(path0, 'test', {tmpCreate: () => tmpPath0})
|
|
400
397
|
})
|
|
401
|
-
t.
|
|
402
|
-
t.
|
|
398
|
+
t.equal(true,fs.existsSync(path0));
|
|
399
|
+
t.equal(false,fs.existsSync(tmpPath0));
|
|
403
400
|
const path1 = path.join(os.tmpdir(),'atomically-test-norename-1');
|
|
404
401
|
const tmpPath1 = path1 + '.temp';
|
|
405
402
|
throws(t, 'ENORENAME', 'temp files are purged on error', () => {
|
|
406
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
403
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: Object.assign ( {}, fs, { renameSync: fsMock.renameSync })});
|
|
407
404
|
writeFileAtomicSync(path1, 'test', {tmpCreate: () => tmpPath1})
|
|
408
405
|
})
|
|
409
|
-
t.
|
|
410
|
-
t.
|
|
406
|
+
t.equal(false,fs.existsSync(path1));
|
|
407
|
+
t.equal(false,fs.existsSync(tmpPath1));
|
|
411
408
|
const path2 = path.join(os.tmpdir(),'atomically-test-norename-2');
|
|
412
409
|
const tmpPath2 = path2 + '.temp';
|
|
413
410
|
throws(t, 'ENORENAME', 'temp files can also not be purged on error', () => {
|
|
414
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
411
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: Object.assign ( {}, fs, { renameSync: fsMock.renameSync })});
|
|
415
412
|
writeFileAtomicSync(path2, 'test', {tmpCreate: () => tmpPath2,tmpPurge: false})
|
|
416
413
|
})
|
|
417
|
-
t.
|
|
418
|
-
t.
|
|
414
|
+
t.equal(false,fs.existsSync(path2));
|
|
415
|
+
t.equal(true,fs.existsSync(tmpPath2));
|
|
419
416
|
const longPath = path.join(os.tmpdir(),'.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt');
|
|
420
417
|
noexception(t, 'temp files are truncated', () => {
|
|
421
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
418
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
422
419
|
writeFileAtomicSync(longPath, 'test')
|
|
423
420
|
})
|
|
424
421
|
const pathMissingFolders = path.join(os.tmpdir(),String(Math.random()),String(Math.random()),String(Math.random()),'foo.txt');
|
|
425
422
|
noexception(t, 'parent folders are created', () => {
|
|
426
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
423
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs });
|
|
427
424
|
writeFileAtomicSync(pathMissingFolders, 'test')
|
|
428
425
|
})
|
|
429
426
|
})
|
|
@@ -434,7 +431,7 @@ test('sync tests', t => {
|
|
|
434
431
|
t.teardown(() => {
|
|
435
432
|
process.getuid = getuid
|
|
436
433
|
})
|
|
437
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
434
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
438
435
|
t.plan(2)
|
|
439
436
|
throws(t, 'EINVAL', 'Chown error as root user', () => {
|
|
440
437
|
writeFileAtomicSync('einval', 'test', { chown: { uid: 100, gid: 100 } })
|
|
@@ -451,21 +448,22 @@ test('unstable sync tests', t => {
|
|
|
451
448
|
const throws = function (t, msg, todo) {
|
|
452
449
|
let err
|
|
453
450
|
try { todo() } catch (e) { err = e }
|
|
454
|
-
t.
|
|
451
|
+
t.equal(!!err.code, true, msg)
|
|
455
452
|
}
|
|
453
|
+
|
|
456
454
|
const noexception = function (t, msg, todo) {
|
|
457
455
|
let err
|
|
458
456
|
try { todo() } catch (e) { err = e }
|
|
459
|
-
t.
|
|
457
|
+
t.error(err, msg)
|
|
460
458
|
}
|
|
461
459
|
|
|
462
460
|
noexception(t, 'No errors occur when retryable errors are thrown', () => {
|
|
463
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
461
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
464
462
|
writeFileAtomicSync('good', 'test')
|
|
465
463
|
})
|
|
466
464
|
|
|
467
465
|
throws(t, 'retrying can be disabled', () => {
|
|
468
|
-
const {writeFileSync: writeFileAtomicSync} = requireInject('
|
|
466
|
+
const {writeFileSync: writeFileAtomicSync} = requireInject('./atomically.cjs', { fs: fsMockUnstable });
|
|
469
467
|
writeFileAtomicSync('good', 'test', { timeout: 0 })
|
|
470
468
|
})
|
|
471
469
|
});
|
|
@@ -499,7 +497,7 @@ test('promises', async t => {
|
|
|
499
497
|
tmpfile = gottmpfile
|
|
500
498
|
}
|
|
501
499
|
}))
|
|
502
|
-
t.
|
|
500
|
+
t.equal(tmpfile, undefined, 'tmpCreated is not called on open failure')
|
|
503
501
|
|
|
504
502
|
await t.rejects(writeFileAtomic('nowrite', 'test', {
|
|
505
503
|
tmpCreated (gottmpfile) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
process.setMaxListeners(1000000);
|
|
4
2
|
|
|
5
3
|
const fs = require('fs')
|
|
@@ -84,7 +82,7 @@ const fsMock = Object.assign ( {}, fs, {
|
|
|
84
82
|
}
|
|
85
83
|
})
|
|
86
84
|
|
|
87
|
-
const {writeFile: writeFileAtomic} = requireInject('
|
|
85
|
+
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
88
86
|
|
|
89
87
|
// preserve original functions
|
|
90
88
|
const oldRealPath = fsMock.realpath
|
|
@@ -95,16 +93,16 @@ test('ensure writes to the same file are serial', t => {
|
|
|
95
93
|
const ops = 5 // count for how many concurrent write ops to request
|
|
96
94
|
t.plan(ops * 3 + 3)
|
|
97
95
|
fsMock.realpath = (...args) => {
|
|
98
|
-
t.
|
|
96
|
+
t.notOk(fileInUse, 'file not in use')
|
|
99
97
|
fileInUse = true
|
|
100
98
|
oldRealPath(...args)
|
|
101
99
|
}
|
|
102
100
|
fsMock.rename = (...args) => {
|
|
103
|
-
t.
|
|
101
|
+
t.ok(fileInUse, 'file in use')
|
|
104
102
|
fileInUse = false
|
|
105
103
|
oldRename(...args)
|
|
106
104
|
}
|
|
107
|
-
const {writeFile: writeFileAtomic} = requireInject('
|
|
105
|
+
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
108
106
|
for (let i = 0; i < ops; i++) {
|
|
109
107
|
writeFileAtomic('test', 'test', err => {
|
|
110
108
|
if (err) t.fail(err)
|
|
@@ -134,7 +132,7 @@ test('allow write to multiple files in parallel, but same file writes are serial
|
|
|
134
132
|
filesInUse.splice(filesInUse.indexOf(filename), 1)
|
|
135
133
|
oldRename(filename, ...args)
|
|
136
134
|
}
|
|
137
|
-
const {writeFile: writeFileAtomic} = requireInject('
|
|
135
|
+
const {writeFile: writeFileAtomic} = requireInject('./atomically.cjs', { fs: fsMock });
|
|
138
136
|
t.plan(ops * 2 * 2 + 1)
|
|
139
137
|
let opCount = 0
|
|
140
138
|
for (let i = 0; i < ops; i++) {
|
|
@@ -144,7 +142,7 @@ test('allow write to multiple files in parallel, but same file writes are serial
|
|
|
144
142
|
})
|
|
145
143
|
writeFileAtomic('test2', 'test', err => {
|
|
146
144
|
opCount++
|
|
147
|
-
if (opCount === ops) t.
|
|
145
|
+
if (opCount === ops) t.ok(wasParallel, 'parallel writes')
|
|
148
146
|
|
|
149
147
|
if (err) t.fail(err, 'wrote without error')
|
|
150
148
|
else t.pass('wrote without error')
|