2.3.1 • Published 10 months ago

@git.zone/tstest v2.3.1

Weekly downloads
-
License
MIT
Repository
-
Last release
10 months ago

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

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:

    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

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
1.2.0

10 months ago

1.10.2

10 months ago

1.8.0

10 months ago

1.4.0

10 months ago

2.2.1

10 months ago

2.2.0

10 months ago

2.2.2

10 months ago

2.2.5

10 months ago

2.2.4

10 months ago

2.2.6

10 months ago

1.9.4

10 months ago

1.9.3

10 months ago

1.9.2

10 months ago

1.11.0

10 months ago

1.11.4

10 months ago

1.11.3

10 months ago

1.1.0

10 months ago

1.11.2

10 months ago

1.11.1

10 months ago

1.11.5

10 months ago

1.9.1

10 months ago

1.9.0

10 months ago

1.7.0

10 months ago

1.5.0

10 months ago

1.3.1

10 months ago

1.3.0

10 months ago

2.3.0

10 months ago

2.3.1

10 months ago

1.0.91

1 year ago

1.0.95

1 year ago

1.0.94

1 year ago

1.0.93

1 year ago

1.0.92

1 year ago

1.0.96

1 year ago

1.10.1

10 months ago

1.10.0

10 months ago

1.0.90

2 years ago

1.0.89

2 years ago

1.0.88

2 years ago

1.0.87

2 years ago

1.0.86

2 years ago

1.0.84

2 years ago

1.0.83

2 years ago

1.0.82

2 years ago

1.0.81

3 years ago

1.0.80

3 years ago

1.0.79

3 years ago