@git.zone/tstest v2.3.1
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 13Gradual 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
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
10 months ago
10 months ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago