[インデックス 16599] ファイルの概要
このコミットでは、go test コマンドにおけるコードカバレッジ関連のフラグの扱いが簡素化されました。具体的には、以下のファイルが変更されています。
src/cmd/go/doc.go:go testコマンドのドキュメントが更新され、カバレッジ関連フラグの新しい挙動が記述されました。src/cmd/go/test.go:go testコマンドの内部ロジックが変更され、カバレッジフラグの処理が調整されました。src/cmd/go/testflag.go:go testコマンドのフラグ解析ロジックが更新され、新しいカバレッジフラグの相互作用が実装されました。src/pkg/testing/testing.go:testingパッケージ内のカバレッジ関連のフラグ定義が変更されました。
コミット
commit 27cca31ee1619e8820bec79617e0ead54f90adc1
Author: Rob Pike <r@golang.org>
Date: Tue Jun 18 17:15:26 2013 -0700
cmd/go: simplify flags for coverage
The single flag -cover provides the default simplest behavior.
The other flags, -covermode and -coverprofile, provide more
control. The three flags interconnect to work well.
R=rsc, adg
CC=golang-dev
https://golang.org/cl/10364044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/27cca31ee1619e8820bec79617e0ead54f90adc1
元コミット内容
cmd/go: simplify flags for coverage
The single flag -cover provides the default simplest behavior.
The other flags, -covermode and -coverprofile, provide more
control. The three flags interconnect to work well.
変更の背景
このコミットは、Go言語のテストツールにおけるコードカバレッジ機能のユーザビリティを向上させることを目的としています。以前は、コードカバレッジを有効にするためのフラグの指定方法が直感的ではなかった可能性があります。
コミットメッセージによると、主な目的は以下の通りです。
-coverフラグの簡素化: 単一の-coverフラグで、最もシンプルでデフォルトのカバレッジ動作を提供するように変更されました。これにより、ユーザーは詳細な設定なしに基本的なカバレッジ分析を簡単に実行できるようになります。- フラグ間の連携強化:
-cover、-covermode、-coverprofileの3つのフラグが相互に連携し、より柔軟かつ直感的に機能するように設計されました。これにより、基本的なカバレッジから詳細なプロファイリングまで、ユーザーのニーズに応じた制御が可能になります。
この変更は、Go 1.2(2013年12月リリース)でテストカバレッジ機能が導入された時期に行われたものであり、初期の機能実装におけるユーザーフィードバックや使い勝手の改善を反映していると考えられます。
前提知識の解説
Go言語の go test コマンド
go test コマンドは、Go言語のパッケージに含まれるテストを実行するための標準ツールです。テストファイルの命名規則(_test.go)に従ってテスト関数を自動的に検出し、実行します。
コードカバレッジ
コードカバレッジとは、テストが実行された際に、プログラムのソースコードのどの部分が実行されたかを示す指標です。これにより、テストスイートがどれだけコードを網羅しているかを評価し、テストの品質を向上させるための手がかりを得ることができます。
Go言語の go test コマンドは、コードカバレッジ分析をサポートしており、以下の3つのモードがあります。
set(boolean): 各ステートメントが実行されたかどうか(真偽値)を記録します。最も基本的なカバレッジモードです。count(integer): 各ステートメントが何回実行されたかを記録します。atomic(integer):countと同様に実行回数を記録しますが、マルチスレッド環境でのテストにおいて、より正確なカウントを保証するためにアトミック操作を使用します。これはパフォーマンスに大きなオーバーヘッドをもたらす可能性があります。
go test のカバレッジ関連フラグ(変更前後の概念)
-cover: コードカバレッジ分析を有効にするためのフラグ。このコミット以前は、モード(set,count,atomic)を引数として受け取っていました。-covermode: カバレッジ分析のモード(set,count,atomic)を指定するためのフラグ。-coverprofile: カバレッジプロファイルの結果をファイルに書き出すためのフラグ。通常、.out拡張子を持つファイルが指定されます。
このコミットの変更は、これらのフラグの相互作用とデフォルトの挙動を改善することに焦点を当てています。
技術的詳細
このコミットの技術的な核心は、go test コマンドのフラグ解析ロジックと、カバレッジ関連の内部変数の管理方法の変更にあります。
-
-coverフラグの役割変更:- 変更前:
-coverフラグは、カバレッジモード(set,count,atomic)を直接引数として受け取っていました。 - 変更後:
-coverフラグは、引数を取らないブール値フラグとなり、単に「基本的なカバレッジ分析を有効にする」というシグナルとして機能します。これは-covermode=setのショートハンドとして扱われます。
- 変更前:
-
-covermodeフラグの導入と優先順位:-covermodeフラグが明示的に導入され、カバレッジモードを詳細に制御できるようになりました。-coverまたは-coverprofileが指定された場合、明示的に-covermodeが指定されていない限り、デフォルトでsetモードが適用されるようになりました。
-
-coverprofileとカバレッジモードの連携:-coverprofileフラグが指定された場合も、カバレッジ分析が有効になり、デフォルトでsetモードが適用されるようになりました。これにより、プロファイル出力とカバレッジ分析が自動的に連携します。
-
内部変数の変更:
src/cmd/go/test.goおよびsrc/cmd/go/testflag.goにおいて、カバレッジモードを保持する内部変数testCoverの型がstringからboolに変更されました。これは、-coverフラグがモードではなく、単なる有効/無効のシグナルになったことを反映しています。- 新たに
testCoverModeというstring型の変数が導入され、実際のカバレッジモード(set,count,atomic)を保持するようになりました。
-
setCoverMode関数の導入:src/cmd/go/testflag.goにsetCoverModeというヘルパー関数が追加されました。この関数は、カバレッジモードがまだ設定されていない場合にモードを設定し、不正なモードが指定された場合にはエラーを報告します。また、カバレッジが有効になった際に、詳細な出力を保証するために-test.vフラグ(verboseモード)を自動的に有効にするロジックも含まれています。
これらの変更により、ユーザーは -cover だけで簡単にカバレッジを有効にでき、必要に応じて -covermode や -coverprofile で詳細な制御を行うという、より柔軟で直感的なワークフローが実現されました。
コアとなるコードの変更箇所
src/cmd/go/doc.go
go test コマンドのドキュメントが更新され、-cover、-covermode、-coverprofile の説明が変更されました。
--- a/src/cmd/go/doc.go
+++ b/src/cmd/go/doc.go
@@ -738,17 +738,25 @@ control the execution of any test:
if -test.blockprofile is set without this flag, all blocking events
are recorded, equivalent to -test.blockprofilerate=1.
- -cover set,count,atomic
+ -cover
+ Enable basic coverage analysis; shorthand for -covermode=set.
TODO: This feature is not yet fully implemented.
- TODO: Must run with -v to see output.
- TODO: Need control over output format,
- Set the mode for coverage analysis for the package[s] being tested.
- The default is to do none.
+
+ -covermode set,count,atomic
+ Set the mode for coverage analysis for the package[s]
+ being tested. The default is to do none, but if -cover or
+ -coverprofile is specified, coverage is enabled in "set"
+ mode unless this flag is also specified.
The values:
- set: boolean: does this statement execute?
- count: integer: how many times does this statement execute?
- atomic: integer: like count, but correct in multithreaded tests;
+ set: bool: does this statement run?
+ count: int: how many times does this statement run?
+ atomic: int: count, but correct in multithreaded tests;
\t\tsignificantly more expensive.
+ Sets -v. TODO: This will change.
+
+ -coverprofile cover.out
+ Write a coverage profile to the specified file after all tests
+ have passed.
src/cmd/go/test.go
testCover 変数の型が string から bool に変更され、testCoverMode 変数が追加されました。また、カバレッジが有効かどうかのチェックロジックが testCover (bool) を参照するように変更されました。
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -254,7 +259,8 @@ See the documentation of the testing package for more information.
var (
testC bool // -c flag
- testCover string // -cover flag
+ testCover bool // -cover flag
+ testCoverMode string // -covermode flag
testProfile bool // some profiling flag
testI bool // -i flag
testV bool // -v flag
@@ -494,7 +500,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
// - that is, any code imported by the external test that in turn
// imports p - needs to be rebuilt too. For now, just report
// that coverage is unavailable.
- if testCover != "" && contains(p1.Deps, p.ImportPath) {
+ if testCover && contains(p1.Deps, p.ImportPath) {
return nil, nil, nil, fmt.Errorf("coverage analysis cannot handle package (%s_test imports %s imports %s)", p.Name, path, p.ImportPath)
}
}
@@ -535,8 +541,8 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
return nil, nil, nil, err
}
- if testCover != "" {
- p.coverMode = testCover
+ if testCover {
+ p.coverMode = testCoverMode
p.coverVars = declareCoverVars(p.ImportPath, p.GoFiles...)
}
@@ -545,7 +551,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
}
// Test package.
- if len(p.TestGoFiles) > 0 || testCover != "" {
+ if len(p.TestGoFiles) > 0 || testCover {
ptest = new(Package)
*ptest = *p
ptest.GoFiles = nil
@@ -871,7 +877,7 @@ type testFuncs struct {
}
func (t *testFuncs) CoverEnabled() bool {
- return testCover != ""
+ return testCover
}
type testFunc struct {
src/cmd/go/testflag.go
フラグの定義と解析ロジックが大幅に変更されました。
-coverがブール値フラグとして定義されました。-covermodeが新たに定義されました。-coverprofileが指定された場合にsetCoverModeが呼び出されるようになりました。-coverが指定され、かつ-covermodeが未指定の場合にsetCoverMode("set", ...)が呼び出されるロジックが追加されました。setCoverModeヘルパー関数が追加されました。
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -27,7 +27,8 @@ var usageMessage = `Usage of go test:
-bench=\"\": passes -test.bench to test
-benchmem=false: print memory allocation statistics for benchmarks
-benchtime=1s: passes -test.benchtime to test
- -cover=\"\": passes -test.cover to test
+ -cover=false: basic coverage; equivalent to -covermode=set
+ -covermode=\"\": passes -test.covermode to test
-coverprofile=\"\": passes -test.coverprofile to test
-cpu=\"\": passes -test.cpu to test
-cpuprofile=\"\": passes -test.cpuprofile to test
@@ -65,6 +66,7 @@ var testFlagDefn = []*testFlagSpec{
{name: \"c\", boolVar: &testC},\n \t{name: \"file\", multiOK: true},\n \t{name: \"i\", boolVar: &testI},\n+\t{name: \"cover\", boolVar: &testCover},\n \n \t// build flags.\n \t{name: \"a\", boolVar: &buildA},\
@@ -83,7 +85,7 @@ var testFlagDefn = []*testFlagSpec{
\t{name: \"bench\", passToTest: true},\n \t{name: \"benchmem\", boolVar: new(bool), passToTest: true},\n \t{name: \"benchtime\", passToToTest: true},\n-\t{name: \"cover\", passToTest: true},\n+\t{name: \"covermode\"}, // Passed to test by special arrangement.\n \t{name: \"coverprofile\", passToTest: true},\n \t{name: \"cpu\", passToTest: true},\n \t{name: \"cpuprofile\", passToTest: true},\
@@ -144,7 +146,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
\t\tvar err error\n \t\tswitch f.name {\n \t\t// bool flags.\n-\t\tcase \"a\", \"c\", \"i\", \"n\", \"x\", \"v\", \"work\", \"race\":\n+\t\tcase \"a\", \"c\", \"i\", \"n\", \"x\", \"v\", \"race\", \"cover\", \"work\":\n \t\t\tsetBoolFlag(f.boolVar, value)\n \t\tcase \"p\":\n \t\t\tsetIntFlag(&buildP, value)\
@@ -174,19 +176,15 @@ func testFlags(args []string) (packageNames, passToTest []string) {
\t\t\ttestBench = true\n \t\tcase \"timeout\":\n \t\t\ttestTimeout = value\n-\t\tcase \"blockprofile\", \"coverprofile\", \"cpuprofile\", \"memprofile\":\n+\t\tcase \"blockprofile\", \"cpuprofile\", \"memprofile\":\n+\t\t\ttestProfile = true\n+\t\tcase \"coverprofile\":\n+\t\t\tpassToTest = setCoverMode(\"set\", passToTest)\n \t\t\ttestProfile = true\n \t\tcase \"outputdir\":\n \t\t\toutputDir = value\n-\t\tcase \"cover\":\n-\t\t\tswitch value {\n-\t\t\tcase \"set\", \"count\", \"atomic\":\n-\t\t\t\ttestCover = value\n-\t\t\tdefault:\n-\t\t\t\tfatalf(\"invalid flag argument for -cover: %q\", value)\n-\t\t\t}\n-\t\t\t// Guarantee we see the coverage statistics. Doesn\'t turn -v on generally; tricky. TODO?\n-\t\t\ttestV = true\n+\t\tcase \"covermode\":\n+\t\t\tpassToTest = setCoverMode(value, passToTest)\n \t\t}\n \t\tif extraWord {\n \t\t\ti++\
@@ -195,6 +193,24 @@ func testFlags(args []string) (packageNames, passToTest []string) {
\t\t\tpassToTest = append(passToTest, \"-test.\"+f.name+\"=\"+value)\n \t\t}\n \t}\n+\t// -cover is shorthand for -covermode=set.\n+\tif testCover && testCoverMode == \"\" {\n+\t\tpassToTest = setCoverMode(\"set\", passToTest)\n+\t}\n \t// Tell the test what directory we\'re running in, so it can write the profiles there.\n \tif testProfile && outputDir == \"\" {\n \t\tdir, err := os.Getwd()\n@@ -206,6 +208,24 @@ func testFlags(args []string) (packageNames, passToTest []string) {\n \treturn\n }\n \n+// setCoverMode sets the cover mode if not already specified; it captures the default behavior and\n+// canonicalizes the coverage flags to pass to the test binary.\n+func setCoverMode(mode string, passToTest []string) []string {\n+\tif testCoverMode != \"\" {\n+\t\treturn passToTest\n+\t}\n+\tswitch mode {\n+\tcase \"set\", \"count\", \"atomic\":\n+\t\ttestCoverMode = mode\n+\tdefault:\n+\t\tfatalf(\"invalid flag argument for -cover: %q\", mode)\n+\t}\n+\ttestCover = true\n+\t// Guarantee we see the coverage statistics. Doesn\'t turn -v on generally; tricky. TODO?\n+\ttestV = true\n+\treturn append(passToTest, \"-test.covermode\", \"set\")\n+}\n+\n // testFlag sees if argument i is a known flag and returns its definition, value, and whether it consumed an extra word.\n func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool) {\n \targ := args[i]\
src/pkg/testing/testing.go
testing パッケージ内のフラグ定義が変更され、-test.cover が削除され、-test.covermode が追加されました。
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -122,7 +122,7 @@ var (
// Report as tests are run; default is silent for success.
chatty = flag.Bool(\"test.v\", false, \"verbose: print additional output\")
- cover = flag.String(\"test.cover\", \"\", \"cover mode: set, count, atomic; default is none\")
+ coverMode = flag.String(\"test.covermode\", \"\", \"cover mode: set, count, atomic; default is none\")
coverProfile = flag.String(\"test.coverprofile\", \"\", \"write a coveraage profile to the named file after execution\")
match = flag.String(\"test.run\", \"\", \"regular expression to select tests and examples to run\")
memProfile = flag.String(\"test.memprofile\", \"\", \"write a memory profile to the named file after execution\")
@@ -520,7 +520,7 @@ func after() {
}\n \t\tf.Close()\n \t}\n-\tif *cover != \"\" {\n+\tif *coverMode != \"\" {\n \t\tcoverReport()\n \t}\n }\
コアとなるコードの解説
src/cmd/go/test.go の変更
testCover変数がstringからboolに変更されたことで、この変数はカバレッジが有効かどうかを示す単純なフラグとなりました。testCoverModeという新しいstring変数が導入され、実際のカバレッジモード(set,count,atomic)を保持するようになりました。test(p *Package)関数内のカバレッジ関連の条件分岐(例:if testCover != "" ...)がif testCover ...に変更され、testCoverModeがp.coverModeに割り当てられるようになりました。これにより、カバレッジの有効化とモードの指定が分離されました。CoverEnabled()メソッドもtestCover != ""からtestCoverに変更され、カバレッジが有効であるかどうかの判定がよりシンプルになりました。
src/cmd/go/testflag.go の変更
このファイルは、go test コマンドのフラグ解析の中心です。
testFlagDefn配列に、新しい-cover(bool) と-covermodeフラグの定義が追加されました。testFlags関数内で、フラグの解析ロジックが変更されました。-coverprofileが指定された場合、setCoverMode("set", passToTest)が呼び出されるようになりました。これは、プロファイル出力が必要な場合は自動的にカバレッジがsetモードで有効になることを意味します。-covermodeが指定された場合、その値がsetCoverMode関数に渡されます。- 最も重要な変更は、フラグ解析の最後に以下のロジックが追加されたことです。
これにより、ユーザーが単に// -cover is shorthand for -covermode=set. if testCover && testCoverMode == "" { passToTest = setCoverMode("set", passToTest) }-coverを指定し、かつ-covermodeを明示的に指定しなかった場合、自動的にカバレッジモードがsetに設定されます。
setCoverMode関数は、カバレッジモードを設定するための新しいヘルパー関数です。- 既に
testCoverModeが設定されている場合は何もしません(最初の設定を優先)。 - 指定されたモードが
set,count,atomicのいずれかであることを検証し、不正な場合はfatalfでエラーを報告します。 testCoverをtrueに設定し、カバレッジが有効であることをマークします。testV(verboseモード) をtrueに設定します。これは、カバレッジ統計を表示するために-vが必要であるという当時の要件(TODOコメントにも記載)を反映しています。-test.covermodeフラグとその値をpassToTestスライスに追加し、テストバイナリに渡される引数を構築します。
- 既に
src/pkg/testing/testing.go の変更
このファイルは、Goのテストフレームワーク自体を定義しています。
flag.String("test.cover", ...)の定義が削除され、代わりにflag.String("test.covermode", ...)が追加されました。これにより、テストバイナリが直接受け取るフラグが-test.covermodeに統一されました。after()関数内のカバレッジレポート生成の条件が*cover != ""から*coverMode != ""に変更されました。これは、testingパッケージが新しいcoverModeフラグを参照するように更新されたことを示しています。
これらの変更により、go test コマンドのフロントエンド(cmd/go)とテストバイナリ(pkg/testing)の間で、カバレッジフラグの処理がより明確かつ柔軟に連携するようになりました。
関連リンク
- Go Gerrit Change-List: https://golang.org/cl/10364044
参考にした情報源リンク
- go.dev: Go 1.2 Release Notes (Test Coverage)
- stackoverflow.com: Go test coverage flags explanation
- go.dev: Go 1.3 Release Notes (Test Coverage enhancements)