[インデックス 16668] ファイルの概要
このコミットは、Go言語のgo test
コマンドに-coverpkg
フラグを追加し、テストカバレッジ計測の柔軟性を向上させるものです。これにより、特定のパッケージのテストを実行しながら、別のパッケージ群のコードカバレッジを計測することが可能になります。また、既存のrecompileForTest
ロジックがpackageList
を活用して改善され、より効率的でクリーンなコードベースになっています。
コミット
commit 97c19f0f721b1db634dcae6a766712533a8f1bd5
Author: Russ Cox <rsc@golang.org>
Date: Thu Jun 27 17:04:39 2013 -0400
cmd/go: add -coverpkg
The new -coverpkg flag allows computing coverage in
one set of packages while running the tests of a different set.
Also clean up some of the previous CL's recompileForTest,
using packageList to avoid the clumsy recursion.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/10705043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/97c19f0f721b1db634dcae6a766712533a8f1bd5
元コミット内容
このコミットの主な内容は以下の通りです。
-coverpkg
フラグの追加:go test
コマンドに-coverpkg
という新しいフラグが導入されました。このフラグを使用すると、テストを実行するパッケージとは異なる、指定されたパッケージ群のコードカバレッジを計測できるようになります。recompileForTest
の改善: 以前の変更リスト(CL)で導入されたrecompileForTest
関数がクリーンアップされました。これは、packageList
ユーティリティ関数を使用することで、以前の扱いにくい再帰的な処理を回避し、より効率的で理解しやすい実装になったことを意味します。
変更の背景
Go言語のテストカバレッジツールは、元々テスト対象のパッケージとその直接的な依存関係のみを対象としていました。しかし、大規模なプロジェクトやモジュール化されたシステムでは、以下のようなニーズがありました。
- 共通ライブラリのカバレッジ計測: 複数のアプリケーションやサービスが依存する共通ライブラリがある場合、そのライブラリ自体のテストは少ないかもしれませんが、それを利用する上位のアプリケーションのテストを通じて、ライブラリのコードがどれだけ実行されているかを知りたい場合があります。
- 統合テストでのカバレッジ計測: 複数のパッケージにまたがる統合テストを実行する際に、テスト対象のパッケージだけでなく、そのテストが呼び出す他のパッケージのコードカバレッジもまとめて計測したい場合があります。
- テストの分離とカバレッジの結合: テストの実行時間を短縮するために、テストを複数の小さなグループに分割して実行しつつ、最終的なカバレッジレポートはプロジェクト全体で結合したいというシナリオです。
これらのニーズに対応するため、テスト実行とカバレッジ計測の対象を分離し、より柔軟に設定できるメカニズムが必要とされました。-coverpkg
フラグは、この課題を解決するために導入されました。
また、recompileForTest
関数の改善は、既存のコードベースの保守性と効率性を向上させるための一般的なリファクタリングの一環です。テストカバレッジ計測はコンパイル時にコードを変換するため、依存関係の再構築が必要になることがあり、そのロジックをより堅牢にすることが求められました。
前提知識の解説
Go言語のテストとカバレッジ
Go言語には、標準でテストフレームワークが組み込まれており、go test
コマンドを使用してテストを実行します。
- テストファイル:
_test.go
で終わるファイルにテストコードを記述します。 - テスト関数:
TestXxx
という形式の関数がテスト関数として認識されます。 - カバレッジ計測:
go test -cover
を実行すると、テストが実行された際にどのコード行が実行されたかを計測し、その割合(カバレッジ率)をレポートします。-covermode
: カバレッジの計測モードを指定します。set
: 各ステートメントが実行されたかどうかのみを記録します(デフォルト)。count
: 各ステートメントが実行された回数を記録します。atomic
:count
と同様ですが、並行テスト環境でより正確な結果を得るためにアトミック操作を使用します(パフォーマンスコストが高い)。
-coverprofile
: カバレッジプロファイルを指定されたファイルに出力します。このファイルはgo tool cover
コマンドで解析できます。
Goのパッケージと依存関係
Goのコードは「パッケージ」という単位で整理されます。
- インポートパス: パッケージはユニークなインポートパス(例:
fmt
,net/http
,github.com/user/repo/mypkg
)で識別されます。 - 依存関係: あるパッケージが別のパッケージの機能を利用する場合、そのパッケージをインポートします。Goのビルドシステムは、これらの依存関係を解決して実行可能ファイルを生成します。
go
コマンドの内部動作(ビルドとテスト)
go
コマンドは、Goのソースコードをコンパイルし、リンクして実行可能ファイルを生成するツールです。go test
コマンドも内部的にはビルドプロセスを利用します。
- テストバイナリの生成:
go test
は、テスト対象のパッケージとテストコードをコンパイルし、テストを実行するための特別なバイナリを生成します。 - カバレッジ計測の仕組み: カバレッジを計測する場合、
go
コマンドはコンパイル時にソースコードに計測用のコード(インストゥルメンテーション)を挿入します。これにより、実行時に各ステートメントの実行状況が記録されます。
packageList
ユーティリティ
Goのツールチェイン内部では、パッケージの依存関係グラフを効率的に走査し、関連するパッケージのリストを取得するためのユーティリティ関数が多数存在します。packageList
はそのようなユーティリティの一つで、特定のパッケージから到達可能なすべての依存パッケージをリストアップするのに役立ちます。これにより、再帰的な処理を手動で実装するよりも、より簡潔でエラーの少ないコードを書くことができます。
技術的詳細
このコミットは、主にsrc/cmd/go/test.go
ファイルに大きな変更を加えています。
-coverpkg
フラグの追加と処理
testflag.go
:-coverpkg
フラグがtestFlagDefn
に追加され、コマンドライン引数として認識されるようになりました。値はカンマ区切りのパッケージパスのリストとして解析され、testCoverPaths
変数に格納されます。doc.go
:go help test
で表示されるドキュメントに-coverpkg
の説明が追加されました。test.go
:testCoverPaths
という新しいグローバル変数が追加され、-coverpkg
で指定されたパッケージパスを保持します。testCoverPkgs
という[]*Package
型の変数も追加され、testCoverPaths
で指定されたパッケージのPackage
構造体を保持します。runTest
関数内で、testCoverPaths
が設定されている場合、packagesForBuild
関数を使用してこれらのパッケージをロードし、testCoverPkgs
に格納します。-coverpkg
で指定されたパッケージが実際にテスト対象のパッケージの依存関係として使用されているかどうかの警告ロジックが追加されました。これは、指定されたパッケージが全くテストに影響しない場合にユーザーに通知するためです。testCoverPkgs
内の各パッケージは、カバレッジ計測のために再ビルドされるようにマークされます(p.Stale = true
,p.fake = true
,p.coverMode = testCoverMode
,p.coverVars = declareCoverVars(...)
)。localCover
という新しい変数が導入され、-coverpkg
が指定されていない場合にのみ、テスト対象のパッケージに対してローカルなカバレッジ計測を行うように制御します。これにより、-coverpkg
が優先されるようになります。- テストメイン(
_testmain.go
)を生成する際に、testCoverPkgs
に含まれるすべてのパッケージがインポートされるように、pmain.imports
に追加されます。これにより、これらのパッケージの計測コードがテストバイナリに含まれるようになります。
recompileForTest
の改善
recompileForTest
関数のシグネチャ変更:pxtest
引数が削除され、recompileForTest(pmain, preal, ptest, testDir)
となりました。これは、外部テスト(XTestGoFiles
)の処理がこの関数から分離されたことを示唆しています。packageList
の活用: 以前の再帰的なclone
およびrewrite
関数に代わり、packageList([]*Package{pmain})
を使用して、テストメインパッケージ(pmain
)から到達可能なすべてのパッケージを効率的に走査するようになりました。- Copy-on-Writeメカニズム:
testCopy
マップとsplit
関数を導入することで、依存関係ツリーを走査しながら、テストカバレッジ計測のために再ビルドが必要なパッケージのみを「テストコピー」として作成し、その依存関係を更新する「Copy-on-Write」のようなメカニズムが実装されました。これにより、不要なパッケージの再ビルドを避けつつ、必要な依存関係の解決を正確に行うことができます。 pkgdir
とtarget
の更新: テストコピーされたパッケージは、一時的なテストディレクトリ(testDir
)にビルドされるようにpkgdir
が設定され、target
がクリアされます。また、fake
とStale
フラグが設定され、再ビルドが必要であることを示します。
writeTestmain
とtestFuncs
の変更
writeTestmain
のシグネチャ変更:writeTestmain(out string, p *Package, coverVars map[string]*CoverVar)
からwriteTestmain(out string, pmain, p *Package) error
に変更されました。これは、テストメインの生成が単一のパッケージのカバレッジ変数だけでなく、pmain
(テストメインパッケージ)がインポートするすべてのカバレッジ対象パッケージの情報を考慮するようになったことを意味します。testFuncs
構造体の変更:CoverVars map[string]*CoverVar
フィールドが削除され、Cover []coverInfo
という新しいフィールドが追加されました。coverInfo
は、カバレッジ対象のPackage
と、そのパッケージ内のCoverVar
(カバレッジ変数)のマップを保持する構造体です。これにより、複数のパッケージのカバレッジ情報を一元的に管理できるようになりました。- テンプレートの変更:
_testmain.go
を生成するためのGoテンプレートが更新され、{{range $i, $p := .Cover}}
ループを使用して、複数のカバレッジ対象パッケージのインポートとカバレッジ変数の登録を行うようになりました。これにより、-coverpkg
で指定されたすべてのパッケージのカバレッジデータが正しく収集されるようになります。
コアとなるコードの変更箇所
src/cmd/go/doc.go
--- a/src/cmd/go/doc.go
+++ b/src/cmd/go/doc.go
@@ -755,7 +755,12 @@ control the execution of any test:
atomic: int: count, but correct in multithreaded tests;
significantly more expensive.
Implies -cover.
- Sets -v. TODO: This will change.
+
+ -coverpkg pkg1,pkg2,pkg3
+ Apply coverage analysis in each test to the given list of packages.
+ If this option is not present, each test applies coverage analysis to
+ the package being tested. Packages are specified as import paths.
+ Implies -cover.
-coverprofile cover.out
Write a coverage profile to the specified file after all tests
src/cmd/go/test.bash
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -324,6 +324,11 @@ rm -rf $d
# Only succeeds if source order is preserved.
./testgo test testdata/example[12]_test.go
+# Check that coverage analysis works at all.
+# Don't worry about the exact numbers
+./testgo test -coverpkg=strings strings regexp
+./testgo test -cover strings math regexp
+
# clean up
rm -rf testdata/bin testdata/bin1
rm -f testgo
src/cmd/go/test.go
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -12,6 +12,7 @@ import (
"go/doc"
"go/parser"
"go/token"
+ "log"
"os"
"os/exec"
"path"
@@ -129,7 +130,6 @@ control the execution of any test:
-cover
Enable coverage analysis.
- TODO: This feature is not yet fully implemented.
-covermode set,count,atomic
Set the mode for coverage analysis for the package[s]
@@ -140,7 +140,12 @@ control the execution of any test:
atomic: int: count, but correct in multithreaded tests;
significantly more expensive.
Implies -cover.
- Sets -v. TODO: This will change.
+
+ -coverpkg pkg1,pkg2,pkg3
+ Apply coverage analysis in each test to the given list of packages.
+ The default is for each test to analyze only the package being tested.
+ Packages are specified as import paths.
+ Implies -cover.
-coverprofile cover.out
Write a coverage profile to the specified file after all tests
@@ -261,14 +266,16 @@ See the documentation of the testing package for more information.
}
var (
- testC bool // -c flag
- testCover bool // -cover flag
- testCoverMode string // -covermode flag
- testProfile bool // some profiling flag
- testI bool // -i flag
- testV bool // -v flag
- testFiles []string // -file flag(s) TODO: not respected
- testTimeout string // -timeout flag
+ testC bool // -c flag
+ testCover bool // -cover flag
+ testCoverMode string // -covermode flag
+ testCoverPaths []string // -coverpkg flag
+ testCoverPkgs []*Package // -coverpkg flag
+ testProfile bool // some profiling flag
+ testI bool // -i flag
+ testV bool // -v flag
+ testFiles []string // -file flag(s) TODO: not respected
+ testTimeout string // -timeout flag
testArgs []string
testBench bool
testStreamOutput bool // show output as it is generated
@@ -277,6 +284,12 @@ var (
testKillTimeout = 10 * time.Minute
)
+var testMainDeps = map[string]bool{
+ // Dependencies for testmain.
+ "testing": true,
+ "regexp": true,
+}
+
func runTest(cmd *Command, args []string) {
var pkgArgs []string
pkgArgs, testArgs = testFlags(args)
@@ -323,11 +336,11 @@ func runTest(cmd *Command, args []string) {
if testI {
buildV = testV
- deps := map[string]bool{
- // Dependencies for testmain.
- "testing": true,
- "regexp": true,
+ deps := make(map[string]bool)
+ for dep := range testMainDeps {
+ deps[dep] = true
}
+
for _, p := range pkgs {
// Dependencies for each test.
for _, path := range p.Imports {
@@ -373,6 +386,34 @@ func runTest(cmd *Command, args []string) {
var builds, runs, prints []*action
+ if testCoverPaths != nil {
+ // Load packages that were asked about for coverage.
+ // packagesForBuild exits if the packages cannot be loaded.
+ testCoverPkgs = packagesForBuild(testCoverPaths)
+
+ // Warn about -coverpkg arguments that are not actually used.
+ used := make(map[string]bool)
+ for _, p := range pkgs {
+ used[p.ImportPath] = true
+ for _, dep := range p.Deps {
+ used[dep] = true
+ }
+ }
+ for _, p := range testCoverPkgs {
+ if !used[p.ImportPath] {
+ log.Printf("warning: no packages being tested depend on %s", p.ImportPath)
+ }
+ }
+
+ // Mark all the coverage packages for rebuilding with coverage.
+ for _, p := range testCoverPkgs {
+ p.Stale = true // rebuild
+ p.fake = true // do not warn about rebuild
+ p.coverMode = testCoverMode
+ p.coverVars = declareCoverVars(p.ImportPath, p.GoFiles...)
+ }
+ }
+
// Prepare build + run + print actions for all packages being tested.
for _, p := range pkgs {
buildTest, runTest, printTest, err := b.test(p)
@@ -424,11 +465,22 @@ func runTest(cmd *Command, args []string) {
for _, p := range pkgs {
okBuild[p] = true
}
-
warned := false
for _, a := range actionList(root) {
- if a.p != nil && a.f != nil && !okBuild[a.p] && !a.p.fake && !a.p.local {
- okBuild[a.p] = true // don't warn again
+ if a.p == nil || okBuild[a.p] {
+ continue
+ }
+ okBuild[a.p] = true // warn at most once
+
+ // Don't warn about packages being rebuilt because of
+ // things like coverage analysis.
+ for _, p1 := range a.p.imports {
+ if p1.fake {
+ a.p.fake = true
+ }
+ }
+
+ if a.f != nil && !okBuild[a.p] && !a.p.fake && !a.p.local {
if !warned {
fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n")
warned = true
@@ -531,8 +583,14 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
return nil, nil, nil, err
}
+ // Should we apply coverage analysis locally,
+ // only for this package and only for this test?
+ // Yes, if -cover is on but -coverpkg has not specified
+ // a list of packages for global coverage.
+ localCover := testCover && testCoverPaths == nil
+
// Test package.
- if len(p.TestGoFiles) > 0 || testCover {
+ if len(p.TestGoFiles) > 0 || localCover {
ptest = new(Package)
*ptest = *p
ptest.GoFiles = nil
@@ -555,19 +613,15 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
m[k] = append(m[k], v...)
}
ptest.build.ImportPos = m
+
+ if localCover {
+ ptest.coverMode = testCoverMode
+ ptest.coverVars = declareCoverVars(ptest.ImportPath, ptest.GoFiles...)
+ }
} else {
ptest = p
}
- if testCover {
- ptest.coverMode = testCoverMode
- ptest.coverVars = declareCoverVars(ptest.ImportPath, ptest.GoFiles...)
- }
-
- if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), ptest, ptest.coverVars); err != nil {
- return nil, nil, nil, err
- }
-
// External test package.
if len(p.XTestGoFiles) > 0 {
pxtest = &Package{
@@ -597,6 +651,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
Root: p.Root,
imports: []*Package{ptest},
build: &build.Package{Name: "main"},
+ pkgdir: testDir,
fake: true,
Stale: true,
}
@@ -606,21 +661,35 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
// The generated main also imports testing and regexp.
stk.push("testmain")
- ptesting := loadImport("testing", "", &stk, nil)
- if ptesting.Error != nil {
- return nil, nil, nil, ptesting.Error
+ for dep := range testMainDeps {
+ if ptest.ImportPath != dep {
+ p1 := loadImport("testing", "", &stk, nil)
+ if p1.Error != nil {
+ return nil, nil, nil, p1.Error
+ }
+ pmain.imports = append(pmain.imports, p1)
+ }
}
- pregexp := loadImport("regexp", "", &stk, nil)
- if pregexp.Error != nil {
- return nil, nil, nil, pregexp.Error
+
+ if testCoverPkgs != nil {
+ // Add imports, but avoid duplicates.
+ seen := map[*Package]bool{p: true, ptest: true}
+ for _, p1 := range pmain.imports {
+ seen[p1] = true
+ }
+ for _, p1 := range testCoverPkgs {
+ if !seen[p1] {
+ seen[p1] = true
+ pmain.imports = append(pmain.imports, p1)
+ }
+ }
}
- pmain.imports = append(pmain.imports, ptesting, pregexp)
- if ptest != p && testCover {
+ if ptest != p && localCover {
// We have made modifications to the package p being tested
// and are rebuilding p (as ptest), writing it to the testDir tree.
// Arrange to rebuild, writing to that same tree, all packages q
- // such that the test depends on q and q depends on p.
+ // such that the test depends on q, and q depends on p.
// This makes sure that q sees the modifications to p.
// Strictly speaking, the rebuild is only necessary if the
// modifications to p change its export metadata, but
@@ -629,7 +708,11 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
// This will cause extra compilation, so for now we only do it
// when testCover is set. The conditions are more general, though,
// and we may find that we need to do it always in the future.
- recompileForTest(pmain, p, ptest, pxtest, testDir)
+ recompileForTest(pmain, p, ptest, testDir)
+ }
+
+ if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), pmain, ptest); err != nil {
+ return nil, nil, nil, err
}
computeStale(pmain)
@@ -690,47 +773,46 @@ func (b *builder) runTest(a *action) error {
if testShowPass {
a.testOutput.Write(out)
}
- fmt.Fprintf(a.testOutput, "ok \t%s\t%s%s\n", a.p.ImportPath, t, coveragePercentage(out))
+ coverWhere := ""
+ if testCoverPaths != nil {
+ coverWhere = " in " + strings.Join(testCoverPaths, ", ")
+ }
+ fmt.Fprintf(a.testOutput, "ok \t%s\t%s%s%s\n", a.p.ImportPath, t, coveragePercentage(out), coverWhere)
return nil
}
@@ -903,12 +985,24 @@ func isTest(name, prefix string) bool {
return !unicode.IsLower(rune)
}
+type coverInfo struct {
+ Package *Package
+ Vars map[string]*CoverVar
+}
+
// writeTestmain writes the _testmain.go file for package p to
// the file named out.
-func writeTestmain(out string, p *Package, coverVars map[string]*CoverVar) error {
+func writeTestmain(out string, pmain, p *Package) error {
+ var cover []coverInfo
+ for _, cp := range pmain.imports {
+ if cp.coverVars != nil {
+ cover = append(cover, coverInfo{cp, cp.coverVars})
+ }
+ }
+
t := &testFuncs{
- Package: p,
- CoverVars: coverVars,
+ Package: p,
+ Cover: cover,
}
for _, file := range p.TestGoFiles {
if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil {
@@ -941,7 +1045,7 @@ type testFuncs struct {
Package *Package
NeedTest bool
NeedXtest bool
- CoverVars map[string]*CoverVar
+ Cover []coverInfo
}
func (t *testFuncs) CoverEnabled() bool {
@@ -1005,12 +1109,15 @@ import (
"regexp"
"testing"
-{{if or .CoverEnabled .NeedTest}}
+{{if .NeedTest}}
_test {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .NeedXtest}}
_xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
+{{range $i, $p := .Cover}}
+ _cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
+{{end}}
)
var tests = []testing.InternalTest{
@@ -1054,8 +1161,10 @@ var (
)
func init() {
- {{range $file, $cover := .CoverVars}}
- coverRegisterFile({{printf "%q" $cover.File}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:], _test.{{$cover.Var}}.NumStmt[:])
+ {{range $i, $p := .Cover}}
+ {{range $file, $cover := $p.Vars}}
+ coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
+ {{end}}
{{end}}
}
src/cmd/go/testflag.go
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -29,6 +29,7 @@ var usageMessage = `Usage of go test:
-benchtime=1s: passes -test.benchtime to test
-cover=false: enable coverage analysis
-covermode="set": passes -test.covermode to test if -cover
+ -coverpkg="": comma-separated list of packages for coverage analysis
-coverprofile="": passes -test.coverprofile to test if -cover
-cpu="": passes -test.cpu to test
-cpuprofile="": passes -test.cpuprofile to test
@@ -67,6 +68,7 @@ var testFlagDefn = []*testFlagSpec{
{name: "file", multiOK: true},
{name: "i", boolVar: &testI},
{name: "cover", boolVar: &testCover},
+ {name: "coverpkg"},
// build flags.
{name: "a", boolVar: &buildA},
@@ -178,6 +180,13 @@ func testFlags(args []string) (packageNames, passToTest []string) {
case "blockprofile", "cpuprofile", "memprofile":
testProfile = true
case "coverpkg":
+ testCover = true
+ if value == "" {
+ testCoverPaths = nil
+ } else {
+ testCoverPaths = strings.Split(value, ",")
+ }
+ case "coverprofile":
testCover = true
testProfile = true
コアとなるコードの解説
src/cmd/go/test.go
における変更のポイント
-
testCoverPaths
とtestCoverPkgs
:testCoverPaths
は、コマンドラインで-coverpkg
に渡された文字列(カンマ区切りのパッケージパス)を保持します。testCoverPkgs
は、これらのパスに対応する*Package
構造体のスライスです。runTest
関数内でpackagesForBuild
を使ってロードされます。- これらの変数が導入されたことで、テスト対象パッケージとは別に、カバレッジ計測の対象となるパッケージ群を明確に管理できるようになりました。
-
カバレッジ対象パッケージのマーク付け:
for _, p := range testCoverPkgs { p.Stale = true // rebuild p.fake = true // do not warn about rebuild p.coverMode = testCoverMode p.coverVars = declareCoverVars(p.ImportPath, p.GoFiles...) }
-coverpkg
で指定されたパッケージは、強制的に再ビルド(p.Stale = true
)され、カバレッジ計測のためのインストゥルメンテーションが施されます(p.coverMode = testCoverMode
,p.coverVars = declareCoverVars(...)
)。p.fake = true
は、これらのパッケージがテストのために一時的に作成されたものであることを示し、通常のビルド警告を抑制します。 -
localCover
の導入:localCover := testCover && testCoverPaths == nil
この変数は、
-coverpkg
が指定されていない場合にのみ、従来の「テスト対象パッケージのみをカバレッジ計測する」動作を維持するためのものです。これにより、-coverpkg
が指定された場合は、その設定が優先され、ローカルなカバレッジ計測は行われません。 -
recompileForTest
の改善:func recompileForTest(pmain, preal, ptest *Package, testDir string) { // The "test copy" of preal is ptest. // For each package that depends on preal, make a "test copy" // that depends on ptest. And so on, up the dependency tree. testCopy := map[*Package]*Package{preal: ptest} for _, p := range packageList([]*Package{pmain}) { // Copy on write. didSplit := false split := func() { if didSplit { return } didSplit = true if p.pkgdir != testDir { p1 := new(Package) testCopy[p] = p1 *p1 = *p p1.imports = make([]*Package, len(p.imports)) copy(p1.imports, p.imports) p = p1 p.pkgdir = testDir p.target = "" p.fake = true p.Stale = true } } // Update p.deps and p.imports to use at test copies. for i, dep := range p.deps { if p1 := testCopy[dep]; p1 != nil && p1 != dep { split() p.deps[i] = p1 } } for i, imp := range p.imports { if p1 := testCopy[imp]; p1 != nil && p1 != imp { split() p.imports[i] = p1 } } } }
この関数は、テストカバレッジ計測のためにパッケージが再ビルドされる際に、そのパッケージに依存する他のパッケージも適切に再ビルドされるように依存関係を調整します。
packageList([]*Package{pmain})
は、テストメインパッケージから到達可能なすべてのパッケージを効率的に取得します。testCopy
マップは、元のパッケージと、テストのために作成されたそのコピー(再ビルドされたバージョン)のマッピングを保持します。- 「Copy-on-Write」のロジックにより、実際に変更が必要なパッケージ(依存関係がテストコピーに切り替わるパッケージ)のみが複製され、その
pkgdir
がテストディレクトリに設定され、再ビルドが促されます。これにより、ビルドの効率が向上します。
-
writeTestmain
とtestFuncs
の変更:testFuncs
構造体にCover []coverInfo
が追加され、複数のパッケージのカバレッジ情報を保持できるようになりました。writeTestmain
関数は、pmain
(テストメインパッケージ)がインポートするすべてのカバレッジ対象パッケージの情報を集約し、testFuncs.Cover
に格納します。- これにより、生成される
_testmain.go
ファイルは、-coverpkg
で指定されたすべてのパッケージのカバレッジ変数を正しく登録し、カバレッジデータを収集できるようになります。
src/cmd/go/testflag.go
における変更のポイント
-coverpkg
フラグの定義が追加され、testFlags
関数内でその値が解析され、testCoverPaths
に格納されるようになりました。-coverpkg
が指定された場合、自動的に-cover
フラグも有効になるようにtestCover = true
が設定されます。
src/cmd/go/test.bash
における変更のポイント
-coverpkg
の使用例が追加されました。./testgo test -coverpkg=strings strings regexp
:strings
パッケージとregexp
パッケージのテストを実行し、strings
パッケージのカバレッジを計測します。./testgo test -cover strings math regexp
:strings
、math
、regexp
パッケージのテストを実行し、それぞれのパッケージのカバレッジを計測します(-coverpkg
がないため、従来の動作)。
関連リンク
- Go言語のテストに関する公式ドキュメント: https://go.dev/doc/code#Testing
go test
コマンドのドキュメント: https://go.dev/cmd/go/#hdr-Test_packages- Go言語のカバレッジツールに関するドキュメント: https://go.dev/blog/cover
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットの変更リスト(CL): https://golang.org/cl/10705043
- Go言語のテストカバレッジに関するブログ記事: https://go.dev/blog/cover