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

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

このコミットは、Go言語のコマンドラインツールgoにおけるgo testコマンドのバグ修正に関するものです。具体的には、package mainとして定義されたバイナリ(実行可能コマンド)が既にインストールされている場合に、go testがそのパッケージのアーカイブ(.aファイル)のビルドをスキップしてしまう問題を解決します。この問題は、main_testという慣習的なテストパッケージ名を使用している場合に特に顕著でした。go testはテスト実行のためにパッケージアーカイブを必要とするため、このスキップはテストの失敗や不正確な動作を引き起こす可能性がありました。

コミット

commit 627d17cf2980c97b76badf7893cfc2c4b1289738
Author: Russ Cox <rsc@golang.org>
Date:   Tue Sep 10 14:43:35 2013 -0400

    cmd/go: fix go test using package main_test
    
    A package main binary (that is, a command) being installed
    does not mean we can skip the build of the package archive
    during a test.
    
    Fixes #3417.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/13462046

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

https://github.com/golang/go/commit/627d17cf2980c97b76badf7893cfc2c4b1289738

元コミット内容

このコミットは、go testコマンドがpackage main_testを使用する際の挙動を修正することを目的としています。package mainとしてビルドされるバイナリ(実行可能ファイル)がシステムにインストールされている場合でも、go testはそのパッケージのテストを実行するために、パッケージのアーカイブ(コンパイル済みコードのライブラリ形式)をビルドする必要があります。以前のバージョンでは、バイナリがインストール済みであるという理由で、このアーカイブのビルドが不適切にスキップされることがありました。この修正は、この誤った最適化を排除し、package mainのテストが常に正しく実行されるようにします。

変更の背景

Go言語のgo testコマンドは、指定されたパッケージ内のテスト関数を実行するために設計されています。通常、テスト対象のパッケージがライブラリパッケージ(package fooなど)である場合、go testはそのパッケージをコンパイルし、テストコードとリンクしてテストバイナリを生成します。

しかし、package mainとして定義されたパッケージ(つまり、実行可能なコマンドを生成するパッケージ)の場合、go installコマンドによって既にそのバイナリがGOBINなどのパスにインストールされていることがあります。この状況下でgo testを実行すると、Goツールチェインは「バイナリが既に存在し、インストールされているのだから、パッケージアーカイブのビルドは不要だろう」と誤って判断し、テストに必要なアーカイブのビルドをスキップしてしまうバグがありました。

特に、package mainのコードをテストする一般的な慣習として、main_testという名前のパッケージを別途作成し、その中でmainパッケージの関数をテストする手法があります。このmain_testパッケージは、内部的にはmainパッケージのコードに依存しています。go test main_testを実行した際に、mainパッケージのアーカイブが適切にビルドされないと、main_testパッケージがmainパッケージの関数を呼び出そうとしたときにリンクエラーが発生したり、古いバージョンのコードでテストが実行されたりする問題が生じていました。

このコミットは、このgo testの誤った挙動を修正し、package mainであってもテスト時には常に必要なパッケージアーカイブがビルドされるようにすることで、main_testのようなテストパターンが正しく機能するようにします。

前提知識の解説

  • Goパッケージとpackage main:

    • Goのコードはパッケージにまとめられます。各Goファイルはpackage <name>という宣言で始まります。
    • package mainは特別なパッケージ名で、このパッケージに含まれるmain関数はプログラムのエントリポイントとなります。package mainのビルド結果は実行可能なバイナリ(コマンド)になります。
    • それ以外のパッケージ(例: package foo)はライブラリパッケージと呼ばれ、他のプログラムからインポートされて利用されます。ビルド結果はアーカイブファイル(.aファイル)になります。
  • go testコマンド:

    • Goの標準テストツールを実行するコマンドです。
    • go test <package_path>のように使用し、指定されたパッケージ内の_test.goファイルに記述されたテスト関数(TestXxxBenchmarkXxxExampleXxx)を実行します。
    • テスト実行時には、テスト対象のパッケージとテストコードをコンパイルし、一つのテストバイナリを生成して実行します。
  • パッケージアーカイブ(.aファイル):

    • Goのコンパイラがパッケージをコンパイルした際に生成される中間ファイルです。
    • コンパイル済みのオブジェクトコードやメタデータが含まれており、他のパッケージや実行可能バイナリをビルドする際にリンクされます。
    • go installgo buildが実行されると、通常は$GOPATH/pkg$GOROOT/pkg以下に保存されます。
  • main_testパッケージの慣習:

    • package mainのコードをテストする際、テストコードをpackage mainと同じパッケージに含めると、テストバイナリがmain関数を複数持つことになり、ビルドエラーになる可能性があります。
    • これを避けるため、mainパッケージのテストコードをpackage main_testとして別のパッケージに定義し、import . "main"のようにしてmainパッケージの公開された関数をテストする慣習があります。この場合、main_testmainパッケージに依存します。
  • GOBIN環境変数:

    • go installコマンドが生成した実行可能バイナリを配置するディレクトリを指定する環境変数です。
    • GOBINが設定されていない場合、バイナリは$GOPATH/binまたは$GOROOT/binにインストールされます。

技術的詳細

go testコマンドの内部では、テスト対象のパッケージがpackage mainであるかどうかに応じて、ビルドの挙動が異なります。通常のライブラリパッケージの場合、go testはテスト対象パッケージのアーカイブをビルドし、そのアーカイブとテストコードをリンクしてテスト実行ファイルを生成します。

しかし、package mainの場合、Goツールチェインは、そのパッケージが最終的に実行可能なバイナリを生成することを知っています。以前のバージョンでは、go installによってpackage mainのバイナリが既にインストールされていると、go testは「このパッケージは既にビルドされ、インストールされているのだから、テストのために再度アーカイブをビルドする必要はないだろう」と判断していました。これは、go testがテスト実行のために必要とするパッケージアーカイブ(.aファイル)のビルドをスキップしてしまう原因となっていました。

この問題は、src/cmd/go/test.go内のbuilder.test関数における条件分岐の不備に起因していました。この関数は、テストを実行するパッケージのビルドが必要かどうかを判断するロジックを含んでいます。修正前は、テスト対象のGoファイルが存在するか、またはローカルカバレッジが有効な場合にのみビルドを進める条件となっていました。しかし、package mainの場合、たとえTestGoFilesがなくても、テストバイナリを生成するためにそのパッケージのアーカイブが必要となるケースがありました。

このコミットでは、この条件にp.Name == "main"という条件を追加することで、テスト対象のパッケージがpackage mainである場合にも、常にパッケージアーカイブのビルドプロセスが実行されるように変更されました。これにより、go testpackage mainのテストを実行する際に、必要な依存関係(パッケージアーカイブ)を適切にビルドし、テストバイナリのリンクエラーや古いコードでのテスト実行といった問題を回避できるようになりました。

また、src/cmd/go/test.bashに新しいテストケースが追加され、この修正が正しく機能することを確認しています。このテストケースは、package mainpackage main_testの組み合わせでgo testgo installを実行し、go testgo install後も正しく動作すること、そしてパッケージが不適切にstale(古くなっている)と判断されないことを検証しています。

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

このコミットにおける主要なコード変更は以下の2点です。

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

    --- a/src/cmd/go/test.go
    +++ b/src/cmd/go/test.go
    @@ -590,7 +590,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
     	localCover := testCover && testCoverPaths == nil
     
     	// Test package.
    -	if len(p.TestGoFiles) > 0 || localCover {
    +	if len(p.TestGoFiles) > 0 || localCover || p.Name == "main" {
     		ptest = new(Package)
     		*ptest = *p
     		ptest.GoFiles = nil
    
  2. 新しいテストデータの追加:

    • src/cmd/go/testdata/src/main_test/m.go (新規ファイル)
      package main
      
      func F()    {}
      func main() {}
      
    • src/cmd/go/testdata/src/main_test/m_test.go (新規ファイル)
      package main_test
      
      import (
      	. "main_test"
      	"testing"
      )
      
      func Test1(t *testing.T) {
      	F()
      }
      
  3. src/cmd/go/test.bashへのテストケース追加: test.bashスクリプトに、package main_testのシナリオを検証する新しいテストブロックが追加されました。

コアとなるコードの解説

  • src/cmd/go/test.goの変更: builder.test関数は、go testコマンドがパッケージのテストを実行する際のビルドロジックを制御します。変更されたif文は、テスト対象のパッケージをビルドする必要があるかどうかを判断する条件です。

    • len(p.TestGoFiles) > 0: パッケージにテスト用のGoファイル(_test.go)が存在する場合。これは当然ビルドが必要です。
    • localCover: ローカルカバレッジが有効な場合。カバレッジ情報を収集するためには、パッケージのビルドが必要です。
    • p.Name == "main" (追加された条件): テスト対象のパッケージ名が"main"である場合。この条件が追加されたことで、たとえ明示的なテストファイルがなくても、package mainのテスト実行に必要なパッケージアーカイブのビルドが強制されるようになりました。これにより、main_testのような外部テストパッケージがmainパッケージに依存している場合に、mainパッケージのアーカイブが常に最新の状態で利用可能となり、リンクエラーや古いコードでのテスト実行が防止されます。
  • 新しいテストデータファイル (m.go, m_test.go): これらのファイルは、package mainとそのテストパッケージpackage main_testの典型的な構造を示しています。

    • m.go: package mainとして定義されたシンプルなファイルで、F()という関数とmain()関数を含んでいます。これは、テスト対象となるmainパッケージのコードをシミュレートします。
    • m_test.go: package main_testとして定義されたテストファイルで、m.goで定義されたF()関数をテストしています。import . "main_test"は、main_testパッケージがmainパッケージの公開された要素にアクセスできるようにするためのものです。この構造は、go testpackage mainのテストをどのように処理すべきかを示す重要なテストケースとなります。
  • src/cmd/go/test.bashへのテストケース追加: このシェルスクリプトは、goコマンドの様々な挙動を検証するための統合テストです。追加されたテストブロックは、以下のシナリオを検証します。

    1. GOBINを設定し、main_testパッケージを含むテストデータを準備します。
    2. ./testgo test main_testを最初に実行し、インストールされていない状態でのテストが成功することを確認します。
    3. ./testgo install main_testを実行し、main_testパッケージ(およびそれに依存するmainパッケージ)がインストールされることを確認します。
    4. ./testgo list -f '{{.Stale}}' main_testを実行し、インストール後にmain_teststale(古くなっている)と誤って判断されないことを確認します。
    5. 再度./testgo test main_testを実行し、インストール後もテストが正しく実行されることを確認します。 この一連のテストは、package mainのバイナリがインストールされた後でも、go testがそのパッケージのアーカイブを適切にビルドし、テストを成功させるという修正の意図を直接的に検証しています。

関連リンク

  • Go issue tracker (Go言語の公式Issue Tracker): このコミットメッセージに記載されているFixes #3417は、Go言語の内部Issue Trackerにおける問題番号を指していると考えられます。ただし、公開されているGitHubのIssue Trackerでは、この番号は別のSQL Serverのエラーに関連付けられているため、直接的な公開リンクは特定できませんでした。これは、Goプロジェクトが内部で別のIssue管理システムを使用しているか、または過去のIssue番号が再利用されている可能性を示唆しています。

参考にした情報源リンク

  • コミットメッセージと変更されたコード (src/cmd/go/test.go, src/cmd/go/test.bash, src/cmd/go/testdata/src/main_test/m.go, src/cmd/go/testdata/src/main_test/m_test.go)
  • Go言語の公式ドキュメント (go help test, go help build, go help installなど) - 一般的なGoコマンドの挙動に関する知識
  • Go言語のパッケージングとテストに関する一般的な慣習(package main_testなど)に関する知識
  • Go言語のビルドシステムに関する一般的な知識