[インデックス 14033] ファイルの概要
このコミットは、Go言語のテストインフラストラクチャに新たなコマンドを追加し、既存のテスト実行ロジックをリファクタリングするものです。具体的には、test/run.go
と test/testlib
の2つのファイルが変更されています。
test/run.go
: Go言語で書かれたテスト実行のコアロジックを担うファイルです。このコミットでは、新しいディレクトリベースのテストコマンドのサポートが追加され、コードの可読性と保守性が向上するようにリファクタリングされています。test/testlib
: シェルスクリプトで書かれたテストヘルパーライブラリです。新しいテストコマンドに対応するためのシェル関数が追加されています。
コミット
commit ebb0e5db758791966a1afb193ddb021d4250d5d6
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Sat Oct 6 09:23:31 2012 +0200
test: Add rundir, rundircmpout and errorcheckdir commands to testlib and run.go
rundir will compile each file in the directory in lexicographic order, link the last file as the main package and run the resulting program. rundircmpout is an related command, that will compare the output of the program to an corresponding .out file
errorcheckdir will compile each file in a directory in lexicographic order, running errorcheck on each file as it compiles. All compilations are assumed to be successful except for the last file. However, If a -0 flag is present on the command, the last compilation will also be assumed successful
This CL also includes a small refactoring of run.go. It was getting unwieldy and the meaning of the run commands was hidden behind argument line formatting.
Fixes #4058.
R=rsc, minux.ma, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/6554071
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ebb0e5db758791966a1afb193ddb021d4250d5d6
元コミット内容
test: Add rundir, rundircmpout and errorcheckdir commands to testlib and run.go
rundir will compile each file in the directory in lexicographic order, link the last file as the main package and run the resulting program. rundircmpout is an related command, that will compare the output of the program to an corresponding .out file
errorcheckdir will compile each file in a directory in lexicographic order, running errorcheck on each file as it compiles. All compilations are assumed to be successful except for the last file. However, If a -0 flag is present on the command, the last compilation will also be assumed successful
This CL also includes a small refactoring of run.go. It was getting unwieldy and the meaning of the run commands was hidden behind argument line formatting.
Fixes #4058.
R=rsc, minux.ma, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/6554071
変更の背景
このコミットの主な目的は、Go言語のテストスイートにおけるディレクトリベースのテストをより効率的かつ柔軟に行えるようにすることです。これまでのテストフレームワークでは、単一のGoファイルを対象としたテストが主でしたが、複数のファイルで構成されるパッケージや、特定のコンパイル順序が重要なシナリオをテストする際に不便がありました。
具体的には、以下の課題を解決するために新しいコマンドが導入されました。
- ディレクトリ内の複数ファイルのコンパイルと実行: 複数のGoファイルが連携して動作するテストケースを簡単に記述し、実行できるようにするため。特に、辞書順でのコンパイルと、最後のファイルをメインパッケージとしてリンク・実行する機能が求められていました。
- 出力の比較テスト: 実行結果が特定の期待値と一致するかどうかを自動的に検証する機能の必要性。
- エラーチェックの強化: ディレクトリ内の各ファイルに対してコンパイル時のエラーチェックを行い、特定のファイルでのみエラーが期待されるようなシナリオをテストできるようにするため。これにより、インポートエラーなどの複雑なエラーパターンを検出できるようになります。
また、run.go
のコードが肥大化し、引数のフォーマットによってコマンドの意味が隠蔽されがちであったため、コードの可読性と保守性を向上させるためのリファクタリングも同時に行われました。
前提知識の解説
このコミットを理解するためには、以下のGo言語のテストインフラストラクチャと関連する概念についての知識が必要です。
testlib
: Go言語のテストスイートで使用されるシェルスクリプトベースのヘルパーライブラリです。テストの実行、コンパイル、リンクなどの基本的な操作を抽象化し、テストスクリプトから簡単に呼び出せるようにします。Goコンパイラ自体が壊れている場合でもデバッグが可能であるため、重要な役割を担っています。run.go
: Go言語で書かれたテスト実行のコアロジックを担うプログラムです。testlib
から呼び出され、Goのツールチェーン(コンパイラ、リンカなど)を実際に実行してテストを行います。testlib
よりも高速なテスト実行が可能です。go tool gc
: Go言語のコンパイラです。Goのソースコードをコンパイルしてオブジェクトファイルを生成します。go tool ld
: Go言語のリンカです。コンパイルされたオブジェクトファイルをリンクして実行可能ファイルを生成します。errorcheck
: Goのテストフレームワーク内で使用される、コンパイルエラーや警告をチェックするメカニズムです。特定の行に特定のコメント(例:// ERROR "..."
)を記述することで、その行で期待されるエラーメッセージを定義し、実際のコンパイル出力と比較します。- 辞書順 (Lexicographical Order): 文字列やファイル名をアルファベット順(または数値順)に並べる順序のことです。このコミットでは、ディレクトリ内のGoファイルをこの順序でコンパイルすることが指定されています。これは、Goのパッケージシステムにおいて、ファイル間の依存関係が暗黙的にこの順序に影響される場合があるため重要です。
defer
ステートメント: Go言語のキーワードで、関数がリターンする直前に実行される関数呼び出しをスケジュールします。今回のコミットとは直接関係ありませんが、Goのコードベースでよく見られる概念です。コミットメッセージのFixes #4058
が外部のGo-GORMリポジトリのdefer
に関するIssueと誤解される可能性があったため、ここで補足します。このコミットが修正する#4058
は、Go言語の内部的なIssueトラッカーのIDであり、外部のGitHubリポジトリのIssueとは異なります。
技術的詳細
このコミットは、Go言語のテストフレームワークに以下の3つの新しいコマンドを導入し、関連するリファクタリングを行っています。
-
rundir
:- 機能: 指定されたディレクトリ内のすべてのGoファイルを辞書順にコンパイルします。
- コンパイル順序:
ioutil.ReadDir
で取得したファイルリストをそのまま使用し、Goのファイルシステムが返す順序(通常は辞書順)で処理します。 - リンクと実行: ディレクトリ内の最後のGoファイルをメインパッケージとしてリンクし、その結果生成された実行可能ファイルを実行します。
- 目的: 複数のファイルで構成されるGoパッケージの統合テストや、特定のコンパイル順序が重要なテストシナリオに対応します。
-
rundircmpout
:- 機能:
rundir
と同様にディレクトリ内のGoファイルをコンパイル・リンク・実行します。 - 出力比較: 実行結果の標準出力を、対応する
.out
ファイルの内容と比較します。 - 目的: プログラムの出力が期待通りであるかを自動的に検証するテストを可能にします。
- 機能:
-
errorcheckdir
:- 機能: 指定されたディレクトリ内の各Goファイルを辞書順にコンパイルし、それぞれのコンパイル結果に対して
errorcheck
を実行します。 - エラー期待値: 通常、ディレクトリ内の最後のファイル以外のコンパイルは成功すると仮定されます。最後のファイルのみ、
errorcheck
で定義されたエラーが期待されます。 -0
フラグ: コマンドに-0
フラグが渡された場合、最後のファイルのコンパイルも成功すると仮定されます。- 目的: 複数のファイルにまたがるエラー検出(例: インポートパスの誤り)や、特定のコンパイルフェーズでのエラー発生を検証するテストを可能にします。
- 機能: 指定されたディレクトリ内の各Goファイルを辞書順にコンパイルし、それぞれのコンパイル結果に対して
run.go
のリファクタリング:
このコミットでは、run.go
の内部構造も改善されています。
runCmd
型の導入:func(...string) ([]byte, error)
という関数シグネチャを持つrunCmd
型が導入され、コマンド実行ロジックがより抽象化されました。これにより、runcmd
関数を引数として渡すことで、テスト実行の柔軟性が向上しています。- ヘルパー関数の追加:
compileFile
,compileInDir
,linkFile
,goDirFiles
といった、コンパイルやリンク、ディレクトリ内のGoファイル取得に関するヘルパー関数が追加され、run
メソッド内のロジックが整理されました。 - エラーハンドリングの改善:
runcmd
から返されるエラーがより適切に処理され、fmt.Errorf
を使用して詳細なエラーメッセージが生成されるようになりました。 switch action
の拡張:run
メソッド内のswitch
ステートメントに、新しく追加されたrundircmpout
,rundir
,errorcheckdir
の各アクションが追加され、それぞれの処理ロジックが実装されました。wantedErrors
関数の変更:wantedErrors
関数がfile
とshort
という引数を受け取るようになり、エラーメッセージのファイルパスの置換がより柔軟に行えるようになりました。これにより、テスト対象のファイルパスが一時ディレクトリにコピーされた場合でも、元のファイル名でエラーメッセージをフィルタリングできるようになります。
これらの変更により、Go言語のテストフレームワークは、より複雑なテストシナリオに対応できるようになり、テストの記述と実行が効率化されました。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、test/run.go
と test/testlib
の2つのファイルに集中しています。
test/run.go
の変更点:
-
新しい型とヘルパー関数の追加:
type runCmd func(...string) ([]byte, error)
: コマンド実行関数を抽象化する型が定義されました。func compileFile(runcmd runCmd, longname string) ([]byte, error)
: 単一のGoファイルをコンパイルするヘルパー関数。func compileInDir(runcmd runCmd, dir, name string) ([]byte, error)
: ディレクトリ内のGoファイルをコンパイルするヘルパー関数。func linkFile(runcmd runCmd, goname string) error
: コンパイルされたファイルをリンクするヘルパー関数。func goDirFiles(longdir string) ([]os.FileInfo, error)
: 指定されたディレクトリ内のGoファイルのみをフィルタリングして返すヘルパー関数。
-
test.run()
メソッドの変更:switch action
ステートメントにrundircmpout
,rundir
,errorcheckdir
のケースが追加されました。rundircmpout
はrundir
にフォールスルーし、その後出力比較を行います。errorcheckdir
は、ディレクトリ内の各ファイルをerrorcheck
するロジックが追加されました。最後のファイルのエラーチェックはwantError
フラグと-0
フラグによって挙動が変わります。rundir
は、ディレクトリ内のファイルを順にコンパイルし、最後のファイルをリンクして実行するロジックが追加されました。実行結果はt.expectedOutput()
と比較されます。- 既存の
compile
,compiledir
,build
,run
,runoutput
の各ケースで、新しいヘルパー関数 (compileFile
,compileInDir
) が使用されるように変更され、エラーハンドリングが改善されました。
-
test.errorCheck()
およびtest.wantedErrors()
の変更:test.errorCheck()
でエラーメッセージのフォーマットが改善され、fmt.Errorf
が使用されるようになりました。test.wantedErrors()
関数がfile
とshort
という引数を受け取るようになり、エラーメッセージ内のファイルパスの置換がより柔軟に行えるようになりました。これにより、テスト対象のファイルパスが一時ディレクトリにコピーされた場合でも、元のファイル名でエラーメッセージをフィルタリングできるようになります。
test/testlib
の変更点:
- 新しいシェル関数の追加:
errorcheckdir()
:errorcheck
コマンドをディレクトリ内のGoファイルに対して実行するシェル関数。-0
フラグの処理も含まれます。rundir()
: ディレクトリ内のGoファイルをコンパイルし、最後のファイルをリンクして実行するシェル関数。rundircmpout()
:rundir
と同様に実行し、その出力を.out
ファイルと比較するシェル関数。
これらの変更により、Go言語のテストフレームワークは、より複雑なテストシナリオに対応できるようになり、テストの記述と実行が効率化されました。
コアとなるコードの解説
test/run.go
の変更点
test/run.go
は、Go言語のテスト実行のバックエンドを担う重要なファイルです。このコミットでは、特にディレクトリベースのテストをサポートするために、以下のGoコードが追加・変更されました。
-
runCmd
型と関連ヘルパー関数:type runCmd func(...string) ([]byte, error) func compileFile(runcmd runCmd, longname string) (out []byte, err error) { return runcmd("go", "tool", gc, "-e", longname) } func compileInDir(runcmd runCmd, dir, name string) (out []byte, err error) { return runcmd("go", "tool", gc, "-e", "-D.", "-I.", filepath.Join(dir, name)) } func linkFile(runcmd runCmd, goname string) (err error) { pfile := strings.Replace(goname, ".go", "."+letter, -1) _, err = runcmd("go", "tool", ld, "-o", "run.out", "-L", ".", pfile) return } func goDirFiles(longdir string) (filter []os.FileInfo, err error) { files, dirErr := ioutil.ReadDir(longdir) if dirErr != nil { return nil, dirErr } for _, gofile := range files { if filepath.Ext(gofile.Name()) == ".go" { filter = append(filter, gofile) } } return }
runCmd
は、コマンドを実行し、その標準出力とエラーを返す関数のシグネチャを定義します。これにより、実際のコマンド実行ロジック(exec.Command
の呼び出しなど)を抽象化し、テストコードの可読性を高めています。compileFile
は単一のGoファイルをコンパイルします。compileInDir
は、ディレクトリ内のGoファイルをコンパイルする際に、カレントディレクトリ (-D.
) とインクルードパス (-I.
) を指定します。linkFile
は、コンパイルされたオブジェクトファイルをリンクして実行可能ファイル (run.out
) を生成します。goDirFiles
は、指定されたディレクトリから.go
拡張子を持つファイルのみを抽出し、os.FileInfo
のスライスとして返します。これは、ディレクトリ内のGoファイルを処理する際の共通のユーティリティとして機能します。
-
test.run()
メソッド内のswitch action
の拡張:test.run()
メソッドは、テストの種類 (action
) に応じて異なる処理を実行するGoのテストフレームワークの心臓部です。このコミットでは、以下の新しいケースが追加されました。-
case "rundircmpout"
: このケースはrundir
にフォールスルーし、t.action
をrundir
に設定します。これにより、rundir
のロジックが実行された後、追加で出力比較が行われるようになります。 -
case "errorcheckdir"
:case "errorcheckdir": longdir := filepath.Join(cwd, t.goDirName()) files, err := goDirFiles(longdir) if err != nil { t.err = err return } for i, gofile := range files { out, err := compileInDir(runcmd, longdir, gofile.Name()) if i == len(files)-1 { // 最後のファイルの場合 if wantError && err == nil { t.err = fmt.Errorf("compilation succeeded unexpectedly\\n%s", out) return } else if !wantError && err != nil { t.err = err return } } else if err != nil { // 最後のファイル以外でエラーが発生した場合 t.err = err return } longname := filepath.Join(longdir, gofile.Name()) t.err = t.errorCheck(string(out), longname, gofile.Name()) if t.err != nil { break } }
このロジックは、ディレクトリ内の各Goファイルを辞書順にコンパイルし、
errorcheck
を実行します。goDirFiles
を使用してディレクトリ内のGoファイルを取得します。- 各ファイルに対して
compileInDir
を呼び出してコンパイルします。 - 最後のファイルの場合、
wantError
フラグ(-0
フラグの有無によって設定される)に基づいて、コンパイルが成功するか失敗するかを期待します。期待と異なる結果の場合、エラーを設定します。 - 最後のファイル以外でコンパイルエラーが発生した場合、すぐにエラーを設定して処理を中断します。
- 各コンパイルの出力に対して
t.errorCheck
を呼び出し、期待されるエラーメッセージが含まれているかを確認します。
-
case "rundir"
:case "rundir": longdir := filepath.Join(cwd, t.goDirName()) files, err := goDirFiles(longdir) if err != nil { t.err = err return } var gofile os.FileInfo for _, gofile = range files { _, err := compileInDir(runcmd, longdir, gofile.Name()) if err != nil { t.err = err return } } err = linkFile(runcmd, gofile.Name()) // 最後のファイルをリンク if err != nil { t.err = err return } out, err := runcmd(append([]string{filepath.Join(t.tempDir, "run.out")}, args...)...) // 実行 if err != nil { t.err = err return } if strings.Replace(string(out), "\\r\\n", "\\n", -1) != t.expectedOutput() { t.err = fmt.Errorf("incorrect output\\n%s", out) }
このロジックは、ディレクトリ内のGoファイルを辞書順にコンパイルし、最後のファイルをメインパッケージとしてリンクし、実行します。
goDirFiles
でファイルリストを取得します。- 各ファイルを
compileInDir
でコンパイルします。 - ループの最後に残った
gofile
(つまり最後のファイル) をlinkFile
でリンクします。 - 生成された実行可能ファイル (
run.out
) をruncmd
で実行します。 - 実行結果の標準出力を
t.expectedOutput()
と比較し、一致しない場合はエラーを設定します。
-
-
test.wantedErrors()
の変更:func (t *test) wantedErrors(file, short string) (errs []wantedError) { src, _ := ioutil.ReadFile(file) for i, line := range strings.Split(string(src), "\\n") { // ... (既存のロジック) ... filterPattern := fmt.Sprintf(`^(\\w+/)?%s:%d[:[]`, short, lineNum) // ... (既存のロジック) ... errs = append(errs, wantedError{ // ... filterRe: regexp.MustCompile(filterPattern), // ... file: short, // ここが変更点 }) } return }
wantedErrors
関数は、テストファイル内のコメントから期待されるエラーメッセージを抽出します。このコミットでは、file
(絶対パス) とshort
(相対パスまたは短縮名) の2つの引数を受け取るようになりました。これにより、エラーメッセージの正規表現パターン (filterPattern
) やwantedError
構造体のfile
フィールドに、より適切なファイル名を設定できるようになり、一時ディレクトリにコピーされたファイルでも正確なエラーチェックが可能になります。
test/testlib
の変更点
test/testlib
はシェルスクリプトで書かれたテストヘルパーです。GoのテストフレームワークがGoコンパイラ自体をテストする際に、Goコンパイラがまだ完全に機能しない可能性があるため、シェルスクリプトが重要な役割を果たします。
-
errorcheckdir()
関数:errorcheckdir() { lastzero="" if [ "$1" = "-0" ]; then lastzero="-0" fi files=($D/$F.dir/*.go) for gofile in ${files[@]} do zero="-0" if [ ${files[${#files[@]}-1]} = $gofile ]; then zero=$lastzero fi errchk $zero $G -D. -I. -e $gofile done }
このシェル関数は、指定されたディレクトリ (
$D/$F.dir
) 内のすべての.go
ファイルをループ処理します。-0
フラグが渡された場合、lastzero
変数に-0
を設定します。- 各ファイルに対して
errchk
コマンドを実行します。 - 最後のファイルの場合、
errchk
に渡す-0
フラグはlastzero
の値に依存します。これにより、最後のファイルのみエラーを期待するか、またはエラーを期待しないかを制御できます。
-
rundir()
関数:rundir() { lastfile="" for gofile in $D/$F.dir/*.go do name=$(basename ${gofile/\\.go/} ) $G -D. -I. -e "$gofile" || return 1 lastfile=$name done $L -o $A.out -L. $lastfile.$A ./$A.out }
このシェル関数は、ディレクトリ内のGoファイルをコンパイルし、最後のファイルをリンクして実行します。
- ディレクトリ内の各
.go
ファイルをループ処理し、$G
(Goコンパイラ) を使用してコンパイルします。コンパイルが失敗した場合、すぐに終了します。 lastfile
変数には、ループの最後に処理されたファイルの名前(拡張子なし)が格納されます。- ループ終了後、
$L
(Goリンカ) を使用してlastfile
に対応するオブジェクトファイルをリンクし、$A.out
という実行可能ファイルを生成します。 - 最後に、生成された実行可能ファイル
./$A.out
を実行します。
- ディレクトリ内の各
-
rundircmpout()
関数:rundircmpout() { lastfile="" for gofile in $D/$F.dir/*.go do name=$(basename ${gofile/\\.go/} ) $G -D. -I. -e "$gofile" || return 1 lastfile=$name done $L -o $A.out -L. $lastfile.$A ./$A.out | cmp - $D/$F.out }
このシェル関数は
rundir()
とほぼ同じですが、実行結果をcmp - $D/$F.out
を使用して、対応する.out
ファイルと比較します。これにより、プログラムの出力が期待通りであるかをシェルスクリプトレベルで検証できます。
これらのシェルスクリプトの変更は、GoのテストフレームワークがGoコンパイラ自体をテストする際に、Goコンパイラがまだ完全に機能しない可能性があるという状況に対応するために重要です。
関連リンク
- Go Change List (CL) 6554071: https://golang.org/cl/6554071 このコミットに対応するGoのコードレビューページです。詳細な議論やレビューコメントが確認できます。
- Go Issue #4058: このコミットメッセージに記載されている
Fixes #4058
は、Go言語の内部的なIssueトラッカーのIDを指しています。これは、GitHubの公開リポジトリのIssueとは異なります。このIssueは、ディレクトリベースのテストコマンドの必要性に関連するものであったと考えられます。
参考にした情報源リンク
- Go Change List (CL) 6554071: https://golang.org/cl/6554071
- Google Web Search for "Go issue #4058": (この検索結果は、Go-GORMリポジトリのIssueを指しており、今回のコミットとは直接関係ありませんでしたが、調査プロセスの一部として参照しました。)