[インデックス 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
: 実行可能ファイルのアーキテクチャ依存の拡張子(例:.exe
on 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
やテストに関する議論を検索すると、より深い背景情報を得られます。