-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Patch,PatchByName and PatchMethodByName
- Loading branch information
Showing
16 changed files
with
473 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package mock | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/xhd2015/xgo/runtime/core" | ||
) | ||
|
||
func PatchByName(pkgPath string, funcName string, replacer interface{}) func() { | ||
return MockByName(pkgPath, funcName, buildInterceptorFromPatch(replacer)) | ||
} | ||
|
||
func PatchMethodByName(instance interface{}, method string, replacer interface{}) func() { | ||
return MockMethodByName(instance, method, buildInterceptorFromPatch(replacer)) | ||
} | ||
|
||
func buildInterceptorFromPatch(replacer interface{}) func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error { | ||
v := reflect.ValueOf(replacer) | ||
t := v.Type() | ||
if t.Kind() != reflect.Func { | ||
panic(fmt.Errorf("requires func, given %T", replacer)) | ||
} | ||
if v.IsNil() { | ||
panic("replacer is nil") | ||
} | ||
nIn := t.NumIn() | ||
return func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error { | ||
// assemble arguments | ||
callArgs := make([]reflect.Value, nIn) | ||
src := 0 | ||
dst := 0 | ||
if fn.RecvType != "" { | ||
src++ | ||
} | ||
if fn.FirstArgCtx { | ||
callArgs[0] = reflect.ValueOf(ctx) | ||
dst++ | ||
} | ||
for i := 0; i < nIn-dst; i++ { | ||
callArgs[dst+i] = reflect.ValueOf(args.GetFieldIndex(src + i).Value()) | ||
} | ||
|
||
// call the function | ||
var res []reflect.Value | ||
if !t.IsVariadic() { | ||
res = v.Call(callArgs) | ||
} else { | ||
res = v.CallSlice(callArgs) | ||
} | ||
|
||
// assign result | ||
nOut := len(res) | ||
for i := 0; i < nOut; i++ { | ||
results.GetFieldIndex(i).Set(res[i].Interface()) | ||
} | ||
|
||
// check error | ||
if nOut > 0 { | ||
last := res[nOut-1].Interface() | ||
if last != nil { | ||
if err, ok := last.(error); ok { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
//go:build !go1.18 | ||
// +build !go1.18 | ||
|
||
package mock | ||
|
||
func Patch(fn interface{}, replacer interface{}) func() { | ||
return Mock(fn, buildInterceptorFromPatch(replacer)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//go:build go1.18 | ||
// +build go1.18 | ||
|
||
package mock | ||
|
||
// TODO: what if `fn` is a Type function | ||
// instead of an instance method? | ||
func Patch[T any](fn T, replacer T) func() { | ||
return Mock(fn, buildInterceptorFromPatch(replacer)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package patch | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/xhd2015/xgo/runtime/mock" | ||
) | ||
|
||
func greet(s string) string { | ||
return "hello " + s | ||
} | ||
|
||
func greetVaradic(s ...string) string { | ||
return "hello " + strings.Join(s, ",") | ||
} | ||
|
||
func TestPatchSimpleFunc(t *testing.T) { | ||
mock.Patch(greet, func(s string) string { | ||
return "mock " + s | ||
}) | ||
|
||
res := greet("world") | ||
if res != "mock world" { | ||
t.Fatalf("expect patched result to be %q, actual: %q", "mock world", res) | ||
} | ||
} | ||
|
||
func TestPatchVaradicFunc(t *testing.T) { | ||
mock.Patch(greetVaradic, func(s ...string) string { | ||
return "mock " + strings.Join(s, ",") | ||
}) | ||
|
||
res := greetVaradic("earth", "moon") | ||
if res != "mock earth,moon" { | ||
t.Fatalf("expect patched result to be %q, actual: %q", "mock earth,moon", res) | ||
} | ||
} | ||
|
||
type struct_ struct { | ||
s string | ||
} | ||
|
||
func (c *struct_) greet() string { | ||
return "hello " + c.s | ||
} | ||
|
||
func TestPatchMethod(t *testing.T) { | ||
ins := &struct_{ | ||
s: "world", | ||
} | ||
mock.Patch(ins.greet, func() string { | ||
return "mock " + ins.s | ||
}) | ||
|
||
res := ins.greet() | ||
if res != "mock world" { | ||
t.Fatalf("expect patched result to be %q, actual: %q", "mock world", res) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package trap_args | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/xhd2015/xgo/runtime/core" | ||
) | ||
|
||
var gc = func(ctx context.Context) { | ||
panic("gc should be trapped") | ||
} | ||
|
||
func TestClosureShouldRetrieveCtxInfoAtTrapTime(t *testing.T) { | ||
ctx := context.Background() | ||
ctx = context.WithValue(ctx, "test", "mock") | ||
callAndCheck(func() { | ||
gc(ctx) | ||
}, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { | ||
if !f.FirstArgCtx { | ||
t.Fatalf("expect closure also mark firstArgCtx, actually not marked") | ||
} | ||
if trapCtx == nil { | ||
t.Fatalf("expect trapCtx to be non nil, atcual nil") | ||
} | ||
if trapCtx != ctx { | ||
t.Fatalf("expect trapCtx to be the same with ctx, actully different") | ||
} | ||
return nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package trap_args | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/xhd2015/xgo/runtime/core" | ||
) | ||
|
||
func TestPlainCtxArgCanBeRecognized(t *testing.T) { | ||
ctx := context.Background() | ||
ctx = context.WithValue(ctx, "test", "mock") | ||
callAndCheck(func() { | ||
f2(ctx) | ||
}, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { | ||
if !f.FirstArgCtx { | ||
t.Fatalf("expect first arg to be context") | ||
} | ||
if trapCtx != ctx { | ||
t.Fatalf("expect context passed unchanged, actually different") | ||
} | ||
ctxVal := trapCtx.Value("test").(string) | ||
if ctxVal != "mock" { | ||
t.Fatalf("expect context value to be %q, actual: %q", "mock", ctxVal) | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
func TestCtxVariantCanBeRecognized(t *testing.T) { | ||
ctx := context.Background() | ||
ctx = context.WithValue(ctx, "test", "mock") | ||
|
||
myCtx := &MyContext{Context: ctx} | ||
|
||
callAndCheck(func() { | ||
f3(myCtx) | ||
}, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { | ||
if !f.FirstArgCtx { | ||
t.Fatalf("expect first arg to be context") | ||
} | ||
if trapCtx != myCtx { | ||
t.Fatalf("expect context passed unchanged, actually different") | ||
} | ||
ctxVal := trapCtx.Value("test").(string) | ||
if ctxVal != "mock" { | ||
t.Fatalf("expect context value to be %q, actual: %q", "mock", ctxVal) | ||
} | ||
return nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package trap_args | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
|
||
"github.com/xhd2015/xgo/runtime/core" | ||
"github.com/xhd2015/xgo/runtime/trap" | ||
) | ||
|
||
func plainErr() error { | ||
panic("plainErr should be mocked") | ||
} | ||
|
||
func subErr() *Error { | ||
return &Error{"sub error"} | ||
} | ||
|
||
func TestPlainErrShouldSetErrRes(t *testing.T) { | ||
mockErr := errors.New("mock err") | ||
var err error | ||
callAndCheck(func() { | ||
err = plainErr() | ||
}, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { | ||
if !f.LastResultErr { | ||
t.Fatalf("expect f.LastResultErr to be true, actual: false") | ||
} | ||
return mockErr | ||
}) | ||
|
||
if err != mockErr { | ||
t.Fatalf("expect return err %v, actual %v", mockErr, err) | ||
} | ||
} | ||
|
||
func TestSubErrShouldNotSetErrRes(t *testing.T) { | ||
mockErr := errors.New("mock err") | ||
var err *Error | ||
var recoverErr interface{} | ||
func() { | ||
defer func() { | ||
// NOTE: this may have impact | ||
trap.Skip() | ||
recoverErr = recover() | ||
}() | ||
callAndCheck(func() { | ||
err = subErr() | ||
}, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { | ||
if f.LastResultErr { | ||
t.Fatalf("expect f.LastResultErr to be false, actual: true") | ||
} | ||
// even not pl should fail | ||
return mockErr | ||
}) | ||
}() | ||
|
||
if recoverErr == nil { | ||
t.Fatalf("expect error via panic, actually no panic") | ||
} | ||
|
||
if err != nil { | ||
t.Fatalf("expect return error not set, actual: %v", err) | ||
} | ||
|
||
if recoverErr != mockErr { | ||
t.Fatalf("expect panic err to be %v, actual: %v", mockErr, recoverErr) | ||
} | ||
} |
Oops, something went wrong.