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

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

このコミットは、Go言語のコマンドラインツールgoにおけるgo test foo_test.goコマンドの不具合を修正するものです。具体的には、特定のテストファイルを指定してgo testを実行した際に発生していたビルドの問題、特にレース検出器(-raceフラグ)と組み合わせた場合の不具合("race build"の修正)に対処しています。この修正により、go test foo_test.goが意図した通りに機能し、テストが正しくビルドおよび実行されるようになります。

コミット

  • コミットハッシュ: 41e5c398d92b271ca78d1770100051e35b233815
  • Author: Russ Cox rsc@golang.org
  • Date: Mon May 12 20:45:31 2014 -0400

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

https://github.com/golang/go/commit/41e5c398d92b271ca78d1770100051e35b233815

元コミット内容

    cmd/go: fix 'go test foo_test.go'
    
    Fixes race build.
    
    TBR=iant
    CC=golang-codereviews
    https://golang.org/cl/100410044

変更の背景

Go言語のgo testコマンドは、Goプロジェクトのテストを実行するための主要なツールです。通常、引数なしでgo testを実行するとカレントディレクトリのパッケージ内のテストが実行され、パッケージパスを指定するとそのパッケージのテストが実行されます。しかし、go test foo_test.goのように特定のテストファイルを直接指定して実行するモードも存在します。

このコミットが修正しようとしている問題は、このgo test foo_test.goという形式のコマンドが、特にGoのデータ競合検出器(-raceフラグ)と組み合わせて使用された場合に、正しくビルドできない、あるいは期待通りに動作しないというものでした。コミットメッセージの「Fixes race build.」という記述から、レース検出器を有効にしたビルドプロセスで問題が発生していたことが示唆されます。これは、goコマンドが内部的にテスト実行のために生成する一時的なパッケージの依存関係の解決に不備があったためと考えられます。

前提知識の解説

Goのパッケージとテスト

Go言語では、コードは「パッケージ」という単位で管理されます。テストコードは通常、テスト対象のパッケージと同じディレクトリに配置され、ファイル名が_test.goで終わる必要があります。

go testコマンドには主に2つの実行モードがあります。

  1. パッケージテスト: go test [パッケージパス]
    • これは、指定されたパッケージ内のすべての_test.goファイルをコンパイルし、テストを実行します。
    • テスト対象のパッケージと同じパッケージ名を持つテストファイル(内部テスト)と、_testサフィックスを持つ異なるパッケージ名を持つテストファイル(外部テスト)の両方を扱います。外部テストは、テスト対象パッケージの外部からその公開されたAPIのみをテストする際に使用されます。
  2. ファイルテスト: go test [ファイル名.go]
    • このモードは、指定されたGoソースファイル(通常は_test.goファイル)のみをコンパイルし、テストを実行します。この場合、goコマンドは指定されたファイルを含む一時的な「メイン」パッケージを生成し、その中でテストを実行します。このモードは、特定のテストケースのみを素早く実行したい場合などに便利です。

go testの内部動作と_testmain.go

go testコマンドは、テストを実行するためにいくつかの内部的なステップを踏みます。

  1. テストパッケージの構築: go testは、テスト対象のパッケージと、そのテストファイル(内部テストおよび外部テスト)をコンパイルするために必要な情報を収集し、内部的なPackage構造体として表現します。
  2. _testmain.goの生成: 実際のテスト実行は、testingパッケージが提供するフレームワークによって行われます。go testは、テスト対象のパッケージとテストファイルから、すべてのテスト関数(TestXxxBenchmarkXxxExampleXxx)を呼び出すためのエントリポイントとなる_testmain.goというソースファイルを自動的に生成します。この_testmain.goは、mainパッケージとしてコンパイルされ、テスト実行可能バイナリのメイン関数となります。
  3. 依存関係の解決: _testmain.goは、テスト対象のパッケージや外部テストパッケージに依存します。goコマンドは、これらの依存関係を正しく解決し、すべての必要なパッケージがリンクされるようにする必要があります。

Goのレース検出器 (-race)

Goには、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも一方が書き込みを行う場合に発生する競合)を検出するための組み込みのレース検出器があります。go test -raceのように-raceフラグを付けてテストを実行すると、コンパイラとランタイムが計測コードを挿入し、実行時にデータ競合を監視します。レース検出器を有効にすると、ビルドプロセスが通常よりも複雑になり、特定の依存関係の解決がより厳密に求められることがあります。

技術的詳細

このコミットの技術的な核心は、src/cmd/go/test.goファイル内のbuilder.test関数にあります。この関数は、go testコマンドがテスト実行のために内部的にパッケージを構築するロジックを担っています。

builder.test関数は、テスト対象のパッケージ(p)と、それに関連するテストパッケージを表現する複数のPackage構造体を生成します。

  • ptest: テスト対象のパッケージ内のテストファイル(例: mypackage_test.go)を表す内部的なPackage構造体。これはテスト対象のパッケージと同じインポートパスを持ちます。
  • pxtest: 外部テストパッケージ(例: mypackage_testという名前のパッケージ)を表す内部的なPackage構造体。これは通常、テスト対象のパッケージのインポートパスに_testサフィックスを付けたインポートパスを持ちます。
  • pmain: テスト実行可能バイナリのエントリポイントとなる_testmain.goファイルを含むmainパッケージを表す内部的なPackage構造体。

問題は、go test foo_test.goのように単一のテストファイルを指定した場合に、これらの内部的なPackage構造体間の依存関係が正しく設定されていなかったことにありました。特に、pxtestpmainimports(依存するパッケージのリスト)フィールドの扱いが不適切だったため、テスト対象のファイルが正しくリンクされず、結果としてビルドエラーやレース検出器との連携不具合が発生していたと考えられます。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/cmd/go/test.bash:

    • go test testdata/standalone_test.goが正しく動作することを確認するための新しいテストケースが追加されました。これは、go test foo_test.go形式のコマンドの回帰テストとして機能します。
  2. src/cmd/go/test.go:

    • builder.test関数内のpxtestおよびpmainという内部的なPackage構造体のimportsフィールドの初期化ロジックが変更されました。

    変更前:

    			imports: append(ximports, ptest), // pxtestのimports
    
    		imports:    []*Package{ptest}, // pmainのimports
    

    変更後:

    			imports: ximports, // pxtestのimports
    		}
    		if ptest != p {
    			pxtest.imports = append(pxtest.imports, ptest) // ptestがpと異なる場合のみ追加
    		}
    
    		build:      &build.Package{Name: "main"},
    		pkgdir:     testDir,
    		fake:       true,
    		Stale:      true,
    		omitDWARF:  !testC && !testNeedBinary,
    	}
    	if ptest != p {
    		pmain.imports = append(pmain.imports, ptest) // ptestがpと異なる場合のみ追加
    	}
    	if pxtest != nil {
    		pmain.imports = append(pmain.imports, pxtest)
    	}
    
  3. src/cmd/go/testdata/standalone_test.go:

    • go test testdata/standalone_test.goのテストケースで使用される新しいテストファイルが追加されました。これは非常にシンプルなテストファイルで、package standalone_testとして定義されています。
    package standalone_test
    
    import "testing"
    
    func Test(t *testing.T) {
    }
    

コアとなるコードの解説

このコミットの核心的な変更は、src/cmd/go/test.goにおけるpxtestpmainimportsフィールドの修正です。

  1. pxtest.importsの修正:

    • 変更前は、pxtestimportsに無条件にptest(テスト対象パッケージ内のテストファイル)が追加されていました。
    • 変更後は、pxtest.importsはまずximports(外部テストパッケージが元々持っていたインポート)で初期化されます。
    • そして、if ptest != pという条件が追加され、ptestがテスト対象のパッケージpと異なる場合にのみ、ptestpxtest.importsに追加されるようになりました。
      • go test foo_test.goのように単一のファイルを指定した場合、ptestは指定されたファイルを含む一時的なパッケージを指します。この場合、ptestp(カレントディレクトリのパッケージ)とは異なるため、この条件が真となり、pxtestptestに依存するようになります。これにより、外部テストパッケージが、単一ファイルテストのシナリオで正しくテスト対象のコードにアクセスできるようになります。
  2. pmain.importsの修正:

    • 変更前は、pmain_testmain.goを含むパッケージ)のimportsptestのみに依存していました。
    • 変更後は、pmain.importsはまず空で初期化され、その後if ptest != pという条件でptestが追加されます。
    • さらに、if pxtest != nilという条件でpxtestpmain.importsに追加されるようになりました。
      • この変更により、_testmain.goは、内部テストパッケージ(ptest)と外部テストパッケージ(pxtest)の両方に正しく依存するようになります。これにより、go test foo_test.goのようなシナリオで、_testmain.goがすべての必要なテストコードと依存関係を正しくリンクできるようになり、ビルドエラーやレース検出器との連携不具合が解消されます。

これらの修正は、goコマンドが単一のテストファイルを処理する際の内部的なパッケージ依存関係の解決ロジックを改善し、特にレース検出器が有効な場合に発生していたビルドの失敗を防ぐことを目的としています。test.bashに新しいテストケースが追加されたことで、この修正が将来の回帰を防ぐためのテストカバレッジも提供されています。

関連リンク

参考にした情報源リンク

  • コミットの差分情報
  • Go言語のgo testコマンドに関する一般的な知識
  • Go言語のパッケージとテストの仕組みに関する一般的な知識
  • Go言語のレース検出器に関する一般的な知識