Skip to content

After() appears to not reliable work in for select with channels construct #97

@skheyfets-asapp

Description

@skheyfets-asapp

Hi All,

First of all thank you for a great package, that makes unit testing actually possible when dealing with time dependent code logic.

I've come across the issue in my unit tests which appears that fakeClock.After() time not reliably called when part of the for-select code. I've replicated the issue with the code below which I THINK should work, but it doesn't.

For the code below, I expect otherCount to be 3 (which it is), and then I expect timerFired to be true, which it's not.

This is on go 1.23.4 on MacOS 15.3.1

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/jonboulle/clockwork"
)

func main() {

	clock := clockwork.NewFakeClock()

	timerFired := false
	done := make(chan struct{})
	otherChan := make(chan struct{})
	otherCount := 0

	fmt.Println(clock.Now().Format(time.StampMilli), "starting")

	go func() {
		for {
			select {
			case <-done:
				fmt.Println(clock.Now().Format(time.StampMilli), "exiting")
			case <-otherChan:
				otherCount++
				fmt.Println(clock.Now().Format(time.StampMilli), "received from otherChan")
				clock.Sleep(1 * time.Second)
			case <-clock.After(200 * time.Millisecond):
				timerFired = true
				fmt.Println(clock.Now().Format(time.StampMilli), "timer fired")

			}

		}
	}()

	ctx, cancelFn := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancelFn()
	clock.BlockUntilContext(ctx, 1)

	otherChan <- struct{}{}
	time.Sleep(10 * time.Millisecond) // time for goroutine to read
	clock.Advance(1 * time.Second)    // advance to cover clock.Sleep()

	otherChan <- struct{}{}
	time.Sleep(10 * time.Millisecond) // time for goroutine to read
	clock.Advance(1 * time.Second)    // advance to cover clock.Sleep()

	otherChan <- struct{}{}
	time.Sleep(10 * time.Millisecond) // time for goroutine to read
	clock.Advance(1 * time.Second)    // advance to cover clock.Sleep()

	clock.Advance(250 * time.Millisecond) //  advance to cover clock.After()
	time.Sleep(50 * time.Millisecond)

	done <- struct{}{}
	time.Sleep(100 * time.Millisecond)

	fmt.Println("other count", otherCount, "timer fired", timerFired)
	if timerFired && otherCount == 3 {
		fmt.Println("PASSED")
	} else {
		fmt.Println("FAILED")
	}
}

Output:

% go run main.go
Feb 20 21:02:00.917 starting
Feb 20 21:02:00.917 received from otherChan
Feb 20 21:02:01.917 received from otherChan
Feb 20 21:02:02.917 received from otherChan
Feb 20 21:02:04.117 exiting
other count 3 timer fired false
FAILED

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions