287 lines
6.9 KiB
Markdown
287 lines
6.9 KiB
Markdown
# Improved Internal Protocol Design
|
|
|
|
## Current Issues with TAP Protocol
|
|
|
|
1. **Delimiter Conflict**: Using `#` for metadata conflicts with test descriptions containing `#`
|
|
2. **Ambiguous Parsing**: No clear boundary between test name and metadata
|
|
3. **Limited Extensibility**: Adding new metadata requires regex changes
|
|
4. **Mixed Concerns**: Protocol data mixed with human-readable output
|
|
|
|
## Proposed Internal Protocol v2
|
|
|
|
### Design Principles
|
|
|
|
1. **Clear Separation**: Protocol data must be unambiguously separated from user content
|
|
2. **Extensibility**: Easy to add new metadata without breaking parsers
|
|
3. **Backwards Compatible**: Can coexist with standard TAP for gradual migration
|
|
4. **Machine Readable**: Structured format for reliable parsing
|
|
5. **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:
|
|
|
|
1. **For timing and basic metadata**: Use structured delimiters
|
|
```
|
|
ok 1 - test name ⟦time:123,retry:0⟧
|
|
```
|
|
|
|
2. **For complex data (errors, snapshots)**: Use separate protocol lines
|
|
```
|
|
ok 1 - test failed
|
|
⟦TSTEST:ERROR⟧
|
|
{"message":"Assertion failed","stack":"...","diff":"..."}
|
|
⟦/TSTEST:ERROR⟧
|
|
```
|
|
|
|
3. **For human-readable output**: Keep standard TAP comments
|
|
```
|
|
# Test suite: User Authentication
|
|
ok 1 - should login
|
|
```
|
|
|
|
### Implementation Plan
|
|
|
|
#### Phase 1: Parser Enhancement
|
|
1. Add new protocol parser alongside existing TAP parser
|
|
2. Support both old and new formats during transition
|
|
3. Add protocol version negotiation
|
|
|
|
#### Phase 2: Metadata Structure
|
|
```typescript
|
|
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
|
|
|
|
1. **Version Detection**: First line indicates protocol version
|
|
```
|
|
⟦TSTEST:PROTOCOL:2.0⟧
|
|
TAP version 13
|
|
```
|
|
|
|
2. **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
|
|
|
|
3. **Feature Flags**:
|
|
```typescript
|
|
tap.settings({
|
|
protocol: 'v2', // or 'v1', 'auto'
|
|
protocolFeatures: {
|
|
structuredErrors: true,
|
|
enhancedTiming: true,
|
|
binaryMarkers: false
|
|
}
|
|
});
|
|
```
|
|
|
|
### Benefits of New Protocol
|
|
|
|
1. **Reliability**: No more regex fragility or description conflicts
|
|
2. **Performance**: Faster parsing with clear boundaries
|
|
3. **Extensibility**: Easy to add new metadata fields
|
|
4. **Debugging**: Rich error information with stack traces and diffs
|
|
5. **Integration**: Better IDE and CI/CD tool integration
|
|
6. **Forward Compatible**: Room for future enhancements
|
|
|
|
### Example Parser Implementation
|
|
|
|
```typescript
|
|
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
|
|
|
|
1. Implement proof of concept with basic metadata support
|
|
2. Test with real-world test suites for edge cases
|
|
3. Benchmark parsing performance
|
|
4. Get feedback from users
|
|
5. Finalize protocol specification
|
|
6. Implement in both tapbundle and tstest |