175 lines
5.3 KiB
Go
175 lines
5.3 KiB
Go
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<Int> { 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)
|
|
}
|
|
}
|
|
}
|