Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix async writer interface, fixes #38

  • Loading branch information...
commit dbe358220ea72b46275889f9010256f51c2db357 1 parent 889a5e2
@gotwarlost authored
View
9 lib/report/cobertura.js
@@ -5,7 +5,7 @@
var path = require('path'),
Report = require('./index'),
- Writer = require('../util/file-writer'),
+ FileWriter = require('../util/file-writer'),
TreeSummarizer = require('../util/tree-summarizer'),
utils = require('../object-utils');
@@ -29,6 +29,7 @@ function CoberturaReport(opts) {
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file || 'cobertura-coverage.xml';
+ this.opts = opts;
}
CoberturaReport.TYPE = 'cobertura';
@@ -168,7 +169,7 @@ Report.mix(CoberturaReport, {
writeReport: function (collector, sync) {
var summarizer = new TreeSummarizer(),
outputFile = path.join(this.dir, this.file),
- writer = new Writer(sync),
+ writer = this.opts.writer || new FileWriter(sync),
tree,
root;
@@ -177,8 +178,8 @@ Report.mix(CoberturaReport, {
});
tree = summarizer.getTreeSummary();
root = tree.root;
- writer.writeFile(outputFile, function () {
- walk(root, collector, writer, 0);
+ writer.writeFile(outputFile, function (contentWriter) {
+ walk(root, collector, contentWriter, 0);
});
}
});
View
8 lib/report/html.js
@@ -422,8 +422,8 @@ Report.mix(HtmlReport, {
indexFile = path.resolve(dir, 'index.html'),
childFile;
if (this.opts.verbose) { console.error('Writing ' + indexFile); }
- writer.writeFile(indexFile, function () {
- that.writeIndexPage(writer, node);
+ writer.writeFile(indexFile, function (contentWriter) {
+ that.writeIndexPage(contentWriter, node);
});
node.children.forEach(function (child) {
if (child.kind === 'dir') {
@@ -431,8 +431,8 @@ Report.mix(HtmlReport, {
} else {
childFile = path.resolve(dir, child.relativeName + '.html');
if (that.opts.verbose) { console.error('Writing ' + childFile); }
- writer.writeFile(childFile, function () {
- that.writeDetailPage(writer, child, collector.fileCoverageFor(child.fullPath()));
+ writer.writeFile(childFile, function (contentWriter) {
+ that.writeDetailPage(contentWriter, child, collector.fileCoverageFor(child.fullPath()));
});
}
});
View
4 lib/report/lcovonly.js
@@ -82,9 +82,9 @@ Report.mix(LcovOnlyReport, {
var outputFile = path.resolve(this.opts.dir, 'lcov.info'),
writer = this.opts.writer || new Writer(sync),
that = this;
- writer.writeFile(outputFile, function () {
+ writer.writeFile(outputFile, function (contentWriter) {
collector.files().forEach(function (key) {
- that.writeFileCoverage(writer, collector.fileCoverageFor(key));
+ that.writeFileCoverage(contentWriter, collector.fileCoverageFor(key));
});
});
}
View
154 lib/util/file-writer.js
@@ -4,62 +4,128 @@
*/
var path = require('path'),
+ util = require('util'),
fs = require('fs'),
- mkdirp = require('mkdirp');
+ async = require('async'),
+ mkdirp = require('mkdirp'),
+ writer = require('./writer'),
+ Writer = writer.Writer,
+ ContentWriter = writer.ContentWriter;
-function FileWriter(sync) {
- this.writing = false;
- this.start = sync ? this.startSync : this.startAsync;
- this.write = sync ? this.writeSync : this.writeAsync;
- this.end = sync ? this.endSync : this.endAsync;
+function extend(cons, proto) {
+ Object.keys(proto).forEach(function (k) {
+ cons.prototype[k] = proto[k];
+ });
}
-FileWriter.prototype = {
- copyFile: function (source, dest) {
- mkdirp.sync(path.dirname(dest));
- fs.writeFileSync(dest, fs.readFileSync(source));
+function BufferedContentWriter() {
+ ContentWriter.call(this);
+ this.content = '';
+}
+util.inherits(BufferedContentWriter, ContentWriter);
+
+extend(BufferedContentWriter, {
+ write: function (str) {
+ this.content += str;
},
+ getContent: function () {
+ return this.content;
+ }
+});
+
+function StreamContentWriter(stream) {
+ ContentWriter.call(this);
+ this.stream = stream;
+}
+util.inherits(StreamContentWriter, ContentWriter);
+
+extend(StreamContentWriter, {
+ write: function (str) {
+ this.stream.write(str);
+ }
+});
+
+function SyncFileWriter() {
+ Writer.call(this);
+}
+util.inherits(SyncFileWriter, Writer);
+
+extend(SyncFileWriter, {
writeFile: function (file, callback) {
- this.start(file);
- callback(this);
- this.end();
- },
- println: function (str) {
- this.write(str);
- this.write('\n');
- },
- startSync: function (fileName) {
- this.doStart(fileName);
- this.contents = '';
- this.filename = fileName;
- },
- writeSync: function (str) {
- this.contents += str;
+ mkdirp.sync(path.dirname(file));
+ var cw = new BufferedContentWriter();
+ callback(cw);
+ fs.writeFileSync(file, cw.getContent(), 'utf8');
},
- endSync: function () {
- this.doEnd();
- fs.writeFileSync(this.filename, this.contents, 'utf8');
+ done: function () {
+ this.emit('done'); //everything already done
+ }
+});
+
+function AsyncFileWriter() {
+ this.queue = async.queue(this.processFile.bind(this), 20);
+ this.openFileMap = {};
+}
+
+util.inherits(AsyncFileWriter, Writer);
+
+extend(AsyncFileWriter, {
+ writeFile: function (file, callback) {
+ this.openFileMap[file] = true;
+ this.queue.push({ file: file, callback: callback });
},
- startAsync: function (fileName) {
- this.doStart(fileName);
- this.stream = fs.createWriteStream(fileName);
+ processFile: function (task, cb) {
+ var file = task.file,
+ userCallback = task.callback,
+ that = this,
+ stream,
+ contentWriter;
+
+ mkdirp.sync(path.dirname(file));
+ stream = fs.createWriteStream(file);
+ stream.on('close', function () {
+ delete that.openFileMap[file];
+ cb();
+ that.checkDone();
+ });
+ stream.on('error', function (err) { that.emit('error', err); });
+ contentWriter = new StreamContentWriter(stream);
+ userCallback(contentWriter);
+ stream.end();
},
- writeAsync: function (str) {
- this.stream.write(str);
+ done: function () {
+ this.doneCalled = true;
+ this.checkDone();
},
- endAsync: function () {
- this.doEnd();
- this.stream.end();
+ checkDone: function () {
+ if (!this.doneCalled) { return; }
+ if (Object.keys(this.openFileMap).length === 0) {
+ this.emit('done');
+ }
+ }
+});
+
+function FileWriter(sync) {
+ Writer.call(this);
+ var that = this;
+ this.delegate = sync ? new SyncFileWriter() : new AsyncFileWriter();
+ this.delegate.on('error', function (err) { that.emit('error', err); });
+ this.delegate.on('done', function () { that.emit('done'); });
+}
+
+util.inherits(FileWriter, Writer);
+
+extend(FileWriter, {
+ copyFile: function (source, dest) {
+ mkdirp.sync(path.dirname(dest));
+ fs.writeFileSync(dest, fs.readFileSync(source));
},
- doStart: function (fileName) {
- if (this.writing) { throw new Error('Attempt to start a new file before ending the previous one'); }
- this.writing = true;
- mkdirp.sync(path.dirname(fileName));
+ writeFile: function (file, callback) {
+ this.delegate.writeFile(file, callback);
},
- doEnd : function () {
- if (!this.writing) { throw new Error('Attempt to end a file without starting it'); }
- this.writing = false;
+ done: function () {
+ this.delegate.done();
}
-};
+});
module.exports = FileWriter;
View
56 lib/util/writer.js
@@ -0,0 +1,56 @@
+/*
+ Copyright (c) 2012, Yahoo! Inc. All rights reserved.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+
+var util = require('util'),
+ EventEmitter = require('events').EventEmitter;
+
+function extend(cons, proto) {
+ Object.keys(proto).forEach(function (k) {
+ cons.prototype[k] = proto[k];
+ });
+}
+
+//abstract interface for writing content
+function ContentWriter() {
+}
+
+ContentWriter.prototype = {
+ write: function (/* str */) { throw new Error('write: must be overridden'); },
+ println: function (str) { this.write(str); this.write('\n'); }
+};
+
+//abstract interface for writing files and assets
+function Writer() {
+ EventEmitter.call(this);
+}
+
+util.inherits(Writer, EventEmitter);
+
+extend(Writer, {
+ /**
+ * allows writing content to a file using a callback that is passed a content writer
+ * @param file the name of the file to write
+ * @param callback the callback that is called as `callback(contentWriter)`
+ */
+ writeFile: function (/* file, callback */) { throw new Error('writeFile: must be overridden'); },
+ /**
+ * copies a file from source to destination
+ * @param source the file to copy, found on the file system
+ * @param dest the destination path
+ */
+ copyFile: function (/* source, dest */) { throw new Error('copyFile: must be overridden'); },
+ /**
+ * marker method to indicate that the caller is done with this writer object
+ * The writer is expected to emit a `done` event only after this method is called
+ * and it is truly done.
+ */
+ done: function () { throw new Error('done: must be overridden'); }
+});
+
+module.exports = {
+ Writer: Writer,
+ ContentWriter: ContentWriter
+};
+
View
65 test/other/test-file-writer.js
@@ -1,13 +1,14 @@
/*jslint nomen: true */
+
var path = require('path'),
fs = require('fs'),
- exists = fs.existsSync || path.existsSync,
mkdirp = require('mkdirp'),
+ rimraf = require('rimraf'),
FileWriter = require('../../lib/util/file-writer'),
outputDir = path.resolve(__dirname, 'output'),
FOO = path.resolve(outputDir, 'foo-written.js'),
- BAR = path.resolve(outputDir, 'bar-written.js'),
writer,
+ MAX_FILES = 1000,
LINES = [ 'This is line 1', 'This is line 2', 'This is line 3' ];
function testContents(test) {
@@ -15,6 +16,23 @@ function testContents(test) {
test.done();
}
+function fileNames() {
+ var i,
+ ret = [];
+ for (i = 0; i < MAX_FILES; i += 1) {
+ ret.push(path.resolve(outputDir, 'file' + i + '.txt'));
+ }
+ return ret;
+}
+
+function testAllFiles(test) {
+ var files = fileNames();
+ files.forEach(function (file) {
+ test.equal(file + '\n', fs.readFileSync(file, 'utf8'));
+ });
+ test.done();
+}
+
function battery(sync) {
return {
setUp: function (cb) {
@@ -24,42 +42,25 @@ function battery(sync) {
},
tearDown: function (cb) {
writer = null;
- if (exists(FOO)) { fs.unlinkSync(FOO); }
- if (exists(BAR)) { fs.unlinkSync(BAR); }
+ rimraf.sync(outputDir);
cb();
},
- "should write file with a series of calls": function (test) {
- writer.start(FOO);
- LINES.forEach(function (line) { writer.write(line); writer.write('\n'); });
- writer.end(FOO);
- if (sync) {
- testContents(test);
- } else {
- setTimeout(testContents.bind(null, test), 500);
- }
- },
"should write file in callback mode": function (test) {
+ writer.on('done', testContents.bind(null, test));
writer.writeFile(FOO, function (w) {
LINES.forEach(function (line) { w.println(line); });
});
- if (sync) {
- testContents(test);
- } else {
- setTimeout(testContents.bind(null, test), 500);
- }
+ writer.done();
},
- "should not allow end before start": function (test) {
- test.throws(function () {
- writer.end();
- }, /Attempt to end a file without starting it/);
- test.done();
- },
- "should not allow start when started": function (test) {
- writer.start(BAR);
- test.throws(function () {
- writer.start(FOO);
- }, /Attempt to start a new file before ending the previous one/);
- test.done();
+ "should write 1000 files": function (test) {
+ var files = fileNames();
+ writer.on('done', testAllFiles.bind(null, test));
+ files.forEach(function (file) {
+ writer.writeFile(file, function (w) {
+ w.println(file);
+ });
+ });
+ writer.done();
}
};
}
@@ -67,4 +68,4 @@ function battery(sync) {
module.exports = {
"when in sync mode": battery(true),
"when in async mode": battery(false)
-};
+};
Please sign in to comment.
Something went wrong with that request. Please try again.