Backlog/lib/sdl3/parser/TEST_HARNESS_PLAN_V2.md

30 KiB

Enhanced Test Harness Plan with Mock Generation

Status Update (2026-01-22)

Recent Changes

  1. Output parameter implemented - Parser now supports --output=<file> instead of only stdout
  2. AST validation added - Generated code is parsed with std.zig.Ast for syntax validation
  3. Critical bug fixes:
    • Fixed pointer type conversion (?*Type instead of *Type)
    • Fixed struct field parsing for pointer types
    • Handles both SDL_Foo * and SDL_Foo* pointer formats
  4. Usage updated - Help text now shows both redirect and --output options

Remaining Tasks

  • Mock generation (--mocks flag) - NOT YET IMPLEMENTED
  • Test project infrastructure
  • Complete AST rendering (currently warns only, doesn't reformat)
  • Fix remaining 59 syntax errors in full SDL_gpu.h output

Overview

This plan extends the original test harness to:

  1. Generate C mocks - Parser creates mock C implementations when --mocks flag is passed ⚠️ TODO
  2. Build complete test project - Compile C mocks + generated Zig bindings ⚠️ TODO
  3. Exercise all functions - Call every generated wrapper function to verify linkage ⚠️ TODO

Objectives

Primary Goals

  1. Compilation validation - Verify generated Zig code compiles (DONE: AST parsing validates)
  2. ⚠️ Mock generation - Auto-generate minimal C mock implementations (TODO)
  3. ⚠️ Linkage testing - Ensure all Zig wrappers link to C mocks correctly (TODO)
  4. ⚠️ Function coverage - Call every generated function at least once (TODO)
  5. ⚠️ Runtime testing - Verify functions execute without crashes (TODO)

Secondary Goals

  • Detect ABI mismatches between generated bindings and C mocks
  • Provide template for integration testing with real SDL3
  • Create reproducible test environment
  • AST-based formatting of generated code (partially done: validates, needs full render)

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Test Harness Workflow                     │
└─────────────────────────────────────────────────────────────┘

1. Parse Header with --output and optional --mocks
   ┌──────────────┐
   │ SDL_gpu.h    │
   └──────┬───────┘
          │
          v
   ┌──────────────┐  --output=gpu.zig [--mocks]
   │ sdl-parser   │──────────────┐
   └──────┬───────┘              │
          │                      │
          v                      v
   ┌──────────────┐      ┌──────────────┐
   │ gpu.zig      │      │ gpu_mock.c   │ (TODO)
   │ (bindings)   │      │ (C mocks)    │
   └──────────────┘      └──────────────┘
          │
          v
   ┌──────────────┐
   │ std.zig.Ast  │ (validates syntax)
   └──────────────┘

2. Build Test Project
   ┌──────────────┐      ┌──────────────┐
   │ gpu.zig      │      │ gpu_mock.c   │
   └──────┬───────┘      └──────┬───────┘
          │                      │
          └──────────┬───────────┘
                     v
              ┌──────────────┐
              │ build.zig    │
              │ (test proj)  │
              └──────┬───────┘
                     v
              ┌──────────────┐
              │ test binary  │
              └──────────────┘

3. Run Tests
   ┌──────────────┐
   │ test_main.zig│
   └──────┬───────┘
          │
          v
   ┌─────────────────────────────┐
   │ Call all wrapper functions  │
   │ - Opaque type creation      │
   │ - Enum usage               │
   │ - Struct initialization    │
   │ - Flag manipulation        │
   │ - Function calls           │
   └─────────────────────────────┘
          │
          v
   ┌──────────────┐
   │ ✅ Success    │
   │ ❌ Failure    │
   └──────────────┘

Part 1: Mock Generation in Parser

Requirements

Input: C header file + --mocks flag Output:

  • gpu.zig - Zig bindings (as before)
  • gpu_mock.c - C mock implementations
  • gpu_mock.h - C mock header (optional, for documentation)

Mock Generation Strategy

For each C declaration, generate minimal stub:

Opaque Types

// Input: typedef struct SDL_GPUDevice SDL_GPUDevice;
// Mock: (no code needed - just forward declaration)

Functions

// Input:
// extern SDL_DECLSPEC SDL_GPUDevice* SDLCALL SDL_CreateGPUDevice(bool debug_mode);

// Mock:
SDL_GPUDevice* SDL_CreateGPUDevice(bool debug_mode) {
    (void)debug_mode;
    return NULL;  // Safe stub: return null pointer
}

For functions returning primitives:

// Input:
// extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsShaderFormats(...);

// Mock:
bool SDL_GPUSupportsShaderFormats(SDL_GPUShaderFormat format_flags, const char *name) {
    (void)format_flags;
    (void)name;
    return false;  // Safe stub: return false/0
}

For void functions:

// Input:
// extern SDL_DECLSPEC void SDLCALL SDL_DestroyGPUDevice(SDL_GPUDevice *device);

// Mock:
void SDL_DestroyGPUDevice(SDL_GPUDevice *device) {
    (void)device;
    // No-op
}

Implementation in Parser

Add Mock Code Generator

File: mock_codegen.zig (new file)

const std = @import("std");
const patterns = @import("patterns.zig");

pub const MockCodeGen = struct {
    decls: []patterns.Declaration,
    allocator: std.mem.Allocator,
    output: std.ArrayList(u8),

    pub fn generate(allocator: std.mem.Allocator, decls: []patterns.Declaration) ![]const u8 {
        var gen = MockCodeGen{
            .decls = decls,
            .allocator = allocator,
            .output = try std.ArrayList(u8).initCapacity(allocator, 4096),
        };

        try gen.writeHeader();
        try gen.writeMocks();
        
        return try gen.output.toOwnedSlice(allocator);
    }

    fn writeHeader(self: *MockCodeGen) !void {
        const header =
            \\// Auto-generated C mock implementations
            \\// DO NOT EDIT - Generated by sdl-parser --mocks
            \\
            \\#include <stdint.h>
            \\#include <stdbool.h>
            \\
            \\// Forward declarations for opaque types
            \\
        ;
        try self.output.appendSlice(self.allocator, header);
    }

    fn writeMocks(self: *MockCodeGen) !void {
        // Write opaque type forward declarations
        for (self.decls) |decl| {
            if (decl == .opaque_type) {
                const opaque = decl.opaque_type;
                try self.output.writer(self.allocator).print(
                    "typedef struct {s} {s};\n",
                    .{opaque.name, opaque.name}
                );
            }
        }

        try self.output.appendSlice(self.allocator, "\n// Function implementations\n\n");

        // Write function mocks
        for (self.decls) |decl| {
            if (decl == .function_decl) {
                try self.writeFunctionMock(decl.function_decl);
            }
        }
    }

    fn writeFunctionMock(self: *MockCodeGen, func: patterns.FunctionDecl) !void {
        // Write return type
        try self.output.appendSlice(self.allocator, func.return_type);
        try self.output.appendSlice(self.allocator, " ");
        
        // Write function name
        try self.output.appendSlice(self.allocator, func.name);
        try self.output.appendSlice(self.allocator, "(");
        
        // Write parameters
        if (func.params.len == 0) {
            try self.output.appendSlice(self.allocator, "void");
        } else {
            for (func.params, 0..) |param, i| {
                if (i > 0) {
                    try self.output.appendSlice(self.allocator, ", ");
                }
                try self.output.appendSlice(self.allocator, param.type_name);
                if (param.name.len > 0) {
                    try self.output.appendSlice(self.allocator, " ");
                    try self.output.appendSlice(self.allocator, param.name);
                }
            }
        }
        
        try self.output.appendSlice(self.allocator, ") {\n");
        
        // Write function body
        // Void all parameters to avoid unused warnings
        for (func.params) |param| {
            if (param.name.len > 0) {
                try self.output.writer(self.allocator).print("    (void){s};\n", .{param.name});
            }
        }
        
        // Return appropriate value
        const return_value = getDefaultReturnValue(func.return_type);
        if (return_value.len > 0) {
            try self.output.writer(self.allocator).print("    return {s};\n", .{return_value});
        }
        
        try self.output.appendSlice(self.allocator, "}\n\n");
    }

    fn getDefaultReturnValue(return_type: []const u8) []const u8 {
        if (std.mem.eql(u8, return_type, "void")) {
            return "";
        } else if (std.mem.indexOf(u8, return_type, "*") != null) {
            return "NULL";  // Pointer types
        } else if (std.mem.eql(u8, return_type, "bool")) {
            return "false";
        } else if (std.mem.eql(u8, return_type, "int") or 
                   std.mem.indexOf(u8, return_type, "int") != null) {
            return "0";
        } else if (std.mem.eql(u8, return_type, "float") or 
                   std.mem.eql(u8, return_type, "double")) {
            return "0.0";
        } else {
            // For enum/struct types, return zero-initialized
            return "0";
        }
    }
};

Update Parser Main

File: parser.zig - STATUS: PARTIALLY DONE

pub fn main() !void {
    // ... existing setup ...

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        // ✅ DONE: Updated usage message
        std.debug.print("Usage: {s} <header-file> [--output=<output-file>] [--mocks]\n", .{args[0]});
        return error.MissingArgument;
    }

    const header_path = args[1];
    
    // ✅ DONE: Parse --output parameter
    var output_file: ?[]const u8 = null;
    var generate_mocks = false;
    
    // TODO: Proper argument parsing for multiple flags
    for (args[2..]) |arg| {
        if (std.mem.startsWith(u8, arg, "--output=")) {
            output_file = arg["--output=".len..];
        } else if (std.mem.eql(u8, arg, "--mocks")) {
            generate_mocks = true;
        }
    }

    // ... existing parsing ...

    // ✅ DONE: Generate Zig code
    const output = try codegen.CodeGen.generate(allocator, decls);
    defer allocator.free(output);
    
    // ✅ DONE: Write to file or stdout
    if (output_file) |file_path| {
        try std.fs.cwd().writeFile(.{ .sub_path = file_path, .data = output });
        std.debug.print("Generated: {s}\n", .{file_path});
    } else {
        _ = try std.posix.write(std.posix.STDOUT_FILENO, output);
    }
    
    // ✅ DONE: AST validation
    const output_z = try allocator.dupeZ(u8, output);
    defer allocator.free(output_z);
    var ast = try std.zig.Ast.parse(allocator, output_z, .zig);
    defer ast.deinit(allocator);
    if (ast.errors.len > 0) {
        std.debug.print("\nWarning: {d} syntax errors detected\n", .{ast.errors.len});
    }

    // ⚠️ TODO: Generate C mocks if requested
    if (generate_mocks) {
        const mock_codegen = @import("mock_codegen.zig");
        const mock_output = try mock_codegen.MockCodeGen.generate(allocator, decls);
        defer allocator.free(mock_output);
        
        const mock_filename = try std.fmt.allocPrint(allocator, "{s}_mock.c", .{
            std.fs.path.stem(header_path)
        });
        defer allocator.free(mock_filename);
        
        try std.fs.cwd().writeFile(.{ .sub_path = mock_filename, .data = mock_output });
        std.debug.print("Generated C mocks: {s}\n", .{mock_filename});
    }
}

Part 2: Test Project Structure

Directory Layout

lib/sdl3/parser/
├── parser.zig
├── patterns.zig
├── naming.zig
├── codegen.zig
├── mock_codegen.zig          # NEW: Mock C code generator
├── types.zig
├── build.zig
│
└── test_project/              # NEW: Complete test harness
    ├── build.zig              # Test project build
    ├── test_main.zig          # Main test runner
    ├── generated/             # Generated files (gitignored)
    │   ├── gpu.zig            # Generated Zig bindings
    │   └── gpu_mock.c         # Generated C mocks
    ├── tests/
    │   ├── opaque_test.zig    # Test opaque type handling
    │   ├── enum_test.zig      # Test enum usage
    │   ├── struct_test.zig    # Test struct usage
    │   ├── flag_test.zig      # Test flag manipulation
    │   └── function_test.zig  # Test all function calls
    └── golden/
        └── gpu.zig            # Reference output for regression

Test Project Build Configuration

File: test_project/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Step 1: Run parser to generate bindings and mocks
    const parser_path = b.path("../zig-out/bin/sdl-parser");
    const header_path = b.path("../../SDL/include/SDL3/SDL_gpu.h");
    
    const run_parser = b.addSystemCommand(&[_][]const u8{
        parser_path.getPath(b),
        header_path.getPath(b),
        "--mocks",
    });
    
    // Capture stdout to generated/gpu.zig
    const gpu_zig_path = b.path("generated/gpu.zig");
    run_parser.setStdOut(.{ .write_to_file = gpu_zig_path });

    // Step 2: Compile C mocks
    const mock_c = b.addObject(.{
        .name = "gpu_mock",
        .target = target,
        .optimize = optimize,
    });
    mock_c.addCSourceFile(.{
        .file = b.path("generated/gpu_mock.c"),
        .flags = &[_][]const u8{"-std=c11"},
    });
    mock_c.linkLibC();
    mock_c.step.dependOn(&run_parser.step);

    // Step 3: Create test executable
    const test_exe = b.addExecutable(.{
        .name = "gpu-test",
        .root_module = b.createModule(.{
            .root_source_file = b.path("test_main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    
    test_exe.linkLibC();
    test_exe.linkLibrary(mock_c);
    test_exe.step.dependOn(&run_parser.step);
    
    b.installArtifact(test_exe);

    // Step 4: Run test
    const run_test = b.addRunArtifact(test_exe);
    run_test.step.dependOn(b.getInstallStep());

    const test_step = b.step("test", "Run all tests");
    test_step.dependOn(&run_test.step);

    // Step 5: Unit tests for generated code
    const unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("test_main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    
    unit_tests.linkLibC();
    unit_tests.linkLibrary(mock_c);
    unit_tests.step.dependOn(&run_parser.step);

    const run_unit_tests = b.addRunArtifact(unit_tests);
    
    const unit_test_step = b.step("test-unit", "Run unit tests");
    unit_test_step.dependOn(&run_unit_tests.step);
}

Main Test Runner

File: test_project/test_main.zig

const std = @import("std");
const gpu = @import("generated/gpu.zig");

pub fn main() !void {
    std.debug.print("SDL3 GPU Binding Test\n", .{});
    std.debug.print("======================\n\n", .{});

    var test_count: usize = 0;
    var pass_count: usize = 0;

    // Test 1: Opaque type functions
    test_count += 1;
    if (testOpaqueTypes()) {
        pass_count += 1;
        std.debug.print("✅ Opaque types test passed\n", .{});
    } else |err| {
        std.debug.print("❌ Opaque types test failed: {}\n", .{err});
    }

    // Test 2: Enum usage
    test_count += 1;
    if (testEnums()) {
        pass_count += 1;
        std.debug.print("✅ Enum test passed\n", .{});
    } else |err| {
        std.debug.print("❌ Enum test failed: {}\n", .{err});
    }

    // Test 3: Struct initialization
    test_count += 1;
    if (testStructs()) {
        pass_count += 1;
        std.debug.print("✅ Struct test passed\n", .{});
    } else |err| {
        std.debug.print("❌ Struct test failed: {}\n", .{err});
    }

    // Test 4: Flag manipulation
    test_count += 1;
    if (testFlags()) {
        pass_count += 1;
        std.debug.print("✅ Flag test passed\n", .{});
    } else |err| {
        std.debug.print("❌ Flag test failed: {}\n", .{err});
    }

    // Test 5: All function calls
    test_count += 1;
    if (testAllFunctions()) {
        pass_count += 1;
        std.debug.print("✅ Function call test passed\n", .{});
    } else |err| {
        std.debug.print("❌ Function call test failed: {}\n", .{err});
    }

    std.debug.print("\nResults: {}/{} tests passed\n", .{pass_count, test_count});
    
    if (pass_count == test_count) {
        std.debug.print("🎉 All tests passed!\n", .{});
        return;
    } else {
        return error.TestsFailed;
    }
}

fn testOpaqueTypes() !void {
    // Test that we can call functions returning opaque pointers
    const device = gpu.createGPUDevice(false, false, null);
    
    // Device should be null from mock, but call should succeed
    if (device) |d| {
        gpu.destroyGPUDevice(d);
    }
}

fn testEnums() !void {
    // Test enum value access
    const prim_type = gpu.GPUPrimitiveType.primitivetypeTrianglelist;
    _ = prim_type;
    
    // Test numeric enum values don't cause issues
    const sample_count = gpu.GPUSampleCount.samplecount4;
    _ = sample_count;
    
    const tex_type = gpu.GPUTextureType.texturetype2dArray;
    _ = tex_type;
}

fn testStructs() !void {
    // Test struct initialization
    const viewport = gpu.GPUViewport{
        .x = 0.0,
        .y = 0.0,
        .w = 800.0,
        .h = 600.0,
        .min_depth = 0.0,
        .max_depth = 1.0,
    };
    _ = viewport;
}

fn testFlags() !void {
    // Test flag creation and manipulation
    var usage: gpu.GPUTextureUsageFlags = .{};
    usage.textureusageSampler = true;
    usage.textureusageColorTarget = true;
    
    try std.testing.expect(usage.textureusageSampler);
    try std.testing.expect(usage.textureusageColorTarget);
    try std.testing.expect(!usage.textureusageDepthStencilTarget);
}

fn testAllFunctions() !void {
    // Call every generated function at least once
    // This ensures all wrappers link correctly
    
    // Device functions
    const device = gpu.createGPUDevice(false, false, null);
    _ = device;
    
    // Query functions
    const supports = gpu.gpuSupportsShaderFormats(.{}, "test");
    _ = supports;
    
    // ... more function calls ...
    // This can be auto-generated from the function list
}

// Unit tests
test "opaque types compile" {
    try testOpaqueTypes();
}

test "enums accessible" {
    try testEnums();
}

test "structs initialize" {
    try testStructs();
}

test "flags manipulate" {
    try testFlags();
}

Function Coverage Generator

File: test_project/tests/function_test.zig

Auto-generate test that calls every function:

const std = @import("std");
const gpu = @import("../generated/gpu.zig");

test "all functions callable" {
    // This test is auto-generated
    // It calls every function with dummy arguments to verify linkage
    
    // createGPUDevice
    _ = gpu.createGPUDevice(false, false, null);
    
    // destroyGPUDevice
    gpu.destroyGPUDevice(null);
    
    // claimWindowForGPUDevice
    _ = gpu.claimWindowForGPUDevice(null, null);
    
    // ... continue for all 94 functions
    // Can be generated by iterating through function_decl list
}

Part 3: Implementation Plan

Phase 0: Infrastructure Improvements (COMPLETED)

Completed Tasks:

  1. Added --output=<file> parameter support
  2. Integrated std.zig.Ast parsing for validation
  3. Fixed pointer type conversion bugs
  4. Fixed struct field parsing for pointer types
  5. Updated usage documentation

Files Modified:

  • parser.zig - Added output parameter, AST validation
  • types.zig - Fixed pointer type handling for both Foo * and Foo*
  • patterns.zig - Fixed struct field parsing algorithm
  • codegen.zig - Kept trailing commas (valid Zig syntax)

Current State:

zig build run -- ../SDL/include/SDL3/SDL_gpu.h --output=gpu.zig
# ✅ Works! Generates 49KB file with 169 declarations
# ⚠️ 59 syntax errors remain (down from 86)

Phase 1: Mock Code Generator (3 hours) ⚠️ TODO

Tasks:

  1. ⚠️ Create mock_codegen.zig
  2. ⚠️ Implement mock generation for:
    • Opaque type forward declarations
    • Function stubs with parameter voiding
    • Default return values
  3. ⚠️ Add tests for mock generator
  4. ⚠️ Update parser.zig to support --mocks flag (argument parsing needs multi-flag support)

Files:

  • mock_codegen.zig (new, ~200 lines) - NOT CREATED YET
  • parser.zig (modify, +20 lines) - Needs multi-flag argument parsing
  • Add mock_codegen tests

Test:

zig build run -- ../SDL/include/SDL3/SDL_gpu.h --output=gpu.zig --mocks
# Should generate gpu.zig and gpu_mock.c

Phase 2: Test Project Setup (2 hours) ⚠️ TODO

Tasks:

  1. ⚠️ Create test_project directory structure
  2. ⚠️ Write test_project/build.zig (needs update for new --output parameter)
  3. ⚠️ Set up generated/ output directory
  4. ⚠️ Configure gitignore

Files:

  • test_project/build.zig (new, ~100 lines) - Will use --output= instead of stdout redirect
  • test_project/.gitignore (new)
  • Update main build.zig to add test-project step

Updated Build Script:

// Use new --output parameter instead of capturing stdout
const run_parser = b.addRunArtifact(parser_exe);
run_parser.addArgs(&[_][]const u8{
    header_path,
    "--output=generated/gpu.zig",
    "--mocks",  // When Phase 1 is complete
});

Phase 3: Basic Test Runner (2 hours) ⚠️ TODO

Tasks:

  1. ⚠️ Write test_main.zig with basic test framework
  2. ⚠️ Implement opaque type tests
  3. ⚠️ Implement enum tests
  4. ⚠️ Implement struct tests
  5. ⚠️ Implement flag tests
  6. ⚠️ Test with actual generated output (includes nullable pointers now)

Files:

  • test_project/test_main.zig (new, ~150 lines)

Note: Tests should verify:

  • Nullable pointer handling (?*Type)
  • Struct fields with correct pointer types
  • Trailing commas in function parameters (valid syntax)

Test:

cd test_project
zig build test

Phase 4: Function Coverage (2 hours) ⚠️ TODO

Tasks:

  1. ⚠️ Generate function call test
  2. ⚠️ Create helper to call all functions
  3. ⚠️ Add safety checks for null returns (critical with ?* types)
  4. ⚠️ Report coverage statistics

Files:

  • test_project/tests/function_test.zig (new, ~300 lines)
  • Helper script to generate from decls

Important: Function tests must handle:

  • Optional return types (?*GPUDevice can be null)
  • Proper unwrapping before use
  • Trailing commas in test code

Phase 5: Golden File & Regression (1 hour) ⚠️ TODO

Tasks:

  1. ⚠️ Generate golden reference file (from current best output)
  2. ⚠️ Add diff comparison
  3. ⚠️ Add update mechanism
  4. ⚠️ Document workflow
  5. ⚠️ Decide on AST-formatted vs raw output for golden files

Files:

  • test_project/golden/gpu.zig (generated)
  • Update test_main.zig with comparison

Decision Needed:

  • Use AST-rendered output (once errors are fixed) for consistent formatting?
  • Or use raw output to preserve original generation logic?

Phase 6: Fix Remaining Syntax Errors (2-4 hours) ⚠️ TODO

Current Issue: 59 syntax errors in full SDL_gpu.h output

Investigation Needed:

  1. ⚠️ Identify patterns causing remaining errors
  2. ⚠️ Fix flag parsing edge cases
  3. ⚠️ Fix function parameter edge cases
  4. ⚠️ Add tests for problematic patterns
  5. ⚠️ Enable full AST rendering instead of just validation

Goal: Get to 0 syntax errors so AST can format the output

Part 4: Usage Workflow

Developer Workflow

# 1. Build parser
cd lib/sdl3/parser
zig build

# 2. Run test project
cd test_project
zig build test

# Output:
# SDL3 GPU Binding Test
# ======================
#
# Generating bindings...
# Generating C mocks...
# Compiling C mocks...
# Building test executable...
# Running tests...
#
# ✅ Opaque types test passed
# ✅ Enum test passed
# ✅ Struct test passed
# ✅ Flag test passed
# ✅ Function call test passed (94/94 functions)
#
# Results: 5/5 tests passed
# 🎉 All tests passed!

CI/CD Integration

# .github/workflows/parser-test.yml
name: Parser Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # For SDL3
      
      - name: Setup Zig
        uses: goto-bus-stop/setup-zig@v2
        with:
          version: 0.14.0
      
      - name: Build Parser
        run: |
          cd lib/sdl3/parser
          zig build          
      
      - name: Run Unit Tests
        run: |
          cd lib/sdl3/parser
          zig build test          
      
      - name: Run Integration Tests
        run: |
          cd lib/sdl3/parser/test_project
          zig build test          

Part 5: Success Criteria

Mock Generation

  • Parser accepts --mocks flag
  • Generates valid C code
  • All functions have stubs
  • Compiles with standard C compiler
  • No undefined symbols

Test Project

  • Compiles without errors
  • Links Zig bindings with C mocks
  • All tests pass
  • Calls all 94 functions
  • No runtime crashes
  • No memory leaks (valgrind clean)

Regression Testing

  • Golden file comparison works
  • Detects output changes
  • Update mechanism functional

Part 6: Advanced Features

Auto-Generate Function Tests

Script to generate function_test.zig from declarations:

// generate_function_tests.zig
const std = @import("std");
const patterns = @import("../patterns.zig");

pub fn generateFunctionTests(decls: []patterns.Declaration, allocator: Allocator) ![]const u8 {
    var output = std.ArrayList(u8).init(allocator);
    
    try output.appendSlice("test \"all functions callable\" {\n");
    
    for (decls) |decl| {
        if (decl == .function_decl) {
            const func = decl.function_decl;
            try output.writer().print("    _ = gpu.{s}(", .{func.name});
            
            // Generate dummy arguments
            for (func.params, 0..) |param, i| {
                if (i > 0) try output.appendSlice(", ");
                const dummy = try getDummyValue(param.type_name, allocator);
                try output.appendSlice(dummy);
            }
            
            try output.appendSlice(");\n");
        }
    }
    
    try output.appendSlice("}\n");
    return output.toOwnedSlice();
}

Memory Safety Testing

Add valgrind/sanitizer testing:

// In build.zig
const sanitize_test = b.addExecutable(.{
    .name = "gpu-test-sanitize",
    .root_source_file = b.path("test_main.zig"),
    .target = target,
    .optimize = .Debug,
});

// Enable sanitizers
sanitize_test.sanitize = .{ .address = true, .undefined = true };

Total Implementation Time

  • Phase 0: Infrastructure - COMPLETED (4 hours spent)

    • Output parameter
    • AST validation
    • Bug fixes (pointer types, struct fields)
  • Phase 1: Mock Generator ⚠️ - 3 hours (TODO)

  • Phase 2: Test Project Setup ⚠️ - 2 hours (TODO)

  • Phase 3: Basic Tests ⚠️ - 2 hours (TODO)

  • Phase 4: Function Coverage ⚠️ - 2 hours (TODO)

  • Phase 5: Regression ⚠️ - 1 hour (TODO)

  • Phase 6: Fix Syntax Errors ⚠️ - 2-4 hours (NEW)

Total Estimated: 12-14 hours remaining Completed: 4 hours (infrastructure improvements) Grand Total: 16-18 hours

Deliverables

  1. Updated parser.zig - DONE: Support for --output parameter, AST validation
  2. Updated types.zig - DONE: Fixed pointer type conversion
  3. Updated patterns.zig - DONE: Fixed struct field parsing
  4. Updated codegen.zig - DONE: Verified trailing comma validity
  5. ⚠️ mock_codegen.zig - C mock generator (TODO)
  6. ⚠️ Updated parser.zig - Support --mocks flag (TODO - needs multi-flag parsing)
  7. ⚠️ test_project/ - Complete test harness (TODO)
  8. ⚠️ test_main.zig - Test runner (TODO)
  9. ⚠️ function_test.zig - Coverage tests (TODO)
  10. ⚠️ Golden reference files (TODO)
  11. ⚠️ Documentation & README updates (TODO)
  12. ⚠️ CI/CD configuration (TODO)

Current Output Quality

Working Test Case (test_small.h):

pub const c = @import("c.zig").c;

pub const GPUDevice = opaque {};

pub const GPUPrimitiveType = enum(c_int) {
    primitivetypeTrianglelist,
    primitivetypeTrianglestrip,
};

pub inline fn createGPUDevice(debug_mode: bool,) ?*GPUDevice {
    return c.SDL_CreateGPUDevice(debug_mode);
}

Status: Valid Zig code, compiles successfully

Full SDL_gpu.h Output:

  • 169 declarations generated
  • 49KB output file
  • 59 syntax errors remaining (needs investigation)
  • Struct pointer fields now correctly parsed
  • Function return types use nullable pointers