fix(logging): handle mid-line streaming output in test logger and add streaming tests

This commit is contained in:
2026-01-19 20:32:56 +00:00
parent 286030a08d
commit 9ec2c8b6eb
5 changed files with 61 additions and 9 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
## 2026-01-19 - 3.1.6 - fix(logging)
handle mid-line streaming output in test logger and add streaming tests
- Introduce isOutputMidLine flag to track when streaming output does not end with a newline
- Only prepend the visual prefix at the start of a line and append segments to the last buffered entry when mid-line
- Write consistent output to log files for both complete lines and raw streaming segments
- Add tests to exercise streaming behavior: test/tstest/test.gap-debug.ts and test/tstest/test.gap-debug2.ts
## 2026-01-19 - 3.1.5 - fix(tstest) ## 2026-01-19 - 3.1.5 - fix(tstest)
preserve streaming console output and correctly buffer incomplete TAP lines preserve streaming console output and correctly buffer incomplete TAP lines

View File

@@ -0,0 +1,12 @@
import { tap, expect } from '../../ts_tapbundle/index.js';
tap.test('check for gaps in streaming', async () => {
// This should print "ABCD" with no gaps
process.stdout.write("A");
process.stdout.write("B");
process.stdout.write("C");
process.stdout.write("D\n");
expect(true).toEqual(true);
});
export default tap.start();

View File

@@ -0,0 +1,14 @@
import { tap, expect } from '../../ts_tapbundle/index.js';
tap.test('streaming with delays', async (tools) => {
// Simulate real streaming with delays
process.stdout.write("Progress: [");
for (let i = 0; i < 10; i++) {
await tools.delayFor(50);
process.stdout.write("=");
}
process.stdout.write("]\n");
expect(true).toEqual(true);
});
export default tap.start();

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tstest', name: '@git.zone/tstest',
version: '3.1.5', version: '3.1.6',
description: 'a test utility to run tests that match test/**/*.ts' description: 'a test utility to run tests that match test/**/*.ts'
} }

View File

@@ -44,6 +44,7 @@ export class TsTestLogger {
private currentTestLogFile: string | null = null; private currentTestLogFile: string | null = null;
private currentTestLogs: string[] = []; // Buffer for current test logs private currentTestLogs: string[] = []; // Buffer for current test logs
private currentTestFailed: boolean = false; private currentTestFailed: boolean = false;
private isOutputMidLine: boolean = false; // Track whether we're mid-line for streaming output
constructor(options: LogOptions = {}) { constructor(options: LogOptions = {}) {
this.options = options; this.options = options;
@@ -189,6 +190,7 @@ export class TsTestLogger {
// Reset test-specific state // Reset test-specific state
this.currentTestLogs = []; this.currentTestLogs = [];
this.currentTestFailed = false; this.currentTestFailed = false;
this.isOutputMidLine = false;
// Only set up test log file if --logfile option is specified // Only set up test log file if --logfile option is specified
if (this.options.logFile) { if (this.options.logFile) {
@@ -352,17 +354,28 @@ export class TsTestLogger {
testConsoleOutput(message: string) { testConsoleOutput(message: string) {
if (this.options.json) return; if (this.options.json) return;
const prefix = ' ';
// In verbose mode, show console output immediately // In verbose mode, show console output immediately
if (this.options.verbose) { if (this.options.verbose) {
this.log(this.format(` ${message}`, 'dim')); // Only add prefix if we're starting a new line
const output = this.isOutputMidLine ? message : prefix + message;
this.log(this.format(output, 'dim'));
} else { } else {
// In non-verbose mode, buffer the logs // In non-verbose mode, buffer the logs
if (this.isOutputMidLine && this.currentTestLogs.length > 0) {
// Append to the last buffered entry since we're mid-line
this.currentTestLogs[this.currentTestLogs.length - 1] += message;
} else {
this.currentTestLogs.push(message); this.currentTestLogs.push(message);
} }
}
// Reset mid-line state since we just output a complete line
this.isOutputMidLine = false;
// Always log to test file if --logfile is specified // Always log to test file if --logfile is specified
if (this.currentTestLogFile) { if (this.currentTestLogFile) {
this.logToTestFile(` ${message}`); this.logToTestFile(`${prefix}${message}`);
} }
} }
@@ -371,13 +384,15 @@ export class TsTestLogger {
if (this.options.json) return; if (this.options.json) return;
const prefix = ' '; const prefix = ' ';
// Only add prefix if we're starting a new line (not mid-line)
const output = this.isOutputMidLine ? message : prefix + message;
if (this.options.verbose) { if (this.options.verbose) {
// Use process.stdout.write to preserve streaming without adding newlines // Use process.stdout.write to preserve streaming without adding newlines
process.stdout.write(this.format(prefix + message, 'dim')); process.stdout.write(this.format(output, 'dim'));
} else { } else {
// Buffer without trailing newline - append to last entry if exists and incomplete // Buffer mode: append to last entry if mid-line
if (this.currentTestLogs.length > 0) { if (this.isOutputMidLine && this.currentTestLogs.length > 0) {
// Append to the last buffered entry (for streaming segments)
this.currentTestLogs[this.currentTestLogs.length - 1] += message; this.currentTestLogs[this.currentTestLogs.length - 1] += message;
} else { } else {
this.currentTestLogs.push(message); this.currentTestLogs.push(message);
@@ -386,8 +401,11 @@ export class TsTestLogger {
// Log to test file without adding newline // Log to test file without adding newline
if (this.currentTestLogFile) { if (this.currentTestLogFile) {
this.logToTestFileRaw(prefix + message); this.logToTestFileRaw(output);
} }
// We're now mid-line (no newline was written)
this.isOutputMidLine = true;
} }
private logToTestFileRaw(message: string) { private logToTestFileRaw(message: string) {