| require("should"); |
| |
| const fs = require("fs-extra"), |
| path = require("path"), |
| util = require("util"), |
| zlib = require("zlib"), |
| streams = require("stream"), |
| RollingFileStream = require("../lib").RollingFileStream; |
| |
| const gunzip = util.promisify(zlib.gunzip); |
| const fullPath = f => path.join(__dirname, f); |
| const remove = filename => fs.unlink(fullPath(filename)).catch(() => {}); |
| const create = filename => fs.writeFile(fullPath(filename), "test file"); |
| |
| const write = (stream, data) => { |
| return new Promise((resolve, reject) => { |
| stream.write(data, "utf8", e => { |
| if (e) { |
| reject(e); |
| } else { |
| resolve(); |
| } |
| }); |
| }); |
| }; |
| |
| const writeInSequence = async (stream, messages) => { |
| for (let i = 0; i < messages.length; i += 1) { |
| await write(stream, messages[i] + "\n"); |
| } |
| return new Promise(resolve => { |
| stream.end(resolve); |
| }); |
| }; |
| |
| const close = async (stream) => new Promise( |
| (resolve, reject) => stream.end(e => e ? reject(e) : resolve()) |
| ); |
| |
| describe("RollingFileStream", function() { |
| describe("arguments", function() { |
| let stream; |
| |
| before(async function() { |
| await remove("test-rolling-file-stream"); |
| stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream"), |
| 1024, |
| 5 |
| ); |
| }); |
| |
| after(async function() { |
| await close(stream); |
| await remove("test-rolling-file-stream"); |
| }); |
| |
| it("should take a filename, file size (bytes), no. backups, return Writable", function() { |
| stream.should.be.an.instanceOf(streams.Writable); |
| stream.filename.should.eql( |
| path.join(__dirname, "test-rolling-file-stream") |
| ); |
| stream.size.should.eql(1024); |
| stream.backups.should.eql(5); |
| }); |
| |
| it("should apply default settings to the underlying stream", function() { |
| stream.theStream.mode.should.eql(420); |
| stream.theStream.flags.should.eql("a"); |
| }); |
| }); |
| |
| describe("with stream arguments", function() { |
| let stream; |
| it("should pass them to the underlying stream", function() { |
| stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream"), |
| 1024, |
| 5, |
| { mode: parseInt("0666", 8) } |
| ); |
| stream.theStream.mode.should.eql(parseInt("0666", 8)); |
| }); |
| |
| after(async function() { |
| await close(stream); |
| await remove("test-rolling-file-stream"); |
| }); |
| }); |
| |
| describe("without size", function() { |
| let stream; |
| it("should default to max int size", function() { |
| stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream") |
| ); |
| stream.size.should.eql(Number.MAX_SAFE_INTEGER); |
| }); |
| |
| after(async function() { |
| await close(stream); |
| await remove("test-rolling-file-stream"); |
| }); |
| }); |
| |
| describe("without number of backups", function() { |
| let stream; |
| it("should default to 1 backup", function() { |
| stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream"), |
| 1024 |
| ); |
| stream.backups.should.eql(1); |
| }); |
| |
| after(async function() { |
| await close(stream); |
| await remove("test-rolling-file-stream"); |
| }); |
| }); |
| |
| describe("writing less than the file size", function() { |
| before(async function() { |
| await remove("test-rolling-file-stream-write-less"); |
| const stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream-write-less"), |
| 100 |
| ); |
| await writeInSequence(stream, ["cheese"]); |
| }); |
| |
| after(async function() { |
| await remove("test-rolling-file-stream-write-less"); |
| }); |
| |
| it("should write to the file", async function() { |
| const contents = await fs.readFile( |
| path.join(__dirname, "test-rolling-file-stream-write-less"), |
| "utf8" |
| ); |
| contents.should.eql("cheese\n"); |
| }); |
| |
| it("should write one file", async function() { |
| const files = await fs.readdir(__dirname); |
| files |
| .filter( |
| file => file.indexOf("test-rolling-file-stream-write-less") > -1 |
| ) |
| .should.have.length(1); |
| }); |
| }); |
| |
| describe("writing more than the file size", function() { |
| before(async function() { |
| await remove("test-rolling-file-stream-write-more"); |
| await remove("test-rolling-file-stream-write-more.1"); |
| const stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-file-stream-write-more"), |
| 45 |
| ); |
| await writeInSequence( |
| stream, |
| [0, 1, 2, 3, 4, 5, 6].map(i => i + ".cheese") |
| ); |
| }); |
| |
| after(async function() { |
| await remove("test-rolling-file-stream-write-more"); |
| await remove("test-rolling-file-stream-write-more.1"); |
| }); |
| |
| it("should write two files", async function() { |
| const files = await fs.readdir(__dirname); |
| files |
| .filter( |
| file => file.indexOf("test-rolling-file-stream-write-more") > -1 |
| ) |
| .should.have.length(2); |
| }); |
| |
| it("should write the last two log messages to the first file", async function() { |
| const contents = await fs.readFile( |
| path.join(__dirname, "test-rolling-file-stream-write-more"), |
| "utf8" |
| ); |
| contents.should.eql("5.cheese\n6.cheese\n"); |
| }); |
| |
| it("should write the first five log messages to the second file", async function() { |
| const contents = await fs.readFile( |
| path.join(__dirname, "test-rolling-file-stream-write-more.1"), |
| "utf8" |
| ); |
| contents.should.eql("0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n"); |
| }); |
| }); |
| |
| describe("with options.compress = true", function() { |
| before(async function() { |
| const stream = new RollingFileStream( |
| path.join(__dirname, "compressed-backups.log"), |
| 30, //30 bytes max size |
| 2, //two backup files to keep |
| { compress: true } |
| ); |
| const messages = [ |
| "This is the first log message.", |
| "This is the second log message.", |
| "This is the third log message.", |
| "This is the fourth log message." |
| ]; |
| await writeInSequence(stream, messages); |
| }); |
| |
| it("should produce three files, with the backups compressed", async function() { |
| const files = await fs.readdir(__dirname); |
| const testFiles = files |
| .filter(f => f.indexOf("compressed-backups.log") > -1) |
| .sort(); |
| |
| testFiles.length.should.eql(3); |
| testFiles.should.eql([ |
| "compressed-backups.log", |
| "compressed-backups.log.1.gz", |
| "compressed-backups.log.2.gz" |
| ]); |
| |
| let contents = await fs.readFile( |
| path.join(__dirname, testFiles[0]), |
| "utf8" |
| ); |
| contents.should.eql("This is the fourth log message.\n"); |
| |
| let gzipped = await fs.readFile(path.join(__dirname, testFiles[1])); |
| contents = await gunzip(gzipped); |
| contents.toString("utf8").should.eql("This is the third log message.\n"); |
| |
| gzipped = await fs.readFile(path.join(__dirname, testFiles[2])); |
| contents = await gunzip(gzipped); |
| contents.toString("utf8").should.eql("This is the second log message.\n"); |
| }); |
| |
| after(function() { |
| return Promise.all([ |
| remove("compressed-backups.log"), |
| remove("compressed-backups.log.1.gz"), |
| remove("compressed-backups.log.2.gz") |
| ]); |
| }); |
| }); |
| |
| describe("with options.keepFileExt = true", function() { |
| before(async function() { |
| const stream = new RollingFileStream( |
| path.join(__dirname, "extKept-backups.log"), |
| 30, //30 bytes max size |
| 2, //two backup files to keep |
| { keepFileExt: true } |
| ); |
| const messages = [ |
| "This is the first log message.", |
| "This is the second log message.", |
| "This is the third log message.", |
| "This is the fourth log message." |
| ]; |
| await writeInSequence(stream, messages); |
| }); |
| |
| it("should produce three files, with the file-extension kept", async function() { |
| const files = await fs.readdir(__dirname); |
| const testFiles = files |
| .filter(f => f.indexOf("extKept-backups") > -1) |
| .sort(); |
| |
| testFiles.length.should.eql(3); |
| testFiles.should.eql([ |
| "extKept-backups.1.log", |
| "extKept-backups.2.log", |
| "extKept-backups.log" |
| ]); |
| |
| let contents = await fs.readFile( |
| path.join(__dirname, testFiles[0]), |
| "utf8" |
| ); |
| contents.should.eql("This is the third log message.\n"); |
| |
| contents = await fs.readFile(path.join(__dirname, testFiles[1]), "utf8"); |
| contents.toString("utf8").should.eql("This is the second log message.\n"); |
| contents = await fs.readFile(path.join(__dirname, testFiles[2]), "utf8"); |
| contents.toString("utf8").should.eql("This is the fourth log message.\n"); |
| }); |
| |
| after(function() { |
| return Promise.all([ |
| remove("extKept-backups.log"), |
| remove("extKept-backups.1.log"), |
| remove("extKept-backups.2.log") |
| ]); |
| }); |
| }); |
| |
| describe("with options.compress = true and keepFileExt = true", function() { |
| before(async function() { |
| const stream = new RollingFileStream( |
| path.join(__dirname, "compressed-backups.log"), |
| 30, //30 bytes max size |
| 2, //two backup files to keep |
| { compress: true, keepFileExt: true } |
| ); |
| const messages = [ |
| "This is the first log message.", |
| "This is the second log message.", |
| "This is the third log message.", |
| "This is the fourth log message." |
| ]; |
| await writeInSequence(stream, messages); |
| }); |
| |
| it("should produce three files, with the backups compressed", async function() { |
| const files = await fs.readdir(__dirname); |
| const testFiles = files |
| .filter(f => f.indexOf("compressed-backups") > -1) |
| .sort(); |
| |
| testFiles.length.should.eql(3); |
| testFiles.should.eql([ |
| "compressed-backups.1.log.gz", |
| "compressed-backups.2.log.gz", |
| "compressed-backups.log" |
| ]); |
| |
| let contents = await fs.readFile( |
| path.join(__dirname, testFiles[2]), |
| "utf8" |
| ); |
| contents.should.eql("This is the fourth log message.\n"); |
| |
| let gzipped = await fs.readFile(path.join(__dirname, testFiles[1])); |
| contents = await gunzip(gzipped); |
| contents.toString("utf8").should.eql("This is the second log message.\n"); |
| gzipped = await fs.readFile(path.join(__dirname, testFiles[0])); |
| contents = await gunzip(gzipped); |
| contents.toString("utf8").should.eql("This is the third log message.\n"); |
| }); |
| |
| after(function() { |
| return Promise.all([ |
| remove("compressed-backups.log"), |
| remove("compressed-backups.1.log.gz"), |
| remove("compressed-backups.2.log.gz") |
| ]); |
| }); |
| }); |
| |
| describe("when many files already exist", function() { |
| before(async function() { |
| await Promise.all([ |
| remove("test-rolling-stream-with-existing-files.11"), |
| remove("test-rolling-stream-with-existing-files.20"), |
| remove("test-rolling-stream-with-existing-files.-1"), |
| remove("test-rolling-stream-with-existing-files.1.1"), |
| remove("test-rolling-stream-with-existing-files.1") |
| ]); |
| await Promise.all([ |
| create("test-rolling-stream-with-existing-files.11"), |
| create("test-rolling-stream-with-existing-files.20"), |
| create("test-rolling-stream-with-existing-files.-1"), |
| create("test-rolling-stream-with-existing-files.1.1"), |
| create("test-rolling-stream-with-existing-files.1") |
| ]); |
| |
| const stream = new RollingFileStream( |
| path.join(__dirname, "test-rolling-stream-with-existing-files"), |
| 18, |
| 5 |
| ); |
| |
| await writeInSequence( |
| stream, |
| [0, 1, 2, 3, 4, 5, 6].map(i => i + ".cheese") |
| ); |
| }); |
| |
| after(function() { |
| return Promise.all( |
| [ |
| "test-rolling-stream-with-existing-files.-1", |
| "test-rolling-stream-with-existing-files", |
| "test-rolling-stream-with-existing-files.1.1", |
| "test-rolling-stream-with-existing-files.0", |
| "test-rolling-stream-with-existing-files.1", |
| "test-rolling-stream-with-existing-files.2", |
| "test-rolling-stream-with-existing-files.3", |
| "test-rolling-stream-with-existing-files.4", |
| "test-rolling-stream-with-existing-files.5", |
| "test-rolling-stream-with-existing-files.6", |
| "test-rolling-stream-with-existing-files.11", |
| "test-rolling-stream-with-existing-files.20" |
| ].map(remove) |
| ); |
| }); |
| |
| it("should roll the files, removing the highest indices", async function() { |
| const files = await fs.readdir(__dirname); |
| files.should.containEql("test-rolling-stream-with-existing-files"); |
| files.should.containEql("test-rolling-stream-with-existing-files.1"); |
| files.should.containEql("test-rolling-stream-with-existing-files.2"); |
| files.should.containEql("test-rolling-stream-with-existing-files.3"); |
| files.should.containEql("test-rolling-stream-with-existing-files.4"); |
| }); |
| }); |
| |
| // in windows, you can't delete a directory if there is an open file handle |
| if (process.platform !== "win32") { |
| |
| describe("when the directory gets deleted", function() { |
| var stream; |
| before(function(done) { |
| stream = new RollingFileStream( |
| path.join("subdir", "test-rolling-file-stream"), |
| 5, |
| 5 |
| ); |
| stream.write("initial", "utf8", done); |
| }); |
| |
| after(async () => { |
| await fs.unlink(path.join("subdir", "test-rolling-file-stream")); |
| await fs.rmdir("subdir"); |
| }); |
| |
| it("handles directory deletion gracefully", async function() { |
| stream.theStream.on("error", e => { |
| throw e; |
| }); |
| |
| await fs.unlink(path.join("subdir", "test-rolling-file-stream")); |
| await fs.rmdir("subdir"); |
| await new Promise(resolve => stream.write("rollover", "utf8", resolve)); |
| await close(stream); |
| (await fs.readFile( |
| path.join("subdir", "test-rolling-file-stream"), |
| "utf8" |
| )).should.eql("rollover"); |
| }); |
| }); |
| } |
| |
| }); |