Go静态分析工具报告的问题


call to (*testing.T).FailNow from a non-test goroutine 是什么意思?


go vet

1
2
 [github.com/samber/lo]
./lo_test.go:20:4: call to (*testing.T).FailNow from a non-test goroutine

这个错误消息 “call to (*testing.T).FailNow from a non-test goroutine” 是在 Go 语言的测试框架中出现的,它表明在测试过程中出现了一个特定的问题。让我来解释一下这个错误的含义和可能的原因:

  1. 错误的含义:

这个错误意味着 testing.TFailNow() 方法被从非测试 goroutine 中调用了。在 Go 的测试框架中,FailNow() 方法应该只在主测试 goroutine 中调用。

  1. 为什么会出现这个错误:

通常,这个错误发生在以下情况:

a. 在测试函数中创建了新的 goroutine。
b. 在这个新的 goroutine 中调用了 t.FailNow() 或其他会导致 FailNow() 被调用的方法(如 t.Fatal())。

  1. 为什么这是一个问题:

FailNow() 方法设计用来立即停止当前测试。但是,如果它在非主测试 goroutine 中被调用,它无法正确地停止整个测试,这可能导致不可预知的行为。

  1. 如何修复:

要解决这个问题,你可以:

a. 避免在新的 goroutine 中直接调用 FailNow()Fatal()
b. 使用 channel 来通知主测试 goroutine 发生了失败,然后在主 goroutine 中调用 FailNow()
c. 使用 testing.T.Run() 来运行可能需要在单独 goroutine 中执行的子测试。

  1. 示例修复:

这里有一个简单的例子来说明如何修复这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"testing"
"time"
)

func TestProblemExample(t *testing.T) {
// This will cause the error
go func() {
// This call to Fatal will result in the error message
t.Fatal("Error in goroutine")
}()
time.Sleep(time.Second)
}

func TestFixedExample(t *testing.T) {
done := make(chan bool)
var err error

go func() {
// Simulate some work
time.Sleep(time.Millisecond * 100)
err = someFunction()
done <- true
}()

<-done
if err != nil {
t.Fatal("Error occurred:", err)
}
}

func someFunction() error {
// Simulate an error
return nil
}

在这个例子中:

  • TestProblemExample 演示了会导致错误的情况。
  • TestFixedExample 展示了如何正确地在 goroutine 中处理错误,并在主测试 goroutine 中报告失败。

通过使用 channel 来同步 goroutine 并在主测试函数中检查错误,我们避免了直接在 goroutine 中调用 t.Fatal(),从而解决了这个问题。

记住,在编写并发测试时,始终要确保错误报告和测试控制流保持在主测试 goroutine 中。这样可以确保测试框架能够正确地管理测试的执行和报告。



执行 gofmt会增加一行,在原来// +build profile基础上新增//go:build profile



//go:build profile// +build profile 是 Go 语言中用于构建约束的两种语法,它们在语法和使用上有一些区别。

主要区别

  1. 语法

    • //go:build profile 是一种新的语法,从 Go 1.17 开始引入,旨在统一和简化构建约束的表示。
    • // +build profile 是旧的语法,直到 Go 1.16 都在使用。
  2. 可读性

    • 新的 //go:build 语法更加直观,能够更清晰地表达条件,例如可以通过逻辑运算符(如 &&||)组合多个条件。
    • 旧语法的可读性相对较低,特别是在处理复杂条件时。
  3. 兼容性

    • 从 Go 1.17 开始,推荐使用 //go:build 语法,未来的版本可能会逐步弃用旧语法。

为什么 gofmt 会增加一行

当你使用 gofmt 格式化代码时,它会自动将旧的构建约束(// +build ...)转换为新的语法(//go:build ...)。这可能会导致在文件中看到多余的空行,因为 gofmt 在转换时会在新语法前添加一行空行,以分隔构建约束和代码。这样做可以增强代码的可读性。

示例

1
2
3
4
5
6
7
8
9
// Old syntax
// +build profile

package main

// New syntax
//go:build profile

package main

总结

  • 使用 //go:build 是推荐的做法。
  • gofmt 会自动转换旧语法,并可能在转换过程中添加空行以提高可读性。

我给这个项目gofmt时对方提到了这个问题
https://github.com/googleforgames/agones/pull/4000/files

https://github.com/golang/go/issues/41184

https://github.com/golang/go/issues/48383

https://stackoverflow.com/questions/69180548/how-do-i-prevent-gofmt-adding-gobuild-test