[インデックス 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)