This is cool, just don't let Rob Pike see this, he will have a conniption. Glad you called out that it shouldn't be used because this is about the most magical thing I have ever seen in Go
This is all possible and quite neat to dive into the specifics, but if you really want to be able swap a std lib call, just turn it into a variable and change it.
// code.go
var now = time.Now
// code_test.go
func TestCode(t *testing.T) {
nowSwap := now
t.Cleanup (func() {
now = nowSwap
}
now = func() time.Time {
return time.Date(...)
}
}
That is a useful pattern, though I was unclear on why `t.Cleanup` and not `defer`. In case others are curious, too:
> Parallel subtestsWith t.Run(..., func(t testing.T) { t.Parallel(); ... }), the parent test function can return (and thus run its defers) before parallel subtests actually finish.*
I suspect that using a build tag (say `test`) and two function definitions (one that directly calls `time.Now()` and one test-only one that uses a mutable var) will optimize out to zero cost in the non-test case - last I fiddled with that, it was pretty good at consistently inlining trivial wrapper funcs like that.
I've used a different approach to this: there's no real need to modify the compiled binary code because Go compiles everything from source, so you can patch the functions at the source level instead: https://github.com/YuriyNasretdinov/golang-soft-mocks
The way it works is that at the start of every function it adds an if statement that atomically checks whether or not the function has been intercepted, and if it did, then executes the replacement function instead. This also addresses the inlining issue.
My tool no longer works since it was rewriting GOPATH, and Go since effectively switched to Go Modules, but if you're persistent enough you can make it work with Go modules too — all you need to do is rewrite the Go module cache instead of GOPATH and you're good to go.
While I would never consider this approach advisable in any language that doesn't build in support for this sort of thing from the start, the thinner the runtime, the less dangerous it is. Go's runtime is fairly thick, and also, concurrent. The odds of something blowing up are rather too high for me to even dream of putting something like this into production in Go. In C++ it may merely be somewhat crazy rather than completely crazy.
(I suppose Rust is arguably an exception to this; thin runtime, but there's a lot of things the replaced function could do that would still blow Rust up if the rest of the code isn't compiled and correctly optimized to account for whatever the new code does.)
This is cool, just don't let Rob Pike see this, he will have a conniption. Glad you called out that it shouldn't be used because this is about the most magical thing I have ever seen in Go
This is all possible and quite neat to dive into the specifics, but if you really want to be able swap a std lib call, just turn it into a variable and change it.
Examples Code: https://github.com/open-telemetry/opentelemetry-go/blob/main... Test: https://github.com/open-telemetry/opentelemetry-go/blob/490f...This is often the path of pain: https://google.github.io/styleguide/go/best-practices#global....
That is a useful pattern, though I was unclear on why `t.Cleanup` and not `defer`. In case others are curious, too:
> Parallel subtestsWith t.Run(..., func(t testing.T) { t.Parallel(); ... }), the parent test function can return (and thus run its defers) before parallel subtests actually finish.*
The point of the OP is that it changes calls to `time.Now` regardless of whether the code that's calling it uses your variable or not.
I suspect that using a build tag (say `test`) and two function definitions (one that directly calls `time.Now()` and one test-only one that uses a mutable var) will optimize out to zero cost in the non-test case - last I fiddled with that, it was pretty good at consistently inlining trivial wrapper funcs like that.
> There you go. It’s 5PM. It’s always 5PM.
That reminded me of the Go Playground, where it is always 2009
https://go.dev/play/p/VrWYHGbtc6m
Other prior art: https://github.com/bouk/monkey with accompanying blog post https://bou.ke/blog/monkey-patching-in-go/
I've used a different approach to this: there's no real need to modify the compiled binary code because Go compiles everything from source, so you can patch the functions at the source level instead: https://github.com/YuriyNasretdinov/golang-soft-mocks
The way it works is that at the start of every function it adds an if statement that atomically checks whether or not the function has been intercepted, and if it did, then executes the replacement function instead. This also addresses the inlining issue.
My tool no longer works since it was rewriting GOPATH, and Go since effectively switched to Go Modules, but if you're persistent enough you can make it work with Go modules too — all you need to do is rewrite the Go module cache instead of GOPATH and you're good to go.
Yikes, I don't see any legitimate use for this, other than hacking for the sake of hacking. Interesting read though.
Hot reloading for development loops is _the_ canonical use case for this.
I’ve seen this in large C++ systems to allow for a runtime patch, generally to add a simple debug call at the start of a function.
While I would never consider this approach advisable in any language that doesn't build in support for this sort of thing from the start, the thinner the runtime, the less dangerous it is. Go's runtime is fairly thick, and also, concurrent. The odds of something blowing up are rather too high for me to even dream of putting something like this into production in Go. In C++ it may merely be somewhat crazy rather than completely crazy.
(I suppose Rust is arguably an exception to this; thin runtime, but there's a lot of things the replaced function could do that would still blow Rust up if the rest of the code isn't compiled and correctly optimized to account for whatever the new code does.)
wdym, now it will be possible to implement Wordpress in Go
You could already do that today, via OS IPC mechanisms, at the expense of higher systems resources, with each plugin being its own process.
Well, we are on Hacker News after all...