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 |