gotlin/internal/lang/compiler_test.go

1366 lines
26 KiB
Go

package lang
import (
"strings"
"testing"
)
func TestGenerateGoHello(t *testing.T) {
src := `
package demo
fun greet(name: String): String {
return "hello, " + name
}
fun main() {
val message = greet("world")
println(message)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`package demo`,
`func greet(name string) string`,
`return "hello, " + name`,
`fmt.Println(message)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoImports(t *testing.T) {
src := `
package demo
import strings
import rand math.rand
fun main() {
val upper = strings.ToUpper("go")
println(upper)
println(rand.Intn(3))
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`"strings"`,
`rand "math/rand"`,
`upper := gotlinAutoThrow(strings.ToUpper("go"))`,
`fmt.Println(rand.Intn(3))`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoQualifiedTypes(t *testing.T) {
src := `
package demo.web
import fmt
import net.http
fun helloHandler(w: http.ResponseWriter, r: *http.Request) {
fmt.Fprintln(w, "hello")
}
fun main() {
http.HandleFunc("/", helloHandler)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`package web`,
`func helloHandler(w http.ResponseWriter, r *http.Request)`,
`fmt.Fprintln(w, "hello")`,
`http.HandleFunc("/", helloHandler)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoTypedLambda(t *testing.T) {
src := `
package demo
import fmt
import net.http
fun main() {
http.HandleFunc("/", { w: http.ResponseWriter, r: *http.Request ->
fmt.Fprintln(w, "hello from lambda")
})
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {`,
`fmt.Fprintln(w, "hello from lambda")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoTrailingLambda(t *testing.T) {
src := `
package demo
fun apply(value: String, fn: (String) -> Unit) {
fn(value)
}
fun main() {
apply("world") { value -> println(value) }
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`apply("world", func(value string) {`,
`fmt.Println(value)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoHandleFuncKotlinSyntax(t *testing.T) {
src := `
package demo
import fmt
import net.http
fun helloHandler(w: http.ResponseWriter, r: *http.Request) {
fmt.Fprintln(w, "hello")
}
fun main() {
http.HandleFunc("/") { w, r -> helloHandler(w, r) }
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {`,
`helloHandler(w, r)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoImplicitItLambda(t *testing.T) {
src := `
package demo
fun apply(value: String, fn: (String) -> Unit) {
fn(value)
}
fun main() {
apply("world", { println(it) })
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`func apply(value string, fn func(string))`,
`apply("world", func(it string) {`,
`fmt.Println(it)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoClass(t *testing.T) {
src := `
package demo
class Greeter(val name: String) {
fun greet() {
println("hello, " + name)
println(this.name)
}
}
fun main() {
val greeter = Greeter("world")
greeter.greet()
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type Greeter struct {`,
`name string`,
`func NewGreeter(name string) *Greeter`,
`func (self *Greeter) greet() {`,
`fmt.Println("hello, " + self.name)`,
`fmt.Println(self.name)`,
`greeter := gotlinAutoThrow(NewGreeter("world"))`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoInterface(t *testing.T) {
src := `
package demo
interface Greeter {
fun greet(name: String): String
}
class ConsoleGreeter(val prefix: String) {
fun greet(name: String): String {
return prefix + name
}
}
fun greetAll(greeter: Greeter) {
println(greeter.greet("world"))
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type Greeter interface {`,
`greet(name string) string`,
`type ConsoleGreeter struct {`,
`func (self *ConsoleGreeter) greet(name string) string {`,
`return self.prefix + name`,
`func greetAll(greeter Greeter)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoHTTPServerWithInterfaceMethodHandler(t *testing.T) {
src := `
package examples.http
import fmt
import net.http
interface EpicController {
fun hello(w: http.ResponseWriter, r: *http.Request)
}
class EpicControllerImpl: EpicController {
override fun hello(w: http.ResponseWriter, r: *http.Request) {
fmt.Fprintln(w, "hello from gotlin")
}
}
fun main() {
val epicController: EpicController = EpicControllerImpl()
fmt.Println("serving http://localhost:8080")
http.HandleFunc("/", epicController.hello)
http.ListenAndServe(":8080", http.DefaultServeMux)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type EpicController interface {`,
`hello(w http.ResponseWriter, r *http.Request)`,
`type EpicControllerImpl struct {`,
`func NewEpicControllerImpl() *EpicControllerImpl`,
`func (self *EpicControllerImpl) hello(w http.ResponseWriter, r *http.Request) {`,
`var epicController EpicController = gotlinAutoThrow(NewEpicControllerImpl())`,
`http.HandleFunc("/", epicController.hello)`,
`http.ListenAndServe(":8080", http.DefaultServeMux)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoAliasedStringImport(t *testing.T) {
src := `
package demo
import _ github.com.lib.pq
import database.sql
fun main() {
println(sql.Drivers())
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`_ "github.com/lib/pq"`,
`"database/sql"`,
`fmt.Println(sql.Drivers())`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoMultiAssignAndNull(t *testing.T) {
src := `
package demo
import database.sql
fun main() {
val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
if (err != null) {
return
}
db.Close()
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`db, err := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`,
`if err != nil {`,
`db.Close()`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoAutoBlankImportForSideEffects(t *testing.T) {
src := `
package demo
import github.com.lib.pq
import database.sql
fun main() {
val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
if (err != null) {
return
}
db.Close()
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`_ "github.com/lib/pq"`,
`"database/sql"`,
`db, err := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoExceptionsAndRunCatching(t *testing.T) {
src := `
package demo
fun mayFail() {
throw "boom"
}
fun main() {
val result = runCatching({ mayFail() })
if (result.isSuccess()) {
println("ok")
} else {
println(result.exceptionOrNull())
}
try {
mayFail()
} catch (e: String) {
println(e)
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type gotlinResult struct {`,
`func gotlinRunCatching(fn func()) (result gotlinResult) {`,
`panic("boom")`,
`result := gotlinAutoThrow(gotlinRunCatching(func() {`,
`if recovered := recover(); recovered != nil {`,
`e := recovered`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoAutoThrowForGoErrorResults(t *testing.T) {
src := `
package demo
import database.sql
fun main() {
val db = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
val err = db.Ping()
if (err != null) {
throw err
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`func gotlinAutoThrow[T any](value T, rest ...any) T {`,
`db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`,
`err := gotlinAutoThrow(db.Ping())`,
`if err != nil {`,
`panic(err)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoAutoThrowForErrorExprStmt(t *testing.T) {
src := `
package demo
import database.sql
fun main() {
val db = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
db.Ping()
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`,
`db.Ping()`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoClassWithoutBodyBraces(t *testing.T) {
src := `
package demo
class User(val Name: String)
fun main() {
val user = User("alice")
println(user.Name)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type User struct {`,
`func NewUser(Name string) *User`,
`user := gotlinAutoThrow(NewUser("alice"))`,
`fmt.Println(user.Name)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoGenericListAndMapTypes(t *testing.T) {
src := `
package demo
class Store(val names: List<String>, val counts: Map<String, Int>)
fun apply(values: List<String>, f: (Map<String, Int>) -> Unit) {
f(null)
println(values)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`type Store struct {`,
`names []string`,
`counts map[string]int`,
`func NewStore(names []string, counts map[string]int) *Store`,
`func apply(values []string, f func(map[string]int))`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoListAndMapLiterals(t *testing.T) {
src := `
package demo
fun main() {
val names: List<String> = listOf("alice", "bob")
val ages: Map<String, Int> = mapOf("alice", 30, "bob", 25)
println(names)
println(ages)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`var names []string = gotlinAutoThrow([]string{"alice", "bob"})`,
`var ages map[string]int = gotlinAutoThrow(map[string]int{"alice": 30, "bob": 25})`,
`fmt.Println(names)`,
`fmt.Println(ages)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoListAndMapLiteralsWithCallTypeArgs(t *testing.T) {
src := `
package demo
fun main() {
val test = listOf<Int>()
val labels = mapOf<String, Int>()
println(test)
println(labels)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`test := gotlinAutoThrow([]int{})`,
`labels := gotlinAutoThrow(map[string]int{})`,
`fmt.Println(test)`,
`fmt.Println(labels)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoUnusedLocalDoesNotEmitNamedVariable(t *testing.T) {
src := `
package demo
fun main() {
val test = listOf<Int>(1, 7)
println("ok")
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`_ = gotlinAutoThrow([]int{1, 7})`,
`fmt.Println("ok")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
if strings.Contains(code, `test :=`) || strings.Contains(code, `var test `) {
t.Fatalf("generated Go should not contain named unused local:\n%s", code)
}
}
func TestGenerateGoGoStatement(t *testing.T) {
src := `
package demo
import fmt
fun runWorker(name: String) {
fmt.Println(name)
}
fun main() {
go runWorker("alice")
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`func runWorker(name string)`,
`fmt.Println(name)`,
`go func() {`,
`runWorker("alice")`,
`println("async panic:", recovered)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoForbidsWorkerSelfCalls(t *testing.T) {
src := `
package demo
worker Counter {
val n = 0
fun inc() {
inc()
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatal("expected worker self-call generation error")
}
if !strings.Contains(err.Error(), "worker self-calls are forbidden") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGenerateGoRejectsValReassignment(t *testing.T) {
src := `
package demo
fun main() {
val count = 0
count = 1
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatal("expected val reassignment error")
}
if !strings.Contains(err.Error(), "cannot reassign immutable name count") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGenerateGoDefer(t *testing.T) {
src := `
package demo
import database.sql
fun main() {
val rows = sql.Open("postgres", "dsn")
defer rows.Close()
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
if !strings.Contains(string(out), "defer rows.Close()") {
t.Fatalf("generated Go missing defer:\n%s", out)
}
}
func TestGenerateGoDecimalLiteral(t *testing.T) {
src := `
package demo
fun interest(): Double {
return 0.025
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
for _, want := range []string{"func interest() float64", "return 0.025"} {
if !strings.Contains(string(out), want) {
t.Fatalf("generated Go missing %q:\n%s", want, out)
}
}
}
func TestGenerateGoRejectsMultiValReassignment(t *testing.T) {
src := `
package demo
import database.sql
fun main() {
val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
err = nil
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatal("expected multi-val reassignment error")
}
if !strings.Contains(err.Error(), "cannot reassign immutable name err") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGenerateGoAllowsVarReassignment(t *testing.T) {
src := `
package demo
fun main() {
var count = 0
count = 1
count += 2
println(count)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`count := 0`,
`count = 1`,
`count += 2`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoRejectsReassignmentToImmutableClassField(t *testing.T) {
src := `
package demo
class Counter(val count: Int, var total: Int) {
fun freeze() {
count = 1
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatal("expected immutable class field reassignment error")
}
if !strings.Contains(err.Error(), "cannot reassign immutable name count") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGenerateGoAllowsReassignmentToMutableClassField(t *testing.T) {
src := `
package demo
class Counter(var count: Int) {
fun inc() {
count += 1
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
if !strings.Contains(string(out), `self.count += 1`) {
t.Fatalf("generated Go missing mutable field assignment:\n%s", string(out))
}
}
func TestGenerateGoWorkerSyncPanicPropagationScaffolding(t *testing.T) {
src := `
package demo
worker Counter {
fun boom() {
throw "x"
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`panicCh := make(chan any, 1)`,
`if recovered := recover(); recovered != nil {`,
`panicCh <- recovered`,
`case recovered := <-panicCh:`,
`panic(recovered)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestParseGoStatementRequiresCall(t *testing.T) {
src := `
package demo
fun main() {
go 123
}
`
_, err := Parse(src)
if err == nil {
t.Fatal("expected parse error")
}
if !strings.Contains(err.Error(), "'go' expects a function call expression") {
t.Fatalf("unexpected parse error: %v", err)
}
}
func TestGenerateGoChannelsAndSelect(t *testing.T) {
src := `
package demo
fun writer(ch: Channel<Int>) {
ch.send(7)
}
fun main() {
val ch = Channel<Int>()
go writer(ch)
select {
ch -> println(it)
}
val v = ch.read()
println(v)
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`func writer(ch chan int)`,
`ch <- 7`,
`ch := gotlinAutoThrow(make(chan int))`,
`go func() {`,
`writer(ch)`,
`select {`,
`case it := <-ch:`,
`fmt.Println(it)`,
`v := gotlinAutoThrow(<-ch)`,
`fmt.Println(v)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoSelectAfterAndEvery(t *testing.T) {
src := `
package demo
fun main() {
select {
after(1000) -> println(it)
every(250) -> println("tick")
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`"time"`,
`func gotlinEveryMs(ms int) <-chan time.Time {`,
`case it := <-time.After(time.Duration(1000) * time.Millisecond):`,
`fmt.Println(it)`,
`case <-gotlinEveryMs(250):`,
`fmt.Println("tick")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestGenerateGoEveryRejectsNonPositiveMilliseconds(t *testing.T) {
cases := []string{
`every(0)`,
`every(-1)`,
}
for _, timerCall := range cases {
src := `
package demo
fun main() {
select {
` + timerCall + ` -> println("x")
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatalf("expected go generation error for %s", timerCall)
}
if !strings.Contains(err.Error(), "every(ms) requires ms > 0") {
t.Fatalf("unexpected error for %s: %v", timerCall, err)
}
}
}
func TestGenerateGoAfterEveryArityValidation(t *testing.T) {
cases := []struct {
call string
want string
}{
{`after()`, "after(ms) expects exactly one Int argument"},
{`after(1, 2)`, "after(ms) expects exactly one Int argument"},
{`every()`, "every(ms) expects exactly one Int argument"},
{`every(1, 2)`, "every(ms) expects exactly one Int argument"},
}
for _, tc := range cases {
src := `
package demo
fun main() {
select {
` + tc.call + ` -> println("x")
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatalf("expected go generation error for %s", tc.call)
}
if !strings.Contains(err.Error(), tc.want) {
t.Fatalf("unexpected error for %s: %v", tc.call, err)
}
}
}
func TestGenerateGoAfterEveryIntValidation(t *testing.T) {
cases := []struct {
call string
want string
}{
{`after("1s")`, "after(ms) expects an Int argument"},
{`every(true)`, "every(ms) expects an Int argument"},
}
for _, tc := range cases {
src := `
package demo
fun main() {
select {
` + tc.call + ` -> println("x")
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
if err == nil {
t.Fatalf("expected go generation error for %s", tc.call)
}
if !strings.Contains(err.Error(), tc.want) {
t.Fatalf("unexpected error for %s: %v", tc.call, err)
}
}
}
func TestGenerateGoNoTimeImportWhenUnused(t *testing.T) {
src := `
package demo
fun main() {
println("ok")
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
if strings.Contains(code, `"time"`) {
t.Fatalf("unexpected time import in generated code:\n%s", code)
}
}
func TestGenerateGoNoTickerShorthandRewrite(t *testing.T) {
src := `
package demo
import time
fun main() {
val tick = time.NewTicker(time.Second)
select {
tick -> println("x")
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
if strings.Contains(code, "<-tick.C") {
t.Fatalf("ticker shorthand rewrite should be removed:\n%s", code)
}
if !strings.Contains(code, "case <-tick:") {
t.Fatalf("expected raw source receive in generated code:\n%s", code)
}
}
func TestGenerateGoAutoThrowForBunStyleCalls(t *testing.T) {
src := `
package demo
import context
import github.com.uptrace.bun
class Repo(val db: *bun.DB) {
fun run() {
val ctx = context.Background()
db.NewSelect().ColumnExpr("1").Scan(ctx)
val total = db.NewSelect().ColumnExpr("1").Count(ctx)
println(total)
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`self.db.NewSelect().ColumnExpr("1").Scan(ctx)`,
`total := gotlinAutoThrow(self.db.NewSelect().ColumnExpr("1").Count(ctx))`,
`fmt.Println(total)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}