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

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

このコミットは、Go言語の標準ライブラリであるtestingパッケージにおけるパニック(panic)のハンドリングを改善するものです。具体的には、テスト実行中に発生したパニックを捕捉し、そのスタックトレースをruntime/debugパッケージを使用して整形し、テストログに出力するように変更しています。これにより、テスト失敗時のデバッグ情報がより詳細になります。

コミット

  • コミットハッシュ: 8bcfad269e0326004dce1d9bd77e6a4f6d0468e4
  • Author: Russ Cox rsc@golang.org
  • Date: Sun Feb 12 23:41:07 2012 -0500
  • コミットメッセージ:
    testing: use runtime/debug to format panics
    
    Sorry, Mercurial failed me.
    
    TBR=r, dsymonds
    CC=golang-dev
    https://golang.org/cl/5649080
    

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

https://github.com/golang/go/commit/8bcfad269e0326004dce1d9bd77e6a4f6d0468e4

元コミット内容

testing: use runtime/debug to format panics

Sorry, Mercurial failed me.

TBR=r, dsymonds
CC=golang-dev
https://golang.org/cl/5649080

変更の背景

このコミットの背景には、Goのテストフレームワークがテスト実行中に発生したパニックを適切に処理し、デバッグに役立つ情報を提供する必要性がありました。コミットメッセージにある「Sorry, Mercurial failed me.」という記述は、この変更が以前のバージョン管理システム(Mercurial)での問題、例えばコミットの取り消しや不完全な適用などによって、一度失われたか、正しく適用されなかった変更を再適用するものであることを示唆しています。

具体的には、以前のコードではテスト中のパニックを捕捉するロジックがif falseという条件で囲まれており、実質的に無効化されていました。これにより、テスト中にパニックが発生しても、その詳細なスタックトレースがログに出力されず、デバッグが困難になる可能性がありました。このコミットは、この無効化されたパニックハンドリングを有効にし、runtime/debug.Stack()を用いて整形されたスタックトレースをログに出力することで、テストの堅牢性とデバッグのしやすさを向上させることを目的としています。

前提知識の解説

Goにおけるpanicrecover

Go言語には、例外処理のメカニズムとしてpanicrecoverがあります。

  • panic: プログラムの実行を即座に停止させ、現在のゴルーチン(goroutine)のスタックを巻き戻し(unwind)ながら、遅延実行関数(defer関数)を順次実行します。panicは、通常、回復不能なエラーやプログラマの論理的な誤りなど、プログラムがこれ以上続行できない状況で使用されます。
  • recover: defer関数内で呼び出された場合にのみ機能し、panicによって発生したパニックを捕捉し、そのパニック値を返します。recoverがパニックを捕捉すると、スタックの巻き戻しは停止し、プログラムの実行はrecoverが呼び出されたdefer関数の直後から再開されます。これにより、プログラムのクラッシュを防ぎ、エラーハンドリングの機会を提供できます。

Goのtestingパッケージ

testingパッケージは、Go言語に組み込まれているテストフレームワークです。ユニットテスト、ベンチマークテスト、例(Example)テストなどを記述・実行するための機能を提供します。

  • *testing.T: 各テスト関数に渡される型で、テストの状態管理、エラー報告(t.Error, t.Errorf, t.Fatal, t.Fatalf)、ログ出力(t.Log, t.Logf)などのメソッドを提供します。
  • tRunner関数: testingパッケージの内部関数で、個々のテストを実行するゴルーチンを管理します。この関数内でテストのセットアップ、実行、クリーンアップ、そしてパニックのハンドリングが行われます。

runtime/debug.Stack()

runtime/debugパッケージは、Goプログラムのデバッグ情報にアクセスするための機能を提供します。

  • debug.Stack(): 現在のゴルーチンのスタックトレースをバイトスライスとして返します。このスタックトレースには、関数呼び出しの履歴、ファイル名、行番号などが含まれており、プログラムがどのコードパスを通って現在の状態に至ったかを詳細に把握するのに非常に役立ちます。特にパニック発生時に呼び出すことで、パニックがどこで発生し、どのような呼び出しスタックを経て発生したかを特定できます。

技術的詳細

このコミットの技術的な核心は、src/pkg/testing/testing.goファイルのtRunner関数内のdeferブロックの変更にあります。

tRunner関数は、各テストケースを新しいゴルーチンで実行し、その実行結果を管理します。テスト中にパニックが発生した場合、そのパニックを捕捉し、テストの失敗として記録し、詳細なデバッグ情報をログに出力することが重要です。

変更前のコードでは、パニックを捕捉するrecover()とスタックトレースを出力するdebug.Stack()のロジックが、if falseという条件文で囲まれていました。これは、コンパイル時に常に偽となる条件であるため、このコードブロックは決して実行されず、パニックハンドリングのロジックが無効化されている状態でした。

// 変更前
defer func() {
    if false { // この条件により、以下のコードは実行されない
        // Log and recover from panic instead of aborting binary.
        if err := recover(); err != nil {
            t.failed = true
            t.Logf("%s\n%s", err, debug.Stack())
        }
    }
    // ...
}

このコミットでは、このif falseの条件文が削除されました。これにより、defer関数内でrecover()が呼び出され、テスト中に発生したパニックが捕捉されるようになります。パニックが捕捉された場合(err != nil)、テストは失敗としてマークされ(t.failed = true)、パニック値とdebug.Stack()によって取得された詳細なスタックトレースがt.Logfを通じてテストログに出力されます。

// 変更後
defer func() {
    // Log and recover from panic instead of aborting binary.
    if err := recover(); err != nil { // if false が削除され、常に実行される
        t.failed = true
        t.Logf("%s\n%s", err, debug.Stack())
    }
    // ...
}

この変更により、テスト実行中に予期せぬパニックが発生した場合でも、プログラム全体がクラッシュすることなく、テストフレームワークがそのパニックを捕捉し、どこで何が起こったのかを示す詳細なスタックトレースをテスト結果として提供できるようになります。これは、テストの信頼性を高め、開発者が問題を迅速に特定し修正する上で非常に重要な改善です。

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

--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -248,12 +248,10 @@ func tRunner(t *T, test *InternalTest) {
 	// a call to runtime.Goexit, record the duration and send
 	// a signal saying that the test is done.
 	defer func() {
-		if false {
-			// Log and recover from panic instead of aborting binary.
-			if err := recover(); err != nil {
-				t.failed = true
-				t.Logf("%s\n%s", err, debug.Stack())
-			}
+		// Log and recover from panic instead of aborting binary.
+		if err := recover(); err != nil {
+			t.failed = true
+			t.Logf("%s\n%s", err, debug.Stack())
 		}
 
 		t.duration = time.Now().Sub(t.start)

コアとなるコードの解説

上記の差分は、src/pkg/testing/testing.goファイル内のtRunner関数におけるdeferブロックの変更を示しています。

  • - if false {: 削除された行です。この行が存在したことにより、その後のパニックハンドリングのロジックが常にスキップされ、無効化されていました。
  • - // Log and recover from panic instead of aborting binary.: 削除されたコメント行です。これはif falseブロック内にあったため、実質的に意味をなしていませんでした。
  • - if err := recover(); err != nil {: 削除された行です。この行もif falseブロック内にありました。
  • - t.failed = true: 削除された行です。この行もif falseブロック内にありました。
  • - t.Logf("%s\\n%s", err, debug.Stack()): 削除された行です。この行もif falseブロック内にありました。
  • - }: 削除された行です。if falseブロックの閉じ括弧です。
  • + // Log and recover from panic instead of aborting binary.: 新しく追加されたコメント行です。これは、パニックを捕捉してログに記録する意図を明確に示しています。if falseが削除されたことで、このコメントの意図が実際にコードに反映されるようになりました。
  • + if err := recover(); err != nil {: 新しく追加された行です。以前はif falseの中にあったこの行が、deferブロックの直下に移動しました。これにより、tRunnerゴルーチンがパニックを起こした場合、recover()がそのパニックを捕捉し、err変数にパニック値が代入されます。
  • + t.failed = true: 新しく追加された行です。パニックが捕捉された場合、現在のテスト(t)が失敗したことを示すフラグt.failedtrueに設定されます。
  • + t.Logf("%s\\n%s", err, debug.Stack()): 新しく追加された行です。パニックが捕捉された場合、t.Logfを使用してテストログに情報が出力されます。出力される情報は、パニック値(%sでフォーマット)と、debug.Stack()によって取得された現在のゴルーチンのスタックトレース(%sでフォーマット)です。スタックトレースは、パニックがどこで発生したかを特定するのに非常に役立ちます。
  • + }: 新しく追加された行です。if err := recover(); err != nilブロックの閉じ括弧です。

この変更により、testingパッケージはテスト中のパニックを適切に捕捉し、詳細なデバッグ情報をテスト結果に含めることができるようになり、テストの信頼性とデバッグの効率が大幅に向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • GitHubのGoリポジトリのコミット履歴
  • Go言語のpanicrecoverに関する一般的な解説記事
  • Go言語のtestingパッケージに関する一般的な解説記事
  • Go言語のruntime/debug.Stack()に関する一般的な解説記事