This test is rather confusingly / greatly over-complicated anyway, as written.
It's passing an "ignore these errors" func, and then ignoring a pointer to
the same error content as is being returned elsewhere... by passing it
through a closure and a loop of == comparisons on interface-boxed values.
Instead of just return false. Bleh.
More interestingly though, TestIsRetryableFailure runs fine with
make unit_test, as our green CI pipeline shows.
However, when you run it by hand:
❯ go test ./internal/common/backoff
--- FAIL: TestIsRetryableFailure (0.01s)
retry_test.go:150:
Error Trace: retry_test.go:150
Error: An error is expected but got nil.
Test: TestIsRetryableFailure
retry_test.go:151:
Error Trace: retry_test.go:151
Error: Not equal:
expected: 1
actual : 5
Test: TestIsRetryableFailure
FAIL
FAIL go.uber.org/cadence/internal/common/backoff 0.572s
FAIL
After digging around a bit, I noticed that it passed when both -race and -coverprofile
were passed (as make unit_test does), but not with either (or none) were passed.
Adding a print statement to IgnoreErrors also caused the test to pass.
Once the print statement came into play, I figured it had to be due to optimizations of
some kind, so I poked around with -gcflags -m and the most obvious difference with the
print statement was that it prevented inlining of IgnoreErrors and the anonymous func
in TestIsRetryableFailure.
As further evidence:
# default inlining
❯ go test -gcflags -l=0 ./internal/common/backoff
--- FAIL: TestIsRetryableFailure (0.01s)
retry_test.go:150:
Error Trace: retry_test.go:150
Error: An error is expected but got nil.
Test: TestIsRetryableFailure
retry_test.go:151:
Error Trace: retry_test.go:151
Error: Not equal:
expected: 1
actual : 5
Test: TestIsRetryableFailure
FAIL
FAIL go.uber.org/cadence/internal/common/backoff 0.572s
FAIL
# no inlining
❯ go test -gcflags -l=1 ./internal/common/backoff
ok go.uber.org/cadence/internal/common/backoff 0.494s
You can also see the differences between -race +/- -coverprofile optimization
with -gcflags -m if you're interested in more detail.
This test is rather confusingly / greatly over-complicated anyway, as written. It's passing an "ignore these errors" func, and then ignoring a pointer to the same error content as is being returned elsewhere... by passing it through a closure and a loop of
==
comparisons on interface-boxed values.Instead of just
return false
. Bleh.More interestingly though, TestIsRetryableFailure runs fine with
make unit_test
, as our green CI pipeline shows. However, when you run it by hand:After digging around a bit, I noticed that it passed when both
-race
and-coverprofile
were passed (asmake unit_test
does), but not with either (or none) were passed. Adding a print statement toIgnoreErrors
also caused the test to pass.Once the print statement came into play, I figured it had to be due to optimizations of some kind, so I poked around with
-gcflags -m
and the most obvious difference with the print statement was that it prevented inlining ofIgnoreErrors
and the anonymous func inTestIsRetryableFailure
.As further evidence:
You can also see the differences between
-race
+/--coverprofile
optimization with-gcflags -m
if you're interested in more detail.The underlying reason for all this is that, as the go spec notes: https://go.dev/ref/spec#Comparison_operators
When combined with general inlining behavior getting better and better: https://dave.cheney.net/2020/05/02/mid-stack-inlining-in-go you get inconsistent behavior depending on optimization level.
Fun!
The good news is that this is probably a test-only concern in this case.