package lang import ( "strings" "testing" ) func TestGenerateStructuredCoroutines(t *testing.T) { prog, err := Parse(`package demo fun load(): Int { delay(1); return 42 } fun main() { runBlocking { coroutineScope { launch { delay(1) } val result = async { return load() } println(result.await()) } } }`) if err != nil { t.Fatal(err) } out, err := GenerateGo(prog) if err != nil { t.Fatal(err) } for _, want := range []string{"func load(gotlinScope *GotlinCoroutineScope) int", "gotlinScope.Delay(1)", "gotlinScope.Launch", "gotlinAsync[int]", "load(gotlinScope)", ".await()", "gotlinRunBlocking"} { if !strings.Contains(string(out), want) { t.Fatalf("missing %q:\n%s", want, out) } } } func TestInferredCoroutineFunctionRequiresScope(t *testing.T) { prog, err := Parse(`package demo fun load(): Int { delay(1); return 1 } fun main() { println(load()) }`) if err != nil { t.Fatal(err) } out, err := GenerateGo(prog) if err == nil || !strings.Contains(err.Error(), "contextual function main requires a runBlocking boundary") { t.Fatalf("unexpected error: %v\n%s", err, out) } } func TestCoroutineEffectPropagatesTransitively(t *testing.T) { prog, err := Parse(`package demo fun load(): Int { delay(1); return 42 } fun wrapped(): Int { return load() } fun main() { runBlocking { println(wrapped()) } }`) if err != nil { t.Fatal(err) } out, err := GenerateGo(prog) if err != nil { t.Fatal(err) } for _, expected := range []string{"func load(gotlinScope *GotlinCoroutineScope)", "func wrapped(gotlinScope *GotlinCoroutineScope)", "wrapped(gotlinScope)"} { if !strings.Contains(string(out), expected) { t.Fatalf("missing %q:\n%s", expected, out) } } } func TestLegacyConcurrencySyntaxIsRemoved(t *testing.T) { for _, source := range []string{`package demo worker Counter { var count = 0 }`, `package demo fun main() { go println("x") }`, `package demo fun main() { select { channel -> println(it) } }`} { if _, err := Parse(source); err == nil { t.Fatalf("deprecated concurrency syntax parsed: %s", source) } } } func TestSuspendModifierIsRemoved(t *testing.T) { if _, err := Parse(`package demo suspend fun load() {}`); err == nil { t.Fatal("suspend modifier parsed") } } func TestAnalyzeInfersCoroutineEffects(t *testing.T) { program, err := Parse(`package demo fun direct() { delay(1) } fun transitive() { direct() } fun boundary() { runBlocking { transitive() } }`) if err != nil { t.Fatal(err) } semantic, err := Analyze(program) if err != nil { t.Fatal(err) } if !semantic.FunctionEffects["direct"].Has(CoroutineEffect) || !semantic.FunctionEffects["transitive"].Has(CoroutineEffect) { t.Fatalf("effects were not propagated: %#v", semantic.FunctionEffects) } if semantic.FunctionEffects["boundary"].Has(CoroutineEffect) { t.Fatalf("runBlocking did not stop effect propagation: %#v", semantic.FunctionEffects) } } func TestCoroutineContextUsesAmbientScope(t *testing.T) { program, err := Parse(`package demo import fmt fun useContext() { fmt.sprint(coroutineContext()) } fun main() { runBlocking { useContext() } }`) if err != nil { t.Fatal(err) } output, err := GenerateGo(program) if err != nil { t.Fatal(err) } for _, expected := range []string{"func useContext(gotlinScope *GotlinCoroutineScope)", "gotlinScope.Context()", "useContext(gotlinScope)"} { if !strings.Contains(string(output), expected) { t.Fatalf("missing %q:\n%s", expected, output) } } } func TestGoContextIsInjectedFromAmbientScope(t *testing.T) { program, err := Parse(`package demo import exec os.exec fun command(): *exec.Cmd { return exec.commandContext("date") } fun main() { runBlocking { println(command()) } }`) if err != nil { t.Fatal(err) } output, err := GenerateGo(program) if err != nil { t.Fatal(err) } for _, expected := range []string{"func command(gotlinScope *GotlinCoroutineScope) *exec.Cmd", `exec.CommandContext(gotlinScope.Context(), "date")`, "command(gotlinScope)"} { if !strings.Contains(string(output), expected) { t.Fatalf("missing %q:\n%s", expected, output) } } call := program.Functions[0].Body[0].(ReturnStmt).Value.(CallExpr) goCall, ok := call.Meta.Semantic.Node.(HIRGoCall) if !ok || !goCall.InjectContext || !goCall.Variadic { t.Fatalf("Go call context metadata = %#v", call.Meta.Semantic.Node) } } func TestExplicitGoContextPreventsAmbientInjection(t *testing.T) { program, err := Parse(`package demo import context import exec os.exec fun command(ctx: context.Context): *exec.Cmd { return exec.commandContext(ctx, "date") }`) if err != nil { t.Fatal(err) } output, err := GenerateGo(program) if err != nil { t.Fatal(err) } code := string(output) if strings.Contains(code, "func command(gotlinScope") || !strings.Contains(code, `exec.CommandContext(ctx, "date")`) { t.Fatalf("explicit context was not preserved:\n%s", code) } } func TestRunBlockingCancelsAndJoinsChildrenOnPanic(t *testing.T) { prog, err := Parse(`package demo fun main() { runBlocking { launch { delay(1) }; panic("failed") } }`) if err != nil { t.Fatal(err) } out, err := GenerateGo(prog) if err != nil { t.Fatal(err) } for _, want := range []string{"scope.cancel()", "scope.workers.Wait()", "panic(value)", "gotlinRunBlocking"} { if !strings.Contains(string(out), want) { t.Fatalf("missing %q:\n%s", want, out) } } }