line-reader
Version:
Asynchronous, buffered, line-by-line file/stream reader
413 lines (366 loc) • 12.8 kB
JavaScript
var lineReader = require('../lib/line_reader'),
assert = require('assert'),
fs = require('fs'),
testFilePath = __dirname + '/data/normal_file.txt',
windowsFilePath = __dirname + '/data/windows_file.txt',
windowsBufferOverlapFilePath = __dirname + '/data/windows_buffer_overlap_file.txt',
unixFilePath = __dirname + '/data/unix_file.txt',
macOs9FilePath = __dirname + '/data/mac_os_9_file.txt',
separatorFilePath = __dirname + '/data/separator_file.txt',
multiSeparatorFilePath = __dirname + '/data/multi_separator_file.txt',
multibyteFilePath = __dirname + '/data/multibyte_file.txt',
emptyFilePath = __dirname + '/data/empty_file.txt',
oneLineFilePath = __dirname + '/data/one_line_file.txt',
oneLineFileNoEndlinePath = __dirname + '/data/one_line_file_no_endline.txt',
threeLineFilePath = __dirname + '/data/three_line_file.txt',
testSeparatorFile = ['foo', 'bar\n', 'baz\n'],
testFile = [
'Jabberwocky',
'',
'’Twas brillig, and the slithy toves',
'Did gyre and gimble in the wabe;',
'',
''
],
testBufferOverlapFile = [
'test',
'file'
];
describe("lineReader", function() {
describe("eachLine", function() {
it("should read lines using the default separator", function(done) {
var i = 0;
lineReader.eachLine(testFilePath, function(line, last) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 6) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(6, i);
done();
});
});
it("should read windows files by default", function(done) {
var i = 0;
lineReader.eachLine(windowsFilePath, function(line, last) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 6) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(6, i);
done();
});
});
it("should handle \\r\\n overlapping buffer window correctly", function(done) {
var i = 0;
var bufferSize = 5;
lineReader.eachLine(windowsBufferOverlapFilePath, {bufferSize: bufferSize}, function(line, last) {
assert.equal(testBufferOverlapFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 2) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(2, i);
done();
});
});
it("should read unix files by default", function(done) {
var i = 0;
lineReader.eachLine(unixFilePath, function(line, last) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 6) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(6, i);
done();
});
});
it("should read mac os 9 files by default", function(done) {
var i = 0;
lineReader.eachLine(macOs9FilePath, function(line, last) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 6) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(6, i);
done();
});
});
it("should allow continuation of line reading via a callback", function(done) {
var i = 0;
lineReader.eachLine(testFilePath, function(line, last, cb) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 6) {
assert.ok(last);
} else {
assert.ok(!last);
}
process.nextTick(cb);
}, function(err) {
assert.ok(!err);
assert.equal(6, i);
done();
});
});
it("should separate files using given separator", function(done) {
var i = 0;
lineReader.eachLine(separatorFilePath, {separator: ';'}, function(line, last) {
assert.equal(testSeparatorFile[i], line);
i += 1;
if (i === 3) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(3, i);
done();
});
});
it("should separate files using given separator with more than one character", function(done) {
var i = 0;
lineReader.eachLine(multiSeparatorFilePath, {separator: '||'}, function(line, last) {
assert.equal(testSeparatorFile[i], line);
i += 1;
if (i === 3) {
assert.ok(last);
} else {
assert.ok(!last);
}
}, function(err) {
assert.ok(!err);
assert.equal(3, i);
done();
});
});
it("should allow early termination of line reading", function(done) {
var i = 0;
lineReader.eachLine(testFilePath, function(line, last) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 2) {
return false;
}
}, function(err) {
assert.ok(!err);
assert.equal(2, i);
done();
});
});
it("should allow early termination of line reading via a callback", function(done) {
var i = 0;
lineReader.eachLine(testFilePath, function(line, last, cb) {
assert.equal(testFile[i], line, 'Each line should be what we expect');
i += 1;
if (i === 2) {
cb(false);
} else {
cb();
}
}, function(err) {
assert.ok(!err);
assert.equal(2, i);
done();
});
});
it("should not call callback on empty file", function(done) {
lineReader.eachLine(emptyFilePath, function(line) {
assert.ok(false, "Empty file should not cause any callbacks");
}, function(err) {
assert.ok(!err);
done()
});
});
it("should error when the user tries calls nextLine on a closed LineReader", function(done) {
lineReader.open(oneLineFilePath, function(err, reader) {
assert.ok(!err);
reader.close(function(err) {
assert.ok(!err);
reader.nextLine(function(err, line) {
assert.ok(err, "nextLine should have errored because the reader is closed");
done();
});
});
});
});
it("should work with a file containing only one line", function(done) {
lineReader.eachLine(oneLineFilePath, function(line, last) {
return true;
}, function(err) {
assert.ok(!err);
done();
});
});
it("should work with a file containing only one line and no endline character.", function(done) {
var count = 0; var isDone = false;
lineReader.eachLine(oneLineFileNoEndlinePath, function(line, last) {
assert.equal(last, true, 'last should be true');
return true;
}, function(err) {
assert.ok(!err);
done();
});
});
it("should close the file when eachLine finishes", function(done) {
var reader;
lineReader.eachLine(oneLineFilePath, function(line, last) {
return false;
}, function(err) {
assert.ok(!err);
assert.ok(reader.isClosed());
done();
}).getReader(function(_reader) {
reader = _reader;
});
});
it("should close the file if there is an error during eachLine", function(done) {
lineReader.eachLine(testFilePath, {bufferSize: 10}, function(line, last) {
}, function(err) {
assert.equal('a test error', err.message);
assert.ok(reader.isClosed());
done();
}).getReader(function(_reader) {
reader = _reader;
reader.getReadStream().read = function() {
throw new Error('a test error');
};
});
});
});
describe("open", function() {
it("should return a reader object and allow calls to nextLine", function(done) {
lineReader.open(testFilePath, function(err, reader) {
assert.ok(!err);
assert.ok(reader.hasNextLine());
assert.ok(reader.hasNextLine(), 'Calling hasNextLine multiple times should be ok');
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('Jabberwocky', line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('', line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('’Twas brillig, and the slithy toves', line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('Did gyre and gimble in the wabe;', line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('', line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('', line);
assert.ok(!reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(err);
done();
});
});
});
});
});
});
});
});
});
it("should work with a file containing only one line", function(done) {
lineReader.open(oneLineFilePath, function(err, reader) {
assert.ok(!err);
reader.close(function(err) {
assert.ok(!err);
done();
});
});
});
it("should read multibyte characters on the buffer boundary", function(done) {
lineReader.open(multibyteFilePath, {separator: '\n', encoding: 'utf8', bufferSize: 2}, function(err, reader) {
assert.ok(!err);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('ふうりうの初やおくの田植うた', line,
"Should read multibyte characters on buffer boundary");
reader.close(function(err) {
assert.ok(!err);
done();
});
});
});
});
it("should support opened streams", function() {
var readStream = fs.createReadStream(testFilePath);
lineReader.open(readStream, function(err, reader) {
assert.ok(!err);
assert.ok(reader.hasNextLine());
assert.ok(reader.hasNextLine(), 'Calling hasNextLine multiple times should be ok');
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal('Jabberwocky', line);
});
});
});
it("should handle error while opening read stream", function() {
lineReader.open('a file that does not exist', function(err, reader) {
assert.ok(err);
assert.ok(reader.isClosed());
});
});
describe("hasNextLine", function() {
it("should return true when buffer is empty but not at EOF", function(done) {
lineReader.open(threeLineFilePath, {separator: '\n', encoding: 'utf8', bufferSize: 36}, function(err, reader) {
assert.ok(!err);
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal("This is line one.", line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal("This is line two.", line);
assert.ok(reader.hasNextLine());
reader.nextLine(function(err, line) {
assert.ok(!err);
assert.equal("This is line three.", line);
assert.ok(!reader.hasNextLine());
reader.close(function(err) {
assert.ok(!err);
done();
})
});
});
});
});
});
});
});
});