惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
雷峰网
雷峰网
宝玉的分享
宝玉的分享
IT之家
IT之家
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
博客园 - 叶小钗
V
Visual Studio Blog
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
美团技术团队
爱范儿
爱范儿
T
The Blog of Author Tim Ferriss
L
LangChain Blog
U
Unit 42
有赞技术团队
有赞技术团队
博客园_首页

alexwlchan’s notes

What is WS11 1DB? Blocking referrers with Caddy How to type a Spanish question mark (¿) on a Mac Non-overlapping type comparisons and Python type checkers Use Path.glob() and Path.rglob() for typed versions of glob.glob() Curious clocks and colourful eyes Track which templates are used by Jinja2 Archeologists distinguish between “sherds” and “shards” A single command to test all my changed Go packages Disable the new message animations in WhatsApp Finding high-churn folders that bother Backblaze Always-on SSH agent forwarding with my Git pushes Managing the caption of a photo with AppleScript (but not PhotoKit) Goodhart’s and Campbell’s Law are different Notes from The Cornishman No. 176 (Spring 2026) Notes from The Cornishman No. 176 (Spring 2026) GitUp can’t diff text files larger than 8MB Home Testing the width of a page on a mobile device using Playwright Disable AirPods charging notifications Start a Caddy server in a subprocess during a Python session Filter a list of JSON object based on a list of tags HOME_GET_ME_HOME is a Citymapper Shortcuts action The FileExistsError exception exposes a filename attribute The red-lined bubble snail Why can’t Python connect to example.com? Useful type hints for Python How to truncate the middle of long command output AirPlay Receiver can interfere with Flask apps What’s the main prefix in SQLite queries?
Why does t.Setenv panic after t.Parallel?
2026-08-24 · via alexwlchan’s notes

Go’s testing package tracks whether a test is parallel or mutates global state, and panics if you do both in the same test.

Last week, I debugged a flaky test caused by state leaking from another test. One test called os.Setenv with an environment variable that affected the behaviour of the other test. Although the first test cleaned up after itself, the two tests could run in parallel, and if they ran at the same time the second test would fail.

In general, Go tests should use t.Setenv instead of os.Setenv. t.Setenv automatically restores the original value at the end of a test, and prevents parallel execution. Switching to t.Setenv was enough to fix this specific flake, but while looking for similar mistakes, I learnt more about how Go handles parallel tests.

In some of our tests, the os.Setenv call happens deep inside non-test code and it’s awkward to change, so we use a different helper to prevent leaking state:

23// AssertNotParallel asserts that t has not been marked as parallel.

24// It panics (via t.Setenv) if t.Parallel has already been called.

25//

26// Use this when a test modifies package-level globals or other shared

27// state that would be unsafe to modify concurrently with other tests.

28func AssertNotParallel(t testenv.TB) {

29 t.Helper()

30 t.Setenv("ASSERT_NOT_PARALLEL_TEST", "1") // panics if t.Parallel was called

31}

Lines 23–31 of tstest/tstest.go in the tailscale/tailscale repo. Copyright Tailscale Inc & contributors, used under the BSD-3-Clause license. Here testenv.TB is a copy of testing.TB, but without a testing dependency so it can be used in non-test code.

I was intrigued by the comment: why does t.Setenv panic if t.Parallel has already been called?

Reading the source code for the testing package makes this clear. The testing.T struct tracks parallel status with two private fields:

  • isParallel – set when you call t.Parallel().
  • denyParallel – set when calling operations that mutate global state, specifically t.Chdir and t.Setenv.

These fields are mutually exclusive.

The functions t.Parallel, t.Setenv and t.Chdir inspect both flags, and panic if you try to use both in a single test:

package main

import (
	"testing"
)

func TestCannotSetenvAfterParallel(t *testing.T) {
	t.Parallel()
	t.Setenv("COLOUR", "red")
	// panics: "test using t.Setenv or t.Chdir can not use t.Parallel"
}

func TestCannotParallelAfterSetenv(t *testing.T) {
	t.Setenv("COLOUR", "red")
	t.Parallel()
	// panics: "test using t.Setenv or t.Chdir can not use t.Parallel"
}

Now I understand how AssertNotParallel works. It triggers t.Setenv, which inspects isParallel and panics if the test is marked parallel. The protection is also stronger than the doc comment implies – calling AssertNotParallel and t.Setenv sets denyParallel, which blocks any subsequent calls to t.Parallel().

There’s currently an open proposal to add an explicit t.Serial method to the testing package, and I hope it’s accepted. Mutating dummy environment variables is a hack that works, but a dedicated t.Serial method is a much clearer statement of intent.