> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Go 1.24: New STD-lib synctest
- URL: https://huizhou92.com/go-1-24-new-std-lib-synctest/
- Published: 2024-12-23T09:44:34.000Z
- Updated: 2026-09-06T11:39:45.000Z
- Description: Go 1.24 introduces `testing/synctest`, enhancing concurrent testing by using virtual clocks for faster, reliable results. #Golang #Testing
- Author: huizhou92
- Tags: tech, golang, go1_24, #lang-en

Go 1.24: New STD-lib synctest

## Introduction

Testing concurrent code in Go has always been challenging. This is especially true for test cases involving time-dependent behavior. Traditional testing methods often rely on the actual system clock and synchronization mechanisms, leading to slow and unreliable tests.

For instance, when testing the expiration feature of the `go-cache` library, a common approach would look like this:

> [go-cache](https://github.com/patrickmn/go-cache?ref=huizhou92.com) is a popular Go caching library that supports time-to-live (TTL) and periodic cleanup of expired cache items. It is well-suited for caching data and automatically deletes expired entries.

List 1: `TestGoCacheEntryExpires` testing cache in the usual way

```go
func TestGoCacheEntryExpires(t *testing.T) {  
    c := cache.New(5*time.Second, 10*time.Second)  
    c.Set("foo", "bar", cache.DefaultExpiration)  
    v, found := c.Get("foo")  
    assert.True(t, found)  
    assert.Equal(t, "bar", v)  
    time.Sleep(5 * time.Second)  
    v, found = c.Get("foo")  
    assert.False(t, found)  
    assert.Nil(t, v)  
}

```

Running this test requires waiting for 5 seconds for the cache entry to expire:

```shell
➜  synctest git:(main) ✗ time go test go-cache_test.go  
ok      command-line-arguments  5.012s  
go test go-cache_test.go  0.38s user 1.27s system 27% cpu 6.049 total  

```

#### The `testing/synctest` Approach

Starting with Go 1.24, the `testing/synctest` package significantly improves this process. Here's how the same test case can be rewritten using `testing/synctest`  
Test Example 2: `TestGoCacheEntryExpiresWithSynctest` testing cache in the synctest

```go
func TestGoCacheEntryExpiresWithSynctest(t *testing.T) {  
    c := cache.New(2*time.Second, 5*time.Second)  
    synctest.Run(func() {  
        c.Set("foo", "bar", cache.DefaultExpiration)  
        if got, exist := c.Get("foo"); !exist && got != "bar" {  
            t.Errorf("c.Get(k) = %v, want %v", got, "bar")  
        }  

        time.Sleep(1 * time.Second)  
        if got, exist := c.Get("foo"); !exist && got != "bar" {  
            t.Errorf("c.Get(k) = %v, want %v", got, "bar")  
        }  

        time.Sleep(3 * time.Second)  
        if got, exist := c.Get("foo"); exist {  
            t.Errorf("c.Get(k) = %v, want %v", got, nil)  
        }  
    })  
}

```

Running this test

```shell
➜  synctest git:(main) ✗ time GOEXPERIMENT=synctest gotip test -run TestGoCacheEntryExpiresWithSynctest  
PASS  
ok      blog-example/go/go1.24/synctest 0.009s  
GOEXPERIMENT=synctest gotip test -run TestGoCacheEntryExpiresWithSynctest  0.36s user 1.23s system 171% cpu 0.933 total  

```

The test now completes in just `0.009s`.  
**Why is it so fast?**

## Unveiling `testing/synctest`

[testing/synctest](https://github.com/golang/go/issues/67434?ref=huizhou92.com) simplifies testing concurrent code by using virtual clocks and `goroutine` groups (also known as "bubbles"). This makes tests both fast and reliable. The package offers two core functions:

```go
func Run(f func()) { synctest.Run(f) }  
func Wait() { synctest.Wait() }  

```

### Key Features

1. **Run**: Executes the provided function `f` in a new `goroutine` under its bubble, ensuring that the virtual clock controls all related `goroutines`.
2. **Wait**: Synchronizes the bubble’s `goroutines` and blocks the main `goroutine` until all others are in a `durably blocked` state.

A `durably blocked` state occurs when `goroutines` are waiting on:

- Channel send/receive within the bubble.
- `select` statements involving bubble channels
- `sync.Cond.Wait`
- `time.Sleep`  
However, `goroutines` blocked by system calls or external events are not considered `durably blocked`.

### Virtual Clocks

Each bubble has a virtual clock starting from `2000-01-01 00:00:00 UTC`. The clock advances only when all `goroutines` are idle. This allows `time.Sleep` calls will be completed instantly if all `goroutines` are idle, significantly speeding up tests.

List 3: `TestSynctest` Virtual Clocks are not affected by program run time

```go
func TestSynctest(t *testing.T) {  
    synctest.Run(func() {  
       before := time.Now()  
       fmt.Println("before", before)  
       f1 := func() {  
          for i := 0; i < 10e9; i++ { // time consuming, It's about 3s in my machine  
          }  
       }  
       go f1()  
       synctest.Wait()     //wait f1  
       after := time.Now() // time is not affected by the running time of f1  
       fmt.Println("after", after)  
    })  
}

```

Output:

```shell
➜  synctest git:(main) ✗ time GOEXPERIMENT=synctest gotip test -run TestSynctest  
before 2000-01-01 08:00:00 +0800 CST m=+946164282.733407335  
after 2000-01-01 08:00:00 +0800 CST m=+946164282.733407335  
PASS  
ok      blog-example/go/go1.24/synctest 3.131s  
GOEXPERIMENT=synctest gotip test -run TestSynctest  3.45s user 1.25s system 133% cpu 3.533 total  

```

Even computationally intensive functions do not affect the virtual clock’s time.

## Conclusion

The `testing/synctest` package introduces a powerful way to test concurrent Go code efficiently and reliably by leveraging virtual clocks and `goroutine` control. It eliminates the need for real-time waiting and makes time-dependent tests faster and more predictable.

## Reference

[synctest.go](https://github.com/golang/go/blob/05d8984781f7cf2f0f39b53699a558b6a1965c6c/src/testing/synctest/synctest.go?ref=huizhou92.com)