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

[インデックス 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言語のテストツールにおけるコードカバレッジ機能のユーザビリティを向上させることを目的としています。以前は、コードカバレッジを有効にするためのフラグの指定方法が直感的ではなかった可能性があります。

コミットメッセージによると、主な目的は以下の通りです。

  1. -cover フラグの簡素化: 単一の -cover フラグで、最もシンプルでデフォルトのカバレッジ動作を提供するように変更されました。これにより、ユーザーは詳細な設定なしに基本的なカバレッジ分析を簡単に実行できるようになります。
  2. フラグ間の連携強化: -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 コマンドのフラグ解析ロジックと、カバレッジ関連の内部変数の管理方法の変更にあります。

  1. -cover フラグの役割変更:

    • 変更前: -cover フラグは、カバレッジモード(set, count, atomic)を直接引数として受け取っていました。
    • 変更後: -cover フラグは、引数を取らないブール値フラグとなり、単に「基本的なカバレッジ分析を有効にする」というシグナルとして機能します。これは -covermode=set のショートハンドとして扱われます。
  2. -covermode フラグの導入と優先順位:

    • -covermode フラグが明示的に導入され、カバレッジモードを詳細に制御できるようになりました。
    • -cover または -coverprofile が指定された場合、明示的に -covermode が指定されていない限り、デフォルトで set モードが適用されるようになりました。
  3. -coverprofile とカバレッジモードの連携:

    • -coverprofile フラグが指定された場合も、カバレッジ分析が有効になり、デフォルトで set モードが適用されるようになりました。これにより、プロファイル出力とカバレッジ分析が自動的に連携します。
  4. 内部変数の変更:

    • src/cmd/go/test.go および src/cmd/go/testflag.go において、カバレッジモードを保持する内部変数 testCover の型が string から bool に変更されました。これは、-cover フラグがモードではなく、単なる有効/無効のシグナルになったことを反映しています。
    • 新たに testCoverMode という string 型の変数が導入され、実際のカバレッジモード(set, count, atomic)を保持するようになりました。
  5. setCoverMode 関数の導入:

    • src/cmd/go/testflag.gosetCoverMode というヘルパー関数が追加されました。この関数は、カバレッジモードがまだ設定されていない場合にモードを設定し、不正なモードが指定された場合にはエラーを報告します。また、カバレッジが有効になった際に、詳細な出力を保証するために -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 ... に変更され、testCoverModep.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 でエラーを報告します。
    • testCovertrue に設定し、カバレッジが有効であることをマークします。
    • 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.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)