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