| 'use strict'; | ||
| if (process.argv.join(' ').indexOf('qnit ') >= 0) return; | ||
| var fs = require('fs'); | ||
| var fputs = require('../'); | ||
| var setImmediate = global.setImmediate || process.nextTick; | ||
| var filename = '/tmp/test.out'; | ||
| var fp = fputs(fputs.FileWriter(filename, 'a'), { writeSize: 40960 }); | ||
| var str = new Array(200).join('x') + '\n'; | ||
| var nlines = 0; | ||
| console.time('100k fputs'); | ||
| repeatFor(100000, function(cb) { fp.fputs(str); cb() }, function(err) { | ||
| fp.fflush(function(err) { | ||
| console.timeEnd('100k fputs'); | ||
| fs.unlinkSync(filename); | ||
| }); | ||
| }) | ||
| // repeatFor adapted from minisql: | ||
| function repeatFor(n, proc, callback) { | ||
| var ix = 0, ncalls = 0; | ||
| (function _loop(err) { | ||
| if (err || n-- <= 0) return callback(err); | ||
| (ncalls++ > 100) ? setImmediate((++n, (ncalls = 0), _loop)) : proc(_loop, (ix++)); | ||
| })(); | ||
| } |
| // qfputs test | ||
| // Copyright (C) 2014 Andras Radics | ||
| // Licensed under the Apache License, Version 2.0 | ||
| var fs = require('fs'); | ||
| try { | ||
| var fse = require('fs-ext'); | ||
| } catch (e) { | ||
| } | ||
| var Fputs = require('../'); | ||
| var FileWriter = require('../lib/filewriter.js'); | ||
| var fromBuf = eval('parseInt(process.versions.node) >= 7 ? Buffer.from : Buffer'); | ||
| function uniqid( ) { | ||
| return Math.floor(Math.random() * 0x100000000).toString(16); | ||
| } | ||
| module.exports = { | ||
| setUp: function(cb) { | ||
| var tempfile = "/tmp/nodeunit-" + process.pid + ".tmp"; | ||
| var tempfile2 = "/tmp/nodeunit-" + process.pid + "-2.tmp"; | ||
| try { fs.unlinkSync(tempfile); } catch (e) {} | ||
| this.tempfile = tempfile; | ||
| this.tempfile2 = tempfile2; | ||
| this.mockWriter = { | ||
| written: [], | ||
| write: function(str, cb) { this.written.push("" + str); cb(); }, | ||
| fflush: function(cb) { cb(); }, | ||
| sync: function(cb) { cb(); }, | ||
| getContents: function() { return this.written.join(''); }, | ||
| }; | ||
| this.fileWriter = new Fputs.FileWriter(tempfile, "a"); | ||
| this.fileWriter.getContents = function() { return "" + fs.readFileSync(this.tempfile); } | ||
| this.writer = this.mockWriter; | ||
| this.fp = new Fputs(this.writer); | ||
| cb(); | ||
| }, | ||
| tearDown: function(cb) { | ||
| try { fs.unlinkSync(this.tempfile); } catch (e) {} | ||
| try { fs.unlinkSync(this.tempfile2); } catch (e) {} | ||
| cb(); | ||
| }, | ||
| 'package should be valid json': function(t) { | ||
| json = require('../package.json'); | ||
| t.done(); | ||
| }, | ||
| 'should expose FileWriter class': function(t) { | ||
| t.equal(Fputs.FileWriter, FileWriter); | ||
| t.done(); | ||
| }, | ||
| 'should expose FileWriter.renameFile class method': function(t) { | ||
| t.equal(Fputs.renameFile, FileWriter.renameFile); | ||
| t.done(); | ||
| }, | ||
| 'should expose FileWriter.renameFile as an instance method': function(t) { | ||
| t.equal(this.fileWriter.renameFile, FileWriter.renameFile); | ||
| t.done(); | ||
| }, | ||
| 'fputs should lazy create the logfile': function(t) { | ||
| var fp = new Fputs(this.tempfile); | ||
| t.expect(1); | ||
| try { | ||
| fs.statSync(this.tempfile); | ||
| t.ok(false, "logfile should not exist yet"); | ||
| } catch (err) { t.ok(true); } | ||
| t.done(); | ||
| }, | ||
| 'should create writable file with default write mode': function(t) { | ||
| var fp = new Fputs.FileWriter(this.tempfile); | ||
| var self = this; | ||
| fp.write("one\ntwo\n", function(err) { | ||
| t.ifError(err); | ||
| t.equals(fs.readFileSync(self.tempfile), "one\ntwo\n"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'fputs should write contents soon': function(t) { | ||
| var line = "test line " + uniqid() + "\n"; | ||
| this.fp.fputs(line); | ||
| var self = this; | ||
| setTimeout(function(err) { | ||
| t.ifError(err); | ||
| t.equals(self.writer.getContents(), line); | ||
| t.done(); | ||
| }, 10); | ||
| }, | ||
| 'fputs should not clobber write-only files on reopen': function(t) { | ||
| var line1 = "test line " + uniqid() + "\n"; | ||
| var line2 = "test line " + uniqid() + "\n"; | ||
| var self = this; | ||
| fp = new Fputs(new Fputs.FileWriter(self.tempfile, "w")); | ||
| fp.fputs(line1); | ||
| setTimeout(function() { | ||
| fp.fputs(line2); | ||
| fp.fflush(function(err) { | ||
| t.ifError(err); | ||
| t.equal(fs.readFileSync(self.tempfile), line1 + line2); | ||
| t.done(); | ||
| }); | ||
| }, 25); | ||
| }, | ||
| 'fputs should get newline terminated': function(t) { | ||
| var line = "test line " + uniqid(); | ||
| this.fp.fputs(line); | ||
| var self = this; | ||
| setTimeout(function(err) { | ||
| t.ifError(err); | ||
| t.equals(self.writer.getContents(), line + "\n"); | ||
| t.done(); | ||
| }, 10); | ||
| }, | ||
| 'fflush should write pending data': function(t) { | ||
| var line = "test line"; | ||
| this.fp.fputs(line); | ||
| var self = this; | ||
| t.expect(2); | ||
| this.fp.fflush(function(err) { | ||
| t.ifError(err); | ||
| var contents = self.writer.getContents(); | ||
| t.equals(contents, line + "\n"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'fflush should return write errors': function(t) { | ||
| writer = new Fputs.FileWriter("/nonesuch", "a"); | ||
| fp = new Fputs(writer); | ||
| fp.write("data"); | ||
| fp.fflush(function(err) { | ||
| t.ok(err instanceof Error); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'abort should discard unwritten data': function(t) { | ||
| t.expect(4); | ||
| this.fp.fputs("test 1\n"); | ||
| t.ok(this.fp._syncing); | ||
| t.ok(this.fp.datachunks[0]); | ||
| var self = this; | ||
| this.fp.abort(function(err) { | ||
| t.ifError(err); | ||
| var contents = self.writer.getContents(); | ||
| t.equals(contents, ""); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'setOnError handler should report errors before fflush callback runs': function(t) { | ||
| var writer = new Fputs.FileWriter("/nonesuch", "a"); | ||
| var fp = new Fputs(writer); | ||
| var error = null; | ||
| fp.setOnError(function(err) { error = err }); | ||
| fp.write("data"); | ||
| fp.fflush(function(err) { | ||
| t.ok(!err, "fflush callback should not return error"); | ||
| t.ok(error instanceof Error, "setOnError should have reported the error"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'getUnwrittenLength': { | ||
| 'should return unwritten chars for strings': function(t) { | ||
| var self = this; | ||
| this.fp.write("test\x81\n"); | ||
| t.equal(this.fp.getUnwrittenLength(), 6); | ||
| this.fp.write("test\x82\n"); | ||
| t.equal(this.fp.getUnwrittenLength(), 12); | ||
| this.fp.fflush(function(err) { | ||
| t.equal(self.fp.getUnwrittenLength(), 0); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'should return unwritten bytes for string followed by buffer': function(t) { | ||
| var self = this; | ||
| this.fp.write("test\x81\n"); | ||
| this.fp.write("test\x82\n"); | ||
| this.fp.write(fromBuf("test3\n")); | ||
| t.equal(this.fp.getUnwrittenLength(), 20); | ||
| t.done(); | ||
| }, | ||
| }, | ||
| 'drain should write pending data': function(t) { | ||
| this.fp.fputs("test 1\n"); | ||
| var self = this; | ||
| this.fp.drain(0, function(err) { | ||
| t.ifError(err); | ||
| t.equal(self.writer.getContents(), "test 1\n"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'drain should return write errors': function(t) { | ||
| writer = new Fputs.FileWriter("/nonesuch", "a"); | ||
| fp = new Fputs(writer); | ||
| fp.write("data"); | ||
| fp.drain(0, function(err) { | ||
| t.ok(err instanceof Error); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'constructor should accept a filename': function(t) { | ||
| var fp = new Fputs(this.tempfile); | ||
| fp.fputs("test"); | ||
| var self = this; | ||
| fp.fflush(function(err) { | ||
| t.ifError(err); | ||
| t.equals(fs.readFileSync(self.tempfile), "test\n"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'constructor should accept a writable object': function(t) { | ||
| t.expect(1); | ||
| var self = this; | ||
| writer = fs.createWriteStream(this.tempfile, {flags: "a"}); | ||
| var fp = new Fputs(writer); | ||
| fp.write("data"); | ||
| fp.fflush(function() { | ||
| var contents = "" + fs.readFileSync(self.tempfile); | ||
| t.equals(contents, "data"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'write': { | ||
| 'write should write contents, without newline': function(t) { | ||
| var line = "test line " + uniqid(); | ||
| this.fp.write(line); | ||
| var self = this; | ||
| this.fp.fflush(function() { | ||
| var contents = self.writer.getContents(); | ||
| t.equals(contents, line); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'write should take an optional callback': function(t) { | ||
| t.expect(1); | ||
| var self = this; | ||
| this.fp.write("test callback", function(err) { | ||
| t.ifError(err); | ||
| // wait for the write to finish so tempfile can be removed. | ||
| // Nodejs is async, the pending write can land in the next tests tempfile. | ||
| self.fp.fflush(function() { | ||
| t.done(); | ||
| }); | ||
| }); | ||
| }, | ||
| 'write should return true if buffer has room': function(t) { | ||
| var fp = this.fp; | ||
| var ok = fp.write("test"); | ||
| t.equal(ok, true); | ||
| t.done(); | ||
| }, | ||
| 'write should return false if buffer is full': function(t) { | ||
| var fp = this.fp; | ||
| var nleft = fp.highWaterMark; | ||
| while (nleft > 0) { | ||
| fp.write("xxxxxxxxxxxxxxxxxxxx"); | ||
| nleft -= 20; | ||
| } | ||
| var ok = fp.write("test"); | ||
| t.equal(ok, false); | ||
| t.done(); | ||
| }, | ||
| 'should write many lines of various sizes': function(t) { | ||
| var i, line = "", expect = ""; | ||
| for (i=0; i<2000; i++) { | ||
| line += "xxxx"; | ||
| expect += line + "\n"; | ||
| this.fp.fputs(line); | ||
| } | ||
| var tempfile = this.tempfile; | ||
| t.expect(2); | ||
| var self = this; | ||
| this.fp.fflush(function(err) { | ||
| t.ifError(err); | ||
| var contents = "" + self.writer.getContents(tempfile); | ||
| t.equals(contents, expect); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'should write buffers': function(t) { | ||
| var written = []; | ||
| var writer = { | ||
| write: function(s, cb) { written.push(s.toString()); if (cb) cb() } | ||
| } | ||
| var fp = new Fputs(writer, {writesize: 20}); | ||
| var expect = ""; | ||
| for (var i=0; i<100; i++) { | ||
| var line = "test " + i + "\n"; | ||
| fp.write(fromBuf(line)); | ||
| expect += line; | ||
| } | ||
| fp.fflush(function(err){ | ||
| t.ifError(err); | ||
| t.equal(written.join(''), expect); | ||
| t.ok(written.length > 25 && written.length < 50); | ||
| t.done(); | ||
| }) | ||
| }, | ||
| 'should write mix of strings and bufferse': function(t) { | ||
| var written = []; | ||
| var writer = { | ||
| write: function(s, cb) { written.push(s.toString()); if (cb) cb() } | ||
| } | ||
| var fp = new Fputs(writer, {writesize: 20}); | ||
| var expect = ""; | ||
| for (var i=0; i<100; i++) { | ||
| var line = "test " + i + "\n"; | ||
| fp.write(i % 2 ? fromBuf(line) : line); | ||
| expect += line; | ||
| } | ||
| fp.fflush(function(err){ | ||
| t.ifError(err); | ||
| t.equal(written.join(''), expect); | ||
| t.ok(written.length > 10 && written.length < 25); | ||
| t.done(); | ||
| }) | ||
| }, | ||
| }, | ||
| 'FileWriter': { | ||
| 'FileWriter.write should write entire string': function(t) { | ||
| var self = this; | ||
| this.fileWriter.write("test123", function(err) { | ||
| t.equal(fs.readFileSync(self.tempfile), "test123"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'FileWriter.write should write buffers': function(t) { | ||
| var self = this; | ||
| this.fileWriter.write(fromBuf("test123"), function(err) { | ||
| t.equal(fs.readFileSync(self.tempfile), "test123"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'FileWriter.renameFile should rename file': function(t) { | ||
| var self = this; | ||
| fs.writeFileSync(this.tempfile, "test"); | ||
| Fputs.FileWriter.renameFile(self.tempfile, self.tempfile2, function(err) { | ||
| t.equals(fs.readFileSync(self.tempfile2).toString(), "test"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'FileWriter.renameFile should pause N ms': function(t) { | ||
| var self = this; | ||
| fs.writeFileSync(this.tempfile, "test2"); | ||
| var t1 = Date.now(); | ||
| t.expect(1); | ||
| Fputs.FileWriter.renameFile(self.tempfile, self.tempfile2, 66, function(err) { | ||
| t.ok(Date.now() >= t1 + 66 - 1); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'FileWriter.renameFile should return EEXIST error, not pause and not overwrite if target already exists': function(t) { | ||
| var self = this; | ||
| fs.writeFileSync(this.tempfile, "test3a"); | ||
| fs.writeFileSync(this.tempfile2, "test3b"); | ||
| var t1 = Date.now(); | ||
| t.expect(4); | ||
| Fputs.FileWriter.renameFile(self.tempfile, self.tempfile2, 66, function(err, ret) { | ||
| t.ok(Date.now() < t1 + 5); | ||
| t.ok(err instanceof Error); | ||
| t.ok(err.message.indexOf('EEXIST') === 0); | ||
| t.equal(fs.readFileSync(self.tempfile2), "test3b"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'should expose FileWriter.renameFile on fputs instances': function(t) { | ||
| var fp = new Fputs(this.fileWriter); | ||
| t.equals(Fputs.FileWriter.renameFile, fp.renameFile); | ||
| t.done(); | ||
| }, | ||
| 'FileWriter.renameFile should wait for ongoing write to finish': function(t) { | ||
| if (!fse) t.skip(); | ||
| fs.writeFileSync(this.tempfile, "test4"); | ||
| var fd = fs.openSync(this.tempfile, 'r'); | ||
| fse.flockSync(fd, 'ex') ; | ||
| var t1 = Date.now(); | ||
| // protect against setTimeout off-by-one | ||
| setTimeout(function(){ fse.flockSync(fd, 'un'); fs.closeSync(fd) }, 125 + 1); | ||
| var self = this; | ||
| t.expect(3); | ||
| Fputs.FileWriter.renameFile(this.tempfile, this.tempfile2, function(err, ret) { | ||
| t.ifError(err); | ||
| t.ok(Date.now() >= t1 + 125); | ||
| t.equal(fs.readFileSync(self.tempfile2).toString(), "test4"); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| 'FileWriter.renameFile should time out after mutexTimeout': function(t) { | ||
| if (!fse) t.skip(); | ||
| fs.writeFileSync(this.tempfile, "test4"); | ||
| var fd = fs.openSync(this.tempfile, 'r'); | ||
| fse.flockSync(fd, 'ex') ; | ||
| var t1 = Date.now(); | ||
| setTimeout(function(){ fse.flockSync(fd, 'un'); fs.closeSync(fd) }, 200); | ||
| var self = this; | ||
| t.expect(3); | ||
| Fputs.FileWriter.renameFile(this.tempfile, this.tempfile2, {mutexTimeout: 125, waitMs: 10}, function(err, ret) { | ||
| t.ok(err); | ||
| t.ok(Date.now() >= t1 + 125); | ||
| t.ok(Date.now() < t1 + 150); | ||
| // note: node does not exit while fd is locked | ||
| fse.flockSync(fd, 'un'); | ||
| t.done(); | ||
| }); | ||
| }, | ||
| } | ||
| } |
+16
-5
@@ -7,3 +7,3 @@ /** | ||
| * | ||
| * Copyright (C) 2014-2015,2019 Andras Radics | ||
| * Copyright (C) 2014-2015,2019,2021 Andras Radics | ||
| * Licensed under the Apache License, Version 2.0 | ||
@@ -16,3 +16,2 @@ */ | ||
| var fse = _tryRequire('fs-ext'); | ||
| var aflow = require('aflow'); | ||
| if (!fse) { | ||
@@ -40,2 +39,5 @@ console.warn("qfputs: fs-ext not installed, appending lines without flock"); | ||
| var allocBuf = eval('parseInt(process.versions.node) >= 7 ? Buffer.allocUnsafe : Buffer'); | ||
| var fromBuf = eval('parseInt(process.versions.node) >= 7 ? Buffer.from : Buffer'); | ||
| function FileWriter( filename, openmode ) { | ||
@@ -60,3 +62,3 @@ if (!(this instanceof FileWriter)) return new FileWriter(filename, openmode); | ||
| this.fd = undefined; | ||
| this._writebuf = new Buffer(writesize * 1.25); | ||
| this._writebuf = allocBuf(writesize * 1.25); | ||
| } | ||
@@ -138,3 +140,3 @@ | ||
| if (nbytes > buf.length - 4) { | ||
| buf = new Buffer(str); | ||
| buf = fromBuf(str); | ||
| nbytes = buf.length; | ||
@@ -173,3 +175,3 @@ } | ||
| aflow.series([ | ||
| runSteps([ | ||
| function(cb) { | ||
@@ -224,4 +226,13 @@ // only rename if the source exists, otherwise an empty newName would be created "wx" below | ||
| // runSteps from mysqule < miniq < qrepeat and aflow | ||
| function runSteps(steps, callback) { | ||
| var ix = 0; | ||
| (function _loop(err, a1, a2) { | ||
| if (err || ix >= steps.length) return callback(err, a1, a2); | ||
| try { steps[ix++](_loop, a1, a2) } catch (err) { _loop(err) } | ||
| })(); | ||
| } | ||
| // expose renameFile as class method also | ||
| FileWriter.mutexTimeout = FileWriter.prototype.mutexTimeout; | ||
| FileWriter.renameFile = FileWriter.prototype.renameFile; |
+4
-3
@@ -16,2 +16,3 @@ /** | ||
| var setImmediate = global.setImmediate || process.nextTick; | ||
| var fromBuf = eval('parseInt(process.versions.node) >= 7 ? Buffer.from : Buffer'); | ||
| var FileWriter = require('./filewriter'); | ||
@@ -117,3 +118,3 @@ | ||
| // be sure to adjust the buffered length to keep drain and flush synced up | ||
| var chunk = new Buffer(lastChunk); | ||
| var chunk = fromBuf(lastChunk); | ||
| this.unwrittenLength += chunk.length - lastChunk.length; | ||
@@ -127,3 +128,3 @@ chunks[nchunks-2].chunks.push(chunk); | ||
| // if buffer follows string, combine them and swap the string for a list of buffers | ||
| var stringBuffer = new Buffer(lastChunk); | ||
| var stringBuffer = fromBuf(lastChunk); | ||
| chunks[chunks.length-1] = { length: stringBuffer.length + dataItem.length, chunks: [stringBuffer, dataItem] }; | ||
@@ -241,3 +242,3 @@ this.unwrittenLength += stringBuffer.length - lastChunk.length; | ||
| // ncalls = 0; | ||
| // for (i=0; i<100; i++) fs.write(fd, new Buffer("test\n"), 0, 5, null, function(err){ ncalls += 1; }); | ||
| // for (i=0; i<100; i++) fs.write(fd, fromBuf("test\n"), 0, 5, null, function(err){ ncalls += 1; }); | ||
| // setTimeout(function waitdone() { | ||
@@ -244,0 +245,0 @@ // (ncalls < 100) ? setTimeout(waitdone, 10) : console.log("done", ncalls); |
+1
-1
@@ -1,2 +0,2 @@ | ||
| Copyright 2014-2015,2019 Andras Radics | ||
| Copyright 2014-2015,2019,2021 Andras Radics | ||
| andras at andrasq dot com | ||
@@ -3,0 +3,0 @@ |
+1
-2
| { | ||
| "name": "qfputs", | ||
| "version": "1.8.0", | ||
| "version": "1.8.2", | ||
| "description": "very fast write-combining bufferd output", | ||
@@ -27,3 +27,2 @@ "license": "Apache-2.0", | ||
| "dependencies": { | ||
| "aflow": "0.10.0" | ||
| }, | ||
@@ -30,0 +29,0 @@ "optionalDependencies": { |
+1
-0
@@ -197,2 +197,3 @@ qfputs | ||
| ### 1.8.2 - fix Buffer deprecation warnings, remove `aflow` dependency | ||
| ### 1.8.0 - deprecate index.js, make fs-ext an optional dependency | ||
@@ -199,0 +200,0 @@ ### 1.7.2 - upgrade fs-ext to 1.2.1 for node-v10, upgrade qnit |
45267
56.89%1
-50%8
33.33%859
102.12%303
0.33%3
200%5
400%- Removed
- Removed