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

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

このコミットは、Go言語のコマンドラインツール go における go test コマンドの挙動を改善するものです。具体的には、テスト実行時に発生したエラーが複数ある場合、これまでは最初に見つかったエラーのみが表示されていましたが、この変更により複数のエラー(最大10個)が表示されるようになります。

コミット

commit 548591b77d115a557e8e6351b78b96831002b306
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 22 22:33:45 2012 -0800

    go cmd: print more than one error when running go test
    
    Fixes #3055.
    
    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/5683079

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

https://github.com/golang/go/commit/548591b77d115a557e8e6351b78b96831002b306

元コミット内容

go cmd: print more than one error when running go test Fixes #3055.

このコミットは、go test コマンドがテスト実行時に複数のエラーを報告するように変更します。これは、Go Issue #3055 を解決するためのものです。

変更の背景

Go言語の初期のツールチェインでは、go test コマンドがテストのビルドや実行中にエラーを検出した場合、通常は最初に見つかったエラーのみを報告し、それ以上処理を続行しない傾向がありました。これは、開発者がコードの問題を特定し、修正する際に非効率的であるという問題を引き起こしていました。例えば、構文エラーが複数箇所にある場合、一つ修正するたびに再度 go test を実行し、次のエラーを発見するという手間が発生していました。

Issue #3055 は、この「単一エラー報告」の挙動に対する不満を表明しており、より多くのエラー情報を一度に提供することで、開発者のデバッグ体験を向上させることを目的としていました。特に、go/scanner パッケージが提供する ErrorList のような、複数のエラーをまとめて扱うことができるメカニズムが存在するにもかかわらず、それが活用されていない点が指摘されていました。

このコミットは、この問題を解決し、go test がよりユーザーフレンドリーなエラー報告を行うようにするためのものです。

前提知識の解説

  • go test コマンド: Go言語の標準的なテスト実行ツールです。Goのソースコード内のテスト関数(TestXxx で始まる関数)を検出し、ビルドし、実行します。テストの成功/失敗、カバレッジ情報などを報告します。
  • go/scanner パッケージ: Go言語のソースコードを字句解析(スキャン)する機能を提供するパッケージです。ソースコードをトークンに分割する過程で構文エラーなどを検出することがあります。
  • scanner.ErrorList: go/scanner パッケージが提供する型で、複数のスキャンエラーを保持するためのスライス(リスト)です。コンパイルエラーや構文エラーが複数ある場合に、これらをまとめて報告するために使用されます。
  • errorf 関数: Goコマンドラインツール内でエラーメッセージを出力するためのユーティリティ関数です。通常、標準エラー出力にメッセージを書き込み、プログラムの終了コードを非ゼロに設定します。
  • Goのビルドプロセス: Goのソースコードは、コンパイルされて実行可能なバイナリになります。この過程で、字句解析、構文解析、型チェックなどのフェーズがあり、それぞれでエラーが検出される可能性があります。go test も内部的にはテストコードをビルドするプロセスを含んでいます。

技術的詳細

この変更は、src/cmd/go/test.go ファイル内の runTest 関数に焦点を当てています。この関数は、Goパッケージのテストをビルドおよび実行する主要なロジックを含んでいます。

変更前は、b.test(p) から返されるエラー errnil でない場合、単に errorf("%s", err) を呼び出してエラーメッセージを出力し、次のパッケージの処理に移っていました。この挙動では、errscanner.ErrorList のインスタンスであったとしても、そのリストに含まれる個々のエラーは展開されず、単一のエラー文字列として扱われていました。

このコミットでは、以下のロジックが追加されています。

  1. if err != nil のブロック内で、まず errscanner.ErrorList 型に型アサーション可能かどうかをチェックします (if list, ok := err.(scanner.ErrorList); ok)。
  2. もし errscanner.ErrorList であった場合、そのエラーリスト list の要素数が n (ここでは 10 に設定されている) を超えているかをチェックします。
  3. もし要素数が n を超えていれば、リストを最初の n 個のエラーに切り詰めます (list = list[:n])。これにより、あまりにも大量のエラーが出力されてコンソールが溢れるのを防ぎます。
  4. 切り詰められた(または元々の)ErrorList の各エラーについて、ループ (for _, err := range list) を回し、個々のエラーを errorf("%s", err) を使って出力します。
  5. ErrorList のすべてのエラーを出力した後、continue ステートメントによって現在のパッケージの処理をスキップし、次のパッケージのテスト処理へと進みます。これにより、ErrorList が処理された後に、その下の一般的な errorf が再度呼び出されるのを防ぎます。

この変更により、go test は、特に構文解析や字句解析の段階で発生する複数のエラーを、より詳細に開発者に伝えることができるようになりました。

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

変更は src/cmd/go/test.go ファイルの runTest 関数内で行われています。

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -11,6 +11,7 @@ import (
  	"go/build"
  	"go/doc"
  	"go/parser"
+	"go/scanner"
  	"go/token"
  	"os"
  	"os/exec"
@@ -299,6 +300,16 @@ func runTest(cmd *Command, args []string) {
  	for _, p := range pkgs {
  		buildTest, runTest, printTest, err := b.test(p)
  		if err != nil {
+			if list, ok := err.(scanner.ErrorList); ok {
+				const n = 10
+				if len(list) > n {
+					list = list[:n]
+				}
+				for _, err := range list {
+					errorf("%s", err)
+				}
+				continue
+			}
  			errorf("%s", err)
  			continue
  		}

コアとなるコードの解説

  • import "go/scanner": scanner.ErrorList 型を使用するために、go/scanner パッケージがインポートに追加されています。
  • if list, ok := err.(scanner.ErrorList); ok { ... }:
    • これはGoの型アサーションの構文です。err インターフェース変数が scanner.ErrorList 型の具体的な値を持っているかどうかをチェックします。
    • ok はブール値で、型アサーションが成功したかどうかを示します。成功した場合、list には errscanner.ErrorList 型の値が代入されます。
  • const n = 10: 表示するエラーの最大数を定義しています。これにより、無限にエラーが出力されるのを防ぎ、コンソールの可読性を保ちます。
  • if len(list) > n { list = list[:n] }:
    • ErrorList に含まれるエラーの数が n (10) を超える場合、スライスを切り詰めて最初の n 個のエラーのみを保持するようにします。
  • for _, err := range list { errorf("%s", err) }:
    • scanner.ErrorList 内の各エラーをループで処理し、それぞれを errorf 関数を使って個別に標準エラー出力に表示します。これにより、複数のエラーメッセージがそれぞれ独立した行で表示され、開発者にとって読みやすくなります。
  • continue:
    • scanner.ErrorList が処理された後、この continue ステートメントが実行されます。これにより、現在のパッケージの残りの処理(buildTest, runTest, printTest の呼び出しなど)はスキップされ、for _, p := range pkgs ループの次のイテレーション(次のパッケージの処理)へと移ります。これは、ErrorList が検出された時点で、そのパッケージのテストビルドは失敗しているため、これ以上処理を続けても意味がないと判断されるためです。また、この continue がないと、ErrorList の処理後に、その下の一般的な errorf("%s", err) が再度呼び出されてしまい、同じエラーが二重に報告される可能性があります。

この変更は、Goツールのエラー報告の質を向上させ、開発者がより効率的に問題を特定し、修正できるようにするための重要な改善です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go test, go/scanner パッケージに関する情報)
  • Go言語のソースコード (特に src/cmd/go/test.go および go/scanner パッケージ)
  • Go Issue Tracker (Issue #3055 の議論)
  • Gerrit Code Review (CL 5683079 のレビューコメント)
  • Go言語の型アサーションに関するドキュメントやチュートリアル