196 lines
5.2 KiB
TypeScript
196 lines
5.2 KiB
TypeScript
import type {
|
|
ITestResult,
|
|
ITestMetadata,
|
|
IPlanLine,
|
|
ISnapshotData,
|
|
IErrorBlock,
|
|
ITestEvent
|
|
} from './protocol.types.js';
|
|
|
|
import {
|
|
PROTOCOL_MARKERS,
|
|
PROTOCOL_VERSION
|
|
} from './protocol.types.js';
|
|
|
|
/**
|
|
* ProtocolEmitter generates Protocol V2 messages
|
|
* This class is used by tapbundle to emit test results in the new protocol format
|
|
*/
|
|
export class ProtocolEmitter {
|
|
/**
|
|
* Emit protocol version header
|
|
*/
|
|
public emitProtocolHeader(): string {
|
|
return `${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.PROTOCOL_PREFIX}${PROTOCOL_VERSION}${PROTOCOL_MARKERS.END}`;
|
|
}
|
|
|
|
/**
|
|
* Emit TAP version line
|
|
*/
|
|
public emitTapVersion(version: number = 13): string {
|
|
return `TAP version ${version}`;
|
|
}
|
|
|
|
/**
|
|
* Emit test plan
|
|
*/
|
|
public emitPlan(plan: IPlanLine): string {
|
|
if (plan.skipAll) {
|
|
return `1..0 # Skipped: ${plan.skipAll}`;
|
|
}
|
|
return `${plan.start}..${plan.end}`;
|
|
}
|
|
|
|
/**
|
|
* Emit a test result
|
|
*/
|
|
public emitTest(result: ITestResult): string[] {
|
|
const lines: string[] = [];
|
|
|
|
// Build the basic TAP line
|
|
let tapLine = result.ok ? 'ok' : 'not ok';
|
|
tapLine += ` ${result.testNumber}`;
|
|
tapLine += ` - ${result.description}`;
|
|
|
|
// Add directive if present
|
|
if (result.directive) {
|
|
tapLine += ` # ${result.directive.type.toUpperCase()}`;
|
|
if (result.directive.reason) {
|
|
tapLine += ` ${result.directive.reason}`;
|
|
}
|
|
}
|
|
|
|
// Add inline metadata for simple cases
|
|
if (result.metadata && this.shouldUseInlineMetadata(result.metadata)) {
|
|
const metaStr = this.createInlineMetadata(result.metadata);
|
|
if (metaStr) {
|
|
tapLine += ` ${metaStr}`;
|
|
}
|
|
}
|
|
|
|
lines.push(tapLine);
|
|
|
|
// Add block metadata for complex cases
|
|
if (result.metadata && !this.shouldUseInlineMetadata(result.metadata)) {
|
|
lines.push(...this.createBlockMetadata(result.metadata, result.testNumber));
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Emit a comment line
|
|
*/
|
|
public emitComment(comment: string): string {
|
|
return `# ${comment}`;
|
|
}
|
|
|
|
/**
|
|
* Emit bailout
|
|
*/
|
|
public emitBailout(reason: string): string {
|
|
return `Bail out! ${reason}`;
|
|
}
|
|
|
|
/**
|
|
* Emit snapshot data
|
|
*/
|
|
public emitSnapshot(snapshot: ISnapshotData): string[] {
|
|
const lines: string[] = [];
|
|
lines.push(`${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.SNAPSHOT_PREFIX}${snapshot.name}${PROTOCOL_MARKERS.END}`);
|
|
|
|
if (snapshot.format === 'json') {
|
|
lines.push(JSON.stringify(snapshot.content, null, 2));
|
|
} else {
|
|
lines.push(String(snapshot.content));
|
|
}
|
|
|
|
lines.push(`${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.SNAPSHOT_END}${PROTOCOL_MARKERS.END}`);
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Emit error block
|
|
*/
|
|
public emitError(error: IErrorBlock): string[] {
|
|
const lines: string[] = [];
|
|
lines.push(`${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.ERROR_PREFIX}${PROTOCOL_MARKERS.END}`);
|
|
lines.push(JSON.stringify(error, null, 2));
|
|
lines.push(`${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.ERROR_END}${PROTOCOL_MARKERS.END}`);
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Emit test event
|
|
*/
|
|
public emitEvent(event: ITestEvent): string {
|
|
const eventJson = JSON.stringify(event);
|
|
return `${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.EVENT_PREFIX}${eventJson}${PROTOCOL_MARKERS.END}`;
|
|
}
|
|
|
|
/**
|
|
* Check if metadata should be inline
|
|
*/
|
|
private shouldUseInlineMetadata(metadata: ITestMetadata): boolean {
|
|
// Use inline for simple metadata (time, retry, simple skip/todo)
|
|
const hasComplexData = metadata.error ||
|
|
metadata.custom ||
|
|
(metadata.tags && metadata.tags.length > 0) ||
|
|
metadata.file ||
|
|
metadata.line;
|
|
|
|
return !hasComplexData;
|
|
}
|
|
|
|
/**
|
|
* Create inline metadata string
|
|
*/
|
|
private createInlineMetadata(metadata: ITestMetadata): string {
|
|
const parts: string[] = [];
|
|
|
|
if (metadata.time !== undefined) {
|
|
parts.push(`time:${metadata.time}`);
|
|
}
|
|
|
|
if (metadata.retry !== undefined) {
|
|
parts.push(`retry:${metadata.retry}`);
|
|
}
|
|
|
|
if (metadata.skip) {
|
|
return `${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.SKIP_PREFIX}${metadata.skip}${PROTOCOL_MARKERS.END}`;
|
|
}
|
|
|
|
if (metadata.todo) {
|
|
return `${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.TODO_PREFIX}${metadata.todo}${PROTOCOL_MARKERS.END}`;
|
|
}
|
|
|
|
if (parts.length > 0) {
|
|
return `${PROTOCOL_MARKERS.START}${parts.join(',')}${PROTOCOL_MARKERS.END}`;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Create block metadata lines
|
|
*/
|
|
private createBlockMetadata(metadata: ITestMetadata, testNumber?: number): string[] {
|
|
const lines: string[] = [];
|
|
|
|
// Create a clean metadata object without skip/todo (handled inline)
|
|
const blockMeta = { ...metadata };
|
|
delete blockMeta.skip;
|
|
delete blockMeta.todo;
|
|
|
|
// Emit metadata block
|
|
const metaJson = JSON.stringify(blockMeta);
|
|
lines.push(`${PROTOCOL_MARKERS.START}${PROTOCOL_MARKERS.META_PREFIX}${metaJson}${PROTOCOL_MARKERS.END}`);
|
|
|
|
// Emit separate error block if present
|
|
if (metadata.error) {
|
|
lines.push(...this.emitError({ testNumber, error: metadata.error }));
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
} |