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

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

このコミットは、Go言語のgo testコマンドにおけるExample関数の実行時の標準出力(stdout)と標準エラー出力(stderr)のキャプチャ方法を変更するものです。具体的には、Example関数の出力検証において、標準エラー出力のキャプチャを停止し、標準出力のみをキャプチャするように修正されました。これにより、Example関数が標準エラー出力に書き込んだ内容は、テストの出力比較の対象外となり、直接コンソールに出力されるようになります。また、このコミットには、go buildおよびgo testコマンドにいくつかの新しいフラグ(データ競合検出、コンパイラ引数、ベンチマークメモリプロファイリング、ゴルーチンブロッキングプロファイリングなど)を追加するドキュメントの更新も含まれています。

コミット

commit ff5d47ebbaad42862e97e93d46fc89c768c098a3
Author: Andrew Gerrand <adg@golang.org>
Date:   Thu Dec 20 10:48:33 2012 +1100

    testing: only capture stdout when running examples
    
    Fixes #4550.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6973048

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

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

元コミット内容

testing: only capture stdout when running examples

Fixes #4550.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6973048

変更の背景

この変更の背景には、Go言語のtestingパッケージにおけるExample関数の振る舞いの明確化と、テスト実行時の出力管理の改善があります。Example関数は、Goのドキュメント生成ツール(go doc)によって自動的にテストされ、その出力が期待される出力(Output:コメントで指定)と一致するかどうかが検証されます。

以前の実装では、Example関数が実行される際に、標準出力(os.Stdout)と標準エラー出力(os.Stderr)の両方がキャプチャされ、その内容がOutput:コメントと比較されていました。しかし、Example関数の主な目的は、コードの正しい使用方法を標準出力で示すことであり、標準エラー出力は通常、エラーメッセージやデバッグ情報のために使用されます。標準エラー出力の内容までOutput:コメントで検証しようとすると、テストの記述が複雑になったり、意図しないエラーメッセージがテストを失敗させたりする可能性がありました。

コミットメッセージにある "Fixes #4550" は、この問題に関連する内部的な課題追跡システムのエントリを指しています。この変更は、Example関数のテストがより直感的で、その本来の目的に合致するように、標準出力のみを検証対象とすることで、この問題を解決することを目的としています。これにより、開発者はExample関数をより簡単に記述し、その出力が期待通りであることを確認できるようになります。

また、このコミットには、go buildおよびgo testコマンドの機能拡張として、データ競合検出、コンパイラ引数指定、ベンチマークのメモリ割り当て統計、ゴルーチンブロッキングプロファイリングといった、より高度な開発・デバッグ支援機能に関するドキュメントの追加も含まれています。これらは、Goアプリケーションのパフォーマンス分析やデバッグ能力を向上させるための重要な機能です。

前提知識の解説

このコミットの理解を深めるために、以下の前提知識が役立ちます。

  1. Go言語のtestingパッケージ:
    • Go言語には、ユニットテスト、ベンチマークテスト、そしてExample関数をサポートする標準ライブラリtestingパッケージが組み込まれています。
    • go testコマンドは、このパッケージを利用してテストを実行します。
  2. Example関数:
    • Example関数は、func ExampleName()のような形式で定義され、go testコマンドによって実行されます。
    • これらの関数は、コードの利用例を示すために使用され、通常はfmt.Printlnなどを用いて標準出力に結果を出力します。
    • Example関数の本体の最後のコメント行にOutput:というプレフィックスを付けて期待される出力を記述することで、go testは実行時の標準出力とこのコメントの内容を比較し、一致すればテストが成功したとみなします。
    • 例:
      func ExampleHello() {
          fmt.Println("hello")
          // Output: hello
      }
      
  3. 標準出力(stdout)と標準エラー出力(stderr):
    • ほとんどのオペレーティングシステムにおいて、プログラムはデフォルトで2つの主要な出力ストリームを持っています。
      • 標準出力 (stdout): プログラムの通常の出力(結果、情報メッセージなど)が書き込まれる場所です。Goではos.Stdoutを通じてアクセスできます。
      • 標準エラー出力 (stderr): プログラムのエラーメッセージや診断情報が書き込まれる場所です。Goではos.Stderrを通じてアクセスできます。
    • これらは通常、ターミナルやコンソールに表示されますが、ファイルにリダイレクトすることも可能です。
  4. os.Pipe():
    • Go言語のosパッケージにあるPipe()関数は、読み取り側と書き込み側の2つの*os.Fileを返します。これらはパイプを形成し、一方に書き込まれたデータがもう一方から読み取れるようになります。
    • テストフレームワークでは、プログラムの標準出力や標準エラー出力を一時的にこのパイプにリダイレクトすることで、その出力をキャプチャし、後で検証するために使用します。
  5. go buildおよびgo testコマンドのフラグ:
    • goコマンドは、ビルド、テスト、ドキュメント生成など、Goプロジェクトを管理するための多機能なツールです。
    • これらのコマンドには、その動作を制御するための様々なコマンドラインフラグが用意されています。例えば、-vは詳細な出力を表示し、-raceはデータ競合検出を有効にします。

技術的詳細

このコミットの技術的な核心は、src/pkg/testing/example.goファイル内のRunExamples関数の変更にあります。この関数は、go testコマンドがExample関数を実行する際に呼び出され、その出力をキャプチャして検証する役割を担っています。

変更前は、RunExamples関数内でos.Stdoutos.Stderrの両方が一時的にパイプにリダイレクトされていました。これは、Example関数が標準出力と標準エラー出力のどちらに書き込んでも、その内容をキャプチャしてOutput:コメントと比較できるようにするためでした。

// 変更前 (src/pkg/testing/example.go)
stdout, stderr := os.Stdout, os.Stderr
// ...
os.Stdout, os.Stderr = w, w // w はパイプの書き込み側
// ...
os.Stdout, os.Stderr = stdout, stderr // 元に戻す

このコミットでは、この挙動が変更され、os.Stdoutのみがパイプにリダイレクトされるようになりました。os.Stderrはリダイレクトされず、Example関数がos.Stderrに書き込んだ内容は、直接親プロセスの標準エラー出力(通常はコンソール)に流れるようになります。

// 変更後 (src/pkg/testing/example.go)
stdout := os.Stdout // stderr はキャプチャしない
// ...
os.Stdout = w // os.Stderr は変更しない
// ...
os.Stdout = stdout // os.Stderr は変更しない

この変更により、Example関数のOutput:コメントは、純粋に標準出力の内容のみを期待するようになります。標準エラー出力に意図しないデバッグメッセージやエラーが出力されても、それがExample関数のテスト失敗の原因となることはなくなります。これは、Example関数の目的がコードの「正しい使用例」を示すことであり、エラー処理のデモンストレーションは通常、別のテストケースで行われるべきであるという設計思想に合致しています。

また、src/cmd/go/doc.gosrc/cmd/go/test.goの変更は、この主要なロジック変更に付随するドキュメントの更新と、goコマンドの機能拡張に関するものです。

  • src/cmd/go/doc.go:
    • go buildおよびgo testコマンドに新しいフラグの説明が追加されました。
      • -race: データ競合検出を有効にします。並行処理におけるバグ(データ競合)を特定するのに役立ちます。
      • -ccflags 'arg list': Cコンパイラ(5c, 6c, 8cなど)に渡す引数を指定します。GoプログラムがCコードと連携する場合に有用です。
      • -test.benchmem: ベンチマーク実行時にメモリ割り当て統計を出力します。パフォーマンスチューニングに役立ちます。
      • -test.blockprofile block.out: ゴルーチンのブロッキングプロファイルを指定されたファイルに書き込みます。ゴルーチンがブロックされる原因(チャネル操作、ミューテックスなど)を分析するのに役立ちます。
      • -test.blockprofilerate n: ブロッキングプロファイルのサンプリングレートを制御します。
    • go listコマンドの-fフラグに関する説明が更新され、templateパッケージの構文に加えて、strings.Joinを呼び出す"join"という追加のテンプレート関数が利用可能になったことが明記されました。
    • SWIGによって生成される*.soファイルが、go buildによって生成されるファイルタイプとしてリストに追加されました。
  • src/cmd/go/test.go:
    • Example関数の説明が更新され、「*testing.Tを使って成功または失敗を報告する代わりに、os.Stdoutに出力する」という表現に修正されました。これは、os.Stderrへの出力がテストの比較対象から外れたことを反映しています。

これらのドキュメント変更は、Goツールチェーンの進化と、開発者がより高度なデバッグおよびプロファイリングツールを利用できるようにするためのものです。

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

このコミットのコアとなるコードの変更は、src/pkg/testing/example.go ファイルの RunExamples 関数に集中しています。

--- a/src/pkg/testing/example.go
+++ b/src/pkg/testing/example.go
@@ -24,7 +24,7 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
 
 	var eg InternalExample
 
-	stdout, stderr := os.Stdout, os.Stderr
+	stdout := os.Stdout
 
 	for _, eg = range examples {
 		matched, err := matchString(*match, eg.Name)
@@ -39,19 +39,19 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
 			fmt.Printf("=== RUN: %s\\n", eg.Name)
 		}
 
-		// capture stdout and stderr
+		// capture stdout
 		r, w, err := os.Pipe()
 		if err != nil {
 			fmt.Fprintln(os.Stderr, err)
 			os.Exit(1)
 		}
-		os.Stdout, os.Stderr = w, w
+		os.Stdout = w
 		outC := make(chan string)
 		go func() {
 			buf := new(bytes.Buffer)
 			_, err := io.Copy(buf, r)
 			if err != nil {
-				fmt.Fprintf(stderr, "testing: copying pipe: %v\\n", err)
+				fmt.Fprintf(os.Stderr, "testing: copying pipe: %v\\n", err)
 				os.Exit(1)
 			}
 			outC <- buf.String()
@@ -62,9 +62,9 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
 		eg.F()
 		dt := time.Now().Sub(t0)
 
-		// close pipe, restore stdout/stderr, get output
+		// close pipe, restore stdout, get output
 		w.Close()
-		os.Stdout, os.Stderr = stdout, stderr
+		os.Stdout = stdout
 		out := <-outC
 
 		// report any errors

コアとなるコードの解説

上記のコード変更は、Example関数の実行時に標準出力と標準エラー出力のどちらをキャプチャするかという、testingパッケージの基本的な動作を変更しています。

  1. stdout, stderr := os.Stdout, os.Stderr から stdout := os.Stdout:

    • 変更前は、元のos.Stdoutos.Stderrのファイルディスクリプタを両方とも変数stdoutstderrに保存していました。これは、Example関数の実行後に元の状態に戻すためです。
    • 変更後は、os.Stderrをキャプチャしないため、元のos.Stderrを保存する必要がなくなりました。したがって、stdoutのみを保存するように簡略化されています。
  2. os.Stdout, os.Stderr = w, w から os.Stdout = w:

    • この行は、Example関数が実行される間、プログラムの標準出力と標準エラー出力を、新しく作成されたパイプの書き込み側wにリダイレクトする部分です。
    • 変更前は、os.Stdoutos.Stderrの両方をwに設定していました。これにより、Example関数がfmt.Println(標準出力)やfmt.Fprintln(os.Stderr, ...)(標準エラー出力)のどちらを使っても、その出力はパイプを通じてキャプチャされていました。
    • 変更後は、os.Stdoutのみをwに設定しています。os.Stderrは元の状態のまま(通常はコンソール)に保たれます。これにより、Example関数がos.Stderrに書き込んだ内容は、パイプを介さずに直接コンソールに出力されるようになります。
  3. fmt.Fprintf(stderr, "testing: copying pipe: %v\\n", err) から fmt.Fprintf(os.Stderr, "testing: copying pipe: %v\\n", err):

    • これは、パイプからのコピー中にエラーが発生した場合に、そのエラーメッセージを標準エラー出力に書き込む部分です。
    • 変更前は、ローカル変数stderr(元のos.Stderrを指す)を使用していましたが、stderr変数が削除されたため、直接グローバルなos.Stderrを使用するように変更されました。これは、os.Stderrがキャプチャ対象外となったことと整合しています。
  4. os.Stdout, os.Stderr = stdout, stderr から os.Stdout = stdout:

    • Example関数の実行が完了した後、標準出力と標準エラー出力を元の状態に戻す部分です。
    • 変更前は、保存しておいたstdoutstderrの値を両方とも復元していました。
    • 変更後は、os.Stderrはリダイレクトされていなかったため、os.Stdoutのみを元の値に復元すればよくなりました。

これらの変更により、go testコマンドがExample関数を実行する際の出力キャプチャのセマンティクスが明確化され、Output:コメントは純粋に標準出力の検証に特化されることになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(testingパッケージ、goコマンドなど)
  • Go言語のソースコード(特にsrc/pkg/testing/example.gosrc/cmd/go/doc.gosrc/cmd/go/test.go