A Golang runtime and compilation backend for Delta Interaction Nets.
at main 369 lines 10 kB view raw
1package compiler 2 3import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 "strings" 8 "testing" 9) 10 11// Test compiling with additional Go files that provide native functions 12func TestCompileWithNativeFunctions(t *testing.T) { 13 // Create temp dir in a test subdirectory within the project 14 cwd, _ := os.Getwd() 15 projectRoot := filepath.Join(cwd, "../..") 16 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*") 17 if err != nil { 18 t.Fatalf("Failed to create temp dir: %v", err) 19 } 20 defer os.RemoveAll(tmpDir) 21 22 // Create a Go file with native function implementations 23 nativeGoFile := filepath.Join(tmpDir, "natives.go") 24 nativeGoCode := `package main 25 26import "github.com/vic/godnet/pkg/deltanet" 27 28func registerNatives(net *deltanet.Network) { 29 // String concatenation 30 net.RegisterNative("str_concat", func(a interface{}) (interface{}, error) { 31 aStr, ok := a.(string) 32 if !ok { 33 return nil, nil // Return nil for non-string 34 } 35 return func(b interface{}) (interface{}, error) { 36 bStr, ok := b.(string) 37 if !ok { 38 return nil, nil 39 } 40 return aStr + bStr, nil 41 }, nil 42 }) 43 44 // String length 45 net.RegisterNative("str_len", func(a interface{}) (interface{}, error) { 46 aStr, ok := a.(string) 47 if !ok { 48 return 0, nil 49 } 50 return len(aStr), nil 51 }) 52} 53` 54 if err := os.WriteFile(nativeGoFile, []byte(nativeGoCode), 0644); err != nil { 55 t.Fatalf("Failed to write natives.go: %v", err) 56 } 57 58 // Create lambda source that references these natives 59 sourceFile := filepath.Join(tmpDir, "test.lam") 60 source := `x: x` // Simple test - actual native invocation syntax TBD 61 62 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil { 63 t.Fatalf("Failed to write source file: %v", err) 64 } 65 66 // Compile with the native Go file included (must be in same directory) 67 outputFile := filepath.Join(tmpDir, "test_with_natives") 68 c := Compiler{ 69 SourceFile: sourceFile, 70 OutputName: outputFile, 71 GoFlags: []string{nativeGoFile}, // Will be copied to output dir 72 KeepTemp: false, 73 } 74 75 builtFile, err := c.Compile() 76 if err != nil { 77 t.Fatalf("Compilation with natives failed: %v", err) 78 } 79 80 if _, err := os.Stat(builtFile); os.IsNotExist(err) { 81 t.Fatalf("Output binary not found: %s", builtFile) 82 } 83 84 // Verify binary runs 85 cmd := exec.Command(builtFile) 86 output, err := cmd.Output() 87 if err != nil { 88 if exitErr, ok := err.(*exec.ExitError); ok { 89 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr) 90 } 91 t.Fatalf("Binary execution failed: %v", err) 92 } 93 94 result := strings.TrimSpace(string(output)) 95 if !strings.Contains(result, "x0: x0") { 96 t.Errorf("Expected identity function, got: %s", result) 97 } 98} 99 100// Test compiling with additional Go files that provide effect handlers 101func TestCompileWithEffectHandlers(t *testing.T) { 102 cwd, _ := os.Getwd() 103 projectRoot := filepath.Join(cwd, "../..") 104 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*") 105 if err != nil { 106 t.Fatalf("Failed to create temp dir: %v", err) 107 } 108 defer os.RemoveAll(tmpDir) 109 110 // Create a Go file with effect handler implementations 111 handlersGoFile := filepath.Join(tmpDir, "handlers.go") 112 handlersGoCode := `package main 113 114import ( 115 "fmt" 116 "github.com/vic/godnet/pkg/deltanet" 117) 118 119func installHandlers(net *deltanet.Network) *deltanet.HandlerScope { 120 scope := deltanet.NewHandlerScope() 121 122 // Print effect handler 123 scope.Register("Print", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) { 124 fmt.Println(eff.Payload) 125 return cont.Resume(nil) 126 }) 127 128 // FileRead effect handler (mock) 129 scope.Register("FileRead", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) { 130 path, ok := eff.Payload.(string) 131 if !ok { 132 return cont.Resume("invalid path") 133 } 134 // Mock file read 135 content := fmt.Sprintf("contents of %s", path) 136 return cont.Resume(content) 137 }) 138 139 return scope 140} 141` 142 if err := os.WriteFile(handlersGoFile, []byte(handlersGoCode), 0644); err != nil { 143 t.Fatalf("Failed to write handlers.go: %v", err) 144 } 145 146 // Create lambda source 147 sourceFile := filepath.Join(tmpDir, "test.lam") 148 source := `x: x` // Simple test - actual effect syntax TBD 149 150 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil { 151 t.Fatalf("Failed to write source file: %v", err) 152 } 153 154 // Compile with the handlers Go file included 155 outputFile := filepath.Join(tmpDir, "test_with_handlers") 156 c := Compiler{ 157 SourceFile: sourceFile, 158 OutputName: outputFile, 159 GoFlags: []string{handlersGoFile}, // Link with handlers.go 160 KeepTemp: false, 161 } 162 163 builtFile, err := c.Compile() 164 if err != nil { 165 t.Fatalf("Compilation with handlers failed: %v", err) 166 } 167 168 if _, err := os.Stat(builtFile); os.IsNotExist(err) { 169 t.Fatalf("Output binary not found: %s", builtFile) 170 } 171 172 // Verify binary runs 173 cmd := exec.Command(builtFile) 174 output, err := cmd.Output() 175 if err != nil { 176 if exitErr, ok := err.(*exec.ExitError); ok { 177 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr) 178 } 179 t.Fatalf("Binary execution failed: %v", err) 180 } 181 182 result := strings.TrimSpace(string(output)) 183 if !strings.Contains(result, "x0: x0") { 184 t.Errorf("Expected identity function, got: %s", result) 185 } 186} 187 188// Test compiling with both native functions and effect handlers 189func TestCompileMixedNativesAndHandlers(t *testing.T) { 190 cwd, _ := os.Getwd() 191 projectRoot := filepath.Join(cwd, "../..") 192 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*") 193 if err != nil { 194 t.Fatalf("Failed to create temp dir: %v", err) 195 } 196 defer os.RemoveAll(tmpDir) 197 198 // Create Go file with both natives and handlers 199 runtimeGoFile := filepath.Join(tmpDir, "runtime.go") 200 runtimeGoCode := `package main 201 202import ( 203 "fmt" 204 "github.com/vic/godnet/pkg/deltanet" 205) 206 207func setupRuntime(net *deltanet.Network) *deltanet.HandlerScope { 208 // Register native functions 209 net.RegisterNative("add", func(a interface{}) (interface{}, error) { 210 aInt, ok := a.(int) 211 if !ok { 212 return nil, fmt.Errorf("add: expected int") 213 } 214 return func(b interface{}) (interface{}, error) { 215 bInt, ok := b.(int) 216 if !ok { 217 return nil, fmt.Errorf("add: expected int") 218 } 219 return aInt + bInt, nil 220 }, nil 221 }) 222 223 // Register effect handlers 224 scope := deltanet.NewHandlerScope() 225 scope.Register("Log", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) { 226 fmt.Printf("[LOG] %v\n", eff.Payload) 227 return cont.Resume(nil) 228 }) 229 230 return scope 231} 232` 233 if err := os.WriteFile(runtimeGoFile, []byte(runtimeGoCode), 0644); err != nil { 234 t.Fatalf("Failed to write runtime.go: %v", err) 235 } 236 237 // Create lambda source 238 sourceFile := filepath.Join(tmpDir, "test.lam") 239 source := `x: x` // Simple test - actual native/effect syntax TBD 240 241 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil { 242 t.Fatalf("Failed to write source file: %v", err) 243 } 244 245 // Compile with runtime file 246 outputFile := filepath.Join(tmpDir, "test_mixed") 247 c := Compiler{ 248 SourceFile: sourceFile, 249 OutputName: outputFile, 250 GoFlags: []string{runtimeGoFile}, // Link with runtime.go 251 KeepTemp: false, 252 } 253 254 builtFile, err := c.Compile() 255 if err != nil { 256 t.Fatalf("Compilation failed: %v", err) 257 } 258 259 if _, err := os.Stat(builtFile); os.IsNotExist(err) { 260 t.Fatalf("Output binary not found: %s", builtFile) 261 } 262 263 // Verify binary runs 264 cmd := exec.Command(builtFile) 265 output, err := cmd.Output() 266 if err != nil { 267 if exitErr, ok := err.(*exec.ExitError); ok { 268 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr) 269 } 270 t.Fatalf("Binary execution failed: %v", err) 271 } 272 273 result := strings.TrimSpace(string(output)) 274 if !strings.Contains(result, "x0: x0") { 275 t.Errorf("Expected identity function, got: %s", result) 276 } 277} 278 279// Test multiple Go files can be linked together 280func TestCompileWithMultipleRuntimeFiles(t *testing.T) { 281 cwd, _ := os.Getwd() 282 projectRoot := filepath.Join(cwd, "../..") 283 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*") 284 if err != nil { 285 t.Fatalf("Failed to create temp dir: %v", err) 286 } 287 defer os.RemoveAll(tmpDir) 288 289 // Create separate files for natives and handlers 290 nativesFile := filepath.Join(tmpDir, "natives.go") 291 nativesCode := `package main 292 293import "github.com/vic/godnet/pkg/deltanet" 294 295func setupNatives(net *deltanet.Network) { 296 net.RegisterNative("mul", func(a interface{}) (interface{}, error) { 297 aInt := a.(int) 298 return func(b interface{}) (interface{}, error) { 299 bInt := b.(int) 300 return aInt * bInt, nil 301 }, nil 302 }) 303} 304` 305 if err := os.WriteFile(nativesFile, []byte(nativesCode), 0644); err != nil { 306 t.Fatalf("Failed to write natives: %v", err) 307 } 308 309 handlersFile := filepath.Join(tmpDir, "handlers.go") 310 handlersCode := `package main 311 312import ( 313 "fmt" 314 "github.com/vic/godnet/pkg/deltanet" 315) 316 317func setupHandlers() *deltanet.HandlerScope { 318 scope := deltanet.NewHandlerScope() 319 scope.Register("Debug", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) { 320 fmt.Printf("[DEBUG] %v\n", eff.Payload) 321 return cont.Resume(nil) 322 }) 323 return scope 324} 325` 326 if err := os.WriteFile(handlersFile, []byte(handlersCode), 0644); err != nil { 327 t.Fatalf("Failed to write handlers: %v", err) 328 } 329 330 // Create lambda source 331 sourceFile := filepath.Join(tmpDir, "test.lam") 332 source := `x: x` 333 334 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil { 335 t.Fatalf("Failed to write source: %v", err) 336 } 337 338 // Compile with multiple runtime files 339 outputFile := filepath.Join(tmpDir, "test_multi") 340 c := Compiler{ 341 SourceFile: sourceFile, 342 OutputName: outputFile, 343 GoFlags: []string{nativesFile, handlersFile}, 344 KeepTemp: false, 345 } 346 347 builtFile, err := c.Compile() 348 if err != nil { 349 t.Fatalf("Compilation with multiple files failed: %v", err) 350 } 351 352 if _, err := os.Stat(builtFile); os.IsNotExist(err) { 353 t.Fatalf("Output binary not found: %s", builtFile) 354 } 355 356 // Verify binary runs 357 cmd := exec.Command(builtFile) 358 if output, err := cmd.Output(); err != nil { 359 if exitErr, ok := err.(*exec.ExitError); ok { 360 t.Fatalf("Binary failed: %v\nStderr: %s", err, exitErr.Stderr) 361 } 362 t.Fatalf("Binary failed: %v", err) 363 } else { 364 result := strings.TrimSpace(string(output)) 365 if !strings.Contains(result, "x0: x0") { 366 t.Errorf("Expected identity, got: %s", result) 367 } 368 } 369}