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

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

このコミットは、Go言語のテストフレームワークにおいて、複数のGoファイルを特定の順序でコンパイルする必要があるテストケースをより効率的に扱うための新しいパターン compiledir を導入するものです。具体的には、xxx.dir/*.go の形式で配置されたGoファイルを辞書順にコンパイルすることで、依存関係のトポロジカル順序と一致させることを目的としています。これにより、複雑なマルチファイルテストの記述と実行が簡素化されます。

コミット

commit adc933726206e7f2be6a2ded826b6a3596df5ffd
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Mon Jul 30 21:12:05 2012 +0200

    test: add a compiledir pattern in run.go
    
    The compiledir pattern compiles all files xxx.dir/*.go
    in lexicographic order (which is assumed to coincide with
    the topological order of dependencies).
    
    R=rsc
    CC=golang-dev, remy
    https://golang.org/cl/6440048

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

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

元コミット内容

test: add a compiledir pattern in run.go

The compiledir pattern compiles all files xxx.dir/*.go
in lexicographic order (which is assumed to coincide with
the topological order of dependencies).

R=rsc
CC=golang-dev, remy
https://golang.org/cl/6440048

変更の背景

Go言語のテストスイートには、複数のGoソースファイルが相互に依存し、特定の順序でコンパイルされる必要があるテストケースが存在します。これまでのGoのテスト実行スクリプト(test/run.go)では、このようなケースを扱うために、各ファイルを個別にコンパイルするコマンドをテストファイルの先頭に記述する必要がありました。例えば、// $G $D/$F.dir/bug0.go && $G $D/$F.dir/bug1.go のような形式です。

この方法は、テスト対象のファイルが増えるにつれて記述が冗長になり、管理が煩雑になるという問題がありました。また、ファイルの依存関係が複雑な場合、手動でコンパイル順序を管理するのはエラーの原因にもなりかねません。

このコミットは、このような問題を解決するために、compiledir という新しいテストパターンを導入します。これにより、特定のディレクトリ内のすべてのGoファイルを自動的に辞書順でコンパイルする機能が提供され、テストの記述が簡素化され、保守性が向上します。特に、辞書順が依存関係のトポロジカル順序と一致するという仮定のもと、複数のファイルにまたがるテストの実行がより効率的になります。

前提知識の解説

Go言語のテストフレームワーク

Go言語には、標準ライブラリとして testing パッケージが提供されており、これを用いてユニットテスト、ベンチマークテスト、サンプルテストなどを記述できます。テストファイルは通常、テスト対象のソースファイルと同じディレクトリに _test.go というサフィックスを付けて配置されます。テストの実行は go test コマンドによって行われます。

Goプロジェクトの内部では、より低レベルなテスト実行の仕組みが存在します。特に、test/run.go はGo言語のコンパイラやランタイム自体のテストを実行するための内部ツールであり、様々なテストパターン(compile, run, errorcheck など)を解釈し、テストケースに応じた処理を実行します。

test/run.gotest/testlib

  • test/run.go: Go言語のテストスイートの主要な実行エンジンです。各テストファイルの先頭に記述された特殊なコメント(例: // $G $D/$F.dir/bug0.go)を解析し、それに基づいてコンパイルや実行などのアクションを決定します。このファイルは、Goのコンパイラや標準ライブラリの動作を検証するための多様なテストパターンをサポートしています。
  • test/testlib: シェルスクリプトで書かれたヘルパー関数群です。test/run.go がGoのテストを実行する際に、コンパイルや実行などの具体的な操作を行うために呼び出されます。例えば、$G はGoコンパイラ(go tool compile)を指し、$L はGoリンカ(go tool link)を指すなど、テスト環境固有のコマンドを抽象化しています。

辞書順(Lexicographical Order)

辞書順とは、文字列やファイル名を辞書に載っている順番で並べる方法です。例えば、a.go, b.go, c.go はこの順序で並びます。また、file1.go, file10.go, file2.go の場合、数値として見ると file1.go, file2.go, file10.go となりますが、辞書順では file1.go, file10.go, file2.go となります。

このコミットでは、xxx.dir/*.go の形式で配置されたファイルを辞書順にコンパイルすることで、依存関係のトポロジカル順序(AがBに依存する場合、Aより先にBがコンパイルされる必要がある、といった順序)と一致すると仮定しています。これは、テストケースの設計において、ファイル名にコンパイル順序が反映されるように命名規則を設けることで実現されます。

go tool gc

go tool gc は、Go言語のコンパイラ(gc は "Go compiler" の略)を直接呼び出すためのコマンドです。通常、Goプログラムのコンパイルは go buildgo install コマンドを通じて行われますが、go tool gc を使用することで、より低レベルなコンパイルオプションを細かく制御できます。このコミットでは、compiledir パターン内で個々のGoファイルをコンパイルするために go tool gc が使用されています。

技術的詳細

このコミットの主要な変更点は、test/run.gotest/testlibcompiledir という新しいテストパターンを導入したことです。

test/run.go の変更

  1. goDirName() 関数の追加: goDirName() は、テスト対象のGoファイル名(例: bug088.go)から、対応するディレクトリ名(例: bug088.dir)を生成するヘルパー関数です。これは、filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) というロジックで実現されています。
  2. run() 関数における compiledir アクションの追加: run() 関数の switch ステートメントに compiledir ケースが追加されました。このケースは、以下の処理を実行します。
    • テスト対象のディレクトリ(例: test/fixedbugs/bug088.dir)の絶対パスを構築します。
    • ioutil.ReadDir() を使用して、そのディレクトリ内のすべてのファイルとディレクトリの情報を取得します。
    • 取得したファイルリストをループ処理します。ファイルは ioutil.ReadDir() によって辞書順に返されるため、この順序がコンパイル順序として利用されます。
    • .go ファイルに対して、go tool gc コマンドを実行してコンパイルします。コンパイルされたオブジェクトファイルは、元のファイル名(例: bug0.go)から拡張子を .o に変更した名前(例: bug0.o)で出力されます。
    • コンパイル中にエラーが発生した場合、そのエラーを t.err に設定し、処理を中断します。

test/testlib の変更

  1. compiledir() シェル関数の追加: test/testlibcompiledir() という新しいシェル関数が追加されました。この関数は、$D/$F.dir/*.go のパターンにマッチするすべてのGoファイルをループ処理し、それぞれのファイルに対して $G コマンド(Goコンパイラ)を実行します。これは、test/run.gocompiledir ロジックと連携して、テスト環境で実際にコンパイルを実行するためのものです。

test/fixedbugs/*.go ファイルの変更

既存の複数の test/fixedbugs ディレクトリ内のテストファイル(例: bug088.go, bug106.go など)の先頭に記述されていた、複数のGoファイルを個別にコンパイルする冗長なコマンド(例: // $G $D/$F.dir/bug0.go && $G $D/$F.dir/bug1.go)が、新しい // compiledir ディレクティブに置き換えられました。これにより、テストファイルの記述が大幅に簡素化されています。

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

test/run.go

--- a/test/run.go
+++ b/test/run.go
@@ -216,6 +216,10 @@ func (t *test) goFileName() string {
 	return filepath.Join(t.dir, t.gofile)
 }
 
+func (t *test) goDirName() string {
+	return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1))
+}
+
 // run runs a test.
 func (t *test) run() {
 	defer close(t.donec)
@@ -251,7 +255,7 @@ func (t *test) run() {
 	case "cmpout":
 		action = "run" // the run case already looks for <dir>/<test>.out files
 		fallthrough
-	case "compile", "build", "run", "errorcheck", "runoutput":
+	case "compile", "compiledir", "build", "run", "errorcheck", "runoutput":
 		t.action = action
 	case "skip":
 		t.action = "skip"
@@ -301,6 +305,23 @@ func (t *test) run() {
 			t.err = fmt.Errorf("%s\\n%s", err, out)
 		}
 
+	case "compiledir":
+		// Compile all files in the directory in lexicographic order.
+		longdir := filepath.Join(cwd, t.goDirName())
+		files, dirErr := ioutil.ReadDir(longdir)
+		if dirErr != nil {
+			t.err = dirErr
+			return
+		}
+		for _, gofile := range files {
+			afile := strings.Replace(gofile.Name(), ".go", "."+letter, -1)
+			out, err := runcmd("go", "tool", gc, "-e", "-o", afile, filepath.Join(longdir, gofile.Name()))
+			if err != nil {
+				t.err = fmt.Errorf("%s\\n%s", err, out)
+				break
+			}
+		}
+
 	case "build":
 		out, err := runcmd("go", "build", "-o", "a.exe", long)
 		if err != nil {

test/testlib

--- a/test/testlib
+++ b/test/testlib
@@ -9,6 +9,13 @@ compile() {
 	$G $D/$F.go
 }
 
+compiledir() {
+	for gofile in $D/$F.dir/*.go
+	do
+		$G ${gofile} || return 1
+	done
+}
+
 build() {
 	$G $D/$F.go && $L $F.$A
 }

test/fixedbugs/*.go (例: test/fixedbugs/bug088.go)

--- a/test/fixedbugs/bug088.go
+++ b/test/fixedbugs/bug088.go
@@ -1,4 +1,4 @@
-// $G $D/$F.dir/bug0.go && $G $D/$F.dir/bug1.go || echo BUG: fails incorrectly
+// compiledir
 
 // Copyright 2009 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style

コアとなるコードの解説

test/run.gocompiledir ケース

この部分が、新しい compiledir パターンのGo言語側での主要な実装です。

	case "compiledir":
		// Compile all files in the directory in lexicographic order.
		longdir := filepath.Join(cwd, t.goDirName()) // テスト対象のディレクトリの絶対パスを構築
		files, dirErr := ioutil.ReadDir(longdir)     // ディレクトリ内のファイルリストを取得
		if dirErr != nil {
			t.err = dirErr
			return
		}
		for _, gofile := range files { // 各ファイルをループ
			afile := strings.Replace(gofile.Name(), ".go", "."+letter, -1) // 出力ファイル名を生成 (例: bug0.o)
			// go tool gc コマンドで個々のGoファイルをコンパイル
			out, err := runcmd("go", "tool", gc, "-e", "-o", afile, filepath.Join(longdir, gofile.Name()))
			if err != nil {
				t.err = fmt.Errorf("%s\\n%s", err, out) // エラー処理
				break
			}
		}

このコードは、test/fixedbugs/bugXXX.go のようなテストファイルが // compiledir ディレクティブを持つ場合に実行されます。

  1. t.goDirName() を使って、テスト対象のGoファイル(例: bug088.go)に対応するディレクトリ(例: bug088.dir)のパスを生成します。
  2. ioutil.ReadDir() でそのディレクトリ内のすべてのファイル(例: bug0.go, bug1.go など)を読み込みます。ioutil.ReadDir() はファイル名を辞書順で返すため、これがコンパイル順序となります。
  3. 各ファイルに対して go tool gc コマンドを実行し、コンパイルを行います。-e オプションはエラーが発生しても処理を続行する(ただし、このコードではエラー時に break している)、-o は出力ファイル名を指定します。
  4. コンパイル中にエラーが発生した場合は、そのエラーを記録し、ループを中断します。

test/testlibcompiledir() 関数

compiledir() {
	for gofile in $D/$F.dir/*.go
	do
		$G ${gofile} || return 1
	done
}

このシェル関数は、test/run.gocompiledir パターンを検出した際に、テスト環境で実際にコンパイルを実行するために呼び出されます。

  • $D/$F.dir/*.go は、現在のテストディレクトリ($D)とテストファイル名($F、例: bug088)から、対応するサブディレクトリ(例: bug088.dir)内のすべてのGoファイルを指定します。
  • for gofile in ... ループで、これらのファイルを一つずつ処理します。
  • $G ${gofile} は、Goコンパイラ(go tool compile)を呼び出して、現在の gofile をコンパイルします。
  • || return 1 は、コンパイルが失敗した場合に、このシェル関数の実行を中断し、エラーコード 1 を返します。

これらの変更により、Goのテストフレームワークは、複数のGoファイルから構成されるテストケースを、より簡潔かつ効率的に記述・実行できるようになりました。特に、ファイル名を適切に命名することで、依存関係の解決を自動化できる点が大きなメリットです。

関連リンク

参考にした情報源リンク