<aside> 💡
Go 1.25
JSON evolution in Go: from v1 to v2
Green Tea GC: How Go Stopped Wasting 35% of Your CPU Cycles
</aside>
Go 1.25 is out, so it's a good time to explore what's new. The official release notes are pretty dry, so I prepared an interactive version with lots of examples showing what has changed and what the new behavior is.
Read on and see!
synctest • json/v2 • GOMAXPROCS • New GC • Anti-CSRF • WaitGroup.Go • FlightRecorder • os.Root • reflect.TypeAssert • T.Attr • slog.GroupAttrs • hash.Cloner
This article is based on the official release notes from The Go Authors, licensed under the BSD-3-Clause license. This is not an exhaustive list; see the official release notes for that.
I provide links to the proposals (𝗣) and commits (𝗖𝗟) for the features described. Check them out for motivation and implementation details.
Error handling is often skipped to keep things simple. Don't do this in production ツ
Suppose we have a function that waits for a value from a channel for one minute, then times out:
// Read reads a value from the input channel and returns it.
// Timeouts after 60 seconds.
func Read(in chan int) (int, error) {
select {
case v := <-in:
return v, nil
case <-time.After(60 * time.Second):
return 0, errors.New("timeout")
}
}
We use it like this:
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
val, err := Read(ch)
fmt.Printf("val=%v, err=%v\\n", val, err)
}
How do we test the timeout situation? Surely we don't want the test to actually wait 60 seconds. We could make the timeout a parameter (we probably should), but let's say we prefer not to.
The new synctest
package to the rescue! The synctest.Test
function executes an isolated "bubble". Within the bubble, time
package functions use a fake clock, allowing our test to pass instantly:
func TestReadTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ch := make(chan int)
_, err := Read(ch)
if err == nil {
t.Fatal("expected timeout error, got nil")
}
})
}
The initial time in the bubble is midnight UTC 2000-01-01. Time advances when every goroutine in the bubble is blocked. In our example, when the only goroutine is blocked on select
in Read
, the bubble's clock advances 60 seconds, triggering the timeout case.
Keep in mind that the t
passed to the inner function isn't exactly the usual testing.T
. In particular, you should never call T.Run
, T.Parallel
, or T.Deadline
on it:
func TestSubtest(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
t.Run("subtest", func (t *testing.T) {
t.Log("ok")
})
})
}
So, no inner tests inside the bubble.
Another useful function is synctest.Wait
. It waits for all goroutines in the bubble to block, then resumes execution: