1
0
Fork 0
Go helper module to test context.Context cancellation behavior.
Find a file
2026-04-16 19:28:04 +02:00
canceltest.go Initial commit 2026-04-16 19:28:04 +02:00
go.mod Initial commit 2026-04-16 19:28:04 +02:00
README.md Initial commit 2026-04-16 19:28:04 +02:00

canceltest

Go helper module to test context.Context cancellation behavior.

go get grub4k.dev/canceltest

Usage

Inside of a test, use canceltest.Run to run a test function inside a synctest bubble. It takes a subtest name and a function which must produce a canceltest.Func.

As an example of a simple passing test:

canceltest.Run(t, "valid", func() canceltest.Func {
	return func(ctx context.Context) {
		<-ctx.Done()
	}
})

If the context.Context is not yet cancelled, the test must block; otherwise the prerequisite check will fail:

canceltest.Run(t, "not-blocked", func() canceltest.Func {
	return func(ctx context.Context) {
	}
})

A failing test is indicative of suboptimal cancellation behavior, in contrast to perfect behavior: unblocking when ctx.Done() is closed while avoiding leaked goroutines.

Below are some examples of failing tests.

Blocks infinitely

canceltest.Run(t, "blocked", func() canceltest.Func {
	return func(ctx context.Context) {
		select {}
	}
})

Cancellable only before I/O

This test checks if the context is cancelled only before performing IO, but then blocks forever during I/O.

canceltest.Run(t, "preempt", func() canceltest.Func {
	return func(ctx context.Context) {
		select {
		case <-ctx.Done():
			return
		default:
		}

		select {}
	}
})

Leaked goroutine

Here the cancellation behaves as expected, however a goroutine is leaked, so the test also fails:

canceltest.Run(t, "leaky", func() canceltest.Func {
	return func(ctx context.Context) {
		go func() {
			select {}
		}()
		<-ctx.Done()
	}
})