Files
llgo/internal/header/header_test.go

939 lines
30 KiB
Go

//go:build !llgo
// +build !llgo
package header
import (
"bytes"
"go/token"
"go/types"
"os"
"strings"
"testing"
"github.com/goplus/gogen/packages"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llvm"
)
func init() {
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
llvm.InitializeAllTargetInfos()
llvm.InitializeAllAsmParsers()
llvm.InitializeAllAsmPrinters()
}
func TestGenCHeaderExport(t *testing.T) {
prog := ssa.NewProgram(nil)
prog.SetRuntime(func() *types.Package {
fset := token.NewFileSet()
imp := packages.NewImporter(fset)
pkg, _ := imp.Import(ssa.PkgRuntime)
return pkg
})
// Define main package and the 'Foo' type within it
mainPkgPath := "github.com/goplus/llgo/test_buildmode/main"
mainTypesPkg := types.NewPackage(mainPkgPath, "main")
fooFields := []*types.Var{
types.NewField(token.NoPos, mainTypesPkg, "a", types.Typ[types.Int], false),
types.NewField(token.NoPos, mainTypesPkg, "b", types.Typ[types.Float64], false),
}
fooStruct := types.NewStruct(fooFields, nil)
fooTypeName := types.NewTypeName(token.NoPos, mainTypesPkg, "Foo", nil)
fooNamed := types.NewNamed(fooTypeName, fooStruct, nil)
mainTypesPkg.Scope().Insert(fooTypeName)
// Create SSA package for main
mainPkg := prog.NewPackage("main", mainPkgPath)
// Define exported functions in mainPkg
mainPkg.NewFunc("HelloWorld", types.NewSignatureType(nil, nil, nil, nil, nil, false), ssa.InGo)
useFooPtrParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", types.NewPointer(fooNamed)))
useFooPtrResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", fooNamed))
useFooPtrSig := types.NewSignatureType(nil, nil, nil, useFooPtrParams, useFooPtrResults, false)
mainPkg.NewFunc("UseFooPtr", useFooPtrSig, ssa.InGo)
useFooParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", fooNamed))
useFooSig := types.NewSignatureType(nil, nil, nil, useFooParams, useFooPtrResults, false)
mainPkg.NewFunc("UseFoo", useFooSig, ssa.InGo)
// Set exports for main
mainPkg.SetExport("HelloWorld", "HelloWorld")
mainPkg.SetExport("UseFooPtr", "UseFooPtr")
mainPkg.SetExport("UseFoo", "UseFoo")
// Create package C
cPkgPath := "github.com/goplus/llgo/test_buildmode/bar"
cPkg := prog.NewPackage("C", cPkgPath)
addParams := types.NewTuple(
types.NewVar(token.NoPos, nil, "a", types.Typ[types.Int]),
types.NewVar(token.NoPos, nil, "b", types.Typ[types.Int]))
addResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.Int]))
addSig := types.NewSignatureType(nil, nil, nil, addParams, addResults, false)
cPkg.NewFunc("Add", addSig, ssa.InGo)
cPkg.NewFunc("Sub", addSig, ssa.InGo)
cPkg.SetExport("XAdd", "Add")
cPkg.SetExport("XSub", "Sub")
// Generate header
libname := "testbuild"
headerPath := os.TempDir() + "/testbuild.h"
err := GenHeaderFile(prog, []ssa.Package{mainPkg, cPkg}, libname, headerPath, true)
if err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(headerPath)
if err != nil {
t.Fatal(err)
}
required := []string{
"/* Code generated by llgo; DO NOT EDIT. */",
"#ifndef __TESTBUILD_H_",
"#include <stdbool.h>",
"typedef struct { const char *p; intptr_t n; } GoString;",
"typedef struct {\n intptr_t a;\n double b;\n} main_Foo;",
"void\nHelloWorld(void);",
"main_Foo\nUseFooPtr(main_Foo* f);",
"main_Foo\nUseFoo(main_Foo f);",
"intptr_t\nAdd(intptr_t a, intptr_t b);",
"intptr_t\nSub(intptr_t a, intptr_t b);",
"#endif /* __TESTBUILD_H_ */",
}
got := string(data)
for _, sub := range required {
if !strings.Contains(got, sub) {
t.Fatalf("Generated content: %s\n", got)
t.Fatalf("Generated header is missing expected content:\n%s", sub)
}
}
}
func TestCheaderWriterTypes(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test complex integration scenarios only - basic types are covered by TestGoCTypeName
tests := []struct {
name string
goType types.Type
expected string
}{
{
name: "named struct",
goType: func() types.Type {
pkg := types.NewPackage("main", "main")
s := types.NewStruct([]*types.Var{types.NewField(0, nil, "f1", types.Typ[types.Int], false)}, nil)
return types.NewNamed(types.NewTypeName(0, pkg, "MyStruct", nil), s, nil)
}(),
expected: "typedef struct {\n intptr_t f1;\n} main_MyStruct;\n\n",
},
{
name: "struct with array field",
goType: func() types.Type {
arrayType := types.NewArray(types.Typ[types.Float64], 10)
return types.NewStruct([]*types.Var{
types.NewField(0, nil, "Values", arrayType, false),
}, nil)
}(),
expected: "typedef struct {\n double Values[10];\n} struct_double_Values;\n\n",
},
{
name: "struct with multidimensional array",
goType: func() types.Type {
// Create a 2D array: [4][3]int32
innerArrayType := types.NewArray(types.Typ[types.Int32], 3)
outerArrayType := types.NewArray(innerArrayType, 4)
return types.NewStruct([]*types.Var{
types.NewField(0, nil, "Matrix", outerArrayType, false),
}, nil)
}(),
expected: "typedef struct {\n int32_t Matrix[4][3];\n} struct_int32_t_Matrix;\n\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hw.typeBuf.Reset()
hw.declaredTypes = make(map[string]bool) // Reset declared types for each run
// Mark predefined Go types as declared (same as in genHeader)
hw.declaredTypes["GoString"] = true
hw.declaredTypes["GoSlice"] = true
hw.declaredTypes["GoMap"] = true
hw.declaredTypes["GoChan"] = true
hw.declaredTypes["GoInterface"] = true
hw.declaredTypes["GoComplex64"] = true
hw.declaredTypes["GoComplex128"] = true
if err := hw.writeTypedef(tt.goType); err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
got := hw.typeBuf.String()
if got != tt.expected {
t.Errorf("writeTypedef() got = %q, want %q", got, tt.expected)
}
})
}
}
// Test for goCTypeName function to cover all basic types
func TestGoCTypeName(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
tests := []struct {
name string
goType types.Type
expected string
}{
{name: "bool", goType: types.Typ[types.Bool], expected: "_Bool"},
{name: "int8", goType: types.Typ[types.Int8], expected: "int8_t"},
{name: "uint8", goType: types.Typ[types.Uint8], expected: "uint8_t"},
{name: "int16", goType: types.Typ[types.Int16], expected: "int16_t"},
{name: "uint16", goType: types.Typ[types.Uint16], expected: "uint16_t"},
{name: "int32", goType: types.Typ[types.Int32], expected: "int32_t"},
{name: "uint32", goType: types.Typ[types.Uint32], expected: "uint32_t"},
{name: "int64", goType: types.Typ[types.Int64], expected: "int64_t"},
{name: "uint64", goType: types.Typ[types.Uint64], expected: "uint64_t"},
{name: "int", goType: types.Typ[types.Int], expected: "intptr_t"},
{name: "uint", goType: types.Typ[types.Uint], expected: "uintptr_t"},
{name: "uintptr", goType: types.Typ[types.Uintptr], expected: "uintptr_t"},
{name: "float32", goType: types.Typ[types.Float32], expected: "float"},
{name: "float64", goType: types.Typ[types.Float64], expected: "double"},
{name: "complex64", goType: types.Typ[types.Complex64], expected: "GoComplex64"},
{name: "complex128", goType: types.Typ[types.Complex128], expected: "GoComplex128"},
{name: "string", goType: types.Typ[types.String], expected: "GoString"},
{name: "unsafe pointer", goType: types.Typ[types.UnsafePointer], expected: "void*"},
{name: "slice", goType: types.NewSlice(types.Typ[types.Int]), expected: "GoSlice"},
{name: "map", goType: types.NewMap(types.Typ[types.String], types.Typ[types.Int]), expected: "GoMap"},
{name: "chan", goType: types.NewChan(types.SendRecv, types.Typ[types.Int]), expected: "GoChan"},
{name: "interface", goType: types.NewInterfaceType(nil, nil), expected: "GoInterface"},
{
name: "array",
goType: types.NewArray(types.Typ[types.Int], 5),
expected: "intptr_t",
},
{
name: "pointer to int",
goType: types.NewPointer(types.Typ[types.Int]),
expected: "intptr_t*",
},
{
name: "pointer to unknown type",
goType: types.NewPointer(types.Typ[types.Invalid]),
expected: "void*",
},
{
name: "array of unknown type",
goType: types.NewArray(types.Typ[types.Invalid], 3),
expected: "",
},
{
name: "signature type",
goType: types.NewSignature(nil, nil, nil, false),
expected: "void (*)(void)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.goCTypeName(tt.goType)
if got != tt.expected {
t.Errorf("goCTypeName() = %q, want %q", got, tt.expected)
}
})
}
}
// Test typeReferencesSelf function
func TestTypeReferencesSelf(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a named type for testing
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
nodeStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
}, nil)
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
tests := []struct {
name string
typ types.Type
selfTypeName string
expected bool
}{
{
name: "pointer to self",
typ: types.NewPointer(namedNode),
selfTypeName: "test_Node",
expected: true,
},
{
name: "slice of self",
typ: types.NewSlice(namedNode),
selfTypeName: "test_Node",
expected: true,
},
{
name: "array of self",
typ: types.NewArray(namedNode, 5),
selfTypeName: "test_Node",
expected: true,
},
{
name: "named type self",
typ: namedNode,
selfTypeName: "test_Node",
expected: true,
},
{
name: "basic type not self",
typ: types.Typ[types.Int],
selfTypeName: "test_Node",
expected: false,
},
{
name: "different named type",
typ: namedNode,
selfTypeName: "other_Type",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.typeReferencesSelf(tt.typ, tt.selfTypeName)
if got != tt.expected {
t.Errorf("typeReferencesSelf() = %v, want %v", got, tt.expected)
}
})
}
}
// Test array struct generation functions
func TestArrayStructGeneration(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test ensureArrayStruct
arrayType := types.NewArray(types.Typ[types.Int32], 5)
name := hw.ensureArrayStruct(arrayType)
expectedName := "Array_int32_t_5"
if name != expectedName {
t.Errorf("ensureArrayStruct() = %q, want %q", name, expectedName)
}
// Test that typedef was generated
output := hw.typeBuf.String()
if !strings.Contains(output, "typedef struct") {
t.Error("ensureArrayStruct should generate typedef")
}
if !strings.Contains(output, "int32_t data[5]") {
t.Error("ensureArrayStruct should generate correct array field")
}
// Test duplicate prevention
hw.typeBuf.Reset()
name2 := hw.ensureArrayStruct(arrayType) // Call again
if name2 != name {
t.Errorf("ensureArrayStruct should return same name for same type")
}
duplicateOutput := hw.typeBuf.String()
if duplicateOutput != "" {
t.Error("ensureArrayStruct should not generate duplicate typedef")
}
}
// Test generateReturnType function
func TestGenerateReturnType(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test basic type
basicRet := hw.generateReturnType(types.Typ[types.Int32])
if basicRet != "int32_t" {
t.Errorf("generateReturnType(int32) = %q, want %q", basicRet, "int32_t")
}
// Test array type (should generate struct wrapper)
arrayType := types.NewArray(types.Typ[types.Float64], 3)
arrayRet := hw.generateReturnType(arrayType)
expectedArrayRet := "Array_double_3"
if arrayRet != expectedArrayRet {
t.Errorf("generateReturnType(array) = %q, want %q", arrayRet, expectedArrayRet)
}
}
// Test generateTypedef function
func TestGenerateTypedef(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Test named struct
structType := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
namedType := types.NewNamed(types.NewTypeName(0, pkg, "TestStruct", nil), structType, nil)
typedef := hw.generateTypedef(namedType)
if !strings.Contains(typedef, "typedef struct") {
t.Error("generateTypedef should generate typedef for named struct")
}
// Test named basic type
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
typedef2 := hw.generateTypedef(namedInt)
if !strings.Contains(typedef2, "typedef intptr_t test_MyInt") {
t.Error("generateTypedef should generate typedef for named basic type")
}
}
// Test complex nested structures and dependencies
func TestComplexNestedStructures(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Create a complex nested structure
pkg := types.NewPackage("test", "test")
// Inner struct
innerStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
// Named inner struct
innerTypeName := types.NewTypeName(0, pkg, "InnerStruct", nil)
namedInner := types.NewNamed(innerTypeName, innerStruct, nil)
// Outer struct with inner struct field
outerStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "inner", namedInner, false),
types.NewField(0, nil, "ptr", types.NewPointer(namedInner), false),
types.NewField(0, nil, "slice", types.NewSlice(namedInner), false),
}, nil)
outerTypeName := types.NewTypeName(0, pkg, "OuterStruct", nil)
namedOuter := types.NewNamed(outerTypeName, outerStruct, nil)
// Test writeTypedef for complex structure
err := hw.writeTypedef(namedOuter)
if err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
output := hw.typeBuf.String()
// Should contain both inner and outer struct definitions
if !strings.Contains(output, "test_InnerStruct") {
t.Error("Expected inner struct typedef")
}
if !strings.Contains(output, "test_OuterStruct") {
t.Error("Expected outer struct typedef")
}
}
// Test goCTypeName with more type cases
// Test processDependentTypes for error paths and edge cases
func TestProcessDependentTypesEdgeCases(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test signature type dependency (function parameters and results)
params := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
results := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
sigType := types.NewSignatureType(nil, nil, nil, params, results, false)
err := hw.processDependentTypes(sigType, make(map[string]bool))
if err != nil {
t.Errorf("processDependentTypes(signature) error = %v", err)
}
// Test processSignatureTypes directly
err = hw.processSignatureTypes(sigType, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes error = %v", err)
}
// Test Map type - this should trigger the Map case in writeTypedefRecursive
mapType := types.NewMap(types.Typ[types.String], types.Typ[types.Int])
err = hw.writeTypedefRecursive(mapType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(map) error = %v", err)
}
// Test Chan type - this should trigger the Chan case in writeTypedefRecursive
chanType := types.NewChan(types.SendRecv, types.Typ[types.Bool])
err = hw.writeTypedefRecursive(chanType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(chan) error = %v", err)
}
// Test Map with complex types to trigger both key and value processing
struct1 := types.NewStruct([]*types.Var{
types.NewField(0, nil, "key", types.Typ[types.String], false),
}, nil)
struct2 := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
complexMapType := types.NewMap(struct1, struct2)
err = hw.writeTypedefRecursive(complexMapType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(complex map) error = %v", err)
}
// Test function signature with no parameters (edge case)
noParamsSig := types.NewSignatureType(nil, nil, nil, nil, results, false)
err = hw.processSignatureTypes(noParamsSig, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes(no params) error = %v", err)
}
// Test function signature with no results (edge case)
noResultsSig := types.NewSignatureType(nil, nil, nil, params, nil, false)
err = hw.processSignatureTypes(noResultsSig, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes(no results) error = %v", err)
}
// Test function type (callback) parameters - IntCallback
intCallbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
intCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
intCallbackSig := types.NewSignatureType(nil, nil, nil, intCallbackParams, intCallbackResults, false)
err = hw.writeTypedefRecursive(intCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(IntCallback) error = %v", err)
}
// Test function type (callback) parameters - StringCallback
stringCallbackParams := types.NewTuple(types.NewVar(0, nil, "s", types.Typ[types.String]))
stringCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
stringCallbackSig := types.NewSignatureType(nil, nil, nil, stringCallbackParams, stringCallbackResults, false)
err = hw.writeTypedefRecursive(stringCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(StringCallback) error = %v", err)
}
// Test function type (callback) parameters - VoidCallback
voidCallbackSig := types.NewSignatureType(nil, nil, nil, nil, nil, false)
err = hw.writeTypedefRecursive(voidCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(VoidCallback) error = %v", err)
}
// Test Named function type - this should trigger the function typedef generation
pkg := types.NewPackage("test", "test")
callbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
callbackSig := types.NewSignatureType(nil, nil, nil, callbackParams, nil, false)
callbackTypeName := types.NewTypeName(0, pkg, "Callback", nil)
namedCallback := types.NewNamed(callbackTypeName, callbackSig, nil)
// Test Named function type with no parameters - NoParamCallback func() int
noParamCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
noParamCallbackSig := types.NewSignatureType(nil, nil, nil, nil, noParamCallbackResults, false)
noParamCallbackTypeName := types.NewTypeName(0, pkg, "NoParamCallback", nil)
namedNoParamCallback := types.NewNamed(noParamCallbackTypeName, noParamCallbackSig, nil)
err = hw.writeTypedef(namedCallback)
if err != nil {
t.Errorf("writeTypedef(named function) error = %v", err)
}
err = hw.writeTypedef(namedNoParamCallback)
if err != nil {
t.Errorf("writeTypedef(no param callback) error = %v", err)
}
// Verify the generated typedef contains function pointer syntax
output := hw.typeBuf.String()
if !strings.Contains(output, "test_Callback") {
t.Errorf("Expected named function typedef in output")
}
if !strings.Contains(output, "(*test_Callback)") {
t.Errorf("Expected function pointer syntax in typedef: %s", output)
}
if !strings.Contains(output, "test_NoParamCallback") {
t.Errorf("Expected no-param callback typedef in output")
}
if !strings.Contains(output, "(*test_NoParamCallback)(void)") {
t.Errorf("Expected no-param function pointer syntax in typedef: %s", output)
}
// Test function signature with unnamed parameters (like //export ProcessThreeUnnamedParams)
unnamedParams := types.NewTuple(
types.NewVar(0, nil, "", types.Typ[types.Int]),
types.NewVar(0, nil, "", types.Typ[types.String]),
types.NewVar(0, nil, "", types.Typ[types.Bool]),
)
unnamedResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Float64]))
unnamedSig := types.NewSignatureType(nil, nil, nil, unnamedParams, unnamedResults, false)
err = hw.writeTypedefRecursive(unnamedSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(unnamed params) error = %v", err)
}
}
// Test generateParameterDeclaration function
func TestGenerateParameterDeclaration(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
tests := []struct {
name string
paramType types.Type
paramName string
expected string
}{
{
name: "basic type with name",
paramType: types.Typ[types.Int],
paramName: "x",
expected: "intptr_t x",
},
{
name: "basic type without name",
paramType: types.Typ[types.Int],
paramName: "",
expected: "intptr_t",
},
{
name: "array type with name",
paramType: types.NewArray(types.Typ[types.Int], 5),
paramName: "arr",
expected: "intptr_t* arr",
},
{
name: "array type without name",
paramType: types.NewArray(types.Typ[types.Int], 5),
paramName: "",
expected: "intptr_t*",
},
{
name: "multidimensional array with name",
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
paramName: "matrix",
expected: "intptr_t matrix[3][4]",
},
{
name: "multidimensional array without name",
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
paramName: "",
expected: "intptr_t[3][4]",
},
{
name: "pointer type with name",
paramType: types.NewPointer(types.Typ[types.Int]),
paramName: "ptr",
expected: "intptr_t* ptr",
},
{
name: "pointer type without name",
paramType: types.NewPointer(types.Typ[types.Int]),
paramName: "",
expected: "intptr_t*",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.generateParameterDeclaration(tt.paramType, tt.paramName)
if got != tt.expected {
t.Errorf("generateParameterDeclaration() = %q, want %q", got, tt.expected)
}
})
}
}
// Test generateNamedStructTypedef with forward declaration
func TestGenerateNamedStructTypedefWithForwardDecl(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a self-referential struct that needs forward declaration
nodeName := types.NewTypeName(0, pkg, "Node", nil)
nodeNamed := types.NewNamed(nodeName, nil, nil)
// Create fields including a pointer to itself
fields := []*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
types.NewField(0, nil, "next", types.NewPointer(nodeNamed), false),
}
nodeStruct := types.NewStruct(fields, nil)
nodeNamed.SetUnderlying(nodeStruct)
// Test generateNamedStructTypedef
result := hw.generateNamedStructTypedef(nodeNamed, nodeStruct)
// Should contain forward declaration (with package prefix)
if !strings.Contains(result, "typedef struct test_Node test_Node;") {
t.Errorf("Expected forward declaration in result: %s", result)
}
// Should contain the actual struct definition
if !strings.Contains(result, "struct test_Node {") {
t.Errorf("Expected struct definition in result: %s", result)
}
}
// Test self-referential structures to ensure no infinite recursion
func TestSelfReferentialStructure(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a self-referential struct: Node with a pointer to itself
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
nodeStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
}, nil)
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
// Add a self-referential field after creating the named type
nodeStructWithPtr := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
types.NewField(0, nil, "next", types.NewPointer(namedNode), false),
}, nil)
// Create a new named type with the updated struct
nodeTypeNameFinal := types.NewTypeName(0, pkg, "SelfRefNode", nil)
namedNodeFinal := types.NewNamed(nodeTypeNameFinal, nodeStructWithPtr, nil)
// This should not cause infinite recursion
err := hw.writeTypedef(namedNodeFinal)
if err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
output := hw.typeBuf.String()
if !strings.Contains(output, "test_SelfRefNode") {
t.Error("Expected self-referential struct typedef")
}
}
// Test function signature dependencies
func TestFunctionSignatureDependencies(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create struct type for function parameters
paramStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "id", types.Typ[types.Int], false),
}, nil)
paramTypeName := types.NewTypeName(0, pkg, "ParamStruct", nil)
namedParam := types.NewNamed(paramTypeName, paramStruct, nil)
// Create function signature with struct parameters and return values
params := types.NewTuple(
types.NewVar(0, nil, "input", namedParam),
types.NewVar(0, nil, "count", types.Typ[types.Int]),
)
results := types.NewTuple(
types.NewVar(0, nil, "output", namedParam),
)
funcSig := types.NewSignatureType(nil, nil, nil, params, results, false)
// Test that function signature dependencies are processed
err := hw.processDependentTypes(funcSig, make(map[string]bool))
if err != nil {
t.Fatalf("processDependentTypes() error = %v", err)
}
// Test named basic type alias (should trigger the "else" branch in processDependentTypes)
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
err = hw.processDependentTypes(namedInt, make(map[string]bool))
if err != nil {
t.Errorf("processDependentTypes(named int) error = %v", err)
}
// Test duplicate type prevention - mark type as already declared
hw2 := newCHeaderWriter(prog)
hw2.declaredTypes["test_DuplicateType"] = true
duplicateType := types.NewNamed(
types.NewTypeName(0, pkg, "DuplicateType", nil),
types.Typ[types.Int],
nil,
)
err = hw2.writeTypedefRecursive(duplicateType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(duplicate) error = %v", err)
}
if strings.Contains(hw2.typeBuf.String(), "DuplicateType") {
t.Error("Should not generate typedef for already declared type")
}
// Test visiting map to prevent infinite recursion
visiting := make(map[string]bool)
visiting["test_MyInt"] = true // Mark as visiting
err = hw.writeTypedefRecursive(namedInt, visiting)
if err != nil {
t.Errorf("writeTypedefRecursive(already visiting) error = %v", err)
}
// Test invalid type (should trigger cType == "" path)
invalidType := types.Typ[types.Invalid]
err = hw.writeTypedefRecursive(invalidType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(invalid) error = %v", err)
}
}
// Test error conditions and edge cases
func TestEdgeCasesAndErrorConditions(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test with properly initialized but empty function - skip nil test since it causes panic
// This would require creating a complete Function object which is complex
// Test writeCommonIncludes
hw.typeBuf.Reset()
err := hw.writeCommonIncludes()
if err != nil {
t.Fatalf("writeCommonIncludes() error = %v", err)
}
output := hw.typeBuf.String()
expectedIncludes := []string{
"GoString",
"GoSlice",
"GoMap",
"GoChan",
"GoInterface",
}
for _, expected := range expectedIncludes {
if !strings.Contains(output, expected) {
t.Errorf("Expected %s in common includes", expected)
}
}
}
// Test writeTo function
func TestWriteTo(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Add some content to both buffers
hw.typeBuf.WriteString("typedef struct { int x; } TestStruct;\n")
hw.funcBuf.WriteString("void TestFunction(void);\n")
var output bytes.Buffer
err := hw.writeTo(&output)
if err != nil {
t.Fatalf("writeTo() error = %v", err)
}
got := output.String()
if !strings.Contains(got, "TestStruct") {
t.Error("writeTo() should write type definitions")
}
if !strings.Contains(got, "TestFunction") {
t.Error("writeTo() should write function declarations")
}
}
// Test genHeader function
func TestGenHeader(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a mock package
pkg := prog.NewPackage("", "testpkg")
var output bytes.Buffer
err := genHeader(prog, []ssa.Package{pkg}, &output)
if err != nil {
t.Fatalf("genHeader() error = %v", err)
}
got := output.String()
if !strings.Contains(got, "GoString") {
t.Error("genHeader() should include Go runtime types")
}
}
// Test GenCHeader function
func TestGenCHeader(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a mock package
pkg := prog.NewPackage("", "testpkg")
// Create a temp file for output
tmpfile, err := os.CreateTemp("", "test_header_*.h")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
err = GenHeaderFile(prog, []ssa.Package{pkg}, "testlib", tmpfile.Name(), false)
if err != nil {
t.Fatalf("GenCHeader() error = %v", err)
}
// Read the file and verify content
content, err := os.ReadFile(tmpfile.Name())
if err != nil {
t.Fatalf("Failed to read generated file: %v", err)
}
got := string(content)
if !strings.Contains(got, "#ifndef") {
t.Error("GenCHeader() should generate header guards")
}
if !strings.Contains(got, "GoString") {
t.Error("GenCHeader() should include Go runtime types")
}
}
// Test genHeader with init function coverage
func TestGenHeaderWithInitFunction(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a package
pkgPath := "github.com/test/mypackage"
pkg := prog.NewPackage("", pkgPath)
// Create an init function signature: func()
initSig := types.NewSignature(nil, types.NewTuple(), types.NewTuple(), false)
// Create the init function with the expected name format
initFnName := pkgPath + ".init"
_ = pkg.NewFunc(initFnName, initSig, ssa.InGo)
// Test genHeader which should now detect the init function
var output bytes.Buffer
err := genHeader(prog, []ssa.Package{pkg}, &output)
if err != nil {
t.Fatalf("genHeader() error = %v", err)
}
got := output.String()
// Should contain Go runtime types
if !strings.Contains(got, "GoString") {
t.Error("genHeader() should include Go runtime types")
}
// Should contain the init function declaration with C-compatible name
expectedInitName := "github_com_test_mypackage_init"
if !strings.Contains(got, expectedInitName) {
t.Errorf("genHeader() should include init function declaration with name %s, got: %s", expectedInitName, got)
}
}