
func main() { closeFunc := (*os.File).Close soft.Mock(closeFunc, func(f *os.File) error { fmt.Printf("File is going to be closed: %s\n", f.Name()) res, _ := soft.CallOriginal(closeFunc, f)[0].(error) return res }) fp, _ := os.Open("/dev/null") fmt.Printf("Hello, world: %v!\n", fp.Close()) } File is going to be closed: /dev/null Hello, world: <nil>! @@ -9,6 +9,8 @@ import ( "runtime" "syscall" + + "github.com/YuriyNasretdinov/golang-soft-mocks" ) // fixLongPath is a noop on non-Windows platforms. @@ -126,6 +128,11 @@ // Close closes the File, rendering it unusable for I/O. // It returns an error, if any. func (f *File) Close() error { + if closeFuncIntercepted { + println("Intercepted!") + return nil + } + if f == nil { return ErrInvalid } @@ -293,3 +300,9 @@ } return nil } + +var closeFuncIntercepted bool + +func init() { + soft.RegisterFunc((*File).Close, &closeFuncIntercepted) +} /usr/local/go/src/soft symlink that leads to $GOPATH/src/github.com/YuriyNasretdinov/golang-soft-mocks . After that, the code earned and I managed to achieve that it was possible to turn on and cancel interception at will. map[func()]bool reflect.ValueOf(f).Pointer() to get a pointer to the beginning of the function code. The reason why functions are not compared with each other is that a pointer to a function in go is actually a double pointer and may contain additional fields, such as, for example, receiver. About this in more detail here .github.com/bouk/monkey library explicitly states that the monkey.Patch() method is not thread-safe because it patches the memory directly.atomic.LoadInt32 and atomic.StoreInt32 . In the x86 architecture, atomic operations are the usual LOAD and STORE, so atomic reading and writing will not affect the performance of the resulting code too much.soft package in each file, which is an alias for our package github.com/YuriyNasretdinov/golang-soft-mocks . This package uses the reflect package, so we cannot rewrite reflect, atomic packages and their dependencies, otherwise we will get cyclic imports. And there are surprisingly a lot of dependencies on the package:
func (TestDeps) StartCPUProfile(w io.Writer) error { return pprof.StartCPUProfile(w) } func (file *file) close() error { if file == nil || file.fd == badFd { return syscall.EINVAL } var err error if e := syscall.Close(file.fd); e != nil { err = &PathError{"close", file.name, e} } file.fd = -1 // so it can't be closed again // no need for a finalizer anymore runtime.SetFinalizer(file, nil) return err } (*file).close inside the function body will not mean a pointer to the close method, but an attempt to dereference the file variable and take the close property from there, and such code, of course, does not compile.Source: https://habr.com/ru/post/328620/
All Articles