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

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

このコミットは、go test コマンドの動作を修正し、_test.go ファイルが存在しないパッケージに対しても常にビルドを実行するように変更します。これにより、テストファイルがない場合でも、パッケージ自体のビルドエラーが確実に検出されるようになります。

コミット

commit c8f90979acf078d2cbb18a9bf5c6f8349fab296a
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Thu Apr 10 14:02:24 2014 +1000

    cmd/go: always build package during "go test" command
    
    even when there are no *_test.go files present.
    rsc suggested this change
    
    Fixes #7108
    
    LGTM=r, adg
    R=golang-codereviews, r, adg
    CC=golang-codereviews
    https://golang.org/cl/84300043
---
 src/cmd/go/test.bash                    | 8 ++++++++\n src/cmd/go/test.go                      | 2 +-\n src/cmd/go/testdata/src/notest/hello.go | 6 ++++++\n 3 files changed, 15 insertions(+), 1 deletion(-)

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

https://github.com/golang/go/commit/c8f90979acf078d2cbb18a9bf5c6f8349fab296a

元コミット内容

cmd/go: go test コマンド実行時に常にパッケージをビルドする。 _test.go ファイルが存在しない場合でもビルドを行う。 rsc がこの変更を提案した。

Fixes #7108

LGTM=r, adg R=golang-codereviews, r, adg CC=golang-codereviews https://golang.org/cl/84300043

変更の背景

この変更が行われる前は、go test コマンドは、対象のパッケージ内に _test.go という命名規則に従うテストファイルが存在しない場合、そのパッケージのビルドをスキップすることがありました。これは、テストを実行する必要がないと判断されたためです。

しかし、この挙動には問題がありました。もしパッケージ自体にコンパイルエラーやその他のビルドに関する問題があったとしても、テストファイルが存在しないためにビルドがスキップされてしまうと、go test コマンドはエラーを報告せずに成功と見なしてしまう可能性があったのです。これは、開発者がパッケージの健全性を誤解する原因となり、潜在的なバグや問題を早期に発見できないという課題を抱えていました。

このコミットは、この問題を解決するために導入されました。go test が常にパッケージのビルドを試みるようにすることで、テストファイルの有無にかかわらず、パッケージのビルドに関する問題を確実に検出できるようになります。コミットメッセージにある "Fixes #7108" は、この特定の挙動に関する問題報告に対応するものであることを示唆しています。ただし、このIssue番号は2014年当時のものであり、現在のGoのIssueトラッカーでは直接参照できない可能性があります。

前提知識の解説

go test コマンド

go test はGo言語の標準的なテスト実行ツールです。このコマンドは、指定されたパッケージ内のテスト関数(TestXxx という命名規則に従う関数)を実行します。しかし、単にテストを実行するだけでなく、テスト対象のパッケージとその依存関係をビルドする役割も担っています。テストの実行前に、Goのツールチェインはソースコードをコンパイルし、実行可能なテストバイナリを生成します。

Goパッケージのビルド

Go言語では、ソースコードは「パッケージ」という単位で管理されます。パッケージは、関連するGoソースファイルの集合であり、通常は単一のディレクトリに配置されます。Goのビルドプロセスは、これらのパッケージをコンパイルし、実行可能なバイナリやライブラリを生成します。go test コマンドも、テスト対象のパッケージが正しくビルドできることを前提としており、そのビルドプロセスの一部を自動的に行います。

_test.go ファイル

Go言語のテストは、通常、テスト対象のソースファイルと同じパッケージ内に _test.go というサフィックスを持つファイルとして記述されます。例えば、my_package.go のテストは my_package_test.go に記述されます。go test コマンドは、これらの _test.go ファイルを自動的に検出し、その中のテスト関数を実行します。

ビルドエラーの重要性

ソフトウェア開発において、ビルドエラーはコードの構文的な誤りや、依存関係の不整合など、プログラムが正しくコンパイルできない状態を示します。これらのエラーは、プログラムが実行される前に検出されるべき最も基本的な問題です。ビルドエラーが早期に検出されることで、開発者は問題を迅速に特定し、修正することができます。もしビルドエラーが検出されずに「成功」と報告されてしまうと、その後の実行時になって初めて問題が発覚し、デバッグが困難になる可能性があります。

技術的詳細

このコミットの核心は、go test コマンドがパッケージをビルドする際の内部ロジックの変更にあります。go test は、内部的に builder という構造体を使用して、ビルドとテストの各ステップをアクションとして管理しています。

変更前の src/cmd/go/test.go のコードでは、p.TestGoFilesp.XTestGoFiles (それぞれ通常のテストファイルと外部テストファイル) の長さが0、つまりテストファイルが全く存在しない場合に、build アクションを次のように定義していました。

if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
	build := &action{p: p}
	run := &action{p: p, deps: []*action{build}}
	print := &action{f: (*builder).notest, p: p, deps: []*action{run}}
	return build, run, print, nil
}

ここで注目すべきは、build := &action{p: p} の部分です。この action は、単にパッケージ p に関連付けられたアクションとして定義されていますが、明示的に「ビルドモード」を指定していません。Goのビルドシステムでは、様々なモード(例えば、modeBuild は通常のビルド、modeInstall はインストールなど)が存在し、それぞれ異なる挙動をします。テストファイルがない場合、この action はパッケージのビルドを強制するものではなく、単に「このパッケージに関連する何か」として扱われ、結果的にビルドがスキップされる可能性がありました。

変更後のコードでは、この部分が次のように修正されました。

if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
	build := b.action(modeBuild, modeBuild, p)
	run := &action{p: p, deps: []*action{build}}
	print := &action{f: (*builder).notest, p: p, deps: []*action{run}}
	return build, run, print, nil
}

新しいコード build := b.action(modeBuild, modeBuild, p) では、b.action メソッドが呼び出され、第一引数と第二引数に modeBuild が明示的に渡されています。b.action メソッドは、指定されたモードに基づいて適切なビルドアクションを生成します。modeBuild を明示的に指定することで、テストファイルが存在しない場合でも、go test は常にそのパッケージを通常のビルドモードでコンパイルしようとします。これにより、パッケージにコンパイルエラーがあれば、それが確実に報告されるようになります。

また、src/cmd/go/test.bash に追加されたテストケースは、この変更が正しく機能することを確認するためのものです。_test.go ファイルを持たないが、意図的にビルドエラーを含む(またはビルドが成功する)ダミーパッケージを作成し、go test がそのパッケージをビルドしようとすることを確認しています。

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

このコミットによる主要なコード変更は以下のファイルで行われています。

  1. src/cmd/go/test.go: go test コマンドの主要なロジックが含まれるファイル。

    --- a/src/cmd/go/test.go
    +++ b/src/cmd/go/test.go
    @@ -524,7 +524,7 @@ func contains(x []string, s string) bool {
     
     func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) {
     	if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
    -		build := &action{p: p}
    +		build := b.action(modeBuild, modeBuild, p)
     		run := &action{p: p, deps: []*action{build}}
     		print := &action{f: (*builder).notest, p: p, deps: []*action{run}}
     		return build, run, print, nil
    
  2. src/cmd/go/test.bash: go コマンドのテストスクリプト。新しいテストケースが追加されています。

    --- a/src/cmd/go/test.bash
    +++ b/src/cmd/go/test.bash
    @@ -700,6 +700,14 @@ if ! ./testgo list -f "GOARCH: {{context.GOARCH}}"; then
     	ok=false
     fi
     
    +TEST 'Issue 7108: cmd/go: "go test" should fail if package does not build'
    +export GOPATH=$(pwd)/testdata
    +if ./testgo test notest >/dev/null 2>&1; then
    +	echo 'go test notest succeeded, but should fail'
    +	ok=false
    +fi
    +unset GOPATH
    +
     # clean up
     if $started; then stop; fi
     rm -rf testdata/bin testdata/bin1
    
  3. src/cmd/go/testdata/src/notest/hello.go: 新しいテストケースで使用される、テストファイルを持たないダミーパッケージ。

    --- /dev/null
    +++ b/src/cmd/go/testdata/src/notest/hello.go
    @@ -0,0 +1,6 @@
    +package notest
    +
    +func hello() {
    +	println("hello world")
    +}
    +Hello world
    

コアとなるコードの解説

src/cmd/go/test.go の変更

このファイルにおける変更は、go test コマンドがテストファイルを持たないパッケージをどのように扱うかを根本的に変えるものです。

  • 変更前: build := &action{p: p} この行は、build というアクションを定義していますが、そのアクションが具体的に「ビルド」を行うことを明示的に指示していませんでした。action 構造体は、Goコマンドの内部で様々な操作(ビルド、実行、インストールなど)を表現するために使われます。テストファイルがない場合、このアクションは単にパッケージ p に関連付けられるだけで、ビルドフェーズがスキップされる可能性がありました。

  • 変更後: build := b.action(modeBuild, modeBuild, p) この行は、builder オブジェクト baction メソッドを呼び出しています。

    • modeBuild は、Goツールチェインがパッケージをビルドする際のモードを定義する定数です。
    • b.action(modeBuild, modeBuild, p) とすることで、go test は、テストファイルが存在しないパッケージ p であっても、常に modeBuild でビルドを実行するように強制されます。これにより、パッケージのコンパイルエラーが確実に検出されるようになります。

この変更により、go test は、テストの有無にかかわらず、対象パッケージのビルド健全性を常に検証するようになりました。

src/cmd/go/test.bash の変更

このシェルスクリプトは、go コマンド自体の動作をテストするために使用されます。追加されたテストケースは、このコミットの変更が意図通りに機能することを確認するためのものです。

TEST 'Issue 7108: cmd/go: "go test" should fail if package does not build'
export GOPATH=$(pwd)/testdata
if ./testgo test notest >/dev/null 2>&1; then
	echo 'go test notest succeeded, but should fail'
	ok=false
fi
unset GOPATH
  • TEST 'Issue 7108: ...':新しいテストケースのタイトルです。Issue 7108 に関連する問題が修正されたことを示しています。
  • export GOPATH=$(pwd)/testdata:テスト用の GOPATH を設定し、testdata ディレクトリ内のパッケージがGoツールチェインによって認識されるようにします。
  • ./testgo test notest >/dev/null 2>&1testgo (テスト用の go コマンドバイナリ) を使用して、notest パッケージに対して go test を実行します。標準出力と標準エラー出力を /dev/null にリダイレクトし、コマンドの成功/失敗のみをチェックします。
  • if ...; then echo 'go test notest succeeded, but should fail'; ok=false; fi:この条件文が重要です。もし go test notest が成功してしまった場合(つまり、ビルドエラーが検出されなかった場合)、それは期待される動作ではないため、エラーメッセージを出力し、テストの全体的な結果を false に設定します。このテストケースは、notest パッケージがビルドエラーを含むように設計されているため、go test が失敗することを期待しています。

src/cmd/go/testdata/src/notest/hello.go の追加

このファイルは、上記のテストケースで使用される新しいGoパッケージです。

package notest

func hello() {
	println("hello world")
}
Hello world

このファイルは _test.go サフィックスを持たないため、テストファイルは含まれていません。しかし、このコードには Hello world という行があり、これはGoの有効な構文ではありません。したがって、このパッケージはコンパイルエラーを引き起こします。

この notest パッケージに対して go test を実行すると、変更前はテストファイルがないためにビルドがスキップされ、go test が成功してしまう可能性がありました。しかし、このコミットの変更後は、go test がこのパッケージのビルドを強制するため、Hello world の構文エラーが検出され、go test は失敗するようになります。これにより、go test がパッケージのビルド健全性を常に検証するという新しい挙動が確認されます。

関連リンク

参考にした情報源リンク

  • golang/go GitHubリポジトリのコミット履歴
  • コミットメッセージに記載された情報
  • Go言語のソースコード(src/cmd/go/test.go, src/cmd/go/test.bash
  • Go言語の一般的なビルドシステムとテストの知識
  • (注: コミットメッセージに記載された Issue #7108 は、現在のGoのIssueトラッカーでは直接見つけることができませんでした。これは、Issueトラッカーのシステム変更や古いIssueのアーカイブによるものと考えられます。そのため、解説は主にコミットメッセージとコードの変更内容から推測して記述しています。)