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

[インデックス 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 には以下の問題がありました。

  1. テストのサイレントスキップ: テストファイルのヘッダー行(例: // $G $F.go// errchk ...)が run.go の期待する形式と異なる場合、run.go はそのテストをエラーとせず、単に実行対象から外していました。これにより、開発者はテストが実行されていると誤解し、実際にはテストが機能していない、あるいは新しいテストが追加されても実行されていないという状況が発生する可能性がありました。
  2. errorcheck の機能不足: errorcheck アクションは、コンパイルエラーをチェックするためのものでしたが、コンパイラに渡す追加のフラグ(例: -m-l)を直接サポートしていませんでした。このため、bounds.goescape*.go のような、特定のコンパイラフラグを必要とするテストが run.go 経由で適切に実行できないという制約がありました。
  3. スキップされるテストの不明瞭さ: 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 の内部ロジックと、それに付随する多数のテストファイルのヘッダーを変更することで、前述の問題に対処しています。

  1. errorcheck アクションの拡張:

    • run.gotest.run() メソッド内の errorcheck ケースが大幅に修正されました。
    • 以前は errorcheck アクションに続く引数を単純にファイルパスとして扱っていましたが、この変更により、errorcheck-0-m, -l といったコンパイラフラグを渡せるようになりました。
    • wantError という新しいブーリアン変数が導入され、-0 フラグが存在する場合は false に設定されます。これは、-0 が「エラーを期待しない」ことを意味するためです。
    • runcmd 関数を呼び出す際に、これらのフラグが go tool gc コマンドに渡されるようになりました。
    • コンパイル結果のチェックも改善され、wantError の値に基づいて、コンパイルが成功したか失敗したかを適切に判断し、期待と異なる場合にエラーを報告するようになりました。これにより、errorcheck テストがより正確に機能するようになります。
  2. スキップされるテストのホワイトリスト化と厳格化:

    • run.go の末尾に skipOkay という新しい map[string]bool が追加されました。このマップは、run.go によって意図的にスキップされても問題ないテストファイルのパスをキーとして持ちます。
    • main 関数内のテスト結果処理ロジックが変更され、テストがスキップされた (isSkiptrue) 場合に、そのテストが 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 リストに含まれていることを視覚的に示すとともに、開発者に対してこれらのテストの実行方法を明示します。
  3. test/testlib の変更:

    • test/testlib スクリプト内の errorcheck 関数も更新され、-0 フラグを適切に処理できるようになりました。これにより、run.gotestlib の間で errorcheck の挙動の一貫性が保たれます。

これらの変更により、run.go はより堅牢になり、テストの実行状況がより透明になりました。開発者は、テストが実際に実行されているかどうか、または意図的にスキップされているかどうかを容易に確認できるようになります。

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

このコミットの主要な変更は test/run.go に集中しています。

  1. test/run.gomain 関数内の変更 (行 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 マップをチェックし、予期せぬスキップであればエラーとしてマークするロジックが追加されています。

  2. test/run.gotest.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 アクションがコンパイラフラグを受け入れるように拡張され、コンパイル結果の検証ロジックが改善されています。

  3. test/run.goskipOkay マップの追加 (行 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 によってスキップされても許容されるテストのホワイトリストを定義しています。

  4. 多数の 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.gomain 関数におけるスキップチェックの強化

if isSkip && !skipOkay[path.Join(test.dir, test.gofile)] {
    errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr
    isSkip = false
    failed = true
}

このコードブロックは、テストがスキップされた (isSkiptrue) 際に実行されます。skipOkay マップは、run.go が意図的にスキップしても良いと認識しているテストファイルのパスのホワイトリストです。path.Join(test.dir, test.gofile) は、現在のテストファイルの相対パス(例: fixedbugs/bug395.go)を生成します。

もしテストがスキップされたにもかかわらず、そのパスが skipOkay マップに存在しない場合、それは「予期せぬスキップ」と判断されます。この場合、errStr に詳細なエラーメッセージが設定され、isSkipfalse に、failedtrue に変更されます。これにより、run.go はこの予期せぬスキップをテスト失敗として扱い、最終的なテスト結果に反映させます。このメカニズムにより、開発者はテストがサイレントにスキップされることを防ぎ、テストスイートの健全性を維持できます。

test/run.gotest.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 アクションが指定された場合の処理を定義しています。

  1. フラグの解析: errorcheck に続く引数の中から、ハイフン (-) で始まる文字列をコンパイラフラグとして解析します。
    • -0 フラグが見つかった場合、wantErrorfalse に設定します。これは、コンパイルエラーを期待しないことを意味します。
    • それ以外のフラグ(例: -m, -l)は flags スライスに追加されます。
  2. コマンドラインの構築: go tool gc コマンドを実行するための cmdline スライスを構築します。この際、解析された flagscmdline に追加されます。これにより、errorcheck テストが特定のコンパイラフラグを付けて実行できるようになります。
  3. コンパイル結果の検証: runcmd を呼び出してコンパイルを実行し、その結果 (out, err) を検証します。
    • wantErrortrue(エラーを期待している)にもかかわらず errnil(コンパイル成功)の場合、それは予期せぬ成功であるため、エラーとして報告されます。
    • wantErrorfalse(エラーを期待していない)にもかかわらず errnil でない(コンパイル失敗)場合、それは予期せぬ失敗であるため、エラーとして報告されます。
    • これらのチェックを通過した後、t.errorCheck メソッドが呼び出され、コンパイラの出力 (out) から具体的なエラーメッセージを解析し、テストヘッダーで期待されるエラーメッセージと照合します。

この拡張により、errorcheck テストはより柔軟になり、コンパイラの詳細な挙動(例: エスケープ解析や境界チェック)を特定のフラグを付けて検証できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のテストに関するドキュメント (Go公式ドキュメント): https://go.dev/doc/code (特に "How to write Go code" セクションのテストに関する部分)
  • Go言語の testing パッケージのドキュメント: https://pkg.go.dev/testing
  • Go言語のコンパイラフラグに関する情報 (GoコマンドのヘルプやGoのソースコード): go help compilego tool compile -help コマンドで詳細を確認できます。
  • Go言語のテストフレームワークの内部動作に関する議論 (Go開発者メーリングリストやGoのIssueトラッカー): golang-dev メーリングリストのアーカイブや、GoのIssueトラッカーで run.go やテストに関する議論を検索すると、より深い背景情報を得られます。