async

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

🕰️ Async

Provides a test‑aware sync.WaitGroup with configurable goroutine limits.

See Difference from parallel tests.

Quick Start

go get github.com/ozontech/testo-toppings
type T struct{
	*testo.T
	*async.PluginAsync
}

func Test(t *testing.T) {
	testo.RunTest(t, func(t T) {
		async.Run(t, "foo", func(t T) { t.Log("hello from goroutine") })
		async.Run(t, "bar", func(t T) { t.Log("hello from other") })

		t.Wait() // optional, will be called by the plugin after test completion

		t.Log("goroutines are completed")
	})
}

How it works

It creates a sync.WaitGroup for each test (and sub-test) and provides a Run function to run sub-tests in independent goroutines.

Wait group is awaited through Wait in AfterAll hook by the plugin itself. If at least one test called t.FailNow inside Run, Wait will propagate it.

It's also possible to call (potentially multiple times) a Wait method to wait for current goroutines to finish:

async.Run(t, "a", func(t T) { ... })
async.Run(t, "b", func(t T) { ... })
async.Run(t, "c", func(t T) { ... })

t.Wait() // wait for these ^3 goroutines to finish

async.Run(t, "a", func(t T) { ... })
async.Run(t, "b", func(t T) { ... })

t.Wait() // wait for these ^2 goroutines to finish

// test will wait for this goroutine to finish in the end.
async.Run(t, "a", func(t T) { ... })

Difference from parallel tests

[!WARNING] Calls to t.Parallel, t.Setenv and t.Chdir in async tests or tests with async ancestors are forbidden.

When you call t.Parallel it pauses current test until all other synchronous tests are completed. Sometimes it might be a problem.

For example, when testing a concurrent component where you need to run several operations at the same time, then check its state inside the same test function:

type T struct{
	*testo.T
	*async.PluginAsync
}

func Test(t *testing.T) {
	testo.RunTest(t, func(t T) {
		const workers = 10
		const incs = 100

		var counter Counter

		for i := range workers {
			async.Run(t, fmt.Sprintf("worker %d", i), func(t T) {
				for range incs {
					counter.Inc()
				}
			})
		}

		t.Wait()

		want := workers * incs
		got := counter.Value()

		if want != got {
			t.Fatalf("counter = %d, want %d", got, want)
		}
	})
}

Documentation

Overview

Package async provides a test‑aware sync.WaitGroup.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run[T CommonT](t T, name string, f func(t T), options ...testoplugin.Option)

Run calls testo.Run inside independent goroutine and returns immediately.

Inside Run, testo.T.Context is cancelled as soon as at least one neighbor test inside Run calls to testo.T.FailNow.

async.Run(t, "foo", func(t T) { t.FailNow() })
async.Run(t, "bar", func(t T) { t.Log(<-t.Context.Done()) }) // cancelled

All tasks are awaited before test completion with sync.WaitGroup.Wait. Use PluginAsync.Wait to manually await all running goroutines and propagate calls to testo.T.FailNow from goroutines (if any).

Difference from parallel tests

When you call testo.T.Parallel it pauses current test until all other synchronous tests are completed. Sometimes it might be a problem.

For example, when testing a concurrent component where you need to run several operations at the same time, then check its state inside the same test function:

type T struct{
	*testo.T
	*async.PluginAsync
}

func Test(t *testing.T) {
	testo.RunTest(t, func(t T) {
		const workers = 10
		const incs = 100

		var counter Counter

		for i := range workers {
			async.Run(t, fmt.Sprintf("worker %d", i), func(t T) {
				for range incs {
					counter.Inc()
				}
			})
		}

		t.Wait()

		want := workers * incs
		got := counter.Value()

		if want != got {
			t.Fatalf("counter = %d, want %d", got, want)
		}
	})
}

func WithLimit

func WithLimit(n int) testoplugin.Option

WithLimit limits the number of active goroutines in this group to at most n. A negative value indicates no limit. A limit of zero will prevent any new goroutines from being added.

Any subsequent call to the Run function will block until it can add an active goroutine without exceeding the configured limit.

Types

type CommonT

type CommonT interface {
	testo.CommonT

	Wait()
	// contains filtered or unexported methods
}

CommonT is an interface common for all Ts with PluginAsync installed.

type PluginAsync

type PluginAsync struct {
	*testo.T
	// contains filtered or unexported fields
}

PluginAsync simplifies goroutine spawning in tests.

func (*PluginAsync) Plugin

func (pa *PluginAsync) Plugin(
	_ testoplugin.Plugin,
	options ...testoplugin.Option,
) testoplugin.Spec

Plugin implements testoplugin.Plugin.

func (*PluginAsync) Wait

func (pa *PluginAsync) Wait()

Wait blocks until all tests started from Run are finished. If at least one test called testo.T.FailNow inside Run, Wait will propagate it.

Note, that calling this function is optional, as it will be called by the plugin after the current test or sub-test ends.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL