dev/sdl3-parser #1

Open
peterino wants to merge 51 commits from dev/sdl3-parser into dev/variant-engine
2 changed files with 1 additions and 477 deletions
Showing only changes of commit 35a171f804 - Show all commits

@ -0,0 +1 @@
Subproject commit a0f057798ebd70e8262f9fe136349a5b16ba3ec0

View File

@ -1,477 +0,0 @@
# Test Harness Plan for SDL3 Parser Output
## Objective
Create a comprehensive test harness that validates the parser's generated Zig code by:
1. **Compilation check** - Verify the generated code compiles without errors
2. **Syntax validation** - Check that all declarations are syntactically valid
3. **Type checking** - Ensure types are correctly formed
4. **Completeness** - Verify all expected declarations are present
5. **Regression testing** - Detect when parser changes break output
## Requirements Analysis
### What We're Testing
- **Input**: SDL3 C header file (SDL_gpu.h)
- **Parser**: The sdl-parser executable
- **Output**: Generated Zig code (gpu.zig)
- **Dependencies**: The output imports "c.zig" which we'll need to mock
### Challenges
1. Generated code depends on `@import("c.zig")` which doesn't exist in test environment
2. Parser outputs stats to stderr mixed with the actual code
3. Need to separate compilation checks from runtime checks
4. Should test multiple headers, not just SDL_gpu.h
## Test Harness Architecture
### Option 1: Stub-Based Testing (RECOMMENDED)
Create a minimal c.zig stub that provides fake SDL C definitions, allowing the generated code to compile in isolation.
**Pros**:
- Can test compilation without full SDL3 installation
- Fast - no external dependencies
- Can run in CI/CD
- Full control over test environment
**Cons**:
- Need to maintain c.zig stub
- Won't catch ABI mismatches with real SDL3
### Option 2: Integration Testing with Real SDL3
Link against actual SDL3 library and test full compilation chain.
**Pros**:
- Tests real-world usage
- Catches ABI issues
**Cons**:
- Requires SDL3 installation
- Slower
- More brittle (breaks when SDL3 updates)
### Option 3: Hybrid Approach
Use stub-based testing for CI, integration testing for manual verification.
**Recommendation**: Start with Option 1 (stub-based), add Option 2 later if needed.
## Detailed Plan
### Phase 1: Basic Compilation Test
**Goal**: Verify generated code compiles without syntax errors
**Steps**:
1. Create `test_harness.zig` - Main test orchestrator
2. Create `stubs/c.zig` - Minimal SDL C stub
3. Run parser on SDL_gpu.h
4. Strip stats header from output (first 12 lines)
5. Attempt to compile with stub c.zig
6. Report success/failure
**Files to Create**:
- `test_harness/test_harness.zig` - Main test runner
- `test_harness/stubs/c.zig` - Minimal C stubs
- `test_harness/build.zig` - Build configuration
- Update main `build.zig` to add test-harness step
**Implementation**:
```zig
// test_harness.zig
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Run parser
const result = try std.ChildProcess.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"zig-out/bin/sdl-parser",
"../SDL/include/SDL3/SDL_gpu.h",
},
});
defer {
allocator.free(result.stdout);
allocator.free(result.stderr);
}
// Strip stats header (first 12 lines)
const code = try stripHeader(result.stdout, allocator);
defer allocator.free(code);
// Write to test file
try std.fs.cwd().writeFile("test_output/gpu.zig", code);
// Compile test
const compile_result = try std.ChildProcess.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"zig",
"build-lib",
"test_output/gpu.zig",
"-femit-bin=test_output/gpu.o",
},
});
if (compile_result.term.Exited != 0) {
std.debug.print("Compilation failed:\n{s}\n", .{compile_result.stderr});
return error.CompilationFailed;
}
std.debug.print("✅ Compilation test passed!\n", .{});
}
```
### Phase 2: Declaration Counting Test
**Goal**: Verify all expected declarations are present
**Steps**:
1. Parse the generated code
2. Count opaque types, enums, structs, flags, functions
3. Compare against expected counts from parser stats
4. Report any mismatches
**Implementation**:
```zig
const DeclarationCounts = struct {
opaque_types: usize,
enums: usize,
structs: usize,
flags: usize,
functions: usize,
};
fn countDeclarations(code: []const u8) DeclarationCounts {
var counts = DeclarationCounts{};
var lines = std.mem.split(u8, code, "\n");
while (lines.next()) |line| {
if (std.mem.indexOf(u8, line, "opaque {}")) |_| {
counts.opaque_types += 1;
} else if (std.mem.indexOf(u8, line, "= enum(c_int)")) |_| {
counts.enums += 1;
} else if (std.mem.indexOf(u8, line, "= extern struct")) |_| {
counts.structs += 1;
} else if (std.mem.indexOf(u8, line, "= packed struct")) |_| {
counts.flags += 1;
} else if (std.mem.indexOf(u8, line, "pub inline fn")) |_| {
counts.functions += 1;
}
}
return counts;
}
```
### Phase 3: Specific Type Tests
**Goal**: Test specific generated types for correctness
**Steps**:
1. Create test cases for known types
2. Import generated code
3. Verify type properties (size, alignment, fields)
4. Test that enum values are accessible
**Implementation**:
```zig
test "GPUTextureUsageFlags has all fields" {
const gpu = @import("../test_output/gpu.zig");
// These should compile without error
var flags: gpu.GPUTextureUsageFlags = .{};
flags.textureusageSampler = true;
flags.textureusageColorTarget = true;
flags.textureusageDepthStencilTarget = true;
// ... etc
// Check size
try std.testing.expectEqual(@sizeOf(u32), @sizeOf(gpu.GPUTextureUsageFlags));
}
test "GPUPrimitiveType enum values accessible" {
const gpu = @import("../test_output/gpu.zig");
const prim_type = gpu.GPUPrimitiveType.primitivetypeTrianglelist;
try std.testing.expect(prim_type == .primitivetypeTrianglelist);
}
test "No enum values start with numbers" {
const gpu = @import("../test_output/gpu.zig");
// These should compile (would fail if identifiers started with numbers)
_ = gpu.GPUIndexElementSize.indexelementsize16bit;
_ = gpu.GPUSampleCount.samplecount1;
_ = gpu.GPUTextureType.texturetype2d;
}
```
### Phase 4: Golden File Testing
**Goal**: Detect regressions by comparing against known-good output
**Steps**:
1. Generate "golden" reference file from current working parser
2. On subsequent runs, compare output against golden file
3. Report differences
4. Allow updating golden file when changes are intentional
**Implementation**:
```zig
fn compareWithGolden(generated: []const u8, allocator: Allocator) !void {
const golden = try std.fs.cwd().readFileAlloc(
allocator,
"test_harness/golden/gpu.zig",
10 * 1024 * 1024,
);
defer allocator.free(golden);
if (!std.mem.eql(u8, generated, golden)) {
// Show diff
std.debug.print("Output differs from golden file!\n", .{});
// Option: Use external diff tool
const diff_result = try std.ChildProcess.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"diff",
"-u",
"test_harness/golden/gpu.zig",
"test_output/gpu.zig",
},
});
std.debug.print("{s}\n", .{diff_result.stdout});
return error.OutputMismatch;
}
}
```
### Phase 5: Multiple Header Testing
**Goal**: Test parser on multiple SDL3 headers
**Headers to Test**:
- SDL_gpu.h (primary test case)
- SDL_video.h (different patterns)
- SDL_audio.h (different patterns)
- SDL_events.h (lots of enums)
**Implementation**:
```zig
const TestCase = struct {
header: []const u8,
expected_decls: usize,
expected_opaque: usize,
expected_enums: usize,
};
const test_cases = [_]TestCase{
.{
.header = "../SDL/include/SDL3/SDL_gpu.h",
.expected_decls = 169,
.expected_opaque = 13,
.expected_enums = 24,
},
// Add more headers...
};
pub fn runAllTests() !void {
for (test_cases) |test_case| {
std.debug.print("Testing {s}...\n", .{test_case.header});
try testHeader(test_case);
}
}
```
## Build Integration
### Update build.zig
Add test harness steps to main build file:
```zig
// In build.zig, add:
// Test harness executable
const test_harness = b.addExecutable(.{
.name = "test-harness",
.root_module = b.createModule(.{
.root_source_file = b.path("test_harness/test_harness.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(test_harness);
// Test harness run step
const run_harness = b.addRunArtifact(test_harness);
run_harness.step.dependOn(b.getInstallStep());
const harness_step = b.step("test-harness", "Run output validation test harness");
harness_step.dependOn(&run_harness.step);
// Create stub c.zig for testing
const create_stub_cmd = b.addSystemCommand(&[_][]const u8{
"mkdir", "-p", "test_output",
});
create_stub_cmd.step.dependOn(b.getInstallStep());
run_harness.step.dependOn(&create_stub_cmd.step);
```
## Directory Structure
```
lib/sdl3/parser/
├── parser.zig
├── patterns.zig
├── naming.zig
├── codegen.zig
├── types.zig
├── build.zig
├── test_harness/
│ ├── test_harness.zig # Main test orchestrator
│ ├── build.zig # Test harness build config
│ ├── stubs/
│ │ └── c.zig # Minimal SDL C stubs
│ ├── golden/
│ │ └── gpu.zig # Known-good reference output
│ └── tests/
│ ├── compilation_test.zig
│ ├── declaration_test.zig
│ ├── type_test.zig
│ └── regression_test.zig
└── test_output/ # Generated during tests (gitignored)
├── gpu.zig
└── *.o
```
## C Stub Design
Minimal c.zig stub that makes generated code compile:
```zig
// test_harness/stubs/c.zig
// Opaque C types (just declarations, no real implementation)
pub const SDL_Window = opaque {};
pub const SDL_GPUDevice = opaque {};
pub const SDL_GPUBuffer = opaque {};
// ... all other SDL_GPU* types
// C functions (empty implementations)
pub fn SDL_CreateGPUDevice(_: bool, _: bool, _: ?*const anyopaque) ?*SDL_GPUDevice {
return null;
}
pub fn SDL_DestroyGPUDevice(_: ?*SDL_GPUDevice) void {}
// ... stub all functions referenced in generated code
```
**Alternative**: Use `@extern` with no linkage for even simpler stubs.
## Test Execution Workflow
```bash
# 1. Build parser
zig build
# 2. Run test harness
zig build test-harness
# Test harness will:
# - Run parser on SDL_gpu.h
# - Generate test output
# - Compile with stubs
# - Count declarations
# - Compare with golden file
# - Run type tests
# - Report results
```
## Success Criteria
✅ Generated code compiles without errors
✅ All expected declarations present
✅ No invalid identifiers (starting with numbers)
✅ Flag structures have all fields populated
✅ Enum values are accessible
✅ Type sizes match expectations
✅ Output matches golden file (or diff is explained)
✅ Tests run in < 5 seconds
✅ No memory leaks in test harness
## Failure Scenarios & Handling
| Scenario | Detection | Recovery |
|----------|-----------|----------|
| Parser crashes | Check exit code | Report crash, show stderr |
| Compilation fails | Zig build error | Show compiler errors |
| Missing declarations | Count mismatch | List missing items |
| Invalid identifiers | Compilation error | Parser bug - fix naming.zig |
| Empty flags | Field count check | Parser bug - fix patterns.zig |
| Output regression | Golden file diff | Review changes, update golden if OK |
## Future Enhancements
1. **Performance benchmarking** - Track parser speed over time
2. **Fuzz testing** - Generate random C headers
3. **Integration with SDL3 CI** - Auto-test on SDL3 updates
4. **Coverage reporting** - Which C patterns are tested
5. **Error injection** - Test parser error handling
6. **Multi-platform testing** - Test on Windows, macOS, Linux
## Implementation Phases
### Phase 1: MVP (2 hours)
- Basic compilation test
- C stub creation
- Simple pass/fail reporting
### Phase 2: Enhanced (2 hours)
- Declaration counting
- Type-specific tests
- Better error reporting
### Phase 3: Regression (1 hour)
- Golden file generation
- Diff reporting
- Update mechanism
### Phase 4: Multi-header (1 hour)
- Test multiple SDL3 headers
- Test suite organization
**Total Estimated Time**: 6 hours
## Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| C stub maintenance burden | High | Medium | Auto-generate stubs from parser output |
| Golden file becomes stale | Medium | Low | Version control + update script |
| Tests too slow | Low | Medium | Parallel execution, caching |
| False positives | Low | High | Manual review process for failures |
## Dependencies
- Zig 0.14+
- SDL3 headers (for source input)
- diff tool (optional, for golden file comparison)
- No runtime dependencies (stubs only)
## Deliverables
1. ✅ TEST_HARNESS_PLAN.md (this document)
2. ⏳ test_harness/test_harness.zig
3. ⏳ test_harness/stubs/c.zig
4. ⏳ test_harness/build.zig
5. ⏳ Updated main build.zig
6. ⏳ Golden reference file
7. ⏳ README for test harness usage