diff --git a/packages/csv-generate/lib/api/read.js b/packages/csv-generate/lib/api/read.js index f1de454e..5f84bbe5 100644 --- a/packages/csv-generate/lib/api/read.js +++ b/packages/csv-generate/lib/api/read.js @@ -25,7 +25,13 @@ const read = (options, state, size, push, close) => { (options.duration && Date.now() > state.start_time + options.duration) ) { // Flush - if (data.length) { + if ( + data.length || + (!options.objectMode && + options.eof && + state.count_created > 0 && + state.end !== true) + ) { if (options.objectMode) { for (const record of data) { push(record); diff --git a/packages/csv-generate/test/options.eof.js b/packages/csv-generate/test/options.eof.js index 904c5e70..d2f02cd6 100644 --- a/packages/csv-generate/test/options.eof.js +++ b/packages/csv-generate/test/options.eof.js @@ -1,7 +1,81 @@ import "should"; import { generate } from "../lib/index.js"; +import { generate as generateSync } from "../lib/sync.js"; +import { generate as generateStream } from "../lib/stream.js"; describe("Option `eof`", function () { + it("preserves a custom suffix after an overflowing final record", function (next) { + generate( + { + columns: [() => "xx"], + length: 2, + highWaterMark: 3, + eof: "END", + }, + (err, data) => { + if (err) return next(err); + data.toString().should.eql("xx\nxxEND"); + next(); + }, + ); + }); + + it("emits eof once across synchronous read boundaries", function () { + for (const highWaterMark of [1, 2, 3, 4, 5, 6]) { + for (const fixed_size of [false, true]) { + generateSync({ + columns: [() => "xx"], + length: 2, + highWaterMark, + fixed_size, + eof: "END", + }).should.eql("xx\nxxEND"); + } + } + }); + + it("preserves a custom row delimiter as eof in streamed output", async function () { + const chunks = []; + for await (const chunk of generate({ + columns: [() => "xx"], + length: 1, + highWaterMark: 1, + row_delimiter: "\r\n", + eof: true, + sleep: 1, + })) { + chunks.push(chunk); + } + Buffer.concat(chunks).toString().should.eql("xx\r\n"); + }); + + it("preserves eof after overflowing a web stream read", async function () { + const field = "x".repeat(1024); + const chunks = []; + for await (const chunk of generateStream({ + columns: [() => field], + length: 2, + eof: "END", + })) { + chunks.push(chunk); + } + Buffer.concat(chunks).toString().should.eql(`${field}\n${field}END`); + }); + + it("leaves empty output empty", function () { + generateSync({ length: 0, eof: "END" }).should.eql(""); + }); + + it("does not append eof to object records", function () { + generateSync({ + columns: [() => "xx"], + length: 1, + objectMode: true, + highWaterMark: 1, + eof: "END", + }).should.eql([["xx"]]); + }); + it("default to trailing newline character if true", function (next) { generate( {