An open API service indexing awesome lists of open source software.

https://github.com/kunwardeep/paralleltest

Linter to check if your tests have been marked as parallel correctly
https://github.com/kunwardeep/paralleltest

linter parallel test

Last synced: about 2 months ago
JSON representation

Linter to check if your tests have been marked as parallel correctly

Awesome Lists containing this project

README

          

# paralleltest

[![Test](https://github.com/kunwardeep/paralleltest/actions/workflows/test.yml/badge.svg)](https://github.com/kunwardeep/paralleltest/actions/workflows/test.yml)

The Go linter `paralleltest` checks that the t.Parallel gets called for the test method and for the range of test cases within the test.

## Installation

```sh
go install github.com/kunwardeep/paralleltest@latest
```

## Usage

```sh
paralleltest ./...
```

A few options can be activated by flag:

* `-i`: Ignore missing calls to `t.Parallel` and only report incorrect uses of it.
* `-ignoremissingsubtests`: Require that top-level tests specify `t.Parallel`, but don't require it in subtests (`t.Run(...)`).
* `-ignoreloopVar`: Ignore loop variable detection.
* `-checkcleanup`: Check that `defer` is not used with `t.Parallel` (use `t.Cleanup` instead to ensure cleanup runs after parallel subtests complete).

## Development

### Prerequisites

- Go 1.23.0 or later
- Make

### Local Development

1. Clone the repository:
```sh
git clone https://github.com/kunwardeep/paralleltest.git
cd paralleltest
```

2. Install development tools:
```sh
make install_devtools
```

3. Install dependencies:
```sh
make ensure_deps
```

4. Run tests:
```sh
make test
```

5. Run linter:
```sh
make lint
```

To fix linting issues automatically:
```sh
make lint_fix
```

### CI/CD

The project uses GitHub Actions for continuous integration. The workflow includes:

- Running tests with race condition detection
- Running golangci-lint for code quality checks

The workflow runs on:
- Pull requests
- Pushes to the main branch

## Examples

### Missing `t.Parallel()` in the test method

```go
// bad
func TestFunctionMissingCallToParallel(t *testing.T) {
}

// good
func TestFunctionMissingCallToParallel(t *testing.T) {
t.Parallel()
// ^ call to t.Parallel()
}
// Error displayed
// Function TestFunctionMissingCallToParallel missing the call to method parallel
```

### Missing `t.Parallel()` in the range method

```go
// bad
func TestFunctionRangeMissingCallToParallel(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fmt.Println(tc.name)
})
}
}

// good
func TestFunctionRangeMissingCallToParallel(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// ^ call to t.Parallel()
fmt.Println(tc.name)
})
}
}
// Error displayed
// Range statement for test TestFunctionRangeMissingCallToParallel missing the call to method parallel in t.Run
```

### `t.Parallel()` is called in the range method but testcase variable not being used

```go
// bad
func TestFunctionRangeNotUsingRangeValueInTDotRun(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}
for _, tc := range testCases {
t.Run("this is a test name", func(t *testing.T) {
// ^ call to tc.name missing
t.Parallel()
fmt.Println(tc.name)
})
}
}

// good
func TestFunctionRangeNotUsingRangeValueInTDotRun(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fmt.Println(tc.name)
})
}
}
// Error displayed
// Range statement for test TestFunctionRangeNotUsingRangeValueInTDotRun does not use range value in t.Run
```

### `t.Parallel()` is called in the range method and test case variable tc being used, but is not reinitialised (More Info)
```go
// bad
func TestFunctionRangeNotReInitialisingVariable(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fmt.Println(tc.name)
})
}
}

// good
func TestFunctionRangeNotReInitialisingVariable(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
}{{name: "foo"}}
for _, tc := range testCases {
tc:=tc
// ^ tc variable reinitialised
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fmt.Println(tc.name)
})
}
}
// Error displayed
// Range statement for test TestFunctionRangeNotReInitialisingVariable does not reinitialise the variable tc
```

### Using `defer` with `t.Parallel()` (requires `-checkcleanup` flag)

When `t.Parallel()` is called, the test function returns immediately, causing `defer` statements to execute before subtests complete. This can lead to cleanup happening too early, causing test failures or resource leaks.

```go
// bad - defer runs before subtests complete
func TestWithDeferAndParallel(t *testing.T) {
t.Parallel()

tempFile := "test.tmp"
f, _ := os.Create(tempFile)
defer f.Close() // runs immediately when function returns
defer os.Remove(tempFile) // file deleted before subtests finish!

t.Run("subtest1", func(t *testing.T) {
t.Parallel()
fmt.Fprintf(f, "data") // may fail - file already closed!
})
}

// good - t.Cleanup runs after all subtests complete
func TestWithCleanupAndParallel(t *testing.T) {
t.Parallel()

tempFile := "test.tmp"
f, _ := os.Create(tempFile)
t.Cleanup(func() {
f.Close()
os.Remove(tempFile)
})

t.Run("subtest1", func(t *testing.T) {
t.Parallel()
fmt.Fprintf(f, "data") // works correctly
})
}
// Error displayed (with -checkcleanup flag)
// Function TestWithDeferAndParallel uses defer with t.Parallel, use t.Cleanup instead to ensure cleanup runs after parallel subtests complete
```

**Note:** This check is disabled by default. Enable it with the `-checkcleanup` flag.

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.