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

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

このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)のテストドライバに関する修正です。具体的には、テスト対象のコードにコンパイルエラーが存在する場合に、テストドライバが「0個のテストを実行した」と誤って成功を報告する問題を解決します。この修正により、コンパイルエラーが発生した際には、テストドライバが適切に失敗し、コンパイルエラーメッセージを出力するようになります。

コミット

commit 43f2fc308b92cec6071a4af225af1dafd4d7ba54
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Dec 24 15:33:32 2012 +0400

    runtime/race: make test driver print compilation errors
    Currently it silently "succeeds" saying that it run 0 tests
    if there are compilations errors.
    With this change it fails and outputs the compilation error.
    
    R=golang-dev, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/7002058

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

https://github.com/golang/go/commit/43f2fc308b92cec6071a4af225af1dafd4d7ba54

元コミット内容

runtime/race: make test driver print compilation errors
Currently it silently "succeeds" saying that it run 0 tests
if there are compilations errors.
With this change it fails and outputs the compilation error.

変更の背景

Go言語のランタイムには、並行処理におけるデータ競合(data race)を検出するための「Race Detector」という強力なツールが組み込まれています。このツールは、並行に実行されるゴルーチンが共有メモリにアクセスする際に、少なくとも一方が書き込み操作であり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に競合を報告します。

このコミットが作成された時点では、Race Detectorのテストスイート(runtime/race/race_test.go)には問題がありました。テスト対象のコード自体にコンパイルエラーが含まれている場合、テストドライバはエラーを適切に捕捉せず、あたかもテストが正常に実行され、単に「0個のテストが実行された」かのように振る舞っていました。これは、開発者がテストコードのコンパイルエラーに気づきにくく、デバッグを困難にするという問題を引き起こしていました。

このコミットの目的は、このサイレントな成功を修正し、コンパイルエラーが発生した場合にはテストドライバが明確に失敗し、関連するコンパイルエラーメッセージを標準出力に表示するようにすることです。これにより、開発者はテストコードの問題を即座に特定できるようになります。

前提知識の解説

Go言語の testing パッケージ

Go言語には、ユニットテストやベンチマークテストを記述するための標準パッケージ testing が用意されています。

  • func TestXxx(*testing.T): テスト関数は Test で始まり、*testing.T 型の引数を取ります。
  • t.Fatalf(format string, args ...interface{}): テストが致命的なエラーに遭遇した場合に呼び出されます。メッセージをフォーマットして出力し、テストの実行を停止し、テストを失敗としてマークします。

Go言語の Race Detector

GoのRace Detectorは、Go 1.1で導入された並行処理のデバッグツールです。

  • 機能: 実行時にデータ競合を検出します。データ競合は、複数のゴルーチンが同じメモリ位置に同時にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。
  • 有効化: go run -racego build -racego test -race のように -race フラグを付けてビルドまたは実行することで有効になります。
  • GORACE 環境変数: Race Detectorの挙動を制御するための環境変数です。様々なオプションを設定できます。
    • suppress_equal_stacks=0: 同じスタックトレースを持つ競合レポートを抑制しない(デフォルトは抑制)。
    • suppress_equal_addresses=0: 同じアドレスを持つ競合レポートを抑制しない(デフォルトは抑制)。
    • exitcode=0: Race Detectorが競合を検出した場合でも、プロセスが終了する際の終了コードを0にする。通常、競合が検出されると非ゼロの終了コードで終了しますが、テストドライバがRace Detectorの出力を解析する必要がある場合など、特定のシナリオでこのオプションが有用です。

os/exec パッケージと cmd.CombinedOutput()

Go言語の標準ライブラリ os/exec は、外部コマンドを実行するための機能を提供します。

  • cmd.CombinedOutput(): 外部コマンドを実行し、その標準出力(stdout)と標準エラー出力(stderr)を結合したバイトスライスとして返します。コマンドの実行に失敗した場合(非ゼロの終了コードで終了した場合など)は、エラーも返します。

技術的詳細

このコミットは、src/pkg/runtime/race/race_test.go 内の TestRace 関数と runTests 関数に変更を加えています。

  1. TestRace 関数におけるエラーメッセージの改善:

    • 変更前: t.Fatalf("Failed to run tests: %v", err)
    • 変更後: t.Fatalf("Failed to run tests: %v\n%v", err, string(testOutput)) この変更により、runTests 関数がエラーを返した場合に、t.Fatalf がエラーメッセージだけでなく、runTests が返した testOutput(外部コマンドの標準出力と標準エラー出力の結合)も出力するようになります。これにより、コンパイルエラーメッセージが testOutput に含まれている場合、それが直接テストの失敗メッセージとして表示されるようになります。
  2. GORACE 環境変数の変更:

    • 変更前: GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"
    • 変更後: GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0 exitcode=0" exitcode=0 オプションが追加されました。これは非常に重要です。通常、Race Detectorがデータ競合を検出すると、プロセスは非ゼロの終了コードで終了します。しかし、このテストドライバは、Race Detectorが検出した競合情報を解析するために、外部コマンド(テスト対象のGoプログラム)の出力を読み取る必要があります。もしRace Detectorが非ゼロの終了コードで終了してしまうと、cmd.CombinedOutput() はエラーを返し、テストドライバは出力を解析する機会を失ってしまいます。exitcode=0 を設定することで、Race Detectorは競合を検出しても終了コードを0で返し、cmd.CombinedOutput() がエラーを返さずに正常に出力を返すようになります。これにより、テストドライバはコンパイルエラーやRace Detectorのレポートを含む出力を常に取得できるようになります。
  3. runTests 関数におけるエラーハンドリングの改善:

    • 変更前:
      ret, _ := cmd.CombinedOutput()
      return ret, nil
      
    • 変更後:
      return cmd.CombinedOutput()
      

    変更前は、cmd.CombinedOutput() が返すエラーを無視し、常に nil エラーを返していました。これは、外部コマンドが非ゼロの終了コードで終了した場合(例: コンパイルエラーが発生した場合)でも、runTests 関数がエラーを報告しない原因となっていました。変更後は、cmd.CombinedOutput() が返すエラーをそのまま runTests の呼び出し元に返すようになりました。これにより、外部コマンドの実行が失敗した場合(コンパイルエラーなど)に、そのエラーが適切に TestRace 関数に伝播され、t.Fatalf によって報告されるようになります。

これらの変更が組み合わさることで、テスト対象のGoプログラムにコンパイルエラーがあった場合、cmd.CombinedOutput() はエラーを返し、そのエラーとコンパイルエラーメッセージを含む出力が TestRace 関数に伝播され、最終的に t.Fatalf によって詳細なエラーメッセージとして表示されるようになります。

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

--- a/src/pkg/runtime/race/race_test.go
+++ b/src/pkg/runtime/race/race_test.go
@@ -42,7 +42,7 @@ const (
 func TestRace(t *testing.T) {
  	testOutput, err := runTests()
  	if err != nil {
- 		t.Fatalf("Failed to run tests: %v", err)
+ 		t.Fatalf("Failed to run tests: %v\n%v", err, string(testOutput))
  	}
  	reader := bufio.NewReader(bytes.NewBuffer(testOutput))
  
@@ -152,7 +152,6 @@ func runTests() ([]byte, error) {
  		}
  		cmd.Env = append(cmd.Env, env)
  	}
- 	cmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
- 	ret, _ := cmd.CombinedOutput()
- 	return ret, nil
+ 	cmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0 exitcode=0"`)
+ 	return cmd.CombinedOutput()
 }

コアとなるコードの解説

TestRace 関数内の変更

- 		t.Fatalf("Failed to run tests: %v", err)
+ 		t.Fatalf("Failed to run tests: %v\n%v", err, string(testOutput))

この行は、runTests() 関数がエラーを返した場合に、テストを失敗させる t.Fatalf の呼び出しを変更しています。

  • 変更前は、単に runTests() から返されたエラー (err) のみを出力していました。
  • 変更後は、エラーメッセージに加えて、runTests() が返した testOutput(外部コマンドの標準出力と標準エラー出力の結合)も文字列として追加で出力するようにしました。これにより、コンパイルエラーメッセージやその他の診断情報が testOutput に含まれている場合、それが直接テストの失敗メッセージの一部として表示され、デバッグが容易になります。

runTests 関数内の変更

- 	cmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
+ 	cmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0 exitcode=0"`)

この行は、GORACE 環境変数の設定を変更しています。

  • 変更前は、suppress_equal_stacks=0suppress_equal_addresses=0 のオプションのみを設定していました。これらは、Race Detectorが同じスタックトレースやアドレスを持つ競合レポートを抑制しないようにする設定です。
  • 変更後は、これらに加えて exitcode=0 オプションが追加されました。このオプションは、Race Detectorがデータ競合を検出した場合でも、外部コマンドが非ゼロの終了コードで終了するのを防ぎ、常に終了コード0で終了するようにします。これにより、cmd.CombinedOutput() がエラーを返さずに、Race Detectorの出力(競合レポートやコンパイルエラーなど)を常に取得できるようになります。これは、テストドライバが外部コマンドの出力を解析するために不可欠です。
- 	ret, _ := cmd.CombinedOutput()
- 	return ret, nil
+ 	return cmd.CombinedOutput()

この行は、外部コマンドの実行結果の扱いを変更しています。

  • 変更前は、cmd.CombinedOutput() が返すエラーをアンダースコア (_) で破棄し、常に nil エラーを返していました。これは、外部コマンドが非ゼロの終了コードで終了した場合(例: コンパイルエラーが発生した場合)でも、runTests 関数がエラーを報告しない原因となっていました。
  • 変更後は、cmd.CombinedOutput() の戻り値を直接 runTests 関数の戻り値として返しています。これにより、cmd.CombinedOutput() が返すエラー(外部コマンドの実行失敗を示す)が適切に runTests の呼び出し元(TestRace 関数)に伝播されるようになります。結果として、コンパイルエラーなどによって外部コマンドが失敗した場合、TestRace 関数がそのエラーを捕捉し、t.Fatalf を通じて報告できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の testing パッケージ: https://pkg.go.dev/testing
  • Go言語の os/exec パッケージ: https://pkg.go.dev/os/exec
  • Go Race Detectorの環境変数 GORACE についての議論やドキュメント(Goのソースコードや関連するIssue/CLから情報を収集)
    • GORACE 環境変数の詳細なオプションは、Goのソースコード内の src/runtime/race/doc.go や関連するテストファイルで確認できます。
    • GoのIssueトラッカーやメーリングリストのアーカイブも、特定のオプションの背景や意図を理解するのに役立ちます。