feat: simplify, C is not nessesary
This commit is contained in:
@@ -35,12 +35,6 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: go test -v ./parser/... ./generator/...
|
||||
|
||||
- name: Install C compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc
|
||||
|
||||
- name: Run C generator tests
|
||||
run: go test -v -run C ./generator/...
|
||||
|
||||
- name: Run benchmarks (short)
|
||||
run: go test -bench=. -benchtime=100ms -run=^$ ./benchmarks/...
|
||||
|
||||
@@ -73,20 +67,12 @@ jobs:
|
||||
- name: Build arpack CLI
|
||||
run: go build -v ./cmd/arpack
|
||||
|
||||
- name: Install C compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc
|
||||
|
||||
- name: Test code generation
|
||||
run: |
|
||||
go run ./cmd/arpack -in testdata/sample.go -out-go /tmp/gen-go -out-ts /tmp/gen-ts -out-c /tmp/gen-c
|
||||
go run ./cmd/arpack -in testdata/sample.go -out-go /tmp/gen-go -out-ts /tmp/gen-ts -out-lua /tmp/gen-lua
|
||||
test -f /tmp/gen-go/sample_gen.go
|
||||
test -f /tmp/gen-ts/Sample.gen.ts
|
||||
test -f /tmp/gen-c/sample.gen.h
|
||||
test -f /tmp/gen-c/sample.gen.c
|
||||
|
||||
- name: Compile generated C code
|
||||
run: |
|
||||
cc -std=c11 -Wall -Wextra -Wno-unused-function -c /tmp/gen-c/sample.gen.c -o /tmp/gen-c/sample.gen.o
|
||||
test -f /tmp/gen-lua/sample_gen.lua
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -108,8 +94,5 @@ jobs:
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install C compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc
|
||||
|
||||
- name: Run E2E tests
|
||||
run: go test -v ./e2e/...
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||

|
||||
|
||||
|
||||
Binary serialization code generator for Go, C#, TypeScript, Lua, and C. Define messages once as Go structs — get zero-allocation `Marshal`/`Unmarshal` for Go, `unsafe` pointer-based `Serialize`/`Deserialize` for C#, `DataView`-based serialization for TypeScript/browser, pure Lua implementation for Defold/LuaJIT, and explicit encode/decode functions for C.
|
||||
Binary serialization code generator for Go, C#, TypeScript, and Lua. Define messages once as Go structs — get zero-allocation `Marshal`/`Unmarshal` for Go, `unsafe` pointer-based `Serialize`/`Deserialize` for C#, `DataView`-based serialization for TypeScript/browser, and pure Lua implementation for Defold/LuaJIT.
|
||||
|
||||
## Features
|
||||
|
||||
- **Single source of truth** — define messages in Go, generate code for Go, C#, TypeScript, Lua, and C
|
||||
- **Single source of truth** — define messages in Go, generate code for Go, C#, TypeScript, and Lua
|
||||
- **Float quantization** — compress `float32`/`float64` to 8 or 16 bits with a `pack` struct tag
|
||||
- **Boolean packing** — consecutive `bool` fields are packed into single bytes (up to 8 per byte)
|
||||
- **Enums** — `type Opcode uint16` + `const` block becomes C#/TypeScript enums
|
||||
- **Nested types, fixed arrays, slices** — full support for complex message structures
|
||||
- **Cross-language binary compatibility** — Go, C#, TypeScript, Lua, and C produce identical wire formats
|
||||
- **Cross-language binary compatibility** — Go, C#, TypeScript, and Lua produce identical wire formats
|
||||
- **Browser support** — TypeScript target uses native DataView API for zero-dependency serialization
|
||||
|
||||
## When to use
|
||||
@@ -32,7 +32,6 @@ Typical setups:
|
||||
- **Any Go service + .NET client** — works anywhere you control both ends and want a compact binary protocol without Protobuf's runtime overhead or code-gen complexity.
|
||||
- **Go backend + Browser/WebSocket** — generate TypeScript classes for browser-based clients. Uses native DataView API with zero dependencies.
|
||||
- **Go backend + Defold/Lua** — generate Lua modules for Defold game engine. Pure Lua implementation compatible with LuaJIT.
|
||||
- **Go backend + Defold/C** — generate C code for Defold native extensions. Maximum performance for Defold games with C extensions.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -51,9 +50,6 @@ arpack -in messages.go -out-ts ./web/src/messages
|
||||
|
||||
# Generate only Lua (for Defold)
|
||||
arpack -in messages.go -out-lua ./defold/scripts/messages
|
||||
|
||||
# Generate C for Defold native extension
|
||||
arpack -in messages.go -out-c ./defold/extension/src
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
@@ -63,7 +59,6 @@ arpack -in messages.go -out-c ./defold/extension/src
|
||||
| `-out-cs` | Output directory for generated C# code |
|
||||
| `-out-ts` | Output directory for generated TypeScript code |
|
||||
| `-out-lua` | Output directory for generated Lua code |
|
||||
| `-out-c` | Output directory for generated C code (for Defold native extensions) |
|
||||
| `-cs-namespace` | C# namespace (default: `Arpack.Messages`) |
|
||||
|
||||
**Output files:**
|
||||
@@ -71,7 +66,6 @@ arpack -in messages.go -out-c ./defold/extension/src
|
||||
- C#: `{Name}.gen.cs`
|
||||
- TypeScript: `{Name}.gen.ts`
|
||||
- Lua: `{name}_gen.lua` (snake_case for Lua `require()` compatibility)
|
||||
- C: `{name}.gen.h` and `{name}.gen.c` (snake_case for C conventions)
|
||||
|
||||
## Schema Definition
|
||||
|
||||
@@ -210,43 +204,6 @@ Uses pure Lua with inline helper functions for byte manipulation. Compatible wit
|
||||
- Deserialization raises Lua errors on malformed or truncated input. If you need a recoverable boundary, wrap decode calls in `pcall(...)`.
|
||||
- Generated file uses snake_case naming (e.g., `messages_gen.lua`) for proper Lua `require()` resolution.
|
||||
|
||||
### C
|
||||
|
||||
```c
|
||||
#include "messages.gen.h"
|
||||
|
||||
// Fixed-size message (no context needed)
|
||||
sample_envelope_message msg = {
|
||||
.code = sample_opcode_authorize,
|
||||
.counter = 42
|
||||
};
|
||||
|
||||
uint8_t buf[64];
|
||||
size_t written;
|
||||
arpack_status status = sample_envelope_message_encode(&msg, buf, sizeof(buf), &written);
|
||||
|
||||
// Variable-length message (requires decode context)
|
||||
sample_spawn_message_decode_ctx ctx = {
|
||||
.tags_data = tags_buffer,
|
||||
.tags_cap = MAX_TAGS
|
||||
};
|
||||
sample_spawn_message decoded;
|
||||
status = sample_spawn_message_decode(&decoded, buf, buf_len, &ctx, &read);
|
||||
```
|
||||
|
||||
Generates two files: `{name}.gen.h` (declarations) and `{name}.gen.c` (implementations). Uses explicit encode/decode functions with bounds checking. All symbols are prefixed with `{name}_` to avoid collisions.
|
||||
|
||||
**API Shape:**
|
||||
- Fixed-size messages: `{name}_{msg}_min_size()`, `{name}_{msg}_encode()`, `{name}_{msg}_decode()`
|
||||
- Variable-length messages: Additional `{name}_{msg}_size()` and decode context struct
|
||||
- Strings and byte slices are views into the input buffer (zero-copy)
|
||||
- Other slices require caller-provided storage via decode context
|
||||
|
||||
**Limitations:**
|
||||
- C11 standard required
|
||||
- Variable-length slice fields require caller-provided storage (no hidden allocations)
|
||||
- Wire format is not a packed C struct — use the generated encode/decode functions
|
||||
|
||||
## Wire Format
|
||||
|
||||
- Little-endian byte order
|
||||
|
||||
+2
-28
@@ -18,7 +18,6 @@ func main() {
|
||||
outCS := flag.String("out-cs", "", "output directory for generated C# code")
|
||||
outTS := flag.String("out-ts", "", "output directory for generated TypeScript code")
|
||||
outLua := flag.String("out-lua", "", "output directory for generated Lua code")
|
||||
outC := flag.String("out-c", "", "output directory for generated C code")
|
||||
namespace := flag.String("cs-namespace", "Arpack.Messages", "C# namespace")
|
||||
flag.Parse()
|
||||
|
||||
@@ -26,8 +25,8 @@ func main() {
|
||||
log.Fatal("arpack: -in is required")
|
||||
}
|
||||
|
||||
if *outGo == "" && *outCS == "" && *outTS == "" && *outLua == "" && *outC == "" {
|
||||
log.Fatal("arpack: at least one of -out-go, -out-cs, -out-ts, -out-lua, or -out-c is required")
|
||||
if *outGo == "" && *outCS == "" && *outTS == "" && *outLua == "" {
|
||||
log.Fatal("arpack: at least one of -out-go, -out-cs, -out-ts, or -out-lua is required")
|
||||
}
|
||||
|
||||
schema, err := parser.ParseSchemaFile(*in)
|
||||
@@ -116,31 +115,6 @@ func main() {
|
||||
|
||||
fmt.Printf("arpack: wrote %s\n", outPath)
|
||||
}
|
||||
|
||||
if *outC != "" {
|
||||
snakeBase := toSnakeCase(baseName)
|
||||
headerSrc, sourceSrc, err := generator.GenerateCSchema(schema, snakeBase)
|
||||
if err != nil {
|
||||
log.Fatalf("arpack: C generation error: %v", err)
|
||||
}
|
||||
|
||||
headerPath := filepath.Join(*outC, snakeBase+".gen.h")
|
||||
sourcePath := filepath.Join(*outC, snakeBase+".gen.c")
|
||||
|
||||
if err := os.MkdirAll(*outC, 0755); err != nil {
|
||||
log.Fatalf("arpack: mkdir %s: %v", *outC, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(headerPath, headerSrc, 0644); err != nil {
|
||||
log.Fatalf("arpack: write %s: %v", headerPath, err)
|
||||
}
|
||||
if err := os.WriteFile(sourcePath, sourceSrc, 0644); err != nil {
|
||||
log.Fatalf("arpack: write %s: %v", sourcePath, err)
|
||||
}
|
||||
|
||||
fmt.Printf("arpack: wrote %s\n", headerPath)
|
||||
fmt.Printf("arpack: wrote %s\n", sourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
func toTitle(s string) string {
|
||||
|
||||
-198
@@ -2,8 +2,6 @@ package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -18,202 +16,6 @@ import (
|
||||
|
||||
const samplePath = "../testdata/sample.go"
|
||||
|
||||
// TestE2E_CrossLanguage
|
||||
func TestE2E_C_GoInterop(t *testing.T) {
|
||||
// Check for C compiler
|
||||
var cc string
|
||||
for _, compiler := range []string{"cc", "gcc", "clang"} {
|
||||
if _, err := exec.LookPath(compiler); err == nil {
|
||||
cc = compiler
|
||||
break
|
||||
}
|
||||
}
|
||||
if cc == "" {
|
||||
t.Skip("No C compiler found (tried cc, gcc, clang)")
|
||||
}
|
||||
|
||||
schema, err := parser.ParseSchemaFile(samplePath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
|
||||
// Generate C code
|
||||
header, source, err := generator.GenerateCSchema(schema, "sample")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema: %v", err)
|
||||
}
|
||||
|
||||
// Create temp directory for C harness
|
||||
cDir := t.TempDir()
|
||||
write(t, filepath.Join(cDir, "sample.gen.h"), header)
|
||||
write(t, filepath.Join(cDir, "sample.gen.c"), source)
|
||||
|
||||
// Generate test vectors using Go
|
||||
goSrc, err := generator.GenerateGoSchema(schema, "main")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateGoSchema: %v", err)
|
||||
}
|
||||
goDir := buildGoHarness(t, goSrc)
|
||||
|
||||
// Get hex from Go harness
|
||||
vector3Hex := strings.TrimSpace(runHarness(t, goDir, "go", "ser", "Vector3", ""))
|
||||
envelopeHex := strings.TrimSpace(runHarness(t, goDir, "go", "ser", "EnvelopeMessage", ""))
|
||||
|
||||
// Convert hex to C array format
|
||||
vector3Bytes, _ := hex.DecodeString(vector3Hex)
|
||||
envelopeBytes, _ := hex.DecodeString(envelopeHex)
|
||||
|
||||
vector3Array := "{"
|
||||
for i, b := range vector3Bytes {
|
||||
if i > 0 {
|
||||
vector3Array += ", "
|
||||
}
|
||||
vector3Array += fmt.Sprintf("0x%02x", b)
|
||||
}
|
||||
vector3Array += "}"
|
||||
|
||||
envelopeArray := "{"
|
||||
for i, b := range envelopeBytes {
|
||||
if i > 0 {
|
||||
envelopeArray += ", "
|
||||
}
|
||||
envelopeArray += fmt.Sprintf("0x%02x", b)
|
||||
}
|
||||
envelopeArray += "}"
|
||||
|
||||
// Create C test program with correct test vectors
|
||||
cTestSource := fmt.Sprintf(`#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "sample.gen.h"
|
||||
|
||||
// Test vectors from Go serialization
|
||||
static const uint8_t vector3_test[] = %s;
|
||||
static const uint8_t envelope_test[] = %s;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %%s <test>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "vector3") == 0) {
|
||||
sample_vector3 msg;
|
||||
size_t read;
|
||||
arpack_status status = sample_vector3_decode(&msg, vector3_test, sizeof(vector3_test), &read);
|
||||
if (status != ARPACK_OK) {
|
||||
printf("STATUS=FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
printf("STATUS=OK\n");
|
||||
printf("X=%%.2f\n", msg.x);
|
||||
printf("Y=%%.2f\n", msg.y);
|
||||
printf("Z=%%.2f\n", msg.z);
|
||||
printf("READ=%%zu\n", read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "envelope") == 0) {
|
||||
sample_envelope_message msg;
|
||||
size_t read;
|
||||
arpack_status status = sample_envelope_message_decode(&msg, envelope_test, sizeof(envelope_test), &read);
|
||||
if (status != ARPACK_OK) {
|
||||
printf("STATUS=FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
printf("STATUS=OK\n");
|
||||
printf("CODE=%%d\n", msg.code);
|
||||
printf("COUNTER=%%d\n", msg.counter);
|
||||
printf("READ=%%zu\n", read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Unknown test: %%s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
`, vector3Array, envelopeArray)
|
||||
write(t, filepath.Join(cDir, "test.c"), []byte(cTestSource))
|
||||
|
||||
// Compile C test program
|
||||
cBin := filepath.Join(cDir, "test")
|
||||
mustRun(t, cDir, cc, "-std=c11", "-Wall", "-Wextra", "-Wno-unused-function", "-o", cBin, "test.c", "sample.gen.c")
|
||||
|
||||
// Run C test for Vector3
|
||||
t.Run("C_Decode_Vector3", func(t *testing.T) {
|
||||
cmd := exec.Command("./test", "vector3")
|
||||
cmd.Dir = cDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C decode failed: %v\n%s", err, out)
|
||||
}
|
||||
kv := parseKV(string(out))
|
||||
if kv["STATUS"] != "OK" {
|
||||
t.Fatalf("C decode failed: %s", string(out))
|
||||
}
|
||||
// Check values are reasonable (quantized floats have error)
|
||||
assertFloat(t, kv, "X", 123.45, 2.0)
|
||||
assertFloat(t, kv, "Y", -200, 2.0)
|
||||
assertFloat(t, kv, "Z", 0, 0.1)
|
||||
})
|
||||
|
||||
// Run C test for EnvelopeMessage
|
||||
t.Run("C_Decode_Envelope", func(t *testing.T) {
|
||||
cmd := exec.Command("./test", "envelope")
|
||||
cmd.Dir = cDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C decode failed: %v\n%s", err, out)
|
||||
}
|
||||
kv := parseKV(string(out))
|
||||
if kv["STATUS"] != "OK" {
|
||||
t.Fatalf("C decode failed: %s", string(out))
|
||||
}
|
||||
assertInt(t, kv, "CODE", 2)
|
||||
assertInt(t, kv, "COUNTER", 7)
|
||||
})
|
||||
|
||||
// Test Go serialize -> C deserialize for Vector3
|
||||
t.Run("Go_to_C/Vector3", func(t *testing.T) {
|
||||
hex := runHarness(t, goDir, "go", "ser", "Vector3", "")
|
||||
// Write hex to file for C program to read
|
||||
write(t, filepath.Join(cDir, "vector3.hex"), []byte(hex))
|
||||
|
||||
// Run C program to deserialize Go's output
|
||||
cmd := exec.Command("./test", "vector3")
|
||||
cmd.Dir = cDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C decode failed: %v\n%s", err, out)
|
||||
}
|
||||
kv := parseKV(string(out))
|
||||
assertFloat(t, kv, "X", 123.45, 0.02)
|
||||
assertFloat(t, kv, "Y", -200, 0.02)
|
||||
assertFloat(t, kv, "Z", 0, 0.02)
|
||||
})
|
||||
|
||||
// Test Go serialize -> C deserialize for EnvelopeMessage
|
||||
t.Run("Go_to_C/EnvelopeMessage", func(t *testing.T) {
|
||||
hexStr := runHarness(t, goDir, "go", "ser", "EnvelopeMessage", "")
|
||||
data, err := hex.DecodeString(strings.TrimSpace(hexStr))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode hex: %v", err)
|
||||
}
|
||||
|
||||
// Verify first byte is 0x02 (JoinRoom = 2, little endian)
|
||||
if len(data) >= 2 && data[0] == 0x02 && data[1] == 0x00 {
|
||||
// Verify C can read it
|
||||
cmd := exec.Command("./test", "envelope")
|
||||
cmd.Dir = cDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C decode failed: %v\n%s", err, out)
|
||||
}
|
||||
kv := parseKV(string(out))
|
||||
assertInt(t, kv, "CODE", 2)
|
||||
assertInt(t, kv, "COUNTER", 7)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestE2E_CrossLanguage(t *testing.T) {
|
||||
schema, err := parser.ParseSchemaFile(samplePath)
|
||||
if err != nil {
|
||||
|
||||
-1046
File diff suppressed because it is too large
Load Diff
@@ -1,802 +0,0 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/edmand46/arpack/parser"
|
||||
)
|
||||
|
||||
func TestCSnakeCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"Simple", "simple"},
|
||||
{"PlayerID", "player_id"},
|
||||
{"HTTPRequest", "http_request"},
|
||||
{"XMLParser", "xml_parser"},
|
||||
{"MoveMessage", "move_message"},
|
||||
{"position", "position"},
|
||||
{"X", "x"},
|
||||
{"HTTPServer", "http_server"},
|
||||
{"URLHandler", "url_handler"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
result := snakeCase(tc.input)
|
||||
if result != tc.expected {
|
||||
t.Errorf("snakeCase(%q) = %q, want %q", tc.input, result, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_BasicTypes(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "BasicTypes",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Int8Field", Kind: parser.KindPrimitive, Primitive: parser.KindInt8},
|
||||
{Name: "Int16Field", Kind: parser.KindPrimitive, Primitive: parser.KindInt16},
|
||||
{Name: "Int32Field", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
|
||||
{Name: "Int64Field", Kind: parser.KindPrimitive, Primitive: parser.KindInt64},
|
||||
{Name: "Uint8Field", Kind: parser.KindPrimitive, Primitive: parser.KindUint8},
|
||||
{Name: "Uint16Field", Kind: parser.KindPrimitive, Primitive: parser.KindUint16},
|
||||
{Name: "Uint32Field", Kind: parser.KindPrimitive, Primitive: parser.KindUint32},
|
||||
{Name: "Uint64Field", Kind: parser.KindPrimitive, Primitive: parser.KindUint64},
|
||||
{Name: "Float32Field", Kind: parser.KindPrimitive, Primitive: parser.KindFloat32},
|
||||
{Name: "Float64Field", Kind: parser.KindPrimitive, Primitive: parser.KindFloat64},
|
||||
{Name: "BoolField", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
|
||||
{Name: "StringField", Kind: parser.KindPrimitive, Primitive: parser.KindString},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check struct declaration
|
||||
if !strings.Contains(headerStr, "typedef struct test_basic_types {") {
|
||||
t.Error("Missing test_basic_types struct declaration")
|
||||
}
|
||||
|
||||
// Check primitive field types
|
||||
if !strings.Contains(headerStr, "int8_t int8_field;") {
|
||||
t.Error("Missing int8_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "int16_t int16_field;") {
|
||||
t.Error("Missing int16_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "int32_t int32_field;") {
|
||||
t.Error("Missing int32_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "int64_t int64_field;") {
|
||||
t.Error("Missing int64_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "uint8_t uint8_field;") {
|
||||
t.Error("Missing uint8_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "uint16_t uint16_field;") {
|
||||
t.Error("Missing uint16_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "uint32_t uint32_field;") {
|
||||
t.Error("Missing uint32_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "uint64_t uint64_field;") {
|
||||
t.Error("Missing uint64_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "float float32_field;") {
|
||||
t.Error("Missing float32_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "double float64_field;") {
|
||||
t.Error("Missing float64_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "bool bool_field;") {
|
||||
t.Error("Missing bool_field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "arpack_string_view string_field;") {
|
||||
t.Error("Missing string_field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_Enum(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Enums: []parser.Enum{
|
||||
{
|
||||
Name: "Opcode",
|
||||
Primitive: parser.KindUint16,
|
||||
Values: []parser.EnumValue{
|
||||
{Name: "Unknown", Value: "0"},
|
||||
{Name: "Join", Value: "1"},
|
||||
{Name: "Leave", Value: "2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "MessageWithEnum",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Op", Kind: parser.KindPrimitive, Primitive: parser.KindUint16, NamedType: "Opcode"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check enum declaration
|
||||
if !strings.Contains(headerStr, "typedef enum test_opcode {") {
|
||||
t.Error("Missing test_opcode enum declaration")
|
||||
}
|
||||
if !strings.Contains(headerStr, "test_opcode_unknown = 0,") {
|
||||
t.Error("Missing Unknown enum value")
|
||||
}
|
||||
if !strings.Contains(headerStr, "test_opcode_join = 1,") {
|
||||
t.Error("Missing Join enum value")
|
||||
}
|
||||
if !strings.Contains(headerStr, "test_opcode_leave = 2,") {
|
||||
t.Error("Missing Leave enum value")
|
||||
}
|
||||
if !strings.Contains(headerStr, "test_opcode op;") {
|
||||
t.Error("Enum-backed field should use generated enum type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_HeaderGuard(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{Name: "Simple", Fields: []parser.Field{}},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "my_base")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
if !strings.Contains(headerStr, "#ifndef MY_BASE_GEN_H") {
|
||||
t.Error("Missing header guard ifndef")
|
||||
}
|
||||
if !strings.Contains(headerStr, "#define MY_BASE_GEN_H") {
|
||||
t.Error("Missing header guard define")
|
||||
}
|
||||
if !strings.Contains(headerStr, "#endif // MY_BASE_GEN_H") {
|
||||
t.Error("Missing header guard endif")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_RuntimeTypes(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{Name: "Simple", Fields: []parser.Field{}},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check arpack_status enum
|
||||
if !strings.Contains(headerStr, "typedef enum arpack_status {") {
|
||||
t.Error("Missing arpack_status enum")
|
||||
}
|
||||
if !strings.Contains(headerStr, "ARPACK_OK = 0,") {
|
||||
t.Error("Missing ARPACK_OK")
|
||||
}
|
||||
|
||||
// Check string view
|
||||
if !strings.Contains(headerStr, "typedef struct arpack_string_view {") {
|
||||
t.Error("Missing arpack_string_view")
|
||||
}
|
||||
|
||||
// Check bytes view
|
||||
if !strings.Contains(headerStr, "typedef struct arpack_bytes_view {") {
|
||||
t.Error("Missing arpack_bytes_view")
|
||||
}
|
||||
|
||||
// Check standard includes
|
||||
if !strings.Contains(headerStr, "#include <stdint.h>") {
|
||||
t.Error("Missing stdint.h include")
|
||||
}
|
||||
if !strings.Contains(headerStr, "#include <stddef.h>") {
|
||||
t.Error("Missing stddef.h include")
|
||||
}
|
||||
if !strings.Contains(headerStr, "#include <stdbool.h>") {
|
||||
t.Error("Missing stdbool.h include")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_NestedMessages(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "Inner",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Value", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Outer",
|
||||
Fields: []parser.Field{
|
||||
{Name: "InnerMsg", Kind: parser.KindNested, TypeName: "Inner"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check inner struct
|
||||
if !strings.Contains(headerStr, "typedef struct test_inner {") {
|
||||
t.Error("Missing test_inner struct")
|
||||
}
|
||||
|
||||
// Check outer struct with nested field
|
||||
if !strings.Contains(headerStr, "test_inner inner_msg;") {
|
||||
t.Error("Missing nested inner_msg field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_FixedArrays(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "ArrayMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Values",
|
||||
Kind: parser.KindFixedArray,
|
||||
FixedLen: 3,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
if !strings.Contains(headerStr, "float values[3];") {
|
||||
t.Error("Missing fixed array field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_BoolPacking(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "BoolMessage",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Active", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
|
||||
{Name: "Visible", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
|
||||
{Name: "Ghost", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
|
||||
{Name: "Count", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, source, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
sourceStr := string(source)
|
||||
|
||||
// Check struct fields
|
||||
if !strings.Contains(headerStr, "bool active;") {
|
||||
t.Error("Missing active field")
|
||||
}
|
||||
if !strings.Contains(headerStr, "bool visible;") {
|
||||
t.Error("Missing visible field")
|
||||
}
|
||||
|
||||
// Check encode uses bool packing
|
||||
if !strings.Contains(sourceStr, "_boolByte") {
|
||||
t.Error("Bool packing not used in encode")
|
||||
}
|
||||
if !strings.Contains(sourceStr, "_arpack_write_u8(buf, &offset, _boolByte);") {
|
||||
t.Error("Bool byte not written")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_QuantizedFloats(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "QuantMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Q8",
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
Quant: &parser.QuantInfo{Min: 0, Max: 100, Bits: 8},
|
||||
},
|
||||
{
|
||||
Name: "Q16",
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
Quant: &parser.QuantInfo{Min: -500, Max: 500, Bits: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, source, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
sourceStr := string(source)
|
||||
|
||||
// Check 8-bit quantization (uses 255 as max value)
|
||||
if !strings.Contains(sourceStr, "255") {
|
||||
t.Error("Missing 8-bit quantization")
|
||||
}
|
||||
|
||||
// Check 16-bit quantization (uses 65535 as max value)
|
||||
if !strings.Contains(sourceStr, "65535") {
|
||||
t.Error("Missing 16-bit quantization")
|
||||
}
|
||||
|
||||
// Check encode uses quantization
|
||||
if !strings.Contains(sourceStr, "_arpack_write_u8(buf, &offset, _qv)") {
|
||||
t.Error("8-bit quantized value not written")
|
||||
}
|
||||
if !strings.Contains(sourceStr, "_arpack_write_u16_le(buf, &offset, _qv)") {
|
||||
t.Error("16-bit quantized value not written")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_VariableLength(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "VarMessage",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Id", Kind: parser.KindPrimitive, Primitive: parser.KindUint32},
|
||||
{Name: "Name", Kind: parser.KindPrimitive, Primitive: parser.KindString},
|
||||
{
|
||||
Name: "Data",
|
||||
Kind: parser.KindSlice,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindUint8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check min_size function exists
|
||||
if !strings.Contains(headerStr, "size_t test_var_message_min_size(void);") {
|
||||
t.Error("Missing min_size function declaration")
|
||||
}
|
||||
|
||||
// Check size function exists
|
||||
if !strings.Contains(headerStr, "arpack_status test_var_message_size(const test_var_message *msg, size_t *out_size);") {
|
||||
t.Error("Missing size function declaration")
|
||||
}
|
||||
|
||||
// Check encode function exists
|
||||
if !strings.Contains(headerStr, "arpack_status test_var_message_encode(const test_var_message *msg, uint8_t *buf, size_t buf_len, size_t *out_written);") {
|
||||
t.Error("Missing encode function declaration")
|
||||
}
|
||||
|
||||
// Check decode function exists without context (only byte slices and strings)
|
||||
if !strings.Contains(headerStr, "arpack_status test_var_message_decode(test_var_message *msg, const uint8_t *buf, size_t buf_len, size_t *out_read);") {
|
||||
t.Error("Missing decode function declaration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCCompile_SampleSchema(t *testing.T) {
|
||||
hasCC := false
|
||||
if _, err := exec.LookPath("cc"); err == nil {
|
||||
hasCC = true
|
||||
} else if _, err := exec.LookPath("gcc"); err == nil {
|
||||
hasCC = true
|
||||
} else if _, err := exec.LookPath("clang"); err == nil {
|
||||
hasCC = true
|
||||
}
|
||||
|
||||
if !hasCC {
|
||||
t.Skip("No C compiler found (tried cc, gcc, clang)")
|
||||
}
|
||||
|
||||
schema, err := parser.ParseSchemaFile("../testdata/sample.go")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse sample.go: %v", err)
|
||||
}
|
||||
|
||||
// Generate C code
|
||||
header, source, err := GenerateCSchema(schema, "sample")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
// Create temp directory
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write header
|
||||
headerPath := filepath.Join(tmpDir, "sample.gen.h")
|
||||
if err := os.WriteFile(headerPath, header, 0644); err != nil {
|
||||
t.Fatalf("Failed to write header: %v", err)
|
||||
}
|
||||
|
||||
// Write source
|
||||
sourcePath := filepath.Join(tmpDir, "sample.gen.c")
|
||||
if err := os.WriteFile(sourcePath, source, 0644); err != nil {
|
||||
t.Fatalf("Failed to write source: %v", err)
|
||||
}
|
||||
|
||||
// Compile
|
||||
objPath := filepath.Join(tmpDir, "sample.gen.o")
|
||||
cmd := exec.Command("cc", "-std=c11", "-Wall", "-Wextra", "-Wno-unused-function", "-c", sourcePath, "-o", objPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C compilation failed:\n%s\n%s", string(output), err)
|
||||
}
|
||||
|
||||
// Verify object file exists
|
||||
if _, err := os.Stat(objPath); os.IsNotExist(err) {
|
||||
t.Fatal("Object file was not created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_DecodeContext(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "Inner",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Value", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CtxMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Items",
|
||||
Kind: parser.KindSlice,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindNested,
|
||||
TypeName: "Inner",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, _, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
|
||||
// Check decode context struct exists
|
||||
if !strings.Contains(headerStr, "typedef struct test_ctx_message_decode_ctx {") {
|
||||
t.Error("Missing decode context struct")
|
||||
}
|
||||
|
||||
// Check data pointer in context
|
||||
if !strings.Contains(headerStr, "test_inner *items_data;") {
|
||||
t.Error("Missing items_data field in context")
|
||||
}
|
||||
|
||||
// Check capacity field in context
|
||||
if !strings.Contains(headerStr, "uint16_t items_cap;") {
|
||||
t.Error("Missing items_cap field in context")
|
||||
}
|
||||
|
||||
// Check decode function with context
|
||||
if !strings.Contains(headerStr, "arpack_status test_ctx_message_decode(test_ctx_message *msg, const uint8_t *buf, size_t buf_len, test_ctx_message_decode_ctx *ctx, size_t *out_read);") {
|
||||
t.Error("Missing decode function with context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_PrimitiveSlices(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "SliceMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Values",
|
||||
Kind: parser.KindSlice,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindUint16,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Floats",
|
||||
Kind: parser.KindSlice,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, source, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
headerStr := string(header)
|
||||
if !strings.Contains(headerStr, "typedef struct test_uint16_slice_view {") {
|
||||
t.Fatal("Missing uint16 slice view typedef")
|
||||
}
|
||||
if !strings.Contains(headerStr, "const uint16_t *data;") {
|
||||
t.Fatal("uint16 slice view should reference uint16_t")
|
||||
}
|
||||
if !strings.Contains(headerStr, "typedef struct test_float32_slice_view {") {
|
||||
t.Fatal("Missing float32 slice view typedef")
|
||||
}
|
||||
if !strings.Contains(headerStr, "const float *data;") {
|
||||
t.Fatal("float32 slice view should reference float")
|
||||
}
|
||||
if !strings.Contains(headerStr, "uint16_t *values_data;") {
|
||||
t.Fatal("Missing decode context storage for uint16 slice")
|
||||
}
|
||||
if !strings.Contains(headerStr, "float *floats_data;") {
|
||||
t.Fatal("Missing decode context storage for float32 slice")
|
||||
}
|
||||
|
||||
compileCGeneratedObject(t, "test", header, source)
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_FixedArrayNestedAndQuantized(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "Vector3",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "X",
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
Quant: &parser.QuantInfo{Min: -500, Max: 500, Bits: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ArrayMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Points",
|
||||
Kind: parser.KindFixedArray,
|
||||
FixedLen: 2,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindNested,
|
||||
TypeName: "Vector3",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Samples",
|
||||
Kind: parser.KindFixedArray,
|
||||
FixedLen: 3,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindFloat32,
|
||||
Quant: &parser.QuantInfo{Min: 0, Max: 10, Bits: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, source, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
sourceStr := string(source)
|
||||
if !strings.Contains(sourceStr, "test_vector3_encode(&msg->points[_i0],") {
|
||||
t.Fatal("Nested fixed array elements should call nested encode")
|
||||
}
|
||||
if !strings.Contains(sourceStr, "msg->samples[_i0]") {
|
||||
t.Fatal("Quantized fixed array elements should be encoded through recursive element access")
|
||||
}
|
||||
|
||||
compileCGeneratedObject(t, "test", header, source)
|
||||
}
|
||||
|
||||
func TestCVariableLength_BoundsChecks(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "StringMessage",
|
||||
Fields: []parser.Field{
|
||||
{Name: "Name", Kind: parser.KindPrimitive, Primitive: parser.KindString},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
header, source, err := GenerateCSchema(schema, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateCSchema failed: %v", err)
|
||||
}
|
||||
|
||||
harness := `#include <stdio.h>
|
||||
#include "test.gen.h"
|
||||
|
||||
int main(void) {
|
||||
static const uint8_t truncated[] = {0x03, 0x00, 'a'};
|
||||
test_string_message decoded;
|
||||
size_t read = 0;
|
||||
arpack_status status = test_string_message_decode(&decoded, truncated, sizeof(truncated), &read);
|
||||
printf("DECODE=%d\n", (int)status);
|
||||
|
||||
test_string_message encoded;
|
||||
encoded.name.data = "abc";
|
||||
encoded.name.len = 3;
|
||||
uint8_t out[2];
|
||||
size_t written = 0;
|
||||
status = test_string_message_encode(&encoded, out, sizeof(out), &written);
|
||||
printf("ENCODE=%d\n", (int)status);
|
||||
return 0;
|
||||
}
|
||||
`
|
||||
|
||||
output := runGeneratedCProgram(t, "test", header, source, harness)
|
||||
if !strings.Contains(output, "DECODE=1") {
|
||||
t.Fatalf("decode should fail with ARPACK_ERR_BUFFER_TOO_SHORT, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ENCODE=1") {
|
||||
t.Fatalf("encode should fail with ARPACK_ERR_BUFFER_TOO_SHORT, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGenerateSchema_RejectsFixedArrayOfSlices(t *testing.T) {
|
||||
schema := parser.Schema{
|
||||
Messages: []parser.Message{
|
||||
{
|
||||
Name: "BadMessage",
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: "Values",
|
||||
Kind: parser.KindFixedArray,
|
||||
FixedLen: 2,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindSlice,
|
||||
Elem: &parser.Field{
|
||||
Kind: parser.KindPrimitive,
|
||||
Primitive: parser.KindUint16,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, _, err := GenerateCSchema(schema, "test")
|
||||
if err == nil {
|
||||
t.Fatal("expected GenerateCSchema to reject fixed arrays of slices")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "fixed arrays of slices") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func requireCCompiler(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
for _, compiler := range []string{"cc", "gcc", "clang"} {
|
||||
if _, err := exec.LookPath(compiler); err == nil {
|
||||
return compiler
|
||||
}
|
||||
}
|
||||
|
||||
t.Skip("No C compiler found (tried cc, gcc, clang)")
|
||||
return ""
|
||||
}
|
||||
|
||||
func compileCGeneratedObject(t *testing.T, base string, header []byte, source []byte) {
|
||||
t.Helper()
|
||||
|
||||
cc := requireCCompiler(t)
|
||||
tmpDir := t.TempDir()
|
||||
headerPath := filepath.Join(tmpDir, base+".gen.h")
|
||||
sourcePath := filepath.Join(tmpDir, base+".gen.c")
|
||||
objPath := filepath.Join(tmpDir, base+".gen.o")
|
||||
|
||||
if err := os.WriteFile(headerPath, header, 0644); err != nil {
|
||||
t.Fatalf("Failed to write header: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(sourcePath, source, 0644); err != nil {
|
||||
t.Fatalf("Failed to write source: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cc, "-std=c11", "-Wall", "-Wextra", "-Wno-unused-function", "-c", sourcePath, "-o", objPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C compilation failed:\n%s\n%s", string(output), err)
|
||||
}
|
||||
}
|
||||
|
||||
func runGeneratedCProgram(t *testing.T, base string, header []byte, source []byte, harness string) string {
|
||||
t.Helper()
|
||||
|
||||
cc := requireCCompiler(t)
|
||||
tmpDir := t.TempDir()
|
||||
headerPath := filepath.Join(tmpDir, base+".gen.h")
|
||||
sourcePath := filepath.Join(tmpDir, base+".gen.c")
|
||||
testPath := filepath.Join(tmpDir, "test.c")
|
||||
binPath := filepath.Join(tmpDir, "test")
|
||||
|
||||
if err := os.WriteFile(headerPath, header, 0644); err != nil {
|
||||
t.Fatalf("Failed to write header: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(sourcePath, source, 0644); err != nil {
|
||||
t.Fatalf("Failed to write source: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(testPath, []byte(harness), 0644); err != nil {
|
||||
t.Fatalf("Failed to write harness: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cc, "-std=c11", "-Wall", "-Wextra", "-Wno-unused-function", "-o", binPath, testPath, sourcePath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C compilation failed:\n%s\n%s", string(output), err)
|
||||
}
|
||||
|
||||
runCmd := exec.Command(binPath)
|
||||
runCmd.Dir = tmpDir
|
||||
output, err = runCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("C program failed:\n%s\n%s", string(output), err)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
Reference in New Issue
Block a user