"use strict"; require("should"); var async = require("async"), fs = require("fs"), path = require("path"), zlib = require("zlib"), streams = require("stream"), RollingFileStream = require("../lib").RollingFileStream; function remove(filename, cb) { fs.unlink(filename, function() { cb(); }); } function create(filename, cb) { fs.writeFile(filename, "test file", cb); } describe("RollingFileStream", function() { describe("arguments", function() { var stream; before(function(done) { remove(__dirname + "/test-rolling-file-stream", function() { stream = new RollingFileStream( __dirname + "/test-rolling-file-stream", 1024, 5 ); done(); }); }); after(function(done) { remove(__dirname + "/test-rolling-file-stream", done); }); it("should take a filename, file size (bytes), no. backups, return Writable", function() { stream.should.be.an.instanceOf(streams.Writable); stream.filename.should.eql(__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() { it("should pass them to the underlying stream", function() { var stream = new RollingFileStream( __dirname + "/test-rolling-file-stream", 1024, 5, { mode: parseInt("0666", 8) } ); stream.theStream.mode.should.eql(parseInt("0666", 8)); }); after(function(done) { remove(__dirname + "/test-rolling-file-stream", done); }); }); describe("without size", function() { it("should default to max int size", function() { var stream = new RollingFileStream( __dirname + "/test-rolling-file-stream" ); stream.size.should.eql(Number.MAX_SAFE_INTEGER); }); after(function(done) { remove(__dirname + "/test-rolling-file-stream", done); }); }); describe("without number of backups", function() { it("should default to 1 backup", function() { var stream = new RollingFileStream( __dirname + "/test-rolling-file-stream", 1024 ); stream.backups.should.eql(1); }); after(function(done) { remove(__dirname + "/test-rolling-file-stream", done); }); }); describe("writing less than the file size", function() { before(function(done) { remove(__dirname + "/test-rolling-file-stream-write-less", function() { var stream = new RollingFileStream( __dirname + "/test-rolling-file-stream-write-less", 100 ); stream.write("cheese", "utf8", function() { stream.end(done); }); }); }); after(function(done) { remove(__dirname + "/test-rolling-file-stream-write-less", done); }); it("should write to the file", function(done) { fs.readFile( __dirname + "/test-rolling-file-stream-write-less", "utf8", function(err, contents) { contents.should.eql("cheese"); done(err); } ); }); it("should write one file", function(done) { fs.readdir(__dirname, function(err, files) { files .filter(function(file) { return file.indexOf("test-rolling-file-stream-write-less") > -1; }) .should.have.length(1); done(err); }); }); }); describe("writing more than the file size", function() { before(function(done) { async.forEach( [ __dirname + "/test-rolling-file-stream-write-more", __dirname + "/test-rolling-file-stream-write-more.1" ], remove, function() { var stream = new RollingFileStream( __dirname + "/test-rolling-file-stream-write-more", 45 ); async.forEachSeries( [0, 1, 2, 3, 4, 5, 6], function(i, cb) { stream.write(i + ".cheese\n", "utf8", cb); }, function() { stream.end(done); } ); } ); }); after(function(done) { async.forEach( [ __dirname + "/test-rolling-file-stream-write-more", __dirname + "/test-rolling-file-stream-write-more.1" ], remove, done ); }); it("should write two files", function(done) { fs.readdir(__dirname, function(err, files) { files .filter(function(file) { return file.indexOf("test-rolling-file-stream-write-more") > -1; }) .should.have.length(2); done(err); }); }); it("should write the last two log messages to the first file", function(done) { fs.readFile( __dirname + "/test-rolling-file-stream-write-more", "utf8", function(err, contents) { contents.should.eql("5.cheese\n6.cheese\n"); done(err); } ); }); it("should write the first five log messages to the second file", function(done) { fs.readFile( __dirname + "/test-rolling-file-stream-write-more.1", "utf8", function(err, contents) { contents.should.eql( "0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n" ); done(err); } ); }); }); describe("with options.compress = true", function() { before(function(done) { var stream = new RollingFileStream( path.join(__dirname, "compressed-backups.log"), 30, //30 bytes max size 2, //two backup files to keep { compress: true } ); async.forEachSeries( [ "This is the first log message.", "This is the second log message.", "This is the third log message.", "This is the fourth log message." ], function(i, cb) { stream.write(i + "\n", "utf8", cb); }, function() { stream.end(done); } ); }); it("should produce three files, with the backups compressed", function(done) { fs.readdir(__dirname, function(err, files) { var testFiles = files .filter(function(f) { return 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" ]); fs.readFile(path.join(__dirname, testFiles[0]), "utf8", function( err, contents ) { contents.should.eql("This is the fourth log message.\n"); zlib.gunzip( fs.readFileSync(path.join(__dirname, testFiles[1])), function(err, contents) { contents .toString("utf8") .should.eql("This is the third log message.\n"); zlib.gunzip( fs.readFileSync(path.join(__dirname, testFiles[2])), function(err, contents) { contents .toString("utf8") .should.eql("This is the second log message.\n"); done(err); } ); } ); }); }); }); after(function(done) { async.forEach( [ path.join(__dirname, "compressed-backups.log"), path.join(__dirname, "compressed-backups.log.1.gz"), path.join(__dirname, "compressed-backups.log.2.gz") ], remove, done ); }); }); describe("with options.keepFileExt = true", function() { before(function(done) { var stream = new RollingFileStream( path.join(__dirname, "extKept-backups.log"), 30, //30 bytes max size 2, //two backup files to keep { keepFileExt: true } ); async.forEachSeries( [ "This is the first log message.", "This is the second log message.", "This is the third log message.", "This is the fourth log message." ], function(i, cb) { stream.write(i + "\n", "utf8", cb); }, function() { stream.end(done); } ); }); it("should produce three files, with the file-extension kept", function(done) { fs.readdir(__dirname, function(err, files) { var testFiles = files .filter(function(f) { return 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" ]); fs.readFile(path.join(__dirname, testFiles[0]), "utf8", function( err, contents ) { contents.should.eql("This is the third log message.\n"); fs.readFile(path.join(__dirname, testFiles[1]), "utf8", function( err, contents ) { contents .toString("utf8") .should.eql("This is the second log message.\n"); fs.readFile(path.join(__dirname, testFiles[2]), "utf8", function( err, contents ) { contents .toString("utf8") .should.eql("This is the fourth log message.\n"); done(err); }); }); }); }); }); after(function(done) { async.forEach( [ path.join(__dirname, "extKept-backups.log"), path.join(__dirname, "extKept-backups.1.log"), path.join(__dirname, "extKept-backups.2.log") ], remove, done ); }); }); describe("with options.compress = true and keepFileExt = true", function() { before(function(done) { var stream = new RollingFileStream( path.join(__dirname, "compressed-backups.log"), 30, //30 bytes max size 2, //two backup files to keep { compress: true, keepFileExt: true } ); async.forEachSeries( [ "This is the first log message.", "This is the second log message.", "This is the third log message.", "This is the fourth log message." ], function(i, cb) { stream.write(i + "\n", "utf8", cb); }, function() { stream.end(done); } ); }); it("should produce three files, with the backups compressed", function(done) { fs.readdir(__dirname, function(err, files) { var testFiles = files .filter(function(f) { return 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" ]); fs.readFile(path.join(__dirname, testFiles[2]), "utf8", function( err, contents ) { contents.should.eql("This is the fourth log message.\n"); zlib.gunzip( fs.readFileSync(path.join(__dirname, testFiles[1])), function(err, contents) { contents .toString("utf8") .should.eql("This is the second log message.\n"); zlib.gunzip( fs.readFileSync(path.join(__dirname, testFiles[0])), function(err, contents) { contents .toString("utf8") .should.eql("This is the third log message.\n"); done(err); } ); } ); }); }); }); after(function(done) { async.forEach( [ path.join(__dirname, "compressed-backups.log"), path.join(__dirname, "compressed-backups.1.log.gz"), path.join(__dirname, "compressed-backups.2.log.gz") ], remove, done ); }); }); describe("when many files already exist", function() { before(function(done) { async.forEach( [ __dirname + "/test-rolling-stream-with-existing-files.11", __dirname + "/test-rolling-stream-with-existing-files.20", __dirname + "/test-rolling-stream-with-existing-files.-1", __dirname + "/test-rolling-stream-with-existing-files.1.1", __dirname + "/test-rolling-stream-with-existing-files.1" ], remove, function(err) { if (err) done(err); async.forEach( [ __dirname + "/test-rolling-stream-with-existing-files.11", __dirname + "/test-rolling-stream-with-existing-files.20", __dirname + "/test-rolling-stream-with-existing-files.-1", __dirname + "/test-rolling-stream-with-existing-files.1.1", __dirname + "/test-rolling-stream-with-existing-files.1" ], create, function(err) { if (err) done(err); var stream = new RollingFileStream( __dirname + "/test-rolling-stream-with-existing-files", 18, 5 ); async.forEachSeries( [0, 1, 2, 3, 4, 5, 6], function(i, cb) { stream.write(i + ".cheese\n", "utf8", cb); }, function() { stream.end(done); } ); } ); } ); }); after(function(done) { async.forEach( [ __dirname + "/test-rolling-stream-with-existing-files.-1", __dirname + "/test-rolling-stream-with-existing-files", __dirname + "/test-rolling-stream-with-existing-files.1.1", __dirname + "/test-rolling-stream-with-existing-files.0", __dirname + "/test-rolling-stream-with-existing-files.1", __dirname + "/test-rolling-stream-with-existing-files.2", __dirname + "/test-rolling-stream-with-existing-files.3", __dirname + "/test-rolling-stream-with-existing-files.4", __dirname + "/test-rolling-stream-with-existing-files.5", __dirname + "/test-rolling-stream-with-existing-files.6", __dirname + "/test-rolling-stream-with-existing-files.11", __dirname + "/test-rolling-stream-with-existing-files.20" ], remove, done ); }); it("should roll the files, removing the highest indices", function(done) { fs.readdir(__dirname, function(err, files) { 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"); done(err); }); }); }); 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(function() { fs.unlinkSync(path.join("subdir", "test-rolling-file-stream")); fs.rmdirSync("subdir"); }); it("handles directory deletion gracefully", function(done) { stream.theStream.on("error", done); remove(path.join("subdir", "test-rolling-file-stream"), function() { fs.rmdir("subdir", function() { stream.write("rollover", "utf8", function() { fs.readFileSync( path.join("subdir", "test-rolling-file-stream"), "utf8" ).should.eql("rollover"); done(); }); }); }); }); }); });