[インデックス 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.go
と test/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 build
や go install
コマンドを通じて行われますが、go tool gc
を使用することで、より低レベルなコンパイルオプションを細かく制御できます。このコミットでは、compiledir
パターン内で個々のGoファイルをコンパイルするために go tool gc
が使用されています。
技術的詳細
このコミットの主要な変更点は、test/run.go
と test/testlib
に compiledir
という新しいテストパターンを導入したことです。
test/run.go
の変更
goDirName()
関数の追加:goDirName()
は、テスト対象のGoファイル名(例:bug088.go
)から、対応するディレクトリ名(例:bug088.dir
)を生成するヘルパー関数です。これは、filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1))
というロジックで実現されています。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
の変更
compiledir()
シェル関数の追加:test/testlib
にcompiledir()
という新しいシェル関数が追加されました。この関数は、$D/$F.dir/*.go
のパターンにマッチするすべてのGoファイルをループ処理し、それぞれのファイルに対して$G
コマンド(Goコンパイラ)を実行します。これは、test/run.go
のcompiledir
ロジックと連携して、テスト環境で実際にコンパイルを実行するためのものです。
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.go
の compiledir
ケース
この部分が、新しい 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
ディレクティブを持つ場合に実行されます。
t.goDirName()
を使って、テスト対象のGoファイル(例:bug088.go
)に対応するディレクトリ(例:bug088.dir
)のパスを生成します。ioutil.ReadDir()
でそのディレクトリ内のすべてのファイル(例:bug0.go
,bug1.go
など)を読み込みます。ioutil.ReadDir()
はファイル名を辞書順で返すため、これがコンパイル順序となります。- 各ファイルに対して
go tool gc
コマンドを実行し、コンパイルを行います。-e
オプションはエラーが発生しても処理を続行する(ただし、このコードではエラー時にbreak
している)、-o
は出力ファイル名を指定します。 - コンパイル中にエラーが発生した場合は、そのエラーを記録し、ループを中断します。
test/testlib
の compiledir()
関数
compiledir() {
for gofile in $D/$F.dir/*.go
do
$G ${gofile} || return 1
done
}
このシェル関数は、test/run.go
が compiledir
パターンを検出した際に、テスト環境で実際にコンパイルを実行するために呼び出されます。
$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ファイルから構成されるテストケースを、より簡潔かつ効率的に記述・実行できるようになりました。特に、ファイル名を適切に命名することで、依存関係の解決を自動化できる点が大きなメリットです。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語のテストに関するドキュメント: https://go.dev/doc/code#testing
- Goのソースコードリポジトリ: https://github.com/golang/go
- このコミットのGo CL (Code Review): https://golang.org/cl/6440048
参考にした情報源リンク
- Go言語の
testing
パッケージ: https://pkg.go.dev/testing - Go言語の
filepath
パッケージ: https://pkg.go.dev/path/filepath - Go言語の
io/ioutil
パッケージ (Go 1.16以降はos
およびio
パッケージに移行): https://pkg.go.dev/io/ioutil - Go言語の
strings
パッケージ: https://pkg.go.dev/strings - Go言語の
fmt
パッケージ: https://pkg.go.dev/fmt - Goの内部ツール
go tool
: https://go.dev/cmd/go/#hdr-Go_tool_commands - Goコンパイラ
gc
の詳細 (Goのソースコード内): https://github.com/golang/go/tree/master/src/cmd/compile/internal/gc - Lexicographical order - Wikipedia: https://en.wikipedia.org/wiki/Lexicographical_order
- Topological sorting - Wikipedia: https://en.wikipedia.org/wiki/Topological_sorting