[インデックス 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言語のpanic
とrecover
Go言語には、予期せぬエラーや回復不可能な状況を扱うためのメカニズムとしてpanic
とrecover
があります。
panic
: プログラムの実行を中断し、現在のゴルーチン(goroutine)のスタックを巻き戻しながら、遅延関数(deferred function)を実行します。パニックがどこでも捕捉されなければ、プログラムは異常終了し、スタックトレースが出力されます。これは、C++の例外やJavaの未捕捉例外に似ています。recover
: 遅延関数内で呼び出された場合、パニックを捕捉し、パニックが発生した時点からプログラムの実行を再開させることができます。recover
がnil
でない値を返した場合、パニックが捕捉されたことを意味します。recover
は、パニックによってプログラム全体がクラッシュするのを防ぎ、エラーハンドリングの機会を提供するために使用されます。
Go言語のtesting
パッケージ
Go言語の標準ライブラリであるtesting
パッケージは、ユニットテスト、ベンチマークテスト、およびExampleコードを記述するためのフレームワークを提供します。
- テスト関数(Test functions):
func TestXxx(*testing.T)
というシグネチャを持つ関数で、コードの特定の単位が正しく動作するかを検証します。テスト中にエラーが発生した場合、t.Error
やt.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
関数が終了する直前(正常終了またはパニックによる終了)に実行されます。
os.Stdout, os.Stderr = stdout, stderr
: これは、Exampleの実行中にリダイレクトされていた標準出力と標準エラー出力を元の状態に戻すためのものです。Exampleの出力は通常、捕捉されて期待される出力と比較されるため、一時的にリダイレクトされます。if e := recover(); e != nil
: ここがパニック処理の核心です。recover()
が呼び出され、もしパニックが発生していればその値(e
)が返されます。e != nil
の場合、パニックが捕捉されたことを意味します。fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e)
: パニックが捕捉された場合、Exampleの名前とパニックの値を含むエラーメッセージが標準出力に表示されます。os.Exit(1)
: プログラムを終了コード1(エラー)で強制終了させます。
このdefer
ブロックが削除されたことにより、Example関数内で発生したパニックは、もはやtesting
パッケージによって捕捉されなくなりました。その結果、パニックはGoランタイムに伝播し、ランタイムが提供するデフォルトのパニック処理(スタックトレースの出力とプログラムの異常終了)が適用されるようになります。
これにより、Exampleコードのデバッグ時に、どのコードパスでパニックが発生したかを特定するための重要な情報であるスタックトレースが利用可能になり、開発体験が向上しました。
関連リンク
- Go issue #2691: このコミットが修正したとされるGitHub issueへのリンク。Goの公式リポジトリでこの番号のissueを検索することで、より詳細な議論や背景情報が見つかる可能性があります。
- Go CL 5554061: このコミットに対応するGerrit Code Reviewのチェンジリスト。
参考にした情報源リンク
- Go言語の公式ドキュメント(
panic
とrecover
、testing
パッケージに関するセクション) - Go言語のソースコード(
src/pkg/testing/example.go
) - コミットメッセージ自体
- Go言語のテストに関する一般的な知識