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

[インデックス 17542] ファイルの概要

このコミットは、Go言語のコマンドラインツール go test の振る舞いを改善するものです。具体的には、テストのセットアップ中にエラーが発生した場合(例えば、x_test.go ファイルに構文エラーがある場合)に、FAIL という明確なメッセージを出力するように変更されています。これにより、ビルド失敗時と同様に、テスト関連のあらゆる失敗が統一された形式で報告されるようになります。

コミット

commit baed067d879410951279feeebc0ef67702415ffd
Author: Russ Cox <rsc@golang.org>
Date:   Tue Sep 10 14:43:57 2013 -0400

    cmd/go: show FAIL for errors during test setup
    
    For example, if an x_test.go file contains a syntax error,
    b.test fails with an error message. But it wasn't printing
    the same FAIL line that a build failure later would print.
    This makes all the test failures that happen (once we
    decide to start running tests) consistently say FAIL.
    
    Fixes #4701.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/13431044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/baed067d879410951279feeebc0ef67702415ffd

元コミット内容

cmd/go: show FAIL for errors during test setup

このコミットは、テストのセットアップ中に発生するエラーに対して FAIL メッセージを表示するように cmd/go ツールを修正します。例えば、x_test.go ファイルに構文エラーがある場合、go test はエラーメッセージを出力しますが、その際にビルド失敗時と同じ FAIL 行が表示されていませんでした。この変更により、テスト実行が開始されると判断された後に発生するすべてのテスト失敗が、一貫して FAIL と表示されるようになります。

この変更は Issue #4701 を修正します。

変更の背景

Go言語のテストツール go test は、開発者がコードの正確性を検証するための重要な手段です。しかし、テストの実行プロセスにおいて、テストコード自体の問題(例えば構文エラー)によってテストが開始される前に失敗するケースがありました。

従来の go test の挙動では、テスト対象のパッケージのビルドが成功し、テストバイナリが生成された後にテストが失敗した場合(例えば、テスト関数内でアサーションが失敗した場合)には、明確に FAIL というステータスが表示されていました。しかし、テストバイナリのビルド自体が失敗するような、より早い段階でのエラー(例: _test.go ファイル内の構文エラー)の場合、エラーメッセージは表示されるものの、統一された FAIL というステータス表示がありませんでした。

この不整合は、CI/CDパイプラインやスクリプトで go test の出力をパースしてテストの成否を判断する際に問題となる可能性がありました。また、開発者がテスト結果を一目で把握する上でも、一貫性のない出力は混乱を招く要因となります。

このコミットは、この出力の不整合を解消し、テストのセットアップ段階で発生するエラーも、テスト実行中のエラーと同様に FAIL という統一されたステータスで報告することで、go test の出力の一貫性と信頼性を向上させることを目的としています。これにより、テストの失敗原因がどこにあっても、開発者は FAIL というキーワードによってテストが失敗したことを即座に認識できるようになります。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびテストに関する基本的な知識が必要です。

  1. go test コマンド:

    • Go言語に組み込まれているテスト実行ツールです。
    • カレントディレクトリまたは指定されたパッケージ内の _test.go で終わるファイルを自動的に探し、テスト関数(TestXxx)、ベンチマーク関数(BenchmarkXxx)、サンプル関数(ExampleXxx)を実行します。
    • テストの成功/失敗、実行時間などの情報を標準出力に表示します。
    • 通常、テストが成功すると PASS、失敗すると FAIL と表示されます。
  2. Goパッケージとビルドプロセス:

    • Goのコードはパッケージとして組織されます。
    • go test は、テストを実行する前に、テスト対象のパッケージとテストコードをコンパイルして実行可能なテストバイナリを生成します。
    • このコンパイルプロセス中に構文エラーや型エラーなどのビルドエラーが発生することがあります。
  3. テストのライフサイクル:

    • go test の実行は、大きく分けて以下のフェーズで構成されます。
      1. パッケージの解決と依存関係の確認: テスト対象のパッケージとテストファイルの特定。
      2. コンパイル(ビルド): テスト対象のソースコードと _test.go ファイルをコンパイルし、テストバイナリを生成。この段階で構文エラーなどがあるとビルドが失敗します。
      3. テストバイナリの実行: 生成されたテストバイナリを実行し、テスト関数を呼び出します。
      4. 結果の集計と出力: テスト関数の実行結果を収集し、PASS または FAIL を含む最終的なレポートを出力します。
  4. エラーハンドリングと出力の一貫性:

    • CLIツールにおいて、異なる種類の失敗に対して一貫した出力形式を提供することは、自動化スクリプトでのパースやユーザーの理解を助ける上で非常に重要です。
    • 特に、FAIL のような明確なキーワードは、テストの成否を判断するシグナルとして広く利用されます。

このコミットは、上記の「コンパイル(ビルド)」フェーズで発生するエラーの出力形式を、「テストバイナリの実行」フェーズで発生するエラーの出力形式と統一することを目指しています。

技術的詳細

このコミットの技術的詳細は、go test コマンドの内部処理、特にテストバイナリのビルドとエラー報告のメカニズムに焦点を当てています。

変更は主に src/cmd/go/test.go ファイルの runTest 関数にあります。この関数は go test コマンドのテスト実行ロジックの中核を担っています。

コミット前の挙動では、go test がテストバイナリをビルドする際に構文エラーなどの問題が発生した場合、b.test (テストバイナリ) の実行が失敗し、エラーメッセージが標準エラー出力に表示されていました。しかし、このエラーメッセージには、テスト実行後の失敗時に表示されるような FAIL という明確なステータス行が含まれていませんでした。

このコミットでは、runTest 関数内でエラーが発生し、それがテストのセットアップ段階(ビルドエラーなど)によるものであると判断された場合に、出力されるエラーメッセージに FAIL という文字列を追加するように修正されています。

具体的には、runTest 関数内のエラー処理ブロックにおいて、errorf 関数(Goコマンドのエラー報告ユーティリティ)の呼び出しが変更されています。

  • 変更前:

    if p.ImportPath != "" {
        errorf("# %s\n%s", p.ImportPath, str)
    } else {
        errorf("%s", str)
    }
    

    ここでは、インポートパスがあるかないかで異なる形式のエラーメッセージを出力していますが、いずれも FAIL というキーワードは含まれていません。

  • 変更後:

    failed := fmt.Sprintf("FAIL\t%s [setup failed]\n", p.ImportPath)
    
    if p.ImportPath != "" {
        errorf("# %s\n%s\n%s", p.ImportPath, str, failed)
    } else {
        errorf("%s\n%s", str, failed)
    }
    

    変更後では、まず failed という文字列変数に FAIL\t%s [setup failed]\n という形式で FAIL ステータスを含むメッセージを生成しています。そして、この failed 文字列を既存のエラーメッセージの末尾に追加して出力するように errorf の引数が変更されています。これにより、テストのセットアップ段階でのエラーであっても、最終的な出力には FAIL が含まれるようになります。

また、この変更を検証するために、src/cmd/go/test.bash に新しいテストケースが追加されています。このテストケースは、意図的に構文エラーを含む x_test.go ファイル(src/cmd/go/testdata/src/syntaxerror/x_test.go)を作成し、go test syntaxerror を実行します。そして、その標準エラー出力に FAIL という文字列が含まれていることを grep コマンドで確認しています。これにより、期待通りの出力が得られることが保証されます。

この修正は、Goツールのユーザーエクスペリエンスを向上させ、自動化された環境でのテスト結果のパースを容易にするための、細かではあるが重要な改善と言えます。

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

変更は主に以下の2つのファイルに集中しています。

  1. src/cmd/go/test.go: go test コマンドの主要なロジックが含まれるファイル。エラーメッセージの出力形式が変更されています。
  2. src/cmd/go/test.bash: go コマンドのテストスクリプト。今回の変更を検証するための新しいテストケースが追加されています。
  3. src/cmd/go/testdata/src/syntaxerror/x.go: 新しいテストケースで使用される、構文エラーを含むテストデータ用のGoソースファイル。
  4. src/cmd/go/testdata/src/syntaxerror/x_test.go: 新しいテストケースで使用される、構文エラーを含むテストデータ用のGoテストファイル。
diff --git a/src/cmd/go/test.bash b/src/cmd/go/test.bash
index b55989c207..52d2f08337 100755
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -104,6 +104,19 @@ cp -R testdata/local \"testdata/$bad\"\n testlocal \"$bad\" \'with bad characters in path\'\n rm -rf \"testdata/$bad\"\n \n+TEST error message for syntax error in test go file says FAIL\n+export GOPATH=$(pwd)/testdata\n+if ./testgo test syntaxerror 2>testdata/err; then\n+\techo \'go test syntaxerror succeeded\'\n+\tok=false\n+elif ! grep FAIL testdata/err >/dev/null; then\n+\techo \'go test did not say FAIL:\'\n+\tcat testdata/err\n+\tok=false\n+fi\n+rm -f ./testdata/err\n+unset GOPATH\n+\n # Test tests with relative imports.\n TEST relative imports \'(go test)\'\n if ! ./testgo test ./testdata/testimport; then
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index ebc9d28548..eab075db7c 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -423,10 +423,12 @@ func runTest(cmd *Command, args []string) {\n \t\t\tif strings.HasPrefix(str, \"\\n\") {\n \t\t\t\tstr = str[1:]\n \t\t\t}\n+\t\t\tfailed := fmt.Sprintf(\"FAIL\\t%s [setup failed]\\n\", p.ImportPath)\n+\n \t\t\tif p.ImportPath != \"\" {\n-\t\t\t\terrorf(\"# %s\\n%s\", p.ImportPath, str)\n+\t\t\t\terrorf(\"# %s\\n%s\\n%s\", p.ImportPath, str, failed)\n \t\t\t} else {\n-\t\t\t\terrorf(\"%s\", str)\n+\t\t\t\terrorf(\"%s\\n%s\", str, failed)\n \t\t\t}\n \t\t\tcontinue\n \t\t}\
diff --git a/src/cmd/go/testdata/src/syntaxerror/x.go b/src/cmd/go/testdata/src/syntaxerror/x.go
new file mode 100644
index 0000000000..c89cd18d0f\n--- /dev/null
+++ b/src/cmd/go/testdata/src/syntaxerror/x.go
@@ -0,0 +1 @@
+package p
diff --git a/src/cmd/go/testdata/src/syntaxerror/x_test.go b/src/cmd/go/testdata/src/syntaxerror/x_test.go
new file mode 100644
index 0000000000..2460743e50
--- /dev/null
+++ b/src/cmd/go/testdata/src/syntaxerror/x_test.go
@@ -0,0 +1,4 @@
+package p
+\n+func f() (x.y, z int) {\n+}\n

コアとなるコードの解説

src/cmd/go/test.go の変更

runTest 関数内のエラー処理部分が変更されています。

  • 変更前: エラーメッセージは errorf 関数によって出力されていましたが、FAIL というキーワードは含まれていませんでした。インポートパスの有無によって出力形式が異なっていました。

    // 既存のエラーメッセージ出力ロジック
    if p.ImportPath != "" {
        errorf("# %s\n%s", p.ImportPath, str)
    } else {
        errorf("%s", str)
    }
    
  • 変更後:

    1. failed := fmt.Sprintf("FAIL\t%s [setup failed]\n", p.ImportPath): まず、FAIL という文字列と、インポートパス、そして [setup failed] という説明を含む新しいエラーメッセージの行を生成しています。fmt.Sprintf を使用してフォーマットされた文字列を作成しています。\t はタブ文字、\n は改行文字です。
    2. if p.ImportPath != "" { errorf("# %s\n%s\n%s", p.ImportPath, str, failed) }: インポートパスが存在する場合、元のエラーメッセージ(# %s\n%s)に加えて、新しく生成した failed メッセージを改行で区切って追加しています。これにより、元のエラー詳細の後に FAIL ステータスが表示されます。
    3. else { errorf("%s\n%s", str, failed) }: インポートパスが存在しない場合も同様に、元のエラーメッセージ(%s)の後に failed メッセージを追加しています。

この変更により、テストのセットアップ段階で発生したエラー(例えば、テストファイルのコンパイルエラー)であっても、go test の出力の最後に FAIL という明確なステータス行が追加されるようになります。これにより、テストの成否を判断する際の出力の一貫性が保たれます。

src/cmd/go/test.bash の変更

新しいテストケースが追加され、go test が構文エラーのあるテストファイルに対して FAIL を出力するかどうかを検証しています。

  • TEST error message for syntax error in test go file says FAIL: テストケースの目的を説明するコメント。
  • export GOPATH=$(pwd)/testdata: テスト用の GOPATH を設定し、テストデータディレクトリを使用するようにします。
  • if ./testgo test syntaxerror 2>testdata/err; then ...:
    • ./testgo test syntaxerror: go コマンドのテスト版(testgo)を使って、syntaxerror パッケージのテストを実行します。
    • 2>testdata/err: 標準エラー出力を testdata/err ファイルにリダイレクトします。ビルドエラーは通常、標準エラー出力に出力されます。
    • if ...; then echo 'go test syntaxerror succeeded'; ok=false: go test が成功してしまった場合(期待しない挙動)、エラーメッセージを出力し、テストを失敗とマークします。
  • elif ! grep FAIL testdata/err >/dev/null; then ...:
    • ! grep FAIL testdata/err >/dev/null: testdata/err ファイルに FAIL という文字列が含まれていない場合(期待しない挙動)、エラーメッセージを出力し、テストを失敗とマークします。>/dev/nullgrep の出力を抑制します。
    • echo 'go test did not say FAIL:'cat testdata/err: FAIL が見つからなかった場合、その旨を報告し、エラーファイルの内容を表示してデバッグを助けます。
  • rm -f ./testdata/err: テスト後に一時ファイルを削除します。
  • unset GOPATH: 設定した GOPATH を解除します。

このテストケースは、src/cmd/go/testdata/src/syntaxerror/x_test.go に意図的に構文エラー(x.y という無効な型宣言)を挿入することで、テストのセットアップ段階でのエラーをシミュレートし、その際に FAIL が出力されることを確認しています。

src/cmd/go/testdata/src/syntaxerror/x.go および x_test.go

これらは、上記のテストケースで使用されるダミーのGoソースファイルとテストファイルです。

  • x.go:

    package p
    

    これは単に p パッケージを定義するファイルです。

  • x_test.go:

    package p
    
    func f() (x.y, z int) {
    }
    

    このファイルには x.y という構文エラーが含まれています。x.y はGo言語の有効な型名ではありません。このエラーにより、go test はテストバイナリのビルドに失敗し、今回のコミットで修正されたエラーパスがトリガーされます。

これらの変更は、go test の堅牢性とユーザーフレンドリーさを向上させるための、細部にわたる配慮を示しています。

関連リンク

参考にした情報源リンク