"use strict"; var async = require('async') , should = require('should') , fs = require('fs') , path = require('path') , zlib = require('zlib') , streams = require('readable-stream') , RollingFileStream = require('../lib').RollingFileStream; function remove(filename, cb) { fs.unlink(filename, function(err) { 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'); //encoding isn't a property on the underlying stream //assert.equal(stream.theStream.encoding, 'utf8'); }); }); 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", 45, 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', __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.11', __dirname + '/test-rolling-stream-with-existing-files.20' ], remove, done); }); it('should roll the files', 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.11'); files.should.containEql('test-rolling-stream-with-existing-files.20'); 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(); }); }); }); }); }); });