[インデックス 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ツールの概念に関する知識が必要です。
- Go Modulesとパッケージ: Goのコードはパッケージにまとめられ、関連する機能を提供します。パッケージはディレクトリ構造に対応し、
go.mod
ファイルによってモジュールとして管理されます。 go test
コマンド: Go言語の組み込みテストツールです。パッケージ内のテストファイル(_test.go
で終わるファイル)を実行します。- コードカバレッジ (
go test -cover
):go test -cover
コマンドは、テストが実行された際に、どのコード行が実行されたかを計測し、その割合(カバレッジ率)をレポートします。Goのカバレッジツールは、ソースコードを解析し、各ステートメントの実行を追跡するための計測コードを挿入することで機能します。 testmain
パッケージ:go test
コマンドがテストを実行する際、内部的にテスト対象のパッケージとテストファイルを結合し、特別なtestmain
パッケージを生成します。このtestmain
パッケージは、テストの実行とカバレッジ計測のロジックを含んでいます。Package
構造体:cmd/go
ツール内部では、Goのパッケージに関する情報(インポート、エクスポート、ソースファイルなど)をPackage
構造体で表現しています。coverVars
: カバレッジ計測のために、Goコンパイラは各パッケージの計測対象となる変数(カバレッジカウンタなど)を生成します。これらの変数はcoverVars
と呼ばれるマップに格納され、パッケージ内のどのファイル、どのステートメントが計測対象であるかを示します。coverVars
はmap[string]*CoverVar
のような型で、キーはファイルパス、値はカバレッジ変数の情報を持つ構造体です。nil
と空のマップ: Goにおいて、マップはnil
になることがあります。nil
マップは、まだ初期化されていないマップを指します。一方、make(map[key]value)
で作成されたマップは、要素が一つも追加されていなくてもnil
ではありません。nil
マップへの書き込みはランタイムパニックを引き起こしますが、len(nil_map)
は0
を返します。このコミットの核心は、nil
マップと空のマップの区別を正しく行うことにあります。
技術的詳細
このコミットの技術的な核心は、Go言語におけるマップの nil
値と空のマップ(要素が0個のマップ)の挙動の違いを正しく理解し、それに基づいて条件分岐を修正した点にあります。
Goのカバレッジツールは、テスト対象のパッケージを解析し、カバレッジ計測に必要な変数を生成します。これらの変数は、Package
構造体の coverVars
フィールドに格納されます。coverVars
は map[string]*CoverVar
のような型を持つマップです。
問題が発生していたのは、テスト対象のパッケージに通常のGoソースファイル(非テストファイル)が一つも存在しない場合です。このようなパッケージでは、カバレッジ計測の対象となるステートメントが存在しないため、coverVars
マップは空になります。
以前の実装では、writeTestmain
関数内で、cp.coverVars != nil
という条件でカバレッジ情報が存在するかどうかをチェックしていました。
// Before:
if cp.coverVars != nil {
cover = append(cover, coverInfo{cp, cp.coverVars})
}
しかし、Goのマップの性質上、カバレッジ計測対象のファイルが一つもない場合、cp.coverVars
は nil
ではなく、要素が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: https://github.com/golang/go/issues/6065
- Go CL 12916043: https://golang.org/cl/12916043 (これはコミットメッセージに記載されているGo Code Reviewのリンクですが、現在はGitHubにリダイレクトされます)
参考にした情報源リンク
- 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
ツールの内部構造に関する一般的な知識