6.9 KiB
6.9 KiB
Improved Internal Protocol Design
Current Issues with TAP Protocol
- Delimiter Conflict: Using
#
for metadata conflicts with test descriptions containing#
- Ambiguous Parsing: No clear boundary between test name and metadata
- Limited Extensibility: Adding new metadata requires regex changes
- Mixed Concerns: Protocol data mixed with human-readable output
Proposed Internal Protocol v2
Design Principles
- Clear Separation: Protocol data must be unambiguously separated from user content
- Extensibility: Easy to add new metadata without breaking parsers
- Backwards Compatible: Can coexist with standard TAP for gradual migration
- Machine Readable: Structured format for reliable parsing
- Human Friendly: Still readable in raw form
Protocol Options
Option 1: Special Delimiters
ok 1 - test description ::TSTEST:: {"time":123,"retry":0}
not ok 2 - another test ::TSTEST:: {"time":45,"error":"timeout"}
ok 3 - skipped test ::TSTEST:: {"time":0,"skip":"not ready"}
Pros:
- Simple to implement
- Backwards compatible with TAP parsers (they ignore the suffix)
- Easy to parse with split()
Cons:
- Still could conflict if test name contains
::TSTEST::
- Not standard TAP
Option 2: Separate Metadata Lines
ok 1 - test description
::METADATA:: {"test":1,"time":123,"retry":0}
not ok 2 - another test
::METADATA:: {"test":2,"time":45,"error":"timeout"}
Pros:
- Complete separation of concerns
- No chance of conflicts
- Can include arbitrary metadata
Cons:
- Requires correlation between lines
- More complex parsing
Option 3: YAML Blocks (TAP 13 Compatible)
ok 1 - test description
---
time: 123
retry: 0
...
not ok 2 - another test
---
time: 45
error: timeout
stack: |
Error: timeout
at Test.run (test.js:10:5)
...
Pros:
- Standard TAP 13 feature
- Structured data format
- Human readable
- Extensible
Cons:
- More verbose
- YAML parsing overhead
Option 4: Binary Protocol Markers (Recommended)
ok 1 - test description
␛[TSTEST:eyJ0aW1lIjoxMjMsInJldHJ5IjowfQ==]␛
not ok 2 - another test
␛[TSTEST:eyJ0aW1lIjo0NSwiZXJyb3IiOiJ0aW1lb3V0In0=]␛
Using ASCII escape character (␛ = \x1B) with base64 encoded JSON.
Pros:
- Zero chance of accidental conflicts
- Compact
- Fast to parse
- Invisible in most terminals
Cons:
- Not human readable in raw form
- Requires base64 encoding/decoding
Recommended Implementation: Hybrid Approach
Use multiple strategies based on context:
-
For timing and basic metadata: Use structured delimiters
ok 1 - test name ⟦time:123,retry:0⟧
-
For complex data (errors, snapshots): Use separate protocol lines
ok 1 - test failed ⟦TSTEST:ERROR⟧ {"message":"Assertion failed","stack":"...","diff":"..."} ⟦/TSTEST:ERROR⟧
-
For human-readable output: Keep standard TAP comments
# Test suite: User Authentication ok 1 - should login
Implementation Plan
Phase 1: Parser Enhancement
- Add new protocol parser alongside existing TAP parser
- Support both old and new formats during transition
- Add protocol version negotiation
Phase 2: Metadata Structure
interface TestMetadata {
// Timing
time: number; // milliseconds
startTime?: number; // Unix timestamp
endTime?: number; // Unix timestamp
// Status
skip?: string; // skip reason
todo?: string; // todo reason
retry?: number; // retry attempt
maxRetries?: number; // max retries allowed
// Error details
error?: {
message: string;
stack?: string;
diff?: string;
actual?: any;
expected?: any;
};
// Test context
file?: string; // source file
line?: number; // line number
column?: number; // column number
// Custom data
tags?: string[]; // test tags
custom?: Record<string, any>;
}
Phase 3: Protocol Messages
Success Message
ok 1 - user authentication works
⟦TSTEST:META:{"time":123,"tags":["auth","unit"]}⟧
Failure Message
not ok 2 - login fails with invalid password
⟦TSTEST:META:{"time":45,"retry":1,"maxRetries":3}⟧
⟦TSTEST:ERROR⟧
{
"message": "Expected 401 but got 500",
"stack": "Error: Expected 401 but got 500\n at Test.run (auth.test.ts:25:10)",
"actual": 500,
"expected": 401
}
⟦/TSTEST:ERROR⟧
Skip Message
ok 3 - database integration test ⟦TSTEST:SKIP:No database connection⟧
Snapshot Communication
⟦TSTEST:SNAPSHOT:user-profile⟧
{
"name": "John Doe",
"email": "john@example.com",
"roles": ["user", "admin"]
}
⟦/TSTEST:SNAPSHOT⟧
Migration Strategy
-
Version Detection: First line indicates protocol version
⟦TSTEST:PROTOCOL:2.0⟧ TAP version 13
-
Gradual Rollout:
- v1.10: Add protocol v2 parser, keep v1 generator
- v1.11: Generate v2 by default, v1 with --legacy flag
- v2.0: Remove v1 support
-
Feature Flags:
tap.settings({ protocol: 'v2', // or 'v1', 'auto' protocolFeatures: { structuredErrors: true, enhancedTiming: true, binaryMarkers: false } });
Benefits of New Protocol
- Reliability: No more regex fragility or description conflicts
- Performance: Faster parsing with clear boundaries
- Extensibility: Easy to add new metadata fields
- Debugging: Rich error information with stack traces and diffs
- Integration: Better IDE and CI/CD tool integration
- Forward Compatible: Room for future enhancements
Example Parser Implementation
class ProtocolV2Parser {
private readonly MARKER_START = '⟦TSTEST:';
private readonly MARKER_END = '⟧';
parseMetadata(line: string): TestMetadata | null {
const start = line.lastIndexOf(this.MARKER_START);
if (start === -1) return null;
const end = line.indexOf(this.MARKER_END, start);
if (end === -1) return null;
const content = line.substring(start + this.MARKER_START.length, end);
const [type, data] = content.split(':', 2);
switch (type) {
case 'META':
return JSON.parse(data);
case 'SKIP':
return { skip: data };
case 'TODO':
return { todo: data };
default:
return null;
}
}
parseTestLine(line: string): ParsedTest {
// First extract any metadata
const metadata = this.parseMetadata(line);
// Then parse the TAP part (without metadata)
const cleanLine = this.removeMetadata(line);
const tapResult = this.parseTAP(cleanLine);
return { ...tapResult, metadata };
}
}
Next Steps
- Implement proof of concept with basic metadata support
- Test with real-world test suites for edge cases
- Benchmark parsing performance
- Get feedback from users
- Finalize protocol specification
- Implement in both tapbundle and tstest