blog.syfm

徒然なるままに考えていることなどを書いていくブログ

Go 1.13 にアップデートするとテスト時に "flag provided but not defined" エラーが発生するケース

Go でテストを実行する際は動的に生成された main 関数がエントリポイントとなっています。
Go 1.13 からはテスト実行時のテストフラグ (-v-coverprofile など) の登録がこの生成された main 内で行われるようになったため、 main より先に実行される init 内でフラグの登録を行っているテストではフラグのパースがうまく動かず、テストが落ちるようになります。

たとえば、自分が開発している Evans では golden files testing のために --update フラグを e2e パッケージで用意し、これが有効になっている場合のみ golden files を更新するような設定になっていました。

var (
    update = flag.Bool("update", false, "update goldens")
)

func init() {
    flag.Parse()
    if *update {
        os.RemoveAll(filepath.Join("testdata", "fixtures"))
        if err := os.MkdirAll(filepath.Join("testdata", "fixtures"), 0755); err != nil {
            panic(fmt.Sprintf("failed to create fixture dirs: %s", err))
        }
    }
}
$ go test -v
flag provided but not defined: -test.v
Usage of /var/folders/p8/yjvb_xcn54d3dsfjz76rywvh0000gn/T/go-build261037663/b001/e2e.test:
  -update
        update goldens
exit status 2
FAIL    github.com/ktr0731/evans/e2e    0.018s

これを回避するためには Go 1.13 で新たに追加された testing.Init()init 内で呼びます。
これはテストフラグを明示的に登録してくれる関数で、これをフラグのパース前に呼ぶことで正常にパースすることができるようになります。

var (
    update = flag.Bool("update", false, "update goldens")
)

func init() {
    testing.Init()
    flag.Parse()
    if *update {
        os.RemoveAll(filepath.Join("testdata", "fixtures"))
        if err := os.MkdirAll(filepath.Join("testdata", "fixtures"), 0755); err != nil {
            panic(fmt.Sprintf("failed to create fixture dirs: %s", err))
        }
    }
}