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

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

コミット

commit d888ab80a308e30b326a3303cc8c611ca22b9988
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Jan 19 10:19:33 2012 -0800

    testing: do not recover example's panic
            So as to give out stack trace for panic in examples.
            This behavior also matches the tests'.
            Fixes #2691.
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/5554061

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

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

元コミット内容

testing: do not recover example's panic
        So as to give out stack trace for panic in examples.
        This behavior also matches the tests'.
        Fixes #2691.

変更の背景

このコミットの主な目的は、Go言語のtestingパッケージにおけるExample(例)コードの実行中に発生したパニック(panic)の挙動を変更することです。以前の挙動では、Exampleコード内でパニックが発生した場合、testingパッケージがそのパニックを捕捉(recover)し、スタックトレースを出力せずにプログラムを終了させていました。

しかし、これは通常のテスト(Test)関数がパニックを捕捉せず、スタックトレースを伴って終了する挙動とは異なっていました。開発者にとって、Exampleコードのパニック時にスタックトレースが得られないことは、デバッグを困難にする要因となっていました。特に、Exampleコードはドキュメントの一部としても機能するため、その正確性と堅牢性は重要です。

この不整合を解消し、Exampleコードのデバッグを容易にするために、Exampleのパニックを捕捉するrecover処理を削除し、テストと同様にパニックがそのまま伝播してスタックトレースが出力されるように変更されました。これにより、Fixes #2691で言及されている問題が解決されました。

前提知識の解説

Go言語のpanicrecover

Go言語には、予期せぬエラーや回復不可能な状況を扱うためのメカニズムとしてpanicrecoverがあります。

  • panic: プログラムの実行を中断し、現在のゴルーチン(goroutine)のスタックを巻き戻しながら、遅延関数(deferred function)を実行します。パニックがどこでも捕捉されなければ、プログラムは異常終了し、スタックトレースが出力されます。これは、C++の例外やJavaの未捕捉例外に似ています。
  • recover: 遅延関数内で呼び出された場合、パニックを捕捉し、パニックが発生した時点からプログラムの実行を再開させることができます。recovernilでない値を返した場合、パニックが捕捉されたことを意味します。recoverは、パニックによってプログラム全体がクラッシュするのを防ぎ、エラーハンドリングの機会を提供するために使用されます。

Go言語のtestingパッケージ

Go言語の標準ライブラリであるtestingパッケージは、ユニットテスト、ベンチマークテスト、およびExampleコードを記述するためのフレームワークを提供します。

  • テスト関数(Test functions): func TestXxx(*testing.T)というシグネチャを持つ関数で、コードの特定の単位が正しく動作するかを検証します。テスト中にエラーが発生した場合、t.Errort.Fatalなどのメソッドを使用して報告します。
  • Example関数(Example functions): func ExampleXxx()というシグネチャを持つ関数で、コードの使用例を示します。これらの関数は、Goのドキュメント生成ツールによって自動的に抽出され、生成されたドキュメントに表示されます。Example関数は、その出力がコメントとして記述された期待される出力と一致するかどうかをgo testコマンドによって検証されます。これにより、ドキュメントのコード例が常に最新かつ正確であることが保証されます。

このコミットが対象としているのは、このExample関数の実行時のパニック挙動です。

技術的詳細

このコミットの技術的な変更は非常にシンプルですが、その影響は重要です。変更はsrc/pkg/testing/example.goファイル内のRunExamples関数にあります。

以前のコードでは、RunExamples関数内にdeferステートメントで囲まれたrecover()呼び出しが存在していました。このdefer関数は、Example関数が実行中にパニックを起こした場合にそのパニックを捕捉し、エラーメッセージとパニックの値を標準出力に表示した後、os.Exit(1)を呼び出してプログラムを終了させていました。

// 変更前のコードの一部
func RunExamples(examples []InternalExample) (ok bool) {
    var eg InternalExample

    stdout, stderr := os.Stdout, os.Stderr
    defer func() {
        os.Stdout, os.Stderr = stdout, stderr
        if e := recover(); e != nil {
            fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e)
            os.Exit(1)
        }
    }()

    // ... Example関数の実行ロジック ...
}

このコミットでは、上記のdeferブロック全体が削除されました。

// 変更後のコードの一部
func RunExamples(examples []InternalExample) (ok bool) {
    var eg InternalExample

    stdout, stderr := os.Stdout, os.Stderr
    // defer func() { ... }() // このブロックが削除された

    // ... Example関数の実行ロジック ...
}

この変更により、Example関数内でパニックが発生した場合、RunExamples関数はもはやそのパニックを捕捉しなくなります。結果として、パニックはtestingフレームワークのさらに上位の層、あるいは最終的にはGoランタイムにまで伝播し、通常のパニックと同様にスタックトレースを伴ってプログラムが異常終了するようになります。

この挙動は、Goのテスト関数(TestXxx)がパニックを捕捉しない挙動と一致します。テスト関数内でパニックが発生した場合も、スタックトレースが出力され、テストスイート全体が失敗します。この変更により、ExampleとTestのパニック処理の一貫性が保たれ、開発者がExampleコードのバグをデバッグする際に、より詳細な情報(スタックトレース)を得られるようになりました。

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

--- a/src/pkg/testing/example.go
+++ b/src/pkg/testing/example.go
@@ -25,13 +25,6 @@ func RunExamples(examples []InternalExample) (ok bool) {
 	var eg InternalExample
 
 	stdout, stderr := os.Stdout, os.Stderr
-\tdefer func() {\n-\t\tos.Stdout, os.Stderr = stdout, stderr\n-\t\tif e := recover(); e != nil {\n-\t\t\tfmt.Printf(\"--- FAIL: %s\\npanic: %v\\n\", eg.Name, e)\n-\t\t\tos.Exit(1)\n-\t\t}\n-\t}()
-\n \tfor _, eg = range examples {\n \t\tif *chatty {\n

このdiffは、src/pkg/testing/example.goファイルから7行が削除されたことを示しています。削除されたのは、RunExamples関数内のdeferブロックであり、このブロックがExample実行中のパニックを捕捉し、処理していました。

コアとなるコードの解説

削除されたコードは以下の部分です。

	defer func() {
		os.Stdout, os.Stderr = stdout, stderr
		if e := recover(); e != nil {
			fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e)
			os.Exit(1)
		}
	}()

このdefer関数は、RunExamples関数が終了する直前(正常終了またはパニックによる終了)に実行されます。

  1. os.Stdout, os.Stderr = stdout, stderr: これは、Exampleの実行中にリダイレクトされていた標準出力と標準エラー出力を元の状態に戻すためのものです。Exampleの出力は通常、捕捉されて期待される出力と比較されるため、一時的にリダイレクトされます。
  2. if e := recover(); e != nil: ここがパニック処理の核心です。recover()が呼び出され、もしパニックが発生していればその値(e)が返されます。e != nilの場合、パニックが捕捉されたことを意味します。
  3. fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e): パニックが捕捉された場合、Exampleの名前とパニックの値を含むエラーメッセージが標準出力に表示されます。
  4. os.Exit(1): プログラムを終了コード1(エラー)で強制終了させます。

このdeferブロックが削除されたことにより、Example関数内で発生したパニックは、もはやtestingパッケージによって捕捉されなくなりました。その結果、パニックはGoランタイムに伝播し、ランタイムが提供するデフォルトのパニック処理(スタックトレースの出力とプログラムの異常終了)が適用されるようになります。

これにより、Exampleコードのデバッグ時に、どのコードパスでパニックが発生したかを特定するための重要な情報であるスタックトレースが利用可能になり、開発体験が向上しました。

関連リンク

  • Go issue #2691: このコミットが修正したとされるGitHub issueへのリンク。Goの公式リポジトリでこの番号のissueを検索することで、より詳細な議論や背景情報が見つかる可能性があります。
  • Go CL 5554061: このコミットに対応するGerrit Code Reviewのチェンジリスト。

参考にした情報源リンク

  • Go言語の公式ドキュメント(panicrecovertestingパッケージに関するセクション)
  • Go言語のソースコード(src/pkg/testing/example.go
  • コミットメッセージ自体
  • Go言語のテストに関する一般的な知識