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

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

このコミットは、Go言語の標準ライブラリの一部である go/types パッケージ内の gcimporter_test.go ファイルに対する変更です。具体的には、gcimporter のインポートテストのロギングと堅牢性を向上させることを目的としています。

コミット

go/types: gcインポートテストにロギングを追加

* デッドラインを30秒に延長
* 各パッケージインポートの所要時間のロギングを追加
* ディレクトリが読み取れない場合はテストを即座に失敗させる

R=gri, minux.ma
CC=golang-dev
https://golang.org/cl/7030055

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

https://github.com/golang/go/commit/14b654369dcf62505ad9bf8650cbd19d95e1f026

元コミット内容

commit 14b654369dcf62505ad9bf8650cbd19d95e1f026
Author: Dave Cheney <dave@cheney.net>
Date:   Thu Jan 3 16:30:25 2013 +1100

    go/types: add more logging to gc import test
    
    * Extended deadline to 30 seconds
    * Added logging of the duration of each package import
    * Fail the test immediately if directories cannot be read
    
    R=gri, minux.ma
    CC=golang-dev
    https://golang.org/cl/7030055
---\n src/pkg/go/types/gcimporter_test.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/pkg/go/types/gcimporter_test.go b/src/pkg/go/types/gcimporter_test.go
index d1cf605fe9..5f3236e0f3 100644
--- a/src/pkg/go/types/gcimporter_test.go
+++ b/src/pkg/go/types/gcimporter_test.go
@@ -54,21 +54,23 @@ func compile(t *testing.T, dirname, filename string) string {
 var imports = make(map[string]*ast.Object)
 
 func testPath(t *testing.T, path string) bool {
+\tt0 := time.Now()\n \t_, err := GcImport(imports, path)\n \tif err != nil {\n \t\tt.Errorf(\"testPath(%s): %s\", path, err)\n \t\treturn false\n \t}\n+\tt.Logf(\"testPath(%s): %v\", path, time.Since(t0))\n \treturn true
 }\n 
-const maxTime = 3 * time.Second\n+const maxTime = 30 * time.Second\n \n func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {\n \tdirname := filepath.Join(runtime.GOROOT(), \"pkg\", runtime.GOOS+\"_\"+runtime.GOARCH, dir)\n \tlist, err := ioutil.ReadDir(dirname)\n \tif err != nil {\n-\t\tt.Errorf(\"testDir(%s): %s\", dirname, err)\n+\t\tt.Fatalf(\"testDir(%s): %s\", dirname, err)\n \t}\n \tfor _, f := range list {\n \t\tif time.Now().After(endTime) {\

変更の背景

このコミットは、Go言語の go/types パッケージにおける gcimporter のテストの信頼性とデバッグ可能性を向上させるために行われました。

gcimporter は、Goコンパイラ(gc)によって生成されたバイナリ形式のパッケージ情報をインポートする役割を担っています。このインポート処理は、コンパイラやその他のGoツールがGoのソースコードを解析し、型情報を利用するために不可欠です。

元のテストでは、以下の問題があった可能性があります。

  1. タイムアウトの発生: maxTime が3秒と短すぎたため、特に大規模なパッケージのインポートや、I/Oが遅い環境下でテストがタイムアウトし、誤って失敗する可能性がありました。
  2. デバッグ情報の不足: 各パッケージのインポートにかかる時間がログに出力されていなかったため、パフォーマンスの問題やボトルネックを特定することが困難でした。
  3. ディレクトリ読み取りエラーの不適切な処理: ioutil.ReadDir が失敗した場合に t.Errorf を使用していたため、テストはエラーを報告しつつも続行されていました。これにより、後続のテストが不正な状態で行われ、さらなる誤った失敗を引き起こす可能性がありました。ディレクトリの読み取りはテストの前提条件であり、これが失敗した場合はテスト全体を即座に中止すべきです。

これらの問題を解決し、テストの安定性と診断能力を高めるために、今回の変更が導入されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。

  • go/types パッケージ: Go言語の型システムをプログラム的に扱うためのパッケージです。Goのコンパイラやリンター、IDEなどのツールが、Goのソースコードを解析し、変数、関数、型などの型情報を取得・操作するために利用します。このパッケージは、Goプログラムのセマンティックな解析(意味解析)の中核を担います。

  • gcimporter: go/types パッケージの一部であり、Goコンパイラ(gc)が生成するバイナリ形式のパッケージファイル(通常は .a または .o 拡張子を持つアーカイブファイル内に埋め込まれている)を読み込み、その中に含まれる型情報をインポートする役割を担います。これにより、Goのツールはコンパイル済みのパッケージの型情報を利用して、別のGoソースファイルを型チェックしたり、補完機能を提供したりすることができます。

  • testing パッケージ: Go言語に組み込まれているテストフレームワークです。テスト関数は func TestXxx(*testing.T) の形式で定義され、*testing.T 型の引数を通じてテストの実行を制御し、結果を報告します。

    • t.Errorf(format string, args ...interface{}): テスト中にエラーが発生したことを報告しますが、テストの実行は続行されます。
    • t.Fatalf(format string, args ...interface{}): テスト中に致命的なエラーが発生したことを報告し、現在のテスト関数を即座に終了させます。
    • t.Logf(format string, args ...interface{}): テストの実行中に情報をログに出力します。デフォルトでは、テストが失敗した場合にのみ出力されますが、go test -v オプションを使用すると常に表示されます。
  • time パッケージ: Go言語で時間と期間を扱うためのパッケージです。

    • time.Second: 1秒を表す time.Duration 型の定数です。
    • time.Now(): 現在のローカル時刻を返します。
    • time.Since(t time.Time): t から現在までの経過時間を time.Duration として返します。
  • io/ioutil パッケージ: I/O操作に関するユーティリティ関数を提供するパッケージです。

    • ioutil.ReadDir(dirname string): 指定されたディレクトリ内のファイルとサブディレクトリの情報を []os.FileInfo のスライスとして返します。
  • filepath パッケージ: ファイルパスを操作するためのユーティリティ関数を提供するパッケージです。

    • filepath.Join(elem ...string): 複数のパス要素を結合して、プラットフォーム固有のパス区切り文字で区切られた単一のパスを生成します。
  • runtime パッケージ: Goランタイムとの相互作用を提供するパッケージです。

    • runtime.GOROOT(): Goのインストールルートディレクトリのパスを返します。
    • runtime.GOOS: 実行中のオペレーティングシステム(例: "linux", "darwin", "windows")を表す文字列定数です。
    • runtime.GOARCH: 実行中のアーキテクチャ(例: "amd64", "arm")を表す文字列定数です。

技術的詳細

このコミットは、src/pkg/go/types/gcimporter_test.go ファイル内のテストロジックを改善しています。

  1. maxTime の延長: const maxTime = 3 * time.Secondconst maxTime = 30 * time.Second に変更されました。これは、テストがタイムアウトするまでの許容時間を3秒から30秒に延長することを意味します。gcimporter は、Goの標準ライブラリや大規模なプロジェクトのパッケージをインポートする際に、多くのファイルI/Oや解析処理を伴う可能性があります。特に、低速なディスクI/Oや高負荷なシステム環境では、3秒という短い時間では処理が完了しないことがあり、テストが不安定になる原因となっていました。この延長により、テストの安定性が向上し、より現実的な環境でのインポート処理をカバーできるようになります。

  2. パッケージインポート時間のロギング: testPath 関数内で、GcImport の呼び出し前後に time.Now()time.Since() を使用して、各パッケージのインポートにかかる時間を計測し、t.Logf でログに出力するように変更されました。

    • t0 := time.Now(): GcImport 呼び出しの直前に現在の時刻を記録します。
    • t.Logf("testPath(%s): %v", path, time.Since(t0)): GcImport が完了した後、t0 から現在までの経過時間をログに出力します。 この変更により、どのパッケージのインポートに時間がかかっているのかを具体的に把握できるようになり、パフォーマンスのボトルネックを特定したり、テストが遅い原因を診断したりする際に非常に役立ちます。go test -v オプションを付けてテストを実行することで、これらのログを確認できます。
  3. ディレクトリ読み取りエラー時の即時失敗: testDir 関数内で、ioutil.ReadDir(dirname) がエラーを返した場合の処理が t.Errorf から t.Fatalf に変更されました。

    • 変更前: t.Errorf("testDir(%s): %s", dirname, err)
    • 変更後: t.Fatalf("testDir(%s): %s", dirname, err) ioutil.ReadDir は、指定されたディレクトリの内容を読み取る関数です。この操作が失敗するということは、テストが依存するファイルシステムの状態が期待通りではないことを意味します。例えば、テスト対象のディレクトリが存在しない、アクセス権がない、または破損しているなどの状況が考えられます。このような基本的な前提条件が満たされない場合、後続のテストを実行しても意味がなく、さらなるエラーを引き起こす可能性が高いため、t.Fatalf を使用してテストを即座に終了させるのが適切です。これにより、テストの診断がより明確になり、根本的な問題の特定が容易になります。

これらの変更は、Goのツールチェインの品質と信頼性を維持するために、テストの堅牢性とデバッグ能力を向上させるという点で重要です。

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

--- a/src/pkg/go/types/gcimporter_test.go
+++ b/src/pkg/go/types/gcimporter_test.go
@@ -54,21 +54,23 @@ func compile(t *testing.T, dirname, filename string) string {
 var imports = make(map[string]*ast.Object)
 
 func testPath(t *testing.T, path string) bool {
+\tt0 := time.Now()\n \t_, err := GcImport(imports, path)\n \tif err != nil {\n \t\tt.Errorf(\"testPath(%s): %s\", path, err)\n \t\treturn false\n \t}\n+\tt.Logf(\"testPath(%s): %v\", path, time.Since(t0))\n \treturn true
 }\n 
-const maxTime = 3 * time.Second\n+const maxTime = 30 * time.Second\n \n func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {\n \tdirname := filepath.Join(runtime.GOROOT(), \"pkg\", runtime.GOOS+\"_\"+runtime.GOARCH, dir)\n \tlist, err := ioutil.ReadDir(dirname)\n \tif err != nil {\n-\t\tt.Errorf(\"testDir(%s): %s\", dirname, err)\n+\t\tt.Fatalf(\"testDir(%s): %s\", dirname, err)\n \t}\n \tfor _, f := range list {\n \t\tif time.Now().After(endTime) {\

コアとなるコードの解説

  1. maxTime 定数の変更:

    -const maxTime = 3 * time.Second
    +const maxTime = 30 * time.Second
    

    maxTime 定数の値が 3 * time.Second (3秒) から 30 * time.Second (30秒) に変更されました。これは、テストが許容する最大実行時間を10倍に延長し、インポート処理が完了するまでの猶予を増やすことで、テストのタイムアウトによる誤った失敗を減らすことを目的としています。

  2. testPath 関数におけるインポート時間のロギング追加:

    func testPath(t *testing.T, path string) bool {
    +	t0 := time.Now()
     	_, err := GcImport(imports, path)
     	if err != nil {
     		t.Errorf("testPath(%s): %s", path, err)
     		return false
     	}
    +	t.Logf("testPath(%s): %v", path, time.Since(t0))
     	return true
     }
    

    GcImport 関数が呼び出される直前に time.Now() を使って開始時刻 t0 を記録し、GcImport の実行後に time.Since(t0) を使って経過時間を計算しています。この経過時間は t.Logf を使ってテストログに出力されます。これにより、個々のパッケージインポートにかかる時間を詳細に把握できるようになり、パフォーマンス分析やデバッグが容易になります。

  3. testDir 関数におけるエラーハンドリングの変更:

    func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
     	dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
     	list, err := ioutil.ReadDir(dirname)
     	if err != nil {
    -		t.Errorf("testDir(%s): %s", dirname, err)
    +		t.Fatalf("testDir(%s): %s", dirname, err)
     	}
     	for _, f := range list {
     		if time.Now().After(endTime) {
    

    ioutil.ReadDir がエラーを返した場合の処理が t.Errorf から t.Fatalf に変更されました。t.Errorf はエラーを報告しつつテストを続行しますが、t.Fatalf はエラーを報告した上で現在のテスト関数を即座に終了させます。ディレクトリの読み取り失敗はテストの前提条件が満たされていないことを意味するため、テストを続行しても意味がないと判断され、即座に終了させることで、より明確なエラー報告と効率的なデバッグを可能にしています。

関連リンク

参考にした情報源リンク