Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17257] ファイルの概要

このコミットは、Go言語のコマンドラインツール cmd/go における、カバレッジ計測に関するエラーメッセージの不具合修正です。具体的には、テスト対象パッケージに非テストファイルが存在しない場合に発生していた誤ったエラーメッセージを修正し、より適切な警告とカバレッジ結果を表示するように改善しています。

コミット

commit f718036217df2d3386fb6eb72cc6bdcf156f6fc8
Author: Rob Pike <r@golang.org>
Date:   Thu Aug 15 10:36:46 2013 +1000

    cmd/go: fix bad error message in coverage for package without non-test files
    Was checking for nil map; must check for empty map instead.
    
    Fixes #6065
    
    Before:
    
    go test -cover
    # testmain
    /var/folders/00/013l0000h01000cxqpysvccm0004fc/T/go-build233480051/_/Users/r/issue/_test/_testmain.go:11: imported and not used: "_/Users/r/issue"
    FAIL    _/Users/r/issue [build failed]
    
    Now:
    
    go test -cover
    testing: warning: no tests to run
    PASS
    coverage: 0.0% of statements
    ok      _/Users/r/issue 0.021s
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12916043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f718036217df2d3386fb6eb72cc6bdcf156f6fc8

元コミット内容

このコミットは、cmd/go ツールがカバレッジレポートを生成する際に、テスト対象のパッケージに通常の(非テスト)Goソースファイルが含まれていない場合に発生する不適切なエラーメッセージを修正します。以前は、nil マップをチェックしていた箇所を、空のマップをチェックするように変更することで、この問題を解決しています。

修正前は、go test -cover を実行すると、以下のようなビルドエラーが表示されていました。

go test -cover
# testmain
/var/folders/00/013l0000h01000cxqpysvccm0004fc/T/go-build233480051/_/Users/r/issue/_test/_testmain.go:11: imported and not used: "_/Users/r/issue"
FAIL    _/Users/r/issue [build failed]

修正後は、より適切な警告とカバレッジ結果が表示されるようになりました。

go test -cover
testing: warning: no tests to run
PASS
coverage: 0.0% of statements
ok      _/Users/r/issue 0.021s

この変更は、Go issue #6065 を修正するものです。

変更の背景

Go言語のテストツール go test には、コードカバレッジを計測する機能があります。これは、go test -cover コマンドで利用できます。カバレッジ計測は、テストがどの程度のコードを実行したかを示す重要な指標であり、テストの品質を評価するのに役立ちます。

このコミットが行われる前、go test -cover コマンドには特定のシナリオで問題がありました。それは、テスト対象のGoパッケージに、テストファイル(_test.goで終わるファイル)のみが含まれており、通常のGoソースファイル(非テストファイル)が一つも含まれていない場合です。

このような状況では、カバレッジツールは計測対象のコードを見つけることができません。しかし、以前の実装では、この状態を適切に処理できず、nil マップのチェックが原因で、実際にはビルドエラーではないにもかかわらず、「imported and not used」といった誤ったビルドエラーメッセージを出力していました。これはユーザーにとって混乱を招くものであり、実際のテスト結果(テストがないこと、カバレッジが0%であること)を正確に反映していませんでした。

このコミットは、この誤解を招くエラーメッセージを修正し、テスト対象のコードがない場合には、適切な警告("no tests to run")と、0%のカバレッジ結果を正確に報告するように改善することを目的としています。これにより、ユーザーはテスト対象のパッケージの状況をより正確に把握できるようになります。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびGoツールの概念に関する知識が必要です。

  1. Go Modulesとパッケージ: Goのコードはパッケージにまとめられ、関連する機能を提供します。パッケージはディレクトリ構造に対応し、go.mod ファイルによってモジュールとして管理されます。
  2. go test コマンド: Go言語の組み込みテストツールです。パッケージ内のテストファイル(_test.goで終わるファイル)を実行します。
  3. コードカバレッジ (go test -cover): go test -cover コマンドは、テストが実行された際に、どのコード行が実行されたかを計測し、その割合(カバレッジ率)をレポートします。Goのカバレッジツールは、ソースコードを解析し、各ステートメントの実行を追跡するための計測コードを挿入することで機能します。
  4. testmain パッケージ: go test コマンドがテストを実行する際、内部的にテスト対象のパッケージとテストファイルを結合し、特別な testmain パッケージを生成します。この testmain パッケージは、テストの実行とカバレッジ計測のロジックを含んでいます。
  5. Package 構造体: cmd/go ツール内部では、Goのパッケージに関する情報(インポート、エクスポート、ソースファイルなど)を Package 構造体で表現しています。
  6. coverVars: カバレッジ計測のために、Goコンパイラは各パッケージの計測対象となる変数(カバレッジカウンタなど)を生成します。これらの変数は coverVars と呼ばれるマップに格納され、パッケージ内のどのファイル、どのステートメントが計測対象であるかを示します。coverVarsmap[string]*CoverVar のような型で、キーはファイルパス、値はカバレッジ変数の情報を持つ構造体です。
  7. nil と空のマップ: Goにおいて、マップは nil になることがあります。nil マップは、まだ初期化されていないマップを指します。一方、make(map[key]value) で作成されたマップは、要素が一つも追加されていなくても nil ではありません。nil マップへの書き込みはランタイムパニックを引き起こしますが、len(nil_map)0 を返します。このコミットの核心は、nil マップと空のマップの区別を正しく行うことにあります。

技術的詳細

このコミットの技術的な核心は、Go言語におけるマップの nil 値と空のマップ(要素が0個のマップ)の挙動の違いを正しく理解し、それに基づいて条件分岐を修正した点にあります。

Goのカバレッジツールは、テスト対象のパッケージを解析し、カバレッジ計測に必要な変数を生成します。これらの変数は、Package 構造体の coverVars フィールドに格納されます。coverVarsmap[string]*CoverVar のような型を持つマップです。

問題が発生していたのは、テスト対象のパッケージに通常のGoソースファイル(非テストファイル)が一つも存在しない場合です。このようなパッケージでは、カバレッジ計測の対象となるステートメントが存在しないため、coverVars マップは空になります。

以前の実装では、writeTestmain 関数内で、cp.coverVars != nil という条件でカバレッジ情報が存在するかどうかをチェックしていました。

// Before:
if cp.coverVars != nil {
    cover = append(cover, coverInfo{cp, cp.coverVars})
}

しかし、Goのマップの性質上、カバレッジ計測対象のファイルが一つもない場合、cp.coverVarsnil ではなく、要素が0個の空のマップとして初期化されることがあります。nil マップと空のマップはどちらも len() 関数で 0 を返しますが、nil マップは nil と比較すると true になりますが、空のマップは nil と比較すると false になります。

したがって、cp.coverVars != nil という条件では、要素が0個の空のマップの場合でも true と評価されてしまい、カバレッジ情報がないにもかかわらず、cover スライスに空の coverVars マップが追加されていました。これが、testmain パッケージの生成時に「imported and not used」という誤ったエラーメッセージを引き起こす原因となっていました。testmain が存在しないカバレッジ変数を参照しようとしたり、不必要なインポートを行ったりしたためと考えられます。

このコミットでは、この条件を len(cp.coverVars) > 0 に変更しました。

// After:
if len(cp.coverVars) > 0 {
    cover = append(cover, coverInfo{cp, cp.coverVars})
}

len() 関数は、マップが nil であっても、空であっても、含まれる要素の数を正確に返します。したがって、len(cp.coverVars) > 0 という条件は、cp.coverVars マップに実際にカバレッジ計測対象の変数(つまり、計測すべきコード)が存在する場合にのみ true と評価されます。

これにより、テスト対象のパッケージに非テストファイルが存在せず、coverVars が空のマップである場合には、cover スライスに何も追加されなくなり、testmain パッケージが不必要なコードを生成することがなくなりました。結果として、誤ったビルドエラーは解消され、代わりに testing: warning: no tests to run という適切な警告と、coverage: 0.0% of statements という正確なカバレッジ結果が表示されるようになりました。

この修正は、Goのマップのセマンティクスを正確に適用することで、ツールの挙動をより堅牢にし、ユーザーエクスペリエンスを向上させた良い例と言えます。

コアとなるコードの変更箇所

変更は src/cmd/go/test.go ファイルの1箇所のみです。

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -1010,7 +1010,7 @@ type coverInfo struct {
 func writeTestmain(out string, pmain, p *Package) error {
 	var cover []coverInfo
 	for _, cp := range pmain.imports {
-		if cp.coverVars != nil {
+		if len(cp.coverVars) > 0 {
 			cover = append(cover, coverInfo{cp, cp.coverVars})
 		}
 	}

コアとなるコードの解説

変更されたコードは writeTestmain 関数内にあります。この関数は、go test コマンドがテストを実行するために必要な testmain パッケージのソースコードを生成する役割を担っています。

pmain.imports は、testmain パッケージがインポートする他のパッケージのリストです。このループは、これらのインポートされたパッケージを一つずつ処理しています。

変更前のコード if cp.coverVars != nil は、インポートされたパッケージ cp にカバレッジ情報(coverVars)が存在するかどうかをチェックしていました。しかし、前述の通り、coverVars が空のマップである場合でも nil ではないため、この条件は true と評価されてしまい、カバレッジ計測対象のコードがないにもかかわらず、cover スライスにそのパッケージの情報が追加されていました。

変更後のコード if len(cp.coverVars) > 0 は、cp.coverVars マップに実際に要素(カバレッジ計測対象の変数)が1つ以上存在するかどうかをチェックしています。len() 関数はマップの要素数を返すため、この条件は、カバレッジ計測すべきコードが実際に存在する場合にのみ true となります。

この修正により、カバレッジ計測対象のコードがないパッケージは cover スライスに追加されなくなり、結果として testmain パッケージが不必要なカバレッジ関連のコードやインポートを生成することがなくなりました。これにより、誤ったビルドエラーが解消され、go test -cover コマンドの挙動がより正確でユーザーフレンドリーになりました。

関連リンク

参考にした情報源リンク

  • Go issue #6065 の内容
  • Go言語のマップに関する公式ドキュメントやチュートリアル(nil マップと空マップの挙動について)
  • Go言語のテストとカバレッジに関する公式ドキュメント
  • src/cmd/go/test.go の周辺コード(Package 構造体や coverInfo 構造体など)
  • Go言語のソースコードリポジトリ: https://github.com/golang/go
  • Go Code Review: https://go-review.googlesource.com/ (過去のCLを検索する際に参照)
  • Go言語の testing パッケージのドキュメント
  • Go言語の cmd/go ツールの内部構造に関する一般的な知識