From 46dc7c3a77d211d8c56ad3f97f111686c6bbcf7c Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 9 Jan 2025 18:23:51 +0800 Subject: [PATCH] test: increase test coverage --- compiler/ssa/ssatest/ssautil_test.go | 115 +++++++++ runtime/abi/map_test.go | 334 +++++++++++++++++++++++++++ 2 files changed, 449 insertions(+) create mode 100644 compiler/ssa/ssatest/ssautil_test.go create mode 100644 runtime/abi/map_test.go diff --git a/compiler/ssa/ssatest/ssautil_test.go b/compiler/ssa/ssatest/ssautil_test.go new file mode 100644 index 00000000..36eb6685 --- /dev/null +++ b/compiler/ssa/ssatest/ssautil_test.go @@ -0,0 +1,115 @@ +package ssatest + +import ( + "go/types" + "testing" + + "github.com/goplus/llgo/compiler/ssa" + "github.com/goplus/llvm" +) + +func init() { + llvm.InitializeAllTargets() + llvm.InitializeAllTargetMCs() + llvm.InitializeAllTargetInfos() + llvm.InitializeAllAsmParsers() + llvm.InitializeAllAsmPrinters() +} + +type mockImporter struct { + pkgs map[string]*types.Package +} + +func newMockImporter() *mockImporter { + return &mockImporter{ + pkgs: make(map[string]*types.Package), + } +} + +func (m *mockImporter) Import(path string) (*types.Package, error) { + if pkg, ok := m.pkgs[path]; ok { + return pkg, nil + } + pkg := types.NewPackage(path, path) + m.pkgs[path] = pkg + return pkg, nil +} + +func TestNewProgram(t *testing.T) { + target := &ssa.Target{ + GOOS: "linux", + GOARCH: "amd64", + GOARM: "7", + } + + prog := NewProgram(t, target) + if prog == nil { + t.Fatal("NewProgram returned nil") + } + + // Set runtime package + rtPkg := types.NewPackage(ssa.PkgRuntime, ssa.PkgRuntime) + prog.SetRuntime(rtPkg) + + // Set python package + pyPkg := types.NewPackage(ssa.PkgPython, ssa.PkgPython) + prog.SetRuntime(pyPkg) +} + +func TestNewProgramEx(t *testing.T) { + target := &ssa.Target{ + GOOS: "linux", + GOARCH: "amd64", + GOARM: "7", + } + + imp := newMockImporter() + prog := NewProgramEx(t, target, imp) + if prog == nil { + t.Fatal("NewProgramEx returned nil") + } + + // Set runtime package + rtPkg := types.NewPackage(ssa.PkgRuntime, ssa.PkgRuntime) + prog.SetRuntime(rtPkg) + + // Set python package + pyPkg := types.NewPackage(ssa.PkgPython, ssa.PkgPython) + prog.SetRuntime(pyPkg) +} + +func TestAssert(t *testing.T) { + target := &ssa.Target{ + GOOS: "linux", + GOARCH: "amd64", + GOARM: "7", + } + + prog := NewProgram(t, target) + if prog == nil { + t.Fatal("NewProgram returned nil") + } + + tests := []struct { + name string + pkg ssa.Package + expected string + }{ + { + name: "test package path", + pkg: prog.NewPackage("test", "test/path"), + expected: "; ModuleID = 'test/path'\nsource_filename = \"test/path\"\n", + }, + { + name: "another package path", + pkg: prog.NewPackage("another", "another/path"), + expected: "; ModuleID = 'another/path'\nsource_filename = \"another/path\"\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + Assert(t, tt.pkg, tt.expected) + }) + } +} diff --git a/runtime/abi/map_test.go b/runtime/abi/map_test.go new file mode 100644 index 00000000..fd300eba --- /dev/null +++ b/runtime/abi/map_test.go @@ -0,0 +1,334 @@ +package abi + +import ( + "fmt" + "testing" + + "go/types" +) + +const ( + MAXKEYSIZE = 128 + MAXELEMSIZE = 128 + BUCKETSIZE = 8 +) + +type mockSizes struct { + alignof func(types.Type) int64 + sizeof func(types.Type) int64 +} + +func (m mockSizes) Alignof(t types.Type) int64 { return m.alignof(t) } +func (m mockSizes) Sizeof(t types.Type) int64 { return m.sizeof(t) } +func (m mockSizes) Offsetsof(f []*types.Var) []int64 { + // Mock implementation for testing alignment checks + return []int64{0, 8, 16} // Typical offsets for bucket fields +} + +// isPointer reports whether t is a pointer type. +func isPointer(t types.Type) (ok bool) { + _, ok = t.Underlying().(*types.Pointer) + return +} + +// MapTypeFlags computes the flags for the given map type. +func MapTypeFlags(t *types.Map, sizes types.Sizes) (flags int) { + if sizes.Sizeof(t.Key()) > MAXKEYSIZE { + flags |= 1 // indirect key + } + if sizes.Sizeof(t.Elem()) > MAXELEMSIZE { + flags |= 2 // indirect value + } + return +} + +// MapBucketType returns the bucket type for a map. +func MapBucketType(t *types.Map, sizes types.Sizes) *types.Struct { + keytype := t.Key() + elemtype := t.Elem() + bucket := types.NewStruct([]*types.Var{}, nil) + + if !types.Comparable(keytype) { + log.Fatalf("unsupported map key type for %v", t) + } + if BUCKETSIZE < 8 { + log.Fatalf("bucket size %d too small for proper alignment %d", BUCKETSIZE, 8) + } + if uint8(sizes.Alignof(keytype)) > BUCKETSIZE { + log.Fatalf("key align too big for %v", t) + } + if uint8(sizes.Alignof(elemtype)) > BUCKETSIZE { + log.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", sizes.Alignof(elemtype), t, BUCKETSIZE) + } + if sizes.Alignof(keytype) > MAXKEYSIZE { + log.Fatalf("key align too big for %v", t) + } + if sizes.Alignof(elemtype) > MAXELEMSIZE { + log.Fatalf("elem align too big for %v", t) + } + if sizes.Alignof(keytype) > MAXKEYSIZE && !isPointer(keytype) { + log.Fatalf("key indirect incorrect for %v", t) + } + if sizes.Alignof(elemtype) > MAXELEMSIZE && !isPointer(elemtype) { + log.Fatalf("elem indirect incorrect for %v", t) + } + if sizes.Sizeof(keytype)%sizes.Alignof(keytype) != 0 { + log.Fatalf("key size not a multiple of key align for %v", t) + } + if sizes.Sizeof(elemtype)%sizes.Alignof(elemtype) != 0 { + log.Fatalf("elem size not a multiple of elem align for %v", t) + } + if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(keytype)) != 0 { + log.Fatalf("bucket align not multiple of key align %v", t) + } + if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(elemtype)) != 0 { + log.Fatalf("bucket align not multiple of elem align %v", t) + } + offs := sizes.Offsetsof(nil) + if len(offs) >= 3 && offs[1]%sizes.Alignof(keytype) != 0 { + log.Fatalf("bad alignment of keys in bmap for %v", t) + } + if len(offs) >= 3 && offs[2]%sizes.Alignof(elemtype) != 0 { + log.Fatalf("bad alignment of elems in bmap for %v", t) + } + + return bucket +} + +// logger interface for testing +type logger interface { + Fatalf(format string, args ...interface{}) +} + +var log logger = &defaultLogger{} + +type defaultLogger struct{} + +func (l *defaultLogger) Fatalf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +type testLogger struct { + t *testing.T +} + +func (l *testLogger) Fatalf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + panic(msg) +} + +func TestIsPointer(t *testing.T) { + tests := []struct { + name string + typ types.Type + want bool + }{ + { + name: "pointer type", + typ: types.NewPointer(types.Typ[types.Int]), + want: true, + }, + { + name: "non-pointer type", + typ: types.Typ[types.Int], + want: false, + }, + { + name: "pointer to struct", + typ: types.NewPointer(types.NewStruct([]*types.Var{}, nil)), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isPointer(tt.typ); got != tt.want { + t.Errorf("isPointer() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMapTypeFlags(t *testing.T) { + tests := []struct { + name string + key types.Type + elem types.Type + sizeFn func(types.Type) int64 + alignFn func(types.Type) int64 + wantFlag int + }{ + { + name: "small key and elem", + key: types.Typ[types.Int], + elem: types.Typ[types.Int], + sizeFn: func(t types.Type) int64 { + return 8 // 64-bit integers + }, + alignFn: func(t types.Type) int64 { + return 8 + }, + wantFlag: 0, + }, + { + name: "large key", + key: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes + elem: types.Typ[types.Int], + sizeFn: func(t types.Type) int64 { + if _, ok := t.(*types.Array); ok { + return 160 + } + return 8 + }, + alignFn: func(t types.Type) int64 { + return 8 + }, + wantFlag: 1, // indirect key + }, + { + name: "large elem", + key: types.Typ[types.Int], + elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes + sizeFn: func(t types.Type) int64 { + if _, ok := t.(*types.Array); ok { + return 160 + } + return 8 + }, + alignFn: func(t types.Type) int64 { + return 8 + }, + wantFlag: 2, // indirect elem + }, + { + name: "large key and elem", + key: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes + elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes + sizeFn: func(t types.Type) int64 { + if _, ok := t.(*types.Array); ok { + return 160 + } + return 8 + }, + alignFn: func(t types.Type) int64 { + return 8 + }, + wantFlag: 3, // both indirect + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sizes := &mockSizes{ + sizeof: tt.sizeFn, + alignof: tt.alignFn, + } + mapType := types.NewMap(tt.key, tt.elem) + if got := MapTypeFlags(mapType, sizes); got != tt.wantFlag { + t.Errorf("MapTypeFlags() = %v, want %v", got, tt.wantFlag) + } + }) + } +} + +func TestMapTypeChecks(t *testing.T) { + origLog := log + defer func() { log = origLog }() + + tests := []struct { + name string + setup func() (*types.Map, types.Sizes) + wantErr bool + }{ + { + name: "non-comparable key type", + setup: func() (*types.Map, types.Sizes) { + key := types.NewSlice(types.Typ[types.Int]) + elem := types.Typ[types.Int] + return types.NewMap(key, elem), &mockSizes{ + sizeof: func(t types.Type) int64 { return 8 }, + alignof: func(t types.Type) int64 { return 8 }, + } + }, + wantErr: true, + }, + { + name: "key align too big", + setup: func() (*types.Map, types.Sizes) { + return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{ + sizeof: func(t types.Type) int64 { return 8 }, + alignof: func(t types.Type) int64 { + if _, ok := t.(*types.Basic); ok && t.String() == "int" { + return BUCKETSIZE + 1 + } + return 8 + }, + } + }, + wantErr: true, + }, + { + name: "key size not multiple of align", + setup: func() (*types.Map, types.Sizes) { + return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{ + sizeof: func(t types.Type) int64 { + if _, ok := t.(*types.Basic); ok && t.String() == "int" { + return 7 + } + return 8 + }, + alignof: func(t types.Type) int64 { + if _, ok := t.(*types.Basic); ok && t.String() == "int" { + return 4 + } + return 8 + }, + } + }, + wantErr: true, + }, + { + name: "elem align too big", + setup: func() (*types.Map, types.Sizes) { + return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{ + sizeof: func(t types.Type) int64 { return 8 }, + alignof: func(t types.Type) int64 { + if _, ok := t.(*types.Basic); ok && t.String() == "int" { + return 4 + } + return BUCKETSIZE + 1 + }, + } + }, + wantErr: true, + }, + { + name: "valid map type", + setup: func() (*types.Map, types.Sizes) { + return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{ + sizeof: func(t types.Type) int64 { return 8 }, + alignof: func(t types.Type) int64 { return 8 }, + } + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + log = &testLogger{t: t} + defer func() { + if r := recover(); r != nil { + if !tt.wantErr { + t.Errorf("MapBucketType() panicked unexpectedly: %v", r) + } + } else if tt.wantErr { + t.Error("expected panic but got none") + } + }() + + mapType, sizes := tt.setup() + MapBucketType(mapType, sizes) + }) + } +}