[インデックス 13910] ファイルの概要
このコミットは、Go言語のテスト実行ツールである run.go の機能改善に関するものです。具体的には、テストヘッダーの解釈を拡張し、テストが意図せずスキップされることを防ぎ、どのテストが run.go によって実行されるかを明確にすることを目的としています。
コミット
commit cd22afa07b83d56e0563d0ca4343e5c1a20c3e82
Author: Russ Cox <rsc@golang.org>
Date: Sun Sep 23 13:16:14 2012 -0400
test: expand run.go's errorcheck, make clear which bugs run
Today, if run.go doesn't understand a test header line it just ignores
the test, making it too easy to write or edit tests that are not actually
being run.
- expand errorcheck to accept flags, so that bounds.go and escape*.go can run.
- create a whitelist of skippable tests in run.go; skipping others is an error.
- mark all skipped tests at top of file.
Update #4139.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6549054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cd22afa07b83d56e0563d0ca4343e5c1a20c3e82
元コミット内容
run.go は、テストファイルのヘッダー行を解析してテストの実行方法を決定するGo言語のテストハーネスです。これまでのバージョンでは、run.go がテストヘッダー行を理解できない場合、そのテストを単に無視していました。この挙動は、開発者がテストを作成または編集する際に、意図せずテストが実行されない状態を作り出す可能性がありました。
このコミットの主な目的は、この問題を解決し、テストの実行状況をより明確にすることです。
変更の背景
Go言語のテストスイートは、test ディレクトリ内の多数のテストファイルで構成されています。これらのテストファイルは、それぞれが特定のテストシナリオやバグ修正を検証するために設計されています。run.go は、これらのテストを自動的に発見し、実行するためのスクリプトとして機能していました。
しかし、従来の run.go には以下の問題がありました。
- テストのサイレントスキップ: テストファイルのヘッダー行(例:
// $G $F.goや// errchk ...)がrun.goの期待する形式と異なる場合、run.goはそのテストをエラーとせず、単に実行対象から外していました。これにより、開発者はテストが実行されていると誤解し、実際にはテストが機能していない、あるいは新しいテストが追加されても実行されていないという状況が発生する可能性がありました。 errorcheckの機能不足:errorcheckアクションは、コンパイルエラーをチェックするためのものでしたが、コンパイラに渡す追加のフラグ(例:-mや-l)を直接サポートしていませんでした。このため、bounds.goやescape*.goのような、特定のコンパイラフラグを必要とするテストがrun.go経由で適切に実行できないという制約がありました。- スキップされるテストの不明瞭さ:
run.goによって意図的にスキップされるべきテストと、そうでないテストの区別が曖昧でした。
これらの問題は、テストスイートの信頼性を低下させ、開発者がテストの健全性を確認するのを困難にしていました。このコミットは、これらの課題に対処し、テストインフラストラクチャの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のテストに関する基本的な知識が必要です。
run.go: Go言語の公式リポジトリのtestディレクトリに存在する、Goのテストスイートを実行するための内部ツールです。all.bashスクリプトの一部として実行され、Goコンパイラやツールの動作を検証する多数のテストを管理します。各テストファイルは、特別なコメント行(テストヘッダー)によってrun.goにその実行方法を指示します。- テストヘッダー: Goのテストファイル(例:
test/foo.go)の先頭に記述される//で始まるコメント行で、run.goがテストをどのように実行するかを定義します。例えば、// $G $F.goはGoコンパイラ ($G) で現在のファイル ($F.go) をコンパイルすることを意味します。 errorcheckアクション: テストヘッダーで指定できるアクションの一つで、Goコンパイラが特定のコードに対してエラーを報告するかどうかを検証するために使用されます。例えば、// errorcheck $G -e $F.goは、$F.goをコンパイルした際にエラーが発生することを期待します。$G,$L,$F,$D,$A:run.goのテストヘッダーで使用される特殊な変数です。$G: Goコンパイラのパス。$L: Goリンカのパス。$F: 現在のテストファイルのベース名(例:foo.go)。$D: 現在のテストファイルがあるディレクトリのパス。$A: 実行可能ファイルのアーキテクチャ依存の拡張子(例:.exeon Windows)。
all.bash: Go言語のソースツリーのルートにあるシェルスクリプトで、Goのビルド、インストール、およびテストスイートの実行を自動化します。run.goはこのスクリプトによって呼び出されます。- コンパイラフラグ: Goコンパイラ (
go tool compileまたはgo build) に渡されるオプションで、コンパイルの挙動を制御します。例えば、-mはインライン化の決定を表示し、-lはインライン化を無効にします。
技術的詳細
このコミットは、主に test/run.go の内部ロジックと、それに付随する多数のテストファイルのヘッダーを変更することで、前述の問題に対処しています。
-
errorcheckアクションの拡張:run.goのtest.run()メソッド内のerrorcheckケースが大幅に修正されました。- 以前は
errorcheckアクションに続く引数を単純にファイルパスとして扱っていましたが、この変更により、errorcheckに-0や-m,-lといったコンパイラフラグを渡せるようになりました。 wantErrorという新しいブーリアン変数が導入され、-0フラグが存在する場合はfalseに設定されます。これは、-0が「エラーを期待しない」ことを意味するためです。runcmd関数を呼び出す際に、これらのフラグがgo tool gcコマンドに渡されるようになりました。- コンパイル結果のチェックも改善され、
wantErrorの値に基づいて、コンパイルが成功したか失敗したかを適切に判断し、期待と異なる場合にエラーを報告するようになりました。これにより、errorcheckテストがより正確に機能するようになります。
-
スキップされるテストのホワイトリスト化と厳格化:
run.goの末尾にskipOkayという新しいmap[string]boolが追加されました。このマップは、run.goによって意図的にスキップされても問題ないテストファイルのパスをキーとして持ちます。main関数内のテスト結果処理ロジックが変更され、テストがスキップされた (isSkipがtrue) 場合に、そのテストがskipOkayマップに存在するかどうかをチェックするようになりました。- もしテストがスキップされたにもかかわらず
skipOkayマップに存在しない場合、それは「予期せぬスキップ」として扱われ、failedフラグがtrueに設定され、エラーとして報告されます。これにより、意図しないテストのスキップが即座に検出されるようになります。 - この変更に伴い、
run.goによって実行されないことが意図されている多数のテストファイル(例:test/args.go,test/bugs/bug395.goなど)の先頭に、// NOTE: This test is not run by 'run.go' and so not run by all.bash. // To run this test you must use the ./run shell script.というコメントが追加されました。これは、これらのテストがskipOkayリストに含まれていることを視覚的に示すとともに、開発者に対してこれらのテストの実行方法を明示します。
-
test/testlibの変更:test/testlibスクリプト内のerrorcheck関数も更新され、-0フラグを適切に処理できるようになりました。これにより、run.goとtestlibの間でerrorcheckの挙動の一貫性が保たれます。
これらの変更により、run.go はより堅牢になり、テストの実行状況がより透明になりました。開発者は、テストが実際に実行されているかどうか、または意図的にスキップされているかどうかを容易に確認できるようになります。
コアとなるコードの変更箇所
このコミットの主要な変更は test/run.go に集中しています。
-
test/run.goのmain関数内の変更 (行 109-120):--- a/test/run.go +++ b/test/run.go @@ -109,15 +110,17 @@ func main() { <-test.donec _, isSkip := test.err.(skipError) errStr := "pass" - if isSkip { - errStr = "skip" - } if test.err != nil { errStr = test.err.Error() if !isSkip { failed = true } } + if isSkip && !skipOkay[path.Join(test.dir, test.gofile)] { + errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr + isSkip = false + failed = true + } resCount[errStr]++ if isSkip && !*verbose && !*showSkips { continueこの部分で、テストがスキップされた場合に
skipOkayマップをチェックし、予期せぬスキップであればエラーとしてマークするロジックが追加されています。 -
test/run.goのtest.run()メソッド内のerrorcheckアクションの処理 (行 262-274, 302-317):--- a/test/run.go +++ b/test/run.go @@ -262,8 +266,19 @@ func (t *test) run() { case "cmpout": action = "run" // the run case already looks for <dir>/<test>.out files fallthrough - case "compile", "compiledir", "build", "run", "runoutput": + case "compile", "compiledir", "build", "run", "runoutput": t.action = action + case "errorcheck": + t.action = action + wantError = true + for len(args) > 0 && strings.HasPrefix(args[0], "-") { + if args[0] == "-0" { + wantError = false + } else { + flags = append(flags, args[0]) + } + args = args[1:] + } case "skip": t.action = "skip" return @@ -302,7 +317,21 @@ func (t *test) run() { t.err = fmt.Errorf("unimplemented action %q", action) case "errorcheck": - out, _ := runcmd("go", "tool", gc, "-e", "-o", "a."+letter, long) + cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter} + cmdline = append(cmdline, flags...) + cmdline = append(cmdline, long) + out, err := runcmd(cmdline...) + if wantError { + if err == nil { + t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) + return + } + } else { + if err != nil { + t.err = fmt.Errorf("%s\n%s", err, out) + return + } + } t.err = t.errorCheck(string(out), long, t.gofile) returnこの部分で、
errorcheckアクションがコンパイラフラグを受け入れるように拡張され、コンパイル結果の検証ロジックが改善されています。 -
test/run.goのskipOkayマップの追加 (行 517-580):--- a/test/run.go +++ b/test/run.go @@ -517,3 +546,62 @@ func (t *test) wantedErrors() (errs []wantedError) { return } + +var skipOkay = map[string]bool{ + "args.go": true, + "ddd3.go": true, + // ... (多数のテストファイルがリストされています) ... + "bugs/bug395.go": true, + "bugs/bug434.go": true, +}このマップは、
run.goによってスキップされても許容されるテストのホワイトリストを定義しています。 -
多数の
test/*.goファイルへのコメント追加:--- a/test/args.go +++ b/test/args.go @@ -1,5 +1,8 @@ // $G $F.go && $L $F.$A && ./$A.out arg1 arg2 +// NOTE: This test is not run by 'run.go' and so not run by all.bash. +// To run this test you must use the ./run shell script. + // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.skipOkayリストに含まれる各テストファイルの先頭に、このコメントが追加されています。
コアとなるコードの解説
test/run.go の main 関数におけるスキップチェックの強化
if isSkip && !skipOkay[path.Join(test.dir, test.gofile)] {
errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr
isSkip = false
failed = true
}
このコードブロックは、テストがスキップされた (isSkip が true) 際に実行されます。skipOkay マップは、run.go が意図的にスキップしても良いと認識しているテストファイルのパスのホワイトリストです。path.Join(test.dir, test.gofile) は、現在のテストファイルの相対パス(例: fixedbugs/bug395.go)を生成します。
もしテストがスキップされたにもかかわらず、そのパスが skipOkay マップに存在しない場合、それは「予期せぬスキップ」と判断されます。この場合、errStr に詳細なエラーメッセージが設定され、isSkip が false に、failed が true に変更されます。これにより、run.go はこの予期せぬスキップをテスト失敗として扱い、最終的なテスト結果に反映させます。このメカニズムにより、開発者はテストがサイレントにスキップされることを防ぎ、テストスイートの健全性を維持できます。
test/run.go の test.run() メソッドにおける errorcheck の拡張
case "errorcheck":
t.action = action
wantError = true
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
if args[0] == "-0" {
wantError = false
} else {
flags = append(flags, args[0])
}
args = args[1:]
}
// ...
cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter}
cmdline = append(cmdline, flags...)
cmdline = append(cmdline, long)
out, err := runcmd(cmdline...)
if wantError {
if err == nil {
t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
return
}
} else {
if err != nil {
t.err = fmt.Errorf("%s\n%s", err, out)
return
}
}
t.err = t.errorCheck(string(out), long, t.gofile)
return
このコードブロックは、テストヘッダーで errorcheck アクションが指定された場合の処理を定義しています。
- フラグの解析:
errorcheckに続く引数の中から、ハイフン (-) で始まる文字列をコンパイラフラグとして解析します。-0フラグが見つかった場合、wantErrorをfalseに設定します。これは、コンパイルエラーを期待しないことを意味します。- それ以外のフラグ(例:
-m,-l)はflagsスライスに追加されます。
- コマンドラインの構築:
go tool gcコマンドを実行するためのcmdlineスライスを構築します。この際、解析されたflagsがcmdlineに追加されます。これにより、errorcheckテストが特定のコンパイラフラグを付けて実行できるようになります。 - コンパイル結果の検証:
runcmdを呼び出してコンパイルを実行し、その結果 (out,err) を検証します。wantErrorがtrue(エラーを期待している)にもかかわらずerrがnil(コンパイル成功)の場合、それは予期せぬ成功であるため、エラーとして報告されます。wantErrorがfalse(エラーを期待していない)にもかかわらずerrがnilでない(コンパイル失敗)場合、それは予期せぬ失敗であるため、エラーとして報告されます。- これらのチェックを通過した後、
t.errorCheckメソッドが呼び出され、コンパイラの出力 (out) から具体的なエラーメッセージを解析し、テストヘッダーで期待されるエラーメッセージと照合します。
この拡張により、errorcheck テストはより柔軟になり、コンパイラの詳細な挙動(例: エスケープ解析や境界チェック)を特定のフラグを付けて検証できるようになりました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットが解決したIssue: https://golang.org/issue/4139 (Go issue tracker)
- このコミットに対応するGerrit Change-Id: https://golang.org/cl/6549054 (Go Code Review)
参考にした情報源リンク
- Go言語のテストに関するドキュメント (Go公式ドキュメント): https://go.dev/doc/code (特に "How to write Go code" セクションのテストに関する部分)
- Go言語の
testingパッケージのドキュメント: https://pkg.go.dev/testing - Go言語のコンパイラフラグに関する情報 (GoコマンドのヘルプやGoのソースコード):
go help compileやgo tool compile -helpコマンドで詳細を確認できます。 - Go言語のテストフレームワークの内部動作に関する議論 (Go開発者メーリングリストやGoのIssueトラッカー):
golang-devメーリングリストのアーカイブや、GoのIssueトラッカーでrun.goやテストに関する議論を検索すると、より深い背景情報を得られます。