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

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

このコミットは、Go言語の標準ツールチェーンにおけるgo test -coverコマンドの内部実装に関する変更です。具体的には、コードカバレッジ情報を扱う際に使用される変数管理の方法が、個別の変数から構造体(struct)ベースのアプローチへと変更されました。これにより、カバレッジデータの表現がより整理され、ツールの保守性が向上しています。

コミット

commit 92ea9fa108d608a4bd46dc4f51e251e99a2964c5
Author: Rob Pike <r@golang.org>
Date:   Thu Jun 13 13:12:58 2013 -0700

    cmd/go: change to use struct in go test -cover
    Go tool half of https://golang.org/cl/10271044
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/10272043

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

https://github.com/golang/go/commit/92ea9fa108d608a4bd46dc4f51e251e99a2964c5

元コミット内容

cmd/go: change to use struct in go test -cover

このコミットは、go test -coverコマンドにおいて、カバレッジ情報を扱う内部的な変数を構造体を使用するように変更するものです。これは、関連する変更セット https://golang.org/cl/10271044 のGoツール側の対応です。

変更の背景

Go言語のコードカバレッジツール(go tool cover)は、ソースコードの各ステートメントがテストによって実行されたかどうかを追跡するために、特定の変数を生成し、それらの変数をインクリメントするコードを元のソースに挿入します。以前の実装では、カバレッジの「カウント(実行回数)」と「位置(ソースコード上の位置情報)」をそれぞれ独立した変数として扱っていました。

このアプローチは機能的には問題ありませんでしたが、カバレッジ情報が複雑になるにつれて、関連する変数が散在し、管理が煩雑になる可能性がありました。特に、各カバレッジポイントに対して2つの独立した変数名(カウント用と位置用)を生成・管理する必要がありました。

このコミットの目的は、これらの関連するカバレッジ情報を単一の構造体としてまとめることで、内部的なデータ構造を簡素化し、コードの可読性と保守性を向上させることにあります。これにより、go tool coverが生成するコードや、cmd/gogo tool coverを呼び出す際の引数もよりシンプルになります。

前提知識の解説

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

  1. go test -cover: Go言語の標準テストツールであるgo test-coverフラグを付けることで、テスト実行時のコードカバレッジを測定できます。これにより、テストがソースコードのどの部分を実行したか、どの部分を実行しなかったかを把握できます。

  2. go tool cover: go test -coverの裏側で動作する低レベルツールです。このツールは、元のGoソースコードを解析し、カバレッジ計測用のコード(プロファイリングフック)を挿入した新しいGoソースファイルを生成します。この挿入されるコードは、実行時に特定のグローバル変数を更新し、その変数の値からカバレッジ情報を導き出します。

  3. コードカバレッジの仕組み: go tool coverは、ソースコードの各ブロック(ステートメントや条件分岐など)に対して、そのブロックが実行された回数を記録するためのカウンタ変数を挿入します。また、そのカウンタがどのソースコード上の位置に対応するかを示す位置情報も管理します。これらの情報は、最終的にカバレッジレポートの生成に利用されます。

  4. Go言語の構造体 (Struct): Go言語における構造体は、異なる型のフィールドをまとめることができるユーザー定義型です。関連するデータを論理的にグループ化するために使用されます。このコミットでは、カバレッジのカウントと位置情報を単一の構造体としてまとめることで、データの凝集度を高めています。

  5. Gerrit Change-ID (CL): Goプロジェクトでは、コードレビューにGerritを使用しており、各変更セットには一意のChange-ID(CL)が割り当てられます。コミットメッセージ内のhttps://golang.org/cl/で始まるリンクは、Gerrit上の対応する変更セットを示しています。このコミットは、別のCL(10271044)と連携して機能する「Goツール側の半分」であることが示されています。これは、通常、フロントエンド(cmd/go)とバックエンド(go tool coverが生成するコード)の両方で変更が必要な場合に発生します。

技術的詳細

このコミットの核心は、go tool coverが内部的にカバレッジデータを表現する方法の変更です。

以前は、各カバレッジポイントに対して以下のような独立した変数が生成されていました(例):

  • GoCoverCount_N (実行回数を記録する配列)
  • GoCoverPos_N (ソースコード上の位置情報を記録する配列)

このコミットでは、これらを単一の構造体としてまとめることで、以下のような形式に変更されます:

  • GoCover_N (この構造体の中にカウントと位置情報が含まれる)

この変更は、主に以下の3つのファイルに影響を与えています。

  1. src/cmd/go/pkg.go: Package構造体内でカバレッジ変数を表現するCoverVar型が変更されました。

    • 変更前:
      type CoverVar struct {
          File  string // local file name
          Count string // name of count array
          Pos   string // name of position array
      }
      
    • 変更後:
      type CoverVar struct {
          File string // local file name
          Var  string // name of count struct
      }
      

    CountPosフィールドが削除され、代わりにVarフィールドが追加されました。このVarは、生成されるカバレッジ構造体の名前を保持します。

  2. src/cmd/go/test.go: declareCoverVars関数内で、CoverVar構造体のインスタンスを生成するロジックが変更されました。

    • 変更前は、CountPosにそれぞれ異なる変数名を割り当てていました。
    • 変更後は、Varフィールドに単一の構造体名を割り当てています(例: GoCover_N)。 また、テスト実行時にカバレッジデータを登録するテンプレートコード(coverRegisterFileの呼び出し部分)も変更されました。
    • 変更前: coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Count}}[:], _test.{{$cover.Pos}}[:]...)
    • 変更後: coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:]...) これは、_test.{{$cover.Var}}が構造体を表し、その構造体のフィールドとしてCountPosにアクセスすることを示しています。
  3. src/cmd/go/build.go: builder構造体のcoverメソッドのシグネチャと、go tool coverコマンドを呼び出す際の引数が変更されました。

    • coverメソッドのシグネチャが、countposの2つの文字列引数を受け取る代わりに、単一のvarName文字列引数を受け取るように変更されました。
    • go tool coverコマンドの呼び出しにおいて、-count-posオプションが削除され、代わりに-varオプションが追加されました。この-varオプションには、生成されるカバレッジ構造体の名前が渡されます。

これらの変更により、go tool coverが生成するカバレッジ変数の命名規則と、それらを扱う内部ロジックが統一され、より構造化されたアプローチが採用されました。これにより、カバレッジ計測の内部実装がよりクリーンになり、将来的な機能拡張やデバッグが容易になります。

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

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -798,7 +798,7 @@ func (b *builder) build(a *action) (err error) {
 			continue
 		}
 		coverFile := filepath.Join(obj, file)
-		if err := b.cover(a, coverFile, sourceFile, 0666, cover.Count, cover.Pos); err != nil {
+		if err := b.cover(a, coverFile, sourceFile, 0666, cover.Var); err != nil {
 			return err
 		}
 		gofiles = append(gofiles, coverFile)
@@ -1110,13 +1110,12 @@ func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {
 }
 
 // cover runs, in effect,
-//	go tool cover -mode=b.coverMode -count="count" -pos="pos" -o dst.go src.go
-func (b *builder) cover(a *action, dst, src string, perm os.FileMode, count, pos string) error {
+//	go tool cover -mode=b.coverMode -var="varName" -o dst.go src.go
+func (b *builder) cover(a *action, dst, src string, perm os.FileMode, varName string) error {
 	return b.run(a.objdir, "cover "+a.p.ImportPath, nil,
 		tool("cover"),
 		"-mode", a.p.coverMode,
-		"-count", count,
-		"-pos", pos,
+		"-var", varName,
 		"-o", dst,
 		src)
 }

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -90,9 +90,8 @@ type Package struct {
 
 // CoverVar holds the name of the generated coverage variables targeting the named file.
 type CoverVar struct {
-	File  string // local file name
-	Count string // name of count array
-	Pos   string // name of position array
+	File string // local file name
+	Var  string // name of count struct
 }
 
 func (p *Package) copyBuild(pp *build.Package) {

src/cmd/go/test.go

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -682,9 +682,8 @@ func declareCoverVars(files ...string) map[string]*CoverVar {
 	coverVars := make(map[string]*CoverVar)
 	for _, file := range files {
 		coverVars[file] = &CoverVar{
-			File:  file,
-			Count: fmt.Sprintf("GoCoverCount_%d", coverIndex),
-			Pos:   fmt.Sprintf("GoCoverPos_%d", coverIndex),
+			File: file,
+			Var:  fmt.Sprintf("GoCover_%d", coverIndex),
 		}
 		coverIndex++
 	}
@@ -988,7 +987,7 @@ var (
 
 func init() {
 	{{range $file, $cover := .CoverVars}}
-	coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Count}}[:], _test.{{$cover.Pos}}[:]...)
+	coverRegisterFile({{printf "%q" $file}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:]...)
 	{{end}}
 }

コアとなるコードの解説

このコミットの主要な変更点は、カバレッジ情報を表すCoverVar構造体の定義と、それを使用するcmd/goのロジックです。

  1. src/cmd/go/pkg.goCoverVar 構造体:

    • 以前は、CountPosという2つの文字列フィールドを持っていました。これらはそれぞれ、生成されるカバレッジカウンタ配列と位置情報配列のグローバル変数名を保持していました。
    • 変更後は、Varという単一の文字列フィールドを持つようになりました。このVarは、生成されるカバレッジ構造体(例: GoCover_N)のグローバル変数名を保持します。この構造体自体が、内部にカウンタ配列と位置情報配列を持つことになります。これにより、関連する情報が1つのエンティティにまとめられ、より論理的なデータ表現が可能になりました。
  2. src/cmd/go/test.godeclareCoverVars 関数とテンプレート:

    • declareCoverVars関数は、各ソースファイルに対応するCoverVarインスタンスを生成します。変更前はGoCoverCount_NGoCoverPos_Nのような独立した名前を生成していましたが、変更後はGoCover_Nのような単一の名前を生成し、それをCoverVar.Varに設定します。
    • init()関数内で使用されるテンプレート(coverRegisterFileの呼び出し)も変更されました。以前は_test.{{$cover.Count}}[:]_test.{{$cover.Pos}}[:]のように直接配列を参照していましたが、変更後は_test.{{$cover.Var}}.Count[:]_test.{{$cover.Var}}.Pos[:]のように、生成された構造体(_test.{{$cover.Var}})のフィールドとしてカウンタと位置情報にアクセスするようになりました。これは、go tool coverが生成するコードが、GoCover_Nという構造体を定義し、その中にCountPosというフィールドを持つようになったことを示唆しています。
  3. src/cmd/go/build.gocover 関数:

    • この関数は、go tool coverコマンドを呼び出してカバレッジ計測用のソースファイルを生成する役割を担っています。
    • 変更前は、go tool cover-count-posという2つの引数を渡していました。
    • 変更後は、これらの引数が削除され、代わりに-varという単一の引数が渡されるようになりました。この-var引数には、CoverVar.Varで指定された構造体名が渡されます。これにより、go tool coverは、指定された構造体名を使用してカバレッジ計測用のコードを生成するようになります。

これらの変更は、go test -coverの内部的なデータ管理をより構造化されたものにし、将来的な拡張性や保守性を高めるための重要なリファクタリングです。ユーザーから見たgo test -coverの振る舞いに直接的な変更はありませんが、内部的にはより堅牢な基盤が構築されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(go testコマンドとコードカバレッジに関する情報)
  • Go言語のソースコード(特にcmd/gocmd/coverディレクトリ)
  • Go言語のコードレビューシステムGerritのアーカイブ