cronicle-docker/test/newline-regression.js
Emil Lerch e75cc7468a
Some checks failed
Build Cronicle image / build (push) Failing after 29s
initial commit: ai generated all the things
2026-08-11 17:36:02 -07:00

71 lines
2.6 KiB
JavaScript

// Build-time regression gate for the job-log line-concatenation bug.
//
// THE BUG
//
// pixl-json-stream 1.0.9 emitted the last complete line of each read chunk
// WITHOUT a trailing newline:
//
// var text = record + ((idx < len - 1) ? self.EOL : '');
//
// Cronicle's bin/shell-plugin.js appends whatever the 'text' event hands it,
// verbatim, and relies on the library to supply the terminator:
//
// fs.appendFileSync(job.log_file, line);
//
// So whenever a chunk boundary landed mid-line, the preceding complete line
// lost its newline and the next line was glued onto it. Output arriving over a
// pipe -- e.g. a job running `docker exec <container> <cmd>` -- splits mid-line
// constantly, so in practice most of the log ran together:
//
// [2026/08/11 14:00:02] EDGAR company ticker map okinfo(...): provider ...
//
// 1.0.10 restored the unconditional `record + self.EOL`.
//
// WHY THIS FILE EXISTS
//
// This was not a build-time resolution accident that "npm ci" alone prevents.
// Upstream's own package-lock.json pinned the broken version:
//
// Cronicle v0.9.74 -> pixl-json-stream 1.0.9 (broken)
// Cronicle v0.9.126 -> pixl-json-stream 1.0.10 (fixed)
//
// A lockfile delivered the bug, so a lockfile can deliver it again. The failure
// mode is silent: jobs still succeed, logs are just quietly unreadable. Running
// this during `docker build` converts that into a hard build failure.
const JSONStream = require('pixl-json-stream');
const { PassThrough } = require('stream');
const input = new PassThrough();
const stream = new JSONStream(input, new PassThrough());
let log = '';
stream.on('text', (line) => {
log += line;
});
// Chunk boundaries deliberately fall mid-line ("tw" + "o"), reproducing what a
// real pipe does. A library that only terminates non-final records will drop
// the newline after "one" and after "three".
input.write('one\ntw');
input.write('o\nthree\nfou');
input.write('r\n');
// 'text' is emitted from the stream's 'data' handler, so let the event loop
// drain before asserting.
setTimeout(() => {
const want = 'one\ntwo\nthree\nfour\n';
if (log !== want) {
console.error('FAIL: pixl-json-stream is dropping line terminators.');
console.error(' got: ' + JSON.stringify(log));
console.error(' want: ' + JSON.stringify(want));
console.error('');
console.error('Cronicle job logs would silently concatenate lines.');
console.error('See the comment at the top of this file. Refusing to build.');
process.exit(1);
}
const version = require('pixl-json-stream/package.json').version;
console.log('OK: job log line terminators intact (pixl-json-stream ' + version + ')');
}, 200);