diff --git a/packages/csv-generate/lib/api/normalize_options.js b/packages/csv-generate/lib/api/normalize_options.js index ccf6eb169..5e172e00d 100644 --- a/packages/csv-generate/lib/api/normalize_options.js +++ b/packages/csv-generate/lib/api/normalize_options.js @@ -43,6 +43,10 @@ const normalize_options = (opts) => { options[k] = dft[k]; } } + // Zero is a fixed point of the seeded recurrence; use the existing initial seed. + if (options.seed === 0) { + options.seed = 1; + } // Default values if (options.eof === true) { options.eof = options.rowDelimiter; diff --git a/packages/csv-generate/test/options.seed.js b/packages/csv-generate/test/options.seed.js index f68142ce9..425346ac0 100644 --- a/packages/csv-generate/test/options.seed.js +++ b/packages/csv-generate/test/options.seed.js @@ -1,5 +1,7 @@ import "should"; +import { spawnSync } from "node:child_process"; import { generate } from "../lib/index.js"; +import { generate as generateSync } from "../lib/sync.js"; import { random } from "../lib/api/random.js"; describe("Option `seed`", function () { @@ -14,6 +16,78 @@ describe("Option `seed`", function () { }); }); + describe("with zero seed", function () { + it("generate repeatable records synchronously", function () { + const options = { + seed: 0, + columns: ["ascii", "ascii"], + length: 3, + objectMode: true, + }; + const records = generateSync(options); + records.should.eql(generateSync(options)); + records + .every((record) => record.every((word) => word.length > 0)) + .should.be.true(); + }); + + it("generate repeatable records with a callback", function (next) { + const options = { + seed: 0, + columns: ["ascii", "ascii"], + length: 3, + objectMode: true, + }; + generate(options, (err, records) => { + if (err) return next(err); + records.should.eql(generateSync(options)); + records + .every((record) => record.every((word) => word.length > 0)) + .should.be.true(); + next(); + }); + }); + + it("generate repeatable records as a stream", async function () { + const options = { + seed: 0, + columns: ["ascii", "ascii"], + length: 3, + objectMode: true, + }; + const records = []; + for await (const record of generate(options)) records.push(record); + records.should.eql(generateSync(options)); + records + .every((record) => record.every((word) => word.length > 0)) + .should.be.true(); + }); + + it("yield the first record from an unbounded object stream", function () { + const moduleUrl = new URL("../lib/index.js", import.meta.url).href; + const result = spawnSync( + process.execPath, + [ + "--max-old-space-size=32", + "--input-type=module", + "-e", + `import { generate } from ${JSON.stringify(moduleUrl)}; + const generator = generate({ seed: 0, objectMode: true }); + generator.once("data", (record) => { + console.log(JSON.stringify(record)); + generator.destroy(); + });`, + ], + { timeout: 10000, encoding: "utf8" }, + ); + if (result.error) throw result.error; + result.status.should.equal(0, result.stderr); + const record = JSON.parse(result.stdout); + record.should.have.length(8); + record.every((word) => word.length > 0).should.be.true(); + }); + }); + describe("with seed", function () { it("generate same values", function () { random(generate({ seed: 1 }).options).should.equal(