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 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) } } }