diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 50355244..42979db4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,8 +28,11 @@ jobs: - name: Test run: go run ./script/run-test --reset-instrument --debug -v -cover -coverpkg github.com/xhd2015/xgo/runtime/... -coverprofile cover.out + - name: Merge Coverages + run: go run ./script/cover merge ./cover-runtime.out ./cover-runtime-sub.out -o cover-runtime-merged.out + - name: Print coverage - run: cd runtime && go tool cover --func ../cover-runtime.out + run: cd runtime && go tool cover --func ../cover-runtime-merged.out - name: Build Release run: go run ./script/build-release --include-install-src --include-local diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 63885f6f..0fddfe2f 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -2,9 +2,9 @@ package main import "fmt" -const VERSION = "1.0.16" -const REVISION = "b27192a64f46ddf3ddd2b02ca6fe52c8c4e03ffd+1" -const NUMBER = 154 +const VERSION = "1.0.17" +const REVISION = "77848295e3d73a3eba8ce2bbd1b95d8c988929d5+1" +const NUMBER = 156 func getRevision() string { return fmt.Sprintf("%s %s BUILD_%d", VERSION, REVISION, NUMBER) diff --git a/runtime/core/version.go b/runtime/core/version.go index bc478305..2d0608a6 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.16" -const REVISION = "b27192a64f46ddf3ddd2b02ca6fe52c8c4e03ffd+1" -const NUMBER = 154 +const VERSION = "1.0.17" +const REVISION = "77848295e3d73a3eba8ce2bbd1b95d8c988929d5+1" +const NUMBER = 156 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/runtime/go.mod b/runtime/go.mod index 8d518744..cad736ef 100644 --- a/runtime/go.mod +++ b/runtime/go.mod @@ -1,3 +1,3 @@ module github.com/xhd2015/xgo/runtime -go 1.18 +go 1.14 diff --git a/runtime/mock/patch.go b/runtime/mock/patch.go index 447a75ab..7776fd56 100644 --- a/runtime/mock/patch.go +++ b/runtime/mock/patch.go @@ -4,17 +4,72 @@ import ( "context" "fmt" "reflect" + "strings" "github.com/xhd2015/xgo/runtime/core" ) +// Patch replaces `fn` with `replacer` in current goroutine, +// it returns a cleanup function to remove `replacer`. +// the `replacer` will be automatically cleared when current +// gorotuine exits if the returned cleanup function is not +// called. +func Patch(fn interface{}, replacer interface{}) func() { + if fn == nil { + panic("fn cannot be nil") + } + if replacer == nil { + panic("replacer cannot be nil") + } + fnType := reflect.TypeOf(fn) + if fnType.Kind() != reflect.Func { + panic(fmt.Errorf("fn should be func, actual: %T", fn)) + } + if fnType != reflect.TypeOf(replacer) { + panic(fmt.Errorf("replacer should have type: %T, actual: %T", fn, replacer)) + } + + recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn) + return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) +} + func PatchByName(pkgPath string, funcName string, replacer interface{}) func() { + if replacer == nil { + panic("replacer cannot be nil") + } + t := reflect.TypeOf(replacer) + if t.Kind() != reflect.Func { + panic(fmt.Errorf("replacer should be func, actual: %T", replacer)) + } + + // check type recvPtr, funcInfo, funcPC, trappingPC := getFuncByName(pkgPath, funcName) + if funcInfo.Func != nil { + calledType, replacerType, match := checkFuncTypeMatch(reflect.TypeOf(funcInfo.Func), t, recvPtr != nil) + if !match { + panic(fmt.Errorf("replacer should have type: %s, actual: %s", calledType, replacerType)) + } + } return mock(recvPtr, funcInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) } func PatchMethodByName(instance interface{}, method string, replacer interface{}) func() { + if replacer == nil { + panic("replacer cannot be nil") + } + t := reflect.TypeOf(replacer) + if t.Kind() != reflect.Func { + panic(fmt.Errorf("replacer should be func, actual: %T", replacer)) + } + + // check type recvPtr, funcInfo, funcPC, trappingPC := getMethodByName(instance, method) + if funcInfo.Func != nil { + calledType, replacerType, match := checkFuncTypeMatch(reflect.TypeOf(funcInfo.Func), t, recvPtr != nil) + if !match { + panic(fmt.Errorf("replacer should have type: %s, actual: %s", calledType, replacerType)) + } + } return mock(recvPtr, funcInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) } @@ -36,15 +91,19 @@ func buildInterceptorFromPatch(recvPtr interface{}, replacer interface{}) func(c callArgs := make([]reflect.Value, nIn) src := 0 dst := 0 + if fn.RecvType != "" { if recvPtr != nil { // patching an instance method src++ + // replacer's does not have receiver } else { // set receiver - callArgs[dst] = reflect.ValueOf(args.GetFieldIndex(0).Value()) - dst++ - src++ + if nIn > 0 { + callArgs[dst] = reflect.ValueOf(args.GetFieldIndex(0).Value()) + dst++ + src++ + } } } if fn.FirstArgCtx { @@ -80,3 +139,76 @@ func buildInterceptorFromPatch(recvPtr interface{}, replacer interface{}) func(c return nil } } + +func checkFuncTypeMatch(a reflect.Type, b reflect.Type, skipAFirst bool) (atype string, btype string, match bool) { + na := a.NumIn() + nb := b.NumIn() + + base := 0 + if skipAFirst { + base++ + } + if na-base != nb { + return formatFuncType(a, skipAFirst), formatFuncType(b, false), false + } + + for i := 0; i < na; i++ { + ta := a.In(i + base) + tb := b.In(i) + if ta != tb { + return formatFuncType(a, skipAFirst), formatFuncType(b, false), false + } + } + + nouta := a.NumOut() + noutb := b.NumOut() + if nouta != noutb { + return formatFuncType(a, skipAFirst), formatFuncType(b, false), false + } + for i := 0; i < nouta; i++ { + ta := a.Out(i) + tb := b.Out(i) + if ta != tb { + return formatFuncType(a, skipAFirst), formatFuncType(b, false), false + } + } + return "", "", true +} + +func formatFuncType(f reflect.Type, skipFirst bool) string { + n := f.NumIn() + i := 0 + if skipFirst { + i++ + } + var strBuilder strings.Builder + strBuilder.WriteString("func(") + for ; i < n; i++ { + t := f.In(i) + strBuilder.WriteString(t.String()) + if i < n-1 { + strBuilder.WriteString(",") + } + } + strBuilder.WriteString(")") + + nout := f.NumOut() + if nout > 0 { + strBuilder.WriteString(" ") + if nout > 1 { + strBuilder.WriteString("(") + } + for i := 0; i < nout; i++ { + t := f.Out(i) + strBuilder.WriteString(t.String()) + if i < nout-1 { + strBuilder.WriteString(",") + } + } + if nout > 1 { + strBuilder.WriteString(")") + } + } + + return strBuilder.String() +} diff --git a/runtime/mock/patch_go1.17.go b/runtime/mock/patch_go1.17.go deleted file mode 100644 index 3e1f388a..00000000 --- a/runtime/mock/patch_go1.17.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !go1.18 -// +build !go1.18 - -package mock - -func Patch(fn interface{}, replacer interface{}) func() { - recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn) - return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) -} diff --git a/runtime/mock/patch_go1.18.go b/runtime/mock/patch_go1.18.go index 18ca9da3..4a301659 100644 --- a/runtime/mock/patch_go1.18.go +++ b/runtime/mock/patch_go1.18.go @@ -5,7 +5,20 @@ package mock // TODO: what if `fn` is a Type function // instead of an instance method? -func Patch[T any](fn T, replacer T) func() { - recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn) - return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) -} +// func Patch[T any](fn T, replacer T) func() { +// recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn) +// return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer)) +// } + +// NOTE: as a library targeting under go1.18, the library itself should not +// use any generic thing +// +// situiation: +// go.mod: 1.16 +// runtime/go.mod: 1.18 +// go version: 1.20 + +// compile error: +// implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod) +// mock.Patch(...) +// because mock.Patch was defined as generic diff --git a/runtime/test/atomic_generic/main.go b/runtime/test/atomic_generic/main.go index 89d9ede5..c9bdb089 100644 --- a/runtime/test/atomic_generic/main.go +++ b/runtime/test/atomic_generic/main.go @@ -1,3 +1,6 @@ +//go:build go1.18 +// +build go1.18 + package main import ( diff --git a/runtime/test/demo/main.go b/runtime/test/demo/main.go deleted file mode 100644 index fd3be7e6..00000000 --- a/runtime/test/demo/main.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - - "github.com/xhd2015/xgo/runtime/core/functab" - "github.com/xhd2015/xgo/runtime/pkg" -) - -type T struct { -} - -func (c *T) a() {} -func (*T) b() {} -func (T) c() {} - -type T2 struct{} - -func (*T2) b() {} - -func reg(v interface{}) { - -} -func a() { - fmt.Printf("main.a called\n") -} -func b(name string) (age int, err error) { - return -} -func init() { - v := interface{}(testArgs) - println(v) - - func() { - println("closure") - }() - - reg((*T).a) - reg((*T).b) - reg((*T2).b) - reg(T.c) - reg(main) -} - -func main() { - // call registered func - fn := functab.GetFunc("main.a") - if fn == nil { - panic(fmt.Errorf("func main.a not found")) - } - fnv := reflect.ValueOf(fn.Func) - fnv.Call(nil) - - fnb := functab.GetFunc("main.b") - if fnb == nil { - panic(fmt.Errorf("func main.b not found")) - } - fmt.Printf("main.b recvName=%v,argNames=%v,resNames=%v\n", fnb.RecvName, fnb.ArgNames, fnb.ResNames) - res := testArgs("a") - fmt.Printf("res: %v\n", res) -} - -func test_DumpIR() { - after, stop := trap() - if !stop { - if after != nil { - defer after() - } - fmt.Printf("hello IR\n") - } -} - -func testArgs(s string) int { - fmt.Printf("testArgs: %s\n", s) - - num(1).add(2) - return 1 -} - -type num int - -func (c num) add(b int) { - fmt.Printf("%d+%d=%d\n", c, b, int(c)+b) - pkg.Hello("pkg") -} - -func trap() (after func(), stop bool) { - return nil, false -} diff --git a/runtime/test/demo/main_test.go b/runtime/test/demo/main_test.go deleted file mode 100644 index b0eff1fc..00000000 --- a/runtime/test/demo/main_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import "testing" - -func TestTrace(t *testing.T) { - // ./main.go:7:35: append (built-in) must be called - // fmt.Printf("what is append: %v", append) - // a := append - // _ = a - // trace("hello", "world") - trace("hello") -} diff --git a/runtime/test/generic/main.go b/runtime/test/generic/main.go index d75a983f..eeee845d 100644 --- a/runtime/test/generic/main.go +++ b/runtime/test/generic/main.go @@ -1,3 +1,6 @@ +//go:build go1.18 +// +build go1.18 + package main func main() { diff --git a/runtime/test/go.mod b/runtime/test/go.mod new file mode 100644 index 00000000..cd53b2d4 --- /dev/null +++ b/runtime/test/go.mod @@ -0,0 +1,9 @@ +module github.com/xhd2015/xgo/runtime/test + +go 1.18 + +require ( + github.com/xhd2015/xgo/runtime v1.0.16 +) + +replace github.com/xhd2015/xgo/runtime => ../ diff --git a/runtime/test/patch/patch_check_fn_signature_test.go b/runtime/test/patch/patch_check_fn_signature_test.go new file mode 100644 index 00000000..aa4ed9cb --- /dev/null +++ b/runtime/test/patch/patch_check_fn_signature_test.go @@ -0,0 +1,65 @@ +package patch + +import ( + "fmt" + "testing" + + "github.com/xhd2015/xgo/runtime/mock" +) + +func TestPatchUnmatchSignature(t *testing.T) { + s1 := capturePanic(func() { + mock.Patch((*struct_).greet, func() string { + return "" + }) + }) + expectS1 := "replacer should have type: func(*patch.struct_) string, actual: func() string" + if s1 != expectS1 { + t.Fatalf("expect s1 to be %q, actual: %q", expectS1, s1) + } + + s2 := capturePanic(func() { + s := &struct_{} + mock.Patch(s.greet, func(*struct_) string { + return "" + }) + }) + expectS2 := "replacer should have type: func() string, actual: func(*patch.struct_) string" + if s2 != expectS2 { + t.Fatalf("expect s2 to be %q, actual: %q", expectS2, s2) + } +} + +func TestPatchMethodByNameUnmatchSignature(t *testing.T) { + s1 := capturePanic(func() { + s := &struct_{} + mock.PatchMethodByName(s, "greet", func(*struct_) string { + return "" + }) + }) + expectS1 := "replacer should have type: func() string, actual: func(*patch.struct_) string" + if s1 != expectS1 { + t.Fatalf("expect s1 to be %q, actual: %q", expectS1, s1) + } + + s2 := capturePanic(func() { + mock.PatchByName("github.com/xhd2015/xgo/runtime/test/patch", "(*struct_).greet", func() string { + return "" + }) + }) + expectS2 := "replacer should have type: func(*patch.struct_) string, actual: func() string" + if s2 != expectS2 { + t.Fatalf("expect s2 to be %q, actual: %q", expectS2, s2) + } +} + +func capturePanic(fn func()) (s string) { + defer func() { + e := recover() + if e != nil { + s = fmt.Sprint(e) + } + }() + fn() + return +} diff --git a/runtime/test/trap_misc/main.go b/runtime/test/trap_misc/main.go deleted file mode 100644 index 9c6984d5..00000000 --- a/runtime/test/trap_misc/main.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "runtime" - "unsafe" - - "github.com/xhd2015/xgo/runtime/functab" - - "github.com/xhd2015/xgo/runtime/pkg" - "github.com/xhd2015/xgo/runtime/trace" -) - -func init() { - trace.Enable() -} - -// can break some -func regTest() { - -} - -func main() { - // testFindFunc() - // mock.CheckSSA() - // runtime.TestModuleData_Requires_Xgo() - // res := testArgs("a") - // fmt.Printf("res: %v\n", res) - A() - B() - C() -} - -func A() { - fmt.Printf("A\n") -} - -func B() { - fmt.Printf("B\n") - C() -} -func C() { - fmt.Printf("C\n") -} - -func testFindFunc() { - // call registered func - fn := functab.GetFunc("main.a") - if fn == nil { - panic(fmt.Errorf("func main.a not found")) - } - fnv := reflect.ValueOf(fn.Func) - fnv.Call(nil) - - fnb := functab.GetFunc("main.b") - if fnb == nil { - panic(fmt.Errorf("func main.b not found")) - } - fmt.Printf("main.b recvName=%v,argNames=%v,resNames=%v\n", fnb.RecvName, fnb.ArgNames, fnb.ResNames) -} - -// GOSSAFUNC=main.checkSSA ./with-go-devel.sh go build -gcflags="all=-N -l" ./test/test_trap -func checkSSA() { - var v interface{} = testReflect - _ = v - // fmt.Println(testReflect) -} - -func getReflectWord(i interface{}) uintptr { - type IHeader struct { - typ uintptr - word uintptr - } - - return (*IHeader)(unsafe.Pointer(&i)).word -} - -func testReflect() { - pc := runtime.Getcallerpc() - entryPC := runtime.GetcallerFuncPC() - - fmt.Printf("testReflect caller pc: %x\n", pc) - fmt.Printf("testReflect caller entry pc: %x\n", entryPC) -} - -func testArgs(s string) int { - fmt.Printf("testArgs: %s\n", s) - - num(1).add(2) - return 1 -} - -type num int - -func (c num) add(b int) { - fmt.Printf("%d+%d=%d\n", c, b, int(c)+b) - pkg.Hello("pkg") -} - -func a() { - fmt.Printf("main.a called\n") -} -func b(name string) (age int, err error) { - return -} diff --git a/runtime/test/trap_misc/mock/mock.go b/runtime/test/trap_misc/mock/mock.go deleted file mode 100644 index bbb5c0af..00000000 --- a/runtime/test/trap_misc/mock/mock.go +++ /dev/null @@ -1,105 +0,0 @@ -package mock - -import ( - "context" - "fmt" - "runtime" - "strings" - "unsafe" - - "github.com/xhd2015/xgo/runtime/core/functab" - "github.com/xhd2015/xgo/runtime/core/trap" -) - -func Use() { - trap.AddInterceptor(&trap.Interceptor{ - Pre: func(ctx context.Context, f *functab.FuncInfo, args *trap.FuncArgs) (interface{}, error) { - if strings.Contains(f.FullName, "testArgs") { - fmt.Printf("Mock: %s\n", f.FullName) - p := args.Results[0].(*int) - *p = 20 - return nil, trap.ErrAbort - } - return nil, nil - }, - Post: func(ctx context.Context, f *functab.FuncInfo, args *trap.FuncArgs, data interface{}) error { - return nil - }, - }) -} - -type T struct{} - -func (c *T) A() { -} - -func CheckSSA() { - println(dumpA) - println(testReflect) - type emptyInterface struct { - typ uintptr - word uintptr - } - var v interface{} = testReflect - - x := (*emptyInterface)(unsafe.Pointer((&v))) - - typeBase, typeEnd := runtime.TestModuleDataGetType_Requires_Xgo() - typOff := x.typ - typeBase - typLen := typeEnd - typeBase - - // see if any offset is typOff - - // 32992 - fmt.Printf("typOff: %d\n", uint(typOff)) - fmt.Printf("typLen: %d\n", uint(typLen)) - - idx := runtime.TestModuleDataFindTypeLink_Requires_Xgo(int32(typOff)) - - // 416 - fmt.Printf("typOff index: %d\n", idx) - - dumpA(x.typ) - - // fmt.Println(testReflect) -} - -// pc=0xa396cea* - -// pc=0xa396d4a* -// RAX: 1ab70e0 -func dumpA(typ uintptr) { - println(typ) -} - -// var v interface{} = testReflect -// mock.go:28 0x64b9ce0 55 push rbp -// mock.go:28 0x64b9ce1 4889e5 mov rbp, rsp -// mock.go:28 0x64b9ce4* 4883ec18 sub rsp, 0x18 -// mock.go:29 0x64b9ce8 488d0539580300 lea rax, ptr [rip+0x35839] -// => mock.go:29 0x64b9cef 4889442410 mov qword ptr [rsp+0x10], rax -// mock.go:29 0x64b9cf4 488b442410 mov rax, qword ptr [rsp+0x10] -// mock.go:29 0x64b9cf9 488d0de0a30100 lea rcx, ptr [rip+0x1a3e0] -// mock.go:29 0x64b9d00 48890c24 mov qword ptr [rsp], rcx -// mock.go:29 0x64b9d04 4889442408 mov qword ptr [rsp+0x8], rax -// mock.go:32 0x64b9d09 4883c418 add rsp, 0x18 -// mock.go:32 0x64b9d0d 5d pop rbp -// mock.go:32 0x64b9d0e c3 ret - -// var v interface{} = testReflect -// mock.go:28 0x64b9ce0 55 push rbp -// mock.go:28 0x64b9ce1 4889e5 mov rbp, rsp -// mock.go:28 0x64b9ce4* 4883ec18 sub rsp, 0x18 -// mock.go:29 0x64b9ce8 488d0539580300 lea rax, ptr [rip+0x35839] -// => mock.go:29 0x64b9cef 4889442410 mov qword ptr [rsp+0x10], rax -// mock.go:29 0x64b9cf4 488b442410 mov rax, qword ptr [rsp+0x10] -// mock.go:29 0x64b9cf9 488d0de0a30100 lea rcx, ptr [rip+0x1a3e0] -// mock.go:29 0x64b9d00 48890c24 mov qword ptr [rsp], rcx -// mock.go:29 0x64b9d04 4889442408 mov qword ptr [rsp+0x8], rax -// mock.go:32 0x64b9d09 4883c418 add rsp, 0x18 -// mock.go:32 0x64b9d0d 5d pop rbp -// mock.go:32 0x64b9d0e c3 ret - -func testReflect() { - -} diff --git a/runtime/test/trap_mock/main.go b/runtime/test/trap_mock/main.go deleted file mode 100644 index 29346598..00000000 --- a/runtime/test/trap_mock/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/xhd2015/xgo/runtime/trace" - - "github.com/xhd2015/xgo/runtime/test/trap/mock" -) - -func init() { - trace.Enable() - mock.Use() -} - -func main() { - A() - B() - C() -} - -func A() { - fmt.Printf("A\n") -} - -func B() { - fmt.Printf("B\n") - C() -} -func C() { - fmt.Printf("C\n") -} diff --git a/runtime/test/trap_mock/mock/mock.go b/runtime/test/trap_mock/mock/mock.go deleted file mode 100644 index 2a35f282..00000000 --- a/runtime/test/trap_mock/mock/mock.go +++ /dev/null @@ -1,29 +0,0 @@ -package mock - -import ( - "context" - "fmt" - "strings" - - "github.com/xhd2015/xgo/runtime/core/functab" - "github.com/xhd2015/xgo/runtime/core/trap" -) - -// for functions that have trap.AddInterceptor called, trap will skip -// the function and its called function -func Use() { - trap.AddInterceptor(&trap.Interceptor{ - Pre: func(ctx context.Context, f *functab.FuncInfo, args *trap.FuncArgs) (interface{}, error) { - if strings.Contains(f.FullName, "testArgs") { - fmt.Printf("Mock: %s\n", f.FullName) - p := args.Results[0].(*int) - *p = 20 - return nil, trap.ErrAbort - } - return nil, nil - }, - Post: func(ctx context.Context, f *functab.FuncInfo, args *trap.FuncArgs, data interface{}) error { - return nil - }, - }) -} diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index 863c5883..4902e811 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -20,7 +20,7 @@ import ( const __XGO_SKIP_TRAP = true // hold goroutine stacks, keyed by goroutine ptr -var stackMap sync.Map // uintptr(goroutine) -> *Root +var stackMap sync.Map // uintptr(goroutine) -> *Root var testInfoMapping sync.Map // uintptr(goroutine) -> *testInfo type testInfo struct { @@ -363,5 +363,5 @@ func emitTrace(name string, root *Root) error { if err != nil { return err } - return os.WriteFile(subFile, traceOut, 0755) + return WriteFile(subFile, traceOut, 0755) } diff --git a/runtime/trace/write_go1.14.go b/runtime/trace/write_go1.14.go new file mode 100644 index 00000000..25b1a8eb --- /dev/null +++ b/runtime/trace/write_go1.14.go @@ -0,0 +1,13 @@ +//go:build !go1.16 +// +build !go1.16 + +package trace + +import ( + "io/ioutil" + "os" +) + +func WriteFile(name string, data []byte, perm os.FileMode) error { + return ioutil.WriteFile(name, data, perm) +} diff --git a/runtime/trace/write_go1.16.go b/runtime/trace/write_go1.16.go new file mode 100644 index 00000000..478b2d87 --- /dev/null +++ b/runtime/trace/write_go1.16.go @@ -0,0 +1,13 @@ +//go:build go1.16 +// +build go1.16 + +package trace + +import ( + "io/fs" + "os" +) + +func WriteFile(name string, data []byte, perm fs.FileMode) error { + return os.WriteFile(name, data, perm) +} diff --git a/runtime/trap/generic_go1.17.go b/runtime/trap/generic_go1.17_and_under.go similarity index 100% rename from runtime/trap/generic_go1.17.go rename to runtime/trap/generic_go1.17_and_under.go diff --git a/runtime/trap/method_go1.17.go b/runtime/trap/method_go1.17_and_under.go similarity index 81% rename from runtime/trap/method_go1.17.go rename to runtime/trap/method_go1.17_and_under.go index b3d5e280..55d5b379 100644 --- a/runtime/trap/method_go1.17.go +++ b/runtime/trap/method_go1.17_and_under.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.18 -// +build go1.17,!go1.18 +//go:build go1.13 && !go1.18 +// +build go1.13,!go1.18 package trap diff --git a/script/cover/cover.go b/script/cover/cover.go new file mode 100644 index 00000000..a9e7a1f6 --- /dev/null +++ b/script/cover/cover.go @@ -0,0 +1,176 @@ +package main + +import ( + "fmt" + "io" + "os" + "strconv" + "strings" +) + +func main() { + args := os.Args[1:] + + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "requires cmd\n") + os.Exit(1) + } + cmd := args[0] + args = args[1:] + + var remainArgs []string + var outFile string + n := len(args) + for i := 0; i < n; i++ { + arg := args[i] + if arg == "-o" { + outFile = args[i+1] + i++ + continue + } + if !strings.HasPrefix(arg, "-") { + remainArgs = append(remainArgs, arg) + continue + } + fmt.Fprintf(os.Stderr, "unrecognized flag: %s\n", arg) + os.Exit(1) + } + + switch cmd { + case "merge": + if len(remainArgs) == 0 { + fmt.Fprintf(os.Stderr, "requires files\n") + os.Exit(1) + } + err := mergeCover(remainArgs, outFile) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + default: + fmt.Fprintf(os.Stderr, "unrecognized cmd: %s\n", cmd) + os.Exit(1) + } +} + +func mergeCover(files []string, outFile string) error { + covs := make([][]*covLine, 0, len(files)) + for _, file := range files { + content, err := os.ReadFile(file) + if err != nil { + return err + } + covs = append(covs, parseCover(string(content))) + } + res := merge(covs) + res = filter(res, func(line *covLine) bool { + if strings.HasPrefix(line.prefix, "github.com/xhd2015/xgo/runtime/test") { + return false + } + return true + }) + + mergedCov := formatCoverage("set", res) + + var out io.Writer = os.Stdout + if outFile != "" { + file, err := os.Create(outFile) + if err != nil { + return err + } + defer file.Close() + out = file + } + _, err := io.WriteString(out, mergedCov) + return err +} + +func filter(covs []*covLine, check func(line *covLine) bool) []*covLine { + n := len(covs) + j := 0 + for i := 0; i < n; i++ { + if check(covs[i]) { + covs[j] = covs[i] + j++ + } + } + return covs[:j] +} + +func formatCoverage(mode string, lines []*covLine) string { + strs := make([]string, 0, len(lines)+1) + strs = append(strs, "mode: "+mode) + for _, line := range lines { + strs = append(strs, line.prefix+" "+strconv.FormatInt(line.count, 10)) + } + return strings.Join(strs, "\n") +} + +func merge(covs [][]*covLine) []*covLine { + if len(covs) == 0 { + return nil + } + if len(covs) == 1 { + return covs[0] + } + result := covs[0] + for i := 1; i < len(covs); i++ { + result = mergeCov(result, covs[i]) + } + return result +} + +func mergeCov(a []*covLine, b []*covLine) []*covLine { + for _, line := range b { + idx := -1 + for i := 0; i < len(a); i++ { + if a[i].prefix == line.prefix { + idx = i + break + } + } + if idx < 0 { + a = append(a, line) + } else { + // fmt.Printf("add %s %d %d\n", a[idx].prefix, a[idx].count, line.count) + a[idx].count += line.count + } + } + return a +} + +type covLine struct { + prefix string + count int64 +} + +func parseCover(content string) []*covLine { + lines := strings.Split(content, "\n") + if len(lines) > 0 && strings.HasPrefix(lines[0], "mode:") { + lines = lines[1:] + } + covLines := make([]*covLine, 0, len(lines)) + for _, line := range lines { + covLine := parseCovLine(line) + if covLine == nil { + continue + } + covLines = append(covLines, covLine) + } + return covLines +} + +func parseCovLine(line string) *covLine { + idx := strings.LastIndex(line, " ") + if idx < 0 { + return nil + } + cnt, err := strconv.ParseInt(line[idx+1:], 10, 64) + if err != nil { + cnt = 0 + } + return &covLine{ + prefix: line[:idx], + count: cnt, + } +} diff --git a/script/run-test/main.go b/script/run-test/main.go index 4b0e87bd..b21feab0 100644 --- a/script/run-test/main.go +++ b/script/run-test/main.go @@ -33,7 +33,7 @@ import ( // go run ./script/run-test/ --include go1.17.13 --include go1.18.10 --include go1.19.13 --include go1.20.14 --include go1.21.8 --include go1.22.1 -count=1 --xgo-default-test-only -run TestFuncNameSliceShouldWorkWithDebug -v // TODO: remove duplicate test between xgo test and runtime test -var runtimeTests = []string{ +var runtimeSubTests = []string{ "trace_panic_peek", "func_list", "trap_inspect_func", @@ -231,16 +231,20 @@ func main() { runDefault := true runXgoTestFlag := true runRuntimeTestFlag := true + runRuntimeSubTestFlag := true if xgoTestOnly { runRuntimeTestFlag = false + runRuntimeSubTestFlag = false runXgoTestFlag = true runDefault = false } else if xgoRuntimeTestOnly { runRuntimeTestFlag = true + runRuntimeSubTestFlag = true runXgoTestFlag = false runDefault = false } else if xgoDefaultTestOnly { runRuntimeTestFlag = false + runRuntimeSubTestFlag = false runXgoTestFlag = false runDefault = true } @@ -262,7 +266,7 @@ func main() { } args = append(args, "-coverprofile", prefix+"-"+variant+suffix) } - if debug && (variant == "xgo" || variant == "runtime") { + if debug && (variant == "xgo" || variant == "runtime" || variant == "runtime-sub") { args = append(args, "--log-debug=stdout") } return args @@ -282,6 +286,13 @@ func main() { os.Exit(1) } } + if runRuntimeSubTestFlag { + err := runRuntimeSubTest(goroot, addArgs(remainArgs, "runtime-sub"), remainTests) + if err != nil { + fmt.Fprintf(os.Stdout, "FAIL %s: %v(%v)\n", goroot, err, time.Since(begin)) + os.Exit(1) + } + } if runDefault { err := runDefaultTest(goroot, addArgs(remainArgs, "default"), remainTests) if err != nil { @@ -331,13 +342,17 @@ func runXgoTest(goroot string, args []string, tests []string) error { func runRuntimeTest(goroot string, args []string, tests []string) error { return doRunTest(goroot, testKind_runtimeTest, args, tests) } +func runRuntimeSubTest(goroot string, args []string, tests []string) error { + return doRunTest(goroot, testKind_runtimeSubTest, args, tests) +} type testKind string const ( - testKind_default testKind = "" - testKind_xgoTest testKind = "xgo-test" - testKind_runtimeTest testKind = "runtime-test" + testKind_default testKind = "" + testKind_xgoTest testKind = "xgo-test" + testKind_runtimeTest testKind = "runtime-test" + testKind_runtimeSubTest testKind = "runtime-sub-test" ) func doRunTest(goroot string, kind testKind, args []string, tests []string) error { @@ -371,9 +386,6 @@ func doRunTest(goroot string, kind testKind, args []string, tests []string) erro if len(tests) > 0 { testArgs = append(testArgs, tests...) } else { - for _, runtimeTest := range runtimeTests { - testArgs = append(testArgs, "./test/"+runtimeTest) - } testArgs = append(testArgs, "./core/...", "./functab/...", @@ -382,6 +394,16 @@ func doRunTest(goroot string, kind testKind, args []string, tests []string) erro "./mock/...", ) } + case testKind_runtimeSubTest: + testArgs = []string{"run", "./cmd/xgo", "test", "--project-dir", "runtime/test"} + testArgs = append(testArgs, args...) + if len(tests) > 0 { + testArgs = append(testArgs, tests...) + } else { + for _, runtimeTest := range runtimeSubTests { + testArgs = append(testArgs, "./"+runtimeTest+"/...") + } + } } // debug diff --git a/test/build.go b/test/build.go index 1bb54d13..fe78d825 100644 --- a/test/build.go +++ b/test/build.go @@ -9,6 +9,7 @@ import ( "sync" "testing" + "github.com/xhd2015/xgo/support/cmd" "github.com/xhd2015/xgo/support/osinfo" "github.com/xhd2015/xgo/support/filecopy" @@ -38,23 +39,26 @@ func linkRuntimeAndTest(testDir string, goModOnly bool) (rootDir string, subDir return "", "", err } - // Windows no link - if runtime.GOOS != "windows" { - // copy runtime to a tmp directory, and - // test under there - if goModOnly { - err = filecopy.LinkFile(filepath.Join("..", "runtime", "go.mod"), filepath.Join(tmpDir, "go.mod")) - } else { + err = filecopy.CopyFile(filepath.Join("..", "runtime", "go.mod"), filepath.Join(tmpDir, "go.mod")) + if err != nil { + return "", "", err + } + // copy runtime to a tmp directory, and + // test under there + if !goModOnly { + if osinfo.FORCE_COPY_UNSYM { err = filecopy.LinkFiles(filepath.Join("..", "runtime"), tmpDir) - } - } else { - if goModOnly { - err = filecopy.CopyFile(filepath.Join("..", "runtime", "go.mod"), filepath.Join(tmpDir, "go.mod")) } else { + // Windows no link err = filecopy.CopyReplaceDir(filepath.Join("..", "runtime"), tmpDir, false) } + if err != nil { + return "", "", err + } } + // set go.mod to go1.18 + err = cmd.Dir(tmpDir).Run("go", "mod", "edit", "-go=1.18") if err != nil { return "", "", err }