'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', 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(); }); }); }); }); }); });