//go:build !llgo // +build !llgo /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/ast" "go/constant" "go/types" "strings" "testing" "unsafe" llssa "github.com/goplus/llgo/ssa" "golang.org/x/tools/go/ssa" ) func TestConstBool(t *testing.T) { if v, ok := constBool(nil); v || ok { t.Fatal("constBool?") } } func TestToBackground(t *testing.T) { if v := toBackground(""); v != llssa.InGo { t.Fatal("toBackground:", v) } } func TestCollectSkipNames(t *testing.T) { ctx := &context{skips: make(map[string]none)} ctx.collectSkipNames("//llgo:skipall") ctx.collectSkipNames("//llgo:skip") ctx.collectSkipNames("//llgo:skip abs") } func TestCollectSkipNamesByDoc(t *testing.T) { ftest := func(comments string, wantSkips []string, wantAll bool) { t.Helper() ctx := &context{skips: make(map[string]none)} doc := parseComments(t, comments) ctx.collectSkipNamesByDoc(doc) // Check skipall if wantAll != ctx.skipall { t.Errorf("skipall = %v, want %v", ctx.skipall, wantAll) } // Check collected symbols var gotSkips []string for sym := range ctx.skips { gotSkips = append(gotSkips, sym) } if len(gotSkips) != len(wantSkips) { t.Errorf("got %d skips %v, want %d skips %v", len(gotSkips), gotSkips, len(wantSkips), wantSkips) return } // Check each expected symbol exists for _, want := range wantSkips { if _, ok := ctx.skips[want]; !ok { t.Errorf("missing expected symbol %q", want) } } } // Multiple llgo:skip mixed - stops at first non-directive ftest(` //llgo:skip sym1 sym2 //llgo:skip sym3 //llgo:skipall // normal comment // llgo:skip sym4 //llgo:skip sym5 `, []string{"sym4", "sym5"}, false, ) // llgo:skip and go: mixed - processes until non-directive ftest(` //llgo:skip sym1 //llgo:skipall //go:generate // normal comment //go:build linux //llgo:skip sym2 `, []string{"sym2"}, false, ) // Only directives - processes all ftest(` // llgo:skip sym1 //go:generate // llgo:skip sym2 sym3 // llgo:skipall `, []string{"sym1", "sym2", "sym3"}, true, ) // Starts with non-directive - stops immediately ftest(` //llgo:skip sym1 // normal comment //llgo:skip sym2 //llgo:skipall `, []string{"sym2"}, true, ) // Only normal comments ftest(` // normal comment 1 // normal comment 2 `, []string{}, false, ) } func parseComments(t *testing.T, text string) *ast.CommentGroup { t.Helper() var comments []*ast.Comment for _, line := range strings.Split(text, "\n") { line = strings.TrimSpace(line) if line == "" { continue } comments = append(comments, &ast.Comment{Text: line}) } return &ast.CommentGroup{List: comments} } func TestReplaceGoName(t *testing.T) { if ret := replaceGoName("foo", 0); ret != "foo" { t.Fatal("replaceGoName:", ret) } } func TestIsAllocVargs(t *testing.T) { if isAllocVargs(nil, ssaAlloc(&ssa.Return{})) { t.Fatal("isVargs?") } if isAllocVargs(nil, ssaAlloc(ssaSlice(&ssa.Go{}))) { t.Fatal("isVargs?") } if isAllocVargs(nil, ssaAlloc(ssaSlice(&ssa.Return{}))) { t.Fatal("isVargs?") } } func ssaSlice(refs ...ssa.Instruction) *ssa.Slice { a := &ssa.Slice{} setRefs(unsafe.Pointer(a), refs...) return a } func ssaAlloc(refs ...ssa.Instruction) *ssa.Alloc { a := &ssa.Alloc{} setRefs(unsafe.Pointer(a), refs...) return a } func setRefs(v unsafe.Pointer, refs ...ssa.Instruction) { off := unsafe.Offsetof(ssa.Alloc{}.Comment) - unsafe.Sizeof([]int(nil)) ptr := uintptr(v) + off *(*[]ssa.Instruction)(unsafe.Pointer(ptr)) = refs } func TestRecvTypeName(t *testing.T) { if ret := recvTypeName(&ast.IndexExpr{ X: &ast.Ident{Name: "Pointer"}, Index: &ast.Ident{Name: "T"}, }); ret != "Pointer" { t.Fatal("recvTypeName IndexExpr:", ret) } if ret := recvTypeName(&ast.IndexListExpr{ X: &ast.Ident{Name: "Pointer"}, Indices: []ast.Expr{&ast.Ident{Name: "T"}}, }); ret != "Pointer" { t.Fatal("recvTypeName IndexListExpr:", ret) } defer func() { if r := recover(); r == nil { t.Fatal("recvTypeName: no error?") } }() recvTypeName(&ast.BadExpr{}) } /* func TestErrCompileValue(t *testing.T) { defer func() { if r := recover(); r != "can't use llgo instruction as a value" { t.Fatal("TestErrCompileValue:", r) } }() pkg := types.NewPackage("foo", "foo") ctx := &context{ goTyps: pkg, link: map[string]string{ "foo.": "llgo.unreachable", }, } ctx.compileValue(nil, &ssa.Function{ Pkg: &ssa.Package{Pkg: pkg}, Signature: types.NewSignatureType(nil, nil, nil, nil, nil, false), }) } */ func TestErrCompileInstrOrValue(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("compileInstrOrValue: no error?") } }() ctx := &context{ bvals: make(map[ssa.Value]llssa.Expr), } ctx.compileInstrOrValue(nil, &ssa.Call{}, true) } func TestErrBuiltin(t *testing.T) { test := func(builtin string, fn func(ctx *context)) { defer func() { if r := recover(); r == nil { t.Fatal(builtin, ": no error?") } }() var ctx context fn(&ctx) } test("advance", func(ctx *context) { ctx.advance(nil, nil) }) test("alloca", func(ctx *context) { ctx.alloca(nil, nil) }) test("allocaCStr", func(ctx *context) { ctx.allocaCStr(nil, nil) }) test("allocaCStrs", func(ctx *context) { ctx.allocaCStrs(nil, nil) }) test("allocaCStrs(Nonconst)", func(ctx *context) { ctx.allocaCStrs(nil, []ssa.Value{nil, &ssa.Parameter{}}) }) test("string", func(ctx *context) { ctx.string(nil, nil) }) test("stringData", func(ctx *context) { ctx.stringData(nil, nil) }) test("funcAddr", func(ctx *context) { ctx.funcAddr(nil, nil) }) test("sigsetjmp", func(ctx *context) { ctx.sigsetjmp(nil, nil) }) test("siglongjmp", func(ctx *context) { ctx.siglongjmp(nil, nil) }) test("cstr(NoArgs)", func(ctx *context) { cstr(nil, nil) }) test("cstr(Nonconst)", func(ctx *context) { cstr(nil, []ssa.Value{&ssa.Parameter{}}) }) test("pystr(NoArgs)", func(ctx *context) { pystr(nil, nil) }) test("pystr(Nonconst)", func(ctx *context) { pystr(nil, []ssa.Value{&ssa.Parameter{}}) }) test("atomic", func(ctx *context) { ctx.atomic(nil, 0, nil) }) test("atomicLoad", func(ctx *context) { ctx.atomicLoad(nil, nil) }) test("atomicStore", func(ctx *context) { ctx.atomicStore(nil, nil) }) test("atomicCmpXchg", func(ctx *context) { ctx.atomicCmpXchg(nil, nil) }) } func TestErrAsm(t *testing.T) { test := func(testName string, fn func(ctx *context)) { defer func() { if r := recover(); r == nil { t.Fatal(testName, ": no error?") } }() var ctx context fn(&ctx) } test("asm(NoArgs)", func(ctx *context) { ctx.asm(nil, []ssa.Value{}) }) test("asm(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}}) }) test("asmFull(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}, &ssa.Parameter{}}) }) test("asmFull(NonConstKey)", func(ctx *context) { makeMap := &ssa.MakeMap{} nonConstKey := &ssa.Parameter{} mapUpdate := &ssa.MapUpdate{Key: nonConstKey} referrers := []ssa.Instruction{mapUpdate} setRefs(unsafe.Pointer(makeMap), referrers...) strConst := &ssa.Const{ Value: constant.MakeString("nop"), } ctx.asm(nil, []ssa.Value{strConst, makeMap}) }) test("asmFull(RegisterNotFound)", func(ctx *context) { makeMap := &ssa.MakeMap{} referrers := []ssa.Instruction{} setRefs(unsafe.Pointer(makeMap), referrers...) strConst := &ssa.Const{ Value: constant.MakeString("test {missing}"), } ctx.asm(nil, []ssa.Value{strConst, makeMap}) }) test("asmFull(UnknownReferrer)", func(ctx *context) { makeMap := &ssa.MakeMap{} unknownRef := &ssa.Return{} referrers := []ssa.Instruction{unknownRef} setRefs(unsafe.Pointer(makeMap), referrers...) strConst := &ssa.Const{ Value: constant.MakeString("test"), } ctx.asm(nil, []ssa.Value{strConst, makeMap}) }) } func TestPkgNoInit(t *testing.T) { pkg := types.NewPackage("foo", "foo") ctx := &context{ goTyps: pkg, loaded: make(map[*types.Package]*pkgInfo), } if ctx.pkgNoInit(pkg) { t.Fatal("pkgNoInit?") } } func TestPkgKind(t *testing.T) { if v, _ := pkgKind("link: hello.a"); v != PkgLinkExtern { t.Fatal("pkgKind:", v) } if v, _ := pkgKind("noinit"); v != PkgNoInit { t.Fatal("pkgKind:", v) } if v, _ := pkgKind("link"); v != PkgLinkIR { t.Fatal("pkgKind:", v) } if v, _ := pkgKind(""); v != PkgLLGo { t.Fatal("pkgKind:", v) } if v, _ := pkgKind("decl"); v != PkgDeclOnly { t.Fatal("pkgKind:", v) } if v, _ := pkgKind("decl: test.ll"); v != PkgDeclOnly { t.Fatal("pkgKind:", v) } } func TestPkgKindOf(t *testing.T) { if v, _ := PkgKindOf(types.Unsafe); v != PkgDeclOnly { t.Fatal("PkgKindOf unsafe:", v) } pkg := types.NewPackage("foo", "foo") pkg.Scope().Insert( types.NewConst( 0, pkg, "LLGoPackage", types.Typ[types.String], constant.MakeString("noinit")), ) if v, _ := PkgKindOf(pkg); v != PkgNoInit { t.Fatal("PkgKindOf foo:", v) } } func TestIsAny(t *testing.T) { if isAny(types.Typ[types.UntypedInt]) { t.Fatal("isAny?") } } func TestIntVal(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("intVal: no error?") } }() intVal(&ssa.Parameter{}) } func TestErrImport(t *testing.T) { var ctx context pkg := types.NewPackage("foo", "foo") ctx.importPkg(pkg, nil) alt := types.NewPackage("bar", "bar") alt.Scope().Insert( types.NewConst(0, alt, "LLGoPackage", types.Typ[types.String], constant.MakeString("noinit")), ) ctx.patches = Patches{"foo": Patch{Alt: &ssa.Package{Pkg: alt}, Types: alt}} ctx.importPkg(pkg, &pkgInfo{}) } func TestErrInitLinkname(t *testing.T) { var ctx context ctx.initLinkname("//llgo:link abc", func(name string) (string, bool, bool) { return "", false, false }) ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) { return "", false, false }) defer func() { if r := recover(); r == nil { t.Fatal("initLinkname: no error?") } }() ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) { return "foo.Printf", false, name == "Printf" }) } func TestErrVarOf(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("varOf: no error?") } }() prog := llssa.NewProgram(nil) pkg := prog.NewPackage("foo", "foo") pkgTypes := types.NewPackage("foo", "foo") ctx := &context{ pkg: pkg, goTyps: pkgTypes, } ssaPkg := &ssa.Package{Pkg: pkgTypes} g := &ssa.Global{Pkg: ssaPkg} ctx.varOf(nil, g) } func TestContextResolveLinkname(t *testing.T) { tests := []struct { name string link map[string]string input string want string panics bool }{ { name: "Normal", link: map[string]string{ "foo": "C.bar", }, input: "foo", want: "bar", }, { name: "MultipleLinks", link: map[string]string{ "foo1": "C.bar1", "foo2": "C.bar2", }, input: "foo2", want: "bar2", }, { name: "NoLink", link: map[string]string{}, input: "foo", want: "foo", }, { name: "InvalidLink", link: map[string]string{ "foo": "invalid.bar", }, input: "foo", panics: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.panics { defer func() { if r := recover(); r == nil { t.Error("want panic") } }() } ctx := &context{prog: llssa.NewProgram(nil)} for k, v := range tt.link { ctx.prog.SetLinkname(k, v) } got := ctx.resolveLinkname(tt.input) if !tt.panics { if got != tt.want { t.Errorf("got %q, want %q", got, tt.want) } } }) } } func TestInstantiate(t *testing.T) { obj := types.NewTypeName(0, nil, "T", nil) named := types.NewNamed(obj, types.Typ[types.Int], nil) if typ := obj.Type(); typ != instantiate(typ, named) { t.Fatal("error") } tparam := types.NewTypeParam(types.NewTypeName(0, nil, "P", nil), types.NewInterface(nil, nil)) named.SetTypeParams([]*types.TypeParam{tparam}) inamed, err := types.Instantiate(nil, named, []types.Type{types.Typ[types.Int]}, true) if err != nil { t.Fatal(err) } if typ := instantiate(obj.Type(), inamed.(*types.Named)); typ == obj.Type() || typ.(*types.Named).TypeArgs() == nil { t.Fatal("error") } }