-
Notifications
You must be signed in to change notification settings - Fork 18.8k
Closed
Closed
Copy link
Labels
Description
Hi there,
I was playing with go1.7's sub-tests today and I ran into a not really obvious side-effect of marking sub-test with t.Parallel().
func TestSubtests(t *testing.T) {
routes := []struct {
url string
path string
}{
{"http://example.com/1", "/1"},
{"http://example.com/2", "/2"},
{"http://example.com/3", "/3"},
{"http://example.com/4", "/4"},
{"http://example.com/5", "/5"},
}
t.Run("sequential", func(t *testing.T) {
for _, tt := range routes {
t.Run(tt.url, func(t *testing.T) {
u, _ := url.Parse(tt.url)
if u.Path != tt.path {
t.Errorf("expected %v, got %v", tt.path, u.Path)
}
})
}
})
t.Run("parallel", func(t *testing.T) {
for _, tt := range routes {
t.Run(tt.url, func(t *testing.T) {
t.Parallel() // <== trying to set Parallel(), while using tt from the range loop
u, _ := url.Parse(tt.url)
if u.Path != tt.path {
t.Errorf("expected %v, got %v", tt.path, u.Path)
}
})
}
})
}$ go test -v
--- PASS: TestSubtests (0.00s)
--- PASS: TestSubtests/sequential (0.00s)
--- PASS: TestSubtests/sequential/http://example.com/1 (0.00s)
main_test.go:28: tested /1
--- PASS: TestSubtests/sequential/http://example.com/2 (0.00s)
main_test.go:28: tested /2
--- PASS: TestSubtests/sequential/http://example.com/3 (0.00s)
main_test.go:28: tested /3
--- PASS: TestSubtests/sequential/http://example.com/4 (0.00s)
main_test.go:28: tested /4
--- PASS: TestSubtests/sequential/http://example.com/5 (0.00s)
main_test.go:28: tested /5
--- PASS: TestSubtests/parallel (0.00s)
--- PASS: TestSubtests/parallel/http://example.com/1 (0.00s)
main_test.go:48: tested /5
--- PASS: TestSubtests/parallel/http://example.com/4 (0.00s)
main_test.go:48: tested /5
--- PASS: TestSubtests/parallel/http://example.com/5 (0.00s)
main_test.go:48: tested /5
--- PASS: TestSubtests/parallel/http://example.com/3 (0.00s)
main_test.go:48: tested /5
--- PASS: TestSubtests/parallel/http://example.com/2 (0.00s)
main_test.go:48: tested /5
^ always "tested /5"
This bahavior was not obvious to me from the beginning, since all of my tests still passed :) But after a while I figured there was a concurrency issue similar to this:
for _, tt := range routes {
go func() {
fmt.Println(tt.url) // Not guaranteed which item will be stored in tt during the goroutine execution.
}()
}which is easy to solve by passing the value onto goroutine's stack:
for _, tt := range routes {
go func(url string) {
fmt.Println(url)
}(tt.url)
}Question
I'm trying to figure out a fix (similar to passing data onto goroutine's stack) for the above Parallel sub-test. Any suggestions?
Documentation suggestion
Imho, the t.Parallel() behavior should be documented better, especially in the context of sub-tests + table driven tests.
Reactions are currently unavailable