[インデックス 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/go
がgo tool cover
を呼び出す際の引数もよりシンプルになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびGoツールに関する知識が必要です。
-
go test -cover
: Go言語の標準テストツールであるgo test
に-cover
フラグを付けることで、テスト実行時のコードカバレッジを測定できます。これにより、テストがソースコードのどの部分を実行したか、どの部分を実行しなかったかを把握できます。 -
go tool cover
:go test -cover
の裏側で動作する低レベルツールです。このツールは、元のGoソースコードを解析し、カバレッジ計測用のコード(プロファイリングフック)を挿入した新しいGoソースファイルを生成します。この挿入されるコードは、実行時に特定のグローバル変数を更新し、その変数の値からカバレッジ情報を導き出します。 -
コードカバレッジの仕組み:
go tool cover
は、ソースコードの各ブロック(ステートメントや条件分岐など)に対して、そのブロックが実行された回数を記録するためのカウンタ変数を挿入します。また、そのカウンタがどのソースコード上の位置に対応するかを示す位置情報も管理します。これらの情報は、最終的にカバレッジレポートの生成に利用されます。 -
Go言語の構造体 (Struct): Go言語における構造体は、異なる型のフィールドをまとめることができるユーザー定義型です。関連するデータを論理的にグループ化するために使用されます。このコミットでは、カバレッジのカウントと位置情報を単一の構造体としてまとめることで、データの凝集度を高めています。
-
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つのファイルに影響を与えています。
-
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 }
Count
とPos
フィールドが削除され、代わりにVar
フィールドが追加されました。このVar
は、生成されるカバレッジ構造体の名前を保持します。 - 変更前:
-
src/cmd/go/test.go
:declareCoverVars
関数内で、CoverVar
構造体のインスタンスを生成するロジックが変更されました。- 変更前は、
Count
とPos
にそれぞれ異なる変数名を割り当てていました。 - 変更後は、
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}}
が構造体を表し、その構造体のフィールドとしてCount
とPos
にアクセスすることを示しています。
- 変更前は、
-
src/cmd/go/build.go
:builder
構造体のcover
メソッドのシグネチャと、go tool cover
コマンドを呼び出す際の引数が変更されました。cover
メソッドのシグネチャが、count
とpos
の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
のロジックです。
-
src/cmd/go/pkg.go
のCoverVar
構造体:- 以前は、
Count
とPos
という2つの文字列フィールドを持っていました。これらはそれぞれ、生成されるカバレッジカウンタ配列と位置情報配列のグローバル変数名を保持していました。 - 変更後は、
Var
という単一の文字列フィールドを持つようになりました。このVar
は、生成されるカバレッジ構造体(例:GoCover_N
)のグローバル変数名を保持します。この構造体自体が、内部にカウンタ配列と位置情報配列を持つことになります。これにより、関連する情報が1つのエンティティにまとめられ、より論理的なデータ表現が可能になりました。
- 以前は、
-
src/cmd/go/test.go
のdeclareCoverVars
関数とテンプレート:declareCoverVars
関数は、各ソースファイルに対応するCoverVar
インスタンスを生成します。変更前はGoCoverCount_N
とGoCoverPos_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
という構造体を定義し、その中にCount
とPos
というフィールドを持つようになったことを示唆しています。
-
src/cmd/go/build.go
のcover
関数:- この関数は、
go tool cover
コマンドを呼び出してカバレッジ計測用のソースファイルを生成する役割を担っています。 - 変更前は、
go tool cover
に-count
と-pos
という2つの引数を渡していました。 - 変更後は、これらの引数が削除され、代わりに
-var
という単一の引数が渡されるようになりました。この-var
引数には、CoverVar.Var
で指定された構造体名が渡されます。これにより、go tool cover
は、指定された構造体名を使用してカバレッジ計測用のコードを生成するようになります。
- この関数は、
これらの変更は、go test -cover
の内部的なデータ管理をより構造化されたものにし、将来的な拡張性や保守性を高めるための重要なリファクタリングです。ユーザーから見たgo test -cover
の振る舞いに直接的な変更はありませんが、内部的にはより堅牢な基盤が構築されました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/92ea9fa108d608a4bd46dc4f51e251e99a2964c5
- Gerrit Change-ID (このコミット): https://golang.org/cl/10272043
- Gerrit Change-ID (関連する変更セット): https://golang.org/cl/10271044
参考にした情報源リンク
- Go言語の公式ドキュメント(
go test
コマンドとコードカバレッジに関する情報) - Go言語のソースコード(特に
cmd/go
とcmd/cover
ディレクトリ) - Go言語のコードレビューシステムGerritのアーカイブ