test: increase test coverage

This commit is contained in:
Li Jie
2025-01-09 18:23:51 +08:00
parent a8cb551465
commit 46dc7c3a77
2 changed files with 449 additions and 0 deletions

View File

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

334
runtime/abi/map_test.go Normal file
View File

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