Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit b4858247e36d0a717d57892c28682d2e2aaec43b 1 parent 14f79b2
Sébastien Dolard authored
2  .gitignore
View
@@ -0,0 +1,2 @@
+node_modules
+*~
9 .npmignore
View
@@ -0,0 +1,9 @@
+git_hooks/pre-commit
+examples/*~
+bin/*~
+lib/*~
+test/*~
+log/
+*~
+.gitignore
+.npmignore
19 LICENSE
View
@@ -0,0 +1,19 @@
+Copyright © 2011 by Sebastien Dolard (sdolard@gmail.com)
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
0  README
View
No changes.
44 README.md
View
@@ -0,0 +1,44 @@
+# node-log-to-file
+
+A simple and fast log writer. Rotation is supported.
+
+* http://nodejs.org
+
+## Installation with npm > TODO
+### Installing npm (node package manager: http://npmjs.org/)
+
+```
+curl http://npmjs.org/install.sh || sh
+```
+
+### Installing log-to-file
+
+```
+[sudo] npm install [-g] log-to-file
+```
+
+
+## Usage
+### Basic
+```javascript
+logToFile = require('log-to-file'),
+log = logToFile.create({
+ directory: __dirname/log,
+ fileName: 'log.txt'
+});
+log.write('hello world');
+
+```
+
+### Examples
+
+## Exports
+
+## Known issues
+
+## Test
+Just run test/run_test.js
+
+
+## License
+node-log-to-file is licensed under the MIT license.
6 git_hooks/pre-commit
View
@@ -0,0 +1,6 @@
+#!/bin/sh
+RESULT_TEXT_OUTPUT=$(node test/run_test.js)
+RESULT_VALUE_OUTPUT=$?
+echo "$RESULT_TEXT_OUTPUT"
+exit $RESULT_VALUE_OUTPUT
+
373 lib/log-to-file.js
View
@@ -0,0 +1,373 @@
+/*
+Copyright © 2011 by Sebastien Dolard (sdolard@gmail.com)
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+*/
+
+
+
+/**
+*Known issues:
+* - When writting, deleting log file do not throw any error.
+*/
+var
+path = require('path'),
+fs = require('fs'),
+EventEmitter = require('events').EventEmitter,
+util = require('util');
+
+
+function onTimeout(logFile, origin) {
+ // Two possible origin
+ // - drain
+ // - timeout
+ if (origin === 'timeout') {
+ logFile._timeoutId = -1;
+ }
+ if (logFile._rotationPending) {
+ return;
+ }
+ if (logFile._writtenSize < logFile.fileMaxSize) {
+ var buffer = logFile._buffers.shift();
+ if (!buffer) {
+ logFile.emit('written', logFile.filePath);
+ logFile._waitDrain = false; // No more buffer to write, timer can restart
+ return;
+ }
+
+ logFile._writableStream.write(buffer.b.slice(0, buffer.usedBytes));
+ if (!logFile._waitDrain) {
+ // If it's first time we start to write, we emit this event
+ logFile.emit('writting', logFile.filePath);
+ logFile._waitDrain = true; // Timer has not to run
+ }
+
+ logFile._writtenSize += buffer.usedBytes;
+ return;
+ }
+ logFile._rotationPending = true;
+ logFile._writableStream.destroySoon(); // this will flush before then close
+}
+
+function formatIndex(index, length) {
+ index = String(index);
+ while (index.length < length) {
+ index = '0' + index;
+ }
+ return index;
+}
+
+/**
+* @class
+* @params config.fileName {string}
+* @params config.directory {string}
+* @params [config.verbose {boolean}]
+* @event error({object} exception)
+* @event writting({string} filePath)
+* @event written({string} filePath)
+* @throw EEMPTYFILENAME
+* @throw EDIRNOTFOUND
+*/
+var LogToFile = function (config){ // ctor
+ var
+ me = this;
+
+ config = config || {};
+
+ // Config
+ this.fileName = config.fileName || '';
+ if (this.fileName === '') {
+ throw {
+ code: 'EEMPTYFILENAME',
+ message: 'fileName config must be set'
+ };
+ }
+
+ this.directory = config.directory || '';
+ if (this.directory === '') {
+ throw {
+ code: 'EEMPTYDIRECTORY',
+ message: 'directory config must be set'
+ };
+ }
+ this.directory = path.resolve(config.directory);
+ // Directory sync test
+ if (!path.existsSync(this.directory)) {
+ throw {
+ code: 'EDIRNOTFOUND',
+ message: 'Directory not found: "' + this.directory + '"'
+ };
+ }
+ this.filePath = this.directory + '/' + this.fileName;
+ this.writeDelay = config.writeDelay || 200; // Buffer is flush every 200 ms
+ // TODO: check value
+ this.bufferSize = config.bufferSize || 65536; // Buffer blocks size
+ // TODO: check value
+ this.fileMaxSize = config.fileMaxSize || 1024 * 1024 * 5; // 5MB
+ this.verbose = config.verbose || false;
+ this.maxFileNumber = config.maxFileNumber || 99; // TODO: check value
+
+ this._buffers = []; // Array of buffer to write
+ this._timeoutId = -1; // write timer
+ this._waitDrain = false; // Drain flag
+ this._writtenSize = 0; // Quantity of data written. Initialized in _createWriteStream
+ this._rotationPending = false; // File rotation flag
+ this._maxFileNumberLength = String(this.maxFileNumber).length;
+
+ this._createWriteStream(); // We create first stream
+
+ EventEmitter.call(this);
+};
+util.inherits(LogToFile, EventEmitter);
+
+
+/**
+* @private
+*/
+LogToFile.prototype._createWriteStream = function() {
+ var
+ me = this,
+ stats;
+ if (this._writableStream) {
+ return;
+ }
+
+ this.log('Log will be written in "%s"', this.filePath);
+
+ fs.stat(this.filePath, function(err, stats) {
+ if (err && err.code !== 'ENOENT') {
+ me._eexception.call(me, err, '_createWriteStream fs.stat');
+ return;
+ }
+ me._writtenSize = stats ? stats.size : 0;
+ me._rotationPending = false;
+ me._waitDrain = false;
+
+ //Writable stream
+ me._writableStream = fs.createWriteStream(me.filePath, {
+ flags: 'a'
+ });
+ me._writableStream.on('error', function(err) {
+ me._eexception.call(me, err, '_createWriteStream _writableStream on error');
+ });
+
+ // Emitted when the underlying file descriptor has been closed.
+ me._writableStream.on('close', function() {
+ if (me._rotationPending) {
+ delete me._writableStream;
+ me._doFileRotation.call(me);
+ }
+ });
+
+ // Emitted after a write() method was called that returned false to indicate that it is safe to write again.
+ me._writableStream.on('drain', function() {
+ onTimeout(me, 'drain'); // we write next buffer, if there is
+ });
+
+ me._restartTimeout.call(me);
+ });
+};
+
+/**
+* @private
+*/
+function write(logFile, string) {
+ var
+ stringLength = Buffer.byteLength(string),
+ buffer = logFile._getBuffer.call(logFile);
+
+ // Enought place in current buffer?
+ if (buffer.usedBytes + stringLength > logFile.bufferSize) {
+ buffer = logFile._addBuffer.call(logFile);
+ }
+
+ buffer.b.write(string, buffer.usedBytes);
+ buffer.usedBytes += stringLength;
+
+ logFile._restartTimeout.call(logFile);
+}
+
+/**
+* @public
+*/
+LogToFile.prototype.write = function(string) {
+ var
+ me = this;
+ process.nextTick(function(){
+ write(me, string);
+ });
+};
+
+
+/**
+* @private
+*/
+LogToFile.prototype._getBuffer = function() {
+ if (this._buffers.length === 0) {
+ return this._addBuffer();
+ }
+ return this._buffers[this._buffers.length - 1];
+};
+
+
+
+/**
+* @private
+*/
+LogToFile.prototype._addBuffer = function() {
+ var b = {
+ b: new Buffer(this.bufferSize),
+ usedBytes: 0
+ };
+ this._buffers.push(b);
+ return b;
+};
+
+/**
+*
+*/
+LogToFile.prototype._restartTimeout = function() {
+ if (this._waitDrain || // waiting for write buffer to be empty
+ this._timeoutId !== -1 || // timer is already running
+ this._rotationPending || // a file rotation is pending
+ !this._writableStream) { // there is still no write stream
+ return;
+ }
+ clearTimeout(this._timeoutId);
+ this._timeoutId = setTimeout(onTimeout, this.writeDelay, this, 'timeout');
+};
+
+
+/**
+*
+*/
+LogToFile.prototype._doFileRotation = function() {
+ /*
+ File format
+ original: fileName [+ '.' + fileExt]
+ rotation: filename [+ '.' + fileExt] + '.' + fileIndex
+ compression filename [+ '.' + fileExt] + '.' + fileIndex + '.gz'
+ */
+
+ var
+ me = this,
+ oldFilePath = this.filePath,
+ dirname = path.dirname(this.filePath),
+ basename = path.basename(this.filePath);
+ filePath = dirname + '/' + basename + "."+ formatIndex(0, me._maxFileNumberLength);
+
+ fs.readdir(dirname, function(err, files) {
+ if (err) {
+ me._eexception.call(me, err, "_doFileRotation fs.readdir");
+ return;
+ }
+ var
+ i,
+ results = [],
+ fileIndex,
+ newFileIndex,
+ re = new RegExp('(^' + basename + ').(\\d+)'),
+ renameCurrent = function() {
+ fs.rename(me.filePath, filePath, function(err) {
+ if (err) {
+ me._eexception.call(me, err, "_doFileRotation fs.rename");
+ return;
+ }
+ me.emit('renamed', oldFilePath, filePath);
+
+ // new stream
+ me._createWriteStream.call(me);
+ });
+ },
+ renameOld = function() {
+ file = results[i];
+ i++;
+ fs.rename(file.oldFileName, file.newFileName, function(err) {
+ if (err) {
+ me._eexception.call(me, err, "_doFileRotation fs.rename");
+ return;
+ }
+ if (i < results.length) {
+ renameOld();
+ }else {
+ renameCurrent();
+ }
+ });
+ };
+
+ for (i = 0; i < files.length; i++) {
+ file = files[i];
+ matches = file.match(re);
+ if (matches) {
+ //me.log('files: ', files);
+ //me.log('matches: ', matches);
+ fileIndex = parseInt(matches[2], 10);
+ newFileIndex = ++fileIndex;
+ results.push({
+ fileIndex: fileIndex,
+ oldFileName: dirname + '/' + matches[0],
+ newFileName: dirname + '/' + basename + '.' + formatIndex(newFileIndex, me._maxFileNumberLength)
+ });
+ }
+ }
+ if (results.length > 0) {
+ results.sort(function(a, b) {
+ return b.fileIndex - a.fileIndex;
+ });
+ i = 0;
+ renameOld();
+ } else {
+ renameCurrent();
+ }
+ });
+};
+
+
+
+/**
+* Log only if verbose is positive
+* @public
+* @method
+*/
+LogToFile.prototype.log = function() {
+ if (!this.verbose) {
+ return;
+ }
+ var
+ args = arguments,
+ v = 'verbose LogToFile ' + path.basename(this.filePath) +'# ';
+ args[0] = args[0].replace('\n', '\n' + v);
+ args[0] = v.concat(args[0]);
+ console.error.apply(console, args);
+};
+
+
+/**
+* @private
+*/
+LogToFile.prototype._eexception = function(exception, more) {
+ this.log('%s: "%s" (%s)', exception.code, exception.message, more);
+ this.emit('error', exception);
+};
+
+/*******************************************************************************
+* Exports
+*******************************************************************************/
+exports.create = function(config) { return new LogToFile(config); };
+
35 package.json
View
@@ -0,0 +1,35 @@
+{
+ "name" : "log-to-file",
+ "description": "Simple and fast log writer. Rotation is supported. Just set a file name and a directory.",
+ "version" : "0.1.0",
+ "author" : "Sebastien Dolard",
+ "email" : "sdolard@gmail.com",
+ "keywords" : ["log", "writter", "rotation"],
+ "url" : "https://github.com/sdolard/node-log-to-file",
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:sdolard/node-log-to-file.git"
+ },
+ "main" : "lib/log-to-file",
+ "license" : "MIT",
+ "scripts" : {
+ "test" : "node test/run_test.js"
+ },
+ "dependencies": {
+ "pkginfo": "0.2.x"
+ },
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "bugs" :
+ {
+ "web" : "https://github.com/sdolard/node-log-to-file/issues"
+ },
+ "licenses" : [
+ {
+ "type" : "MIT",
+ "url" : "https://raw.github.com/node-log-to-file/master/LICENSE"
+ }
+ ]
+}
+
267 test/log-to-file.js
View
@@ -0,0 +1,267 @@
+/*
+Copyright © 2011 by Sebastien Dolard (sdolard@gmail.com)
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+*/
+
+var
+assert = require('assert'),
+util = require('util'),
+fs = require('fs'),
+logToFile = require('../lib/log-to-file'),
+dataTest = [
+ 'é1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN',
+ '1234567890AZERTYUIOPQSDFGHJKLMWXCVBN'
+].join(''),
+endData = '',
+size = 0,
+rs,
+log,
+EEMPTYFILENAME = false,
+EEMPTYDIRECTORY = false,
+EDIRNOTFOUND = false,
+endEvent = false,
+closeEventUnlink = false,
+readStreamErrorEvent = false,
+elements = 1, // 1, 1000000,
+elementsIO = parseInt(elements, 10) + '-elements-io',
+elementsLoop = parseInt(elements, 10) + '-elements-Loop',
+start, end, mem, writtingEventCount = 0;
+
+
+try {
+ log = logToFile.create();
+}
+catch(exceptionFoo) {
+ if (exceptionFoo.code === 'EEMPTYFILENAME') {
+ EEMPTYFILENAME = true;
+ }
+}
+
+try {
+ log = logToFile.create({
+ fileName: 'l_connection'
+ });
+}
+catch(exceptionBar) {
+ if (exceptionBar.code === 'EEMPTYDIRECTORY') {
+ EEMPTYDIRECTORY = true;
+ }
+}
+
+try {
+ log = logToFile.create({
+ directory: '•ë“‘',
+ fileName: 'l_connection'
+ });
+}
+catch(exceptionBaz) {
+ if (exceptionBaz.code === 'EDIRNOTFOUND') {
+ EDIRNOTFOUND = true;
+ }
+}
+
+
+log = logToFile.create({
+ directory: __dirname,
+ fileName: 'test.txt',
+ writeDelay: 1,
+ verbose: true,
+ fileMaxSize: 1024 * 1024 * 10
+});
+log.on('writting', function(fileName){
+
+ if (elements > 1 && writtingEventCount === 0) {
+ console.time(elementsIO);
+ start = Date.now();
+ }
+ writtingEventCount ++;
+});
+
+log.on('written', function(fileName){
+ var totalSize, duration;
+ if (size > 1024 * 1024 ) {
+ console.timeEnd(elementsIO);
+ end = Date.now();
+ totalSize = size / 1024 / 1024;
+ duration = end - start;
+ console.log('Total:%dMB in %d ms > %dMB/s', totalSize, duration, totalSize * 1000 / duration);
+ }
+ if (elements === 1) {
+ rs = fs.createReadStream(fileName, {
+ encoding: 'utf8'
+ });
+
+ rs.on('data', function (data) {
+ if (elements === 1) {
+ endData += data;
+ }
+ });
+ rs.on('end', function () {
+ endEvent = true;
+ });
+ rs.on('error', function (exception) {
+ readStreamErrorEvent = true;
+ //console.log('ReadStream exception: %s(%s)', exception.message, exception.code);
+ });
+ rs.on('close', function () {
+ // Clean up
+ fs.unlink(fileName, function (err) {
+ if (err) {
+ throw err;
+ }
+ closeEventUnlink = true;
+
+ });
+ if (elements === 1) {
+ assert.equal(endData, dataTest);
+ }
+ log.write(dataTest); // this should throw an error (file do not exists more), but no.
+ });
+ }
+
+});
+
+log.on('renamed', function (oldFilePath, newFilePath) {
+ console.log('%s renamed to %s', oldFilePath, newFilePath);
+});
+
+if (elements > 1 ) {
+ console.time(elementsLoop);
+}
+for (var i = 0; i < elements; i++) {
+ log.write(dataTest);
+ size += dataTest.length;
+}
+if (elements > 1 ) {
+ console.timeEnd(elementsLoop);
+}
+if (size > 1024 * 1024) {
+ mem = process.memoryUsage();
+ console.log('rss: %dMB', mem.rss / 1024 / 1024);
+ console.log('vsize: %dMB', mem.vsize / 1024 / 1024);
+ console.log('heapTotal: %dMB', mem.heapTotal / 1024 / 1024);
+ console.log('heapUsed: %dMB', mem.heapUsed / 1024 / 1024);
+}
+
+process.on('exit', function () {
+ assert.strictEqual(EEMPTYFILENAME, true, 'EEMPTYFILENAME done');
+ assert.strictEqual(EEMPTYDIRECTORY, true, 'EEMPTYDIRECTORY done');
+ assert.strictEqual(EDIRNOTFOUND, true, 'EDIRNOTFOUND done');
+
+ if (elements === 1) {
+ assert.strictEqual(endEvent, true, 'endEvent done');
+ assert.strictEqual(closeEventUnlink, true, 'closeEventUnlink done');
+
+ assert.strictEqual(readStreamErrorEvent, true, 'readStreamErrorEvent done');
+ }
+});
+
+/*
+log.write
+-------------
+1000000-elements-Loop: 986ms
+rss: 191.20703125MB
+vsize: 3129.06640625MB
+heapTotal: 182.4703369140625MB
+heapUsed: 156.18316650390625MB
+1000000-elements-io: 8497ms
+50.39422950057741MB/s
+
+
+log.writeSync > removed
+-------------
+1000000-elements-Loop: 8220ms
+rss: 456.97265625MB
+vsize: 3406.1640625MB
+heapTotal: 23.1846923828125MB
+heapUsed: 6.935150146484375MB
+1000000-elements-io: 8508ms
+50.329074760978635MB/s
+*/
+
+/**
+log.write, buffer size 4096
+-------------
+1000000-elements-Loop: 1098ms
+rss: 191.20703125MB
+vsize: 3129.06640625MB
+heapTotal: 182.4703369140625MB
+heapUsed: 156.18377685546875MB
+1000000-elements-io: 33809ms
+12.665259784862204MB/s
+
+
+log.write, buffer size 16384
+-------------
+rss: 191.22265625MB
+vsize: 3121.09765625MB
+heapTotal: 182.5015869140625MB
+heapUsed: 156.18750762939453MB
+1000000-elements-io: 9676ms
+44.25379992418419MB/s
+
+
+log.write, buffer size 32768
+-------------
+1000000-elements-Loop: 948ms
+rss: 191.2109375MB
+vsize: 3121.08203125MB
+heapTotal: 182.4859619140625MB
+heapUsed: 156.1850128173828MB
+1000000-elements-io: 8217ms
+52.111447981794605MB/s
+
+
+log.write, buffer size 65536
+-------------
+1000000-elements-Loop: 974ms
+rss: 191.22265625MB
+vsize: 3121.09765625MB
+heapTotal: 182.5015869140625MB
+heapUsed: 156.18667602539062MB
+1000000-elements-io: 7909ms
+54.14082286842916MB/s
+
+
+log.write, buffer size 132072
+-------------
+1000000-elements-Loop: 986ms
+rss: 191.22265625MB
+vsize: 3121.09765625MB
+heapTotal: 182.5015869140625MB
+heapUsed: 156.1884536743164MB
+1000000-elements-io: 8048ms
+53.205736588768175MB/s
+
+*/
+
+
+
+
+
36 test/run_test.js
View
@@ -0,0 +1,36 @@
+var
+fs = require('fs'),
+path = require("path"),
+RE_JS_FILE=/.*\.js$/i;
+
+fs.readdir(__dirname, function (err, files) {
+ var
+ i,
+ failures = 0,
+ file;
+
+ for(i = 0; i < files.length; i++) {
+ file = files[i];
+
+ if (!RE_JS_FILE.test(file)) {
+ continue;
+ }
+ if (file === "run_test.js") {
+ continue;
+ }
+
+ try {
+ require(path.resolve(__dirname, file));
+ console.log("OK - %s ", file);
+ } catch (e) {
+ console.log("KO - %s ", file, e);
+ failures++;
+ }
+
+ }
+ if (!failures) {
+ return console.log("#all pass");
+ }
+
+ console.error("%d %s", failures, "failure" + (failures > 1 ? "s" : ""));
+});
Please sign in to comment.
Something went wrong with that request. Please try again.