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

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

このコミットは、Go言語のテストスイートにおけるindexテストの実行方法を変更し、デフォルトで実行されるようにしたものです。具体的には、test/index.goが生成するテストコードを、test/index0.gotest/index1.gotest/index2.goという新しいエントリポイントを通じて実行するように変更し、これらのテストがGoのメインテストランナーであるtest/run.goによって自動的に実行されるように設定されました。これにより、以前は手動で実行する必要があったindexテストが、GoのCI/CDパイプラインや開発者のローカル環境でのテスト実行時に常に含まれるようになり、潜在的なバグの早期発見に貢献します。

コミット

commit e08008e8c5e9205ea4937051fe662fe4d05df657
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Nov 7 12:33:54 2012 -0800

    test: run index test by default

    Running this test via "bash run" uncovered three different
    bugs (4344, 4348, 4353).  We need to run it by default.

    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6832043

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

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

元コミット内容

このコミットの元の内容は、Go言語のテストスイートにおいて、indexテストをデフォルトで実行するように変更することです。このテストは、以前はbash runコマンドを通じて手動で実行する必要がありましたが、その手動実行によって3つの異なるバグ(4344、4348、4353)が発見されたため、このテストをGoの標準テスト実行プロセスに含める必要性が認識されました。

変更の背景

この変更の背景には、Go言語のコンパイラやランタイムにおけるインデックスおよびスライス境界チェックの堅牢性を確保するという目的があります。test/index.goは、様々なインデックスおよびスライス操作を含むGoコードを動的に生成し、それらの操作が正しく境界チェックされるか、あるいは意図通りにコンパイルエラーとなるかを検証するテストです。

以前は、このテストはGoの標準的なテスト実行フロー(all.bashrun.goによる実行)には含まれておらず、特定の開発者が手動でbash runスクリプトを実行した場合にのみ実行されていました。しかし、その手動実行の過程で、Goコンパイラやランタイムにおけるインデックスおよびスライス境界チェックに関連する3つの重要なバグ(Issue 4344, 4348, 4353)が発見されました。これらのバグは、Goプログラムの安全性と信頼性に直接影響を与える可能性のあるものでした。

この経験から、このような重要なテストがデフォルトで実行されないことのリスクが認識され、Goプロジェクトの品質保証プロセスを強化するために、indexテストを標準テストスイートに組み込むことが決定されました。これにより、将来的に同様のバグが導入された場合でも、早期に発見し修正することが可能になります。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびテストに関する知識が必要です。

  • Go言語のテストフレームワーク: Go言語には、標準ライブラリとしてtestingパッケージが提供されており、go testコマンドを通じてテストを実行します。しかし、Goのコンパイラやランタイム自体のテスト(特にGoリポジトリ内のtest/ディレクトリにあるもの)は、より低レベルなテストハーネスを使用することがあります。
  • test/run.go: Goリポジトリのtest/ディレクトリにあるrun.goは、Go言語のコンパイラ、ランタイム、標準ライブラリの広範なテストを実行するための主要なテストランナーです。これは、各テストファイルの先頭に記述された特別なコメント(例: // run, // errorcheck, // runoutputなど)を解析し、それに基づいてテストの実行方法を決定します。
  • test/testlib: test/testlibは、test/run.goが利用するシェルスクリプト関数群を定義したファイルです。runoutputerrorcheckといったテストアクションに対応する具体的なコマンド実行ロジックが含まれています。
  • go run: Goソースファイルをコンパイルして実行するコマンドです。
  • go tool gc -e: Goコンパイラ(gc)のオプションで、エラーチェックのみを行い、コンパイルエラーがあればそれを報告します。これは、特定のコードが意図的にコンパイルエラーを引き起こすことを期待するテスト(エラーチェックテスト)で利用されます。
  • インデックスとスライス境界チェック: Go言語は、配列やスライスへのアクセス時に、インデックスが有効な範囲内にあるかを自動的にチェックします。これにより、C/C++のような言語で発生しがちなバッファオーバーフローなどのメモリ安全性の問題を防ぎます。このチェックは、コンパイル時(静的チェック)または実行時(動的チェック)に行われます。
  • Go Issue Tracker: Goプロジェクトのバグ報告や機能要望が管理されているシステムです。コミットメッセージに記載されている「4344, 4348, 4353」は、Go Issue TrackerにおけるバグのIDを指します。

技術的詳細

このコミットの技術的な核心は、test/index.goが生成するテストコードの実行と検証を、Goの標準テストランナーであるtest/run.goに統合した点にあります。

  1. test/index.goの役割の変更:

    • 以前のtest/index.goは、それ自体が$G $D/$F.go && $L $F.$A && ./$A.outのような複雑なシェルコマンドをコメントとして含み、手動で実行されることを想定していました。
    • このコミットでは、test/index.goの先頭に// skipというディレクティブが追加されました。これはtest/run.goに対して、このファイル自体を直接テストとして実行しないように指示します。
    • test/index.goは、passという定数に基づいて異なる種類のテストコードを生成する「ジェネレータ」としての役割に特化しました。passの値は、動的チェック(pass = 0)、無効な定数の静的チェック(pass = 1)、配列境界の静的チェック(pass = 2)に対応します。
  2. 新しいテストエントリポイントの導入:

    • test/index0.gotest/index1.gotest/index2.goという3つの新しいファイルが導入されました。
    • これらのファイルはそれぞれ、const pass = 0const pass = 1const pass = 2という定数を定義しています。
    • 各ファイルの先頭には、test/run.goが解釈する特別なコメントが追加されました。
      • test/index0.go: // runoutput ./index.go - これは、./index.goを実行し、その標準出力が期待される出力と一致するかを検証するテストであることを示します。この場合、index.gopass = 0の動的チェックコードを生成し、そのコードが実行されます。
      • test/index1.goおよびtest/index2.go: // errorcheckoutput ./index.go - これは、./index.goを実行し、その標準出力(生成されたGoコード)をコンパイルした際に、期待されるコンパイルエラーが発生するかを検証するテストであることを示します。index.goはそれぞれpass = 1pass = 2の静的チェックコードを生成し、それらがコンパイルエラーを引き起こすことを確認します。
  3. test/run.gotest/testlibの拡張:

    • test/run.goには、新しいテストアクションタイプerrorcheckoutputが追加されました。このアクションは、指定されたGoファイルをgo runで実行し、その出力を一時ファイルに保存します。その後、その一時ファイルをgo tool gc -eでコンパイルし、コンパイルエラーの有無をチェックします。これにより、index1.goindex2.goのような、生成されたコードがコンパイルエラーを引き起こすことを期待するテストを自動化できます。
    • test/run.gorunoutputケースも修正され、go runコマンドに追加の引数(args...)を渡せるようになりました。これは、index0.goindex.gopassの値を渡すために利用されます。
    • test/testlibにも、errorcheckoutputという新しいシェル関数が追加されました。これはtest/run.goerrorcheckoutputアクションに対応するシェルスクリプト側の実装であり、go runで生成されたコードをerrchk(Goのテストユーティリティ)でエラーチェックするロジックを含みます。また、runoutput関数も引数を渡せるように修正されています。

これらの変更により、indexテストはGoの標準テストスイートの一部となり、GoのビルドプロセスやCIシステムによって自動的に実行されるようになりました。これにより、インデックスおよびスライス境界チェックに関連する潜在的なバグが、より迅速かつ確実に検出されるようになります。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  • test/index.go:
    • ファイルの先頭に// skipが追加され、このファイル自体が直接テストとして実行されないように変更されました。
    • flagパッケージの使用が削除され、pass変数がflag.Intではなく、外部から提供される定数として扱われるようになりました。
    • main関数内のflag.Parse()呼び出しが削除されました。
    • 生成されるGoコードのコメントが、新しいテスト実行フローに合わせて変更されました(例: // $G $D/$F.go ... から // run// errorcheck へ)。
  • test/index0.go (新規追加):
    • // runoutput ./index.go ディレクティブと const pass = 0 を含む。
  • test/index1.go (新規追加):
    • // errorcheckoutput ./index.go ディレクティブと const pass = 1 を含む。
  • test/index2.go (新規追加):
    • // errorcheckoutput ./index.go ディレクティブと const pass = 2 を含む。
  • test/run.go:
    • test構造体のactionフィールドのコメントが更新されました。
    • test.run()メソッドのswitch文にerrorcheckoutputケースが追加されました。このケースは、go runで生成されたコードを一時ファイルに書き込み、その一時ファイルをgo tool gc -eでコンパイルしてエラーチェックを行うロジックを含みます。
    • runoutputケースのruncmd呼び出しが、可変引数(args...)を受け入れるように変更されました。
  • test/testlib:
    • runoutputシェル関数が、追加の引数("$@")をgo runに渡せるように変更されました。
    • errorcheckoutputという新しいシェル関数が追加されました。これは、go runで生成されたコードをtmp.goにリダイレクトし、そのtmp.goに対してerrchkを実行するロジックを含みます。

コアとなるコードの解説

このコミットのコアとなる変更は、test/run.goにおけるerrorcheckoutputアクションの追加と、それに対応するtest/testliberrorcheckoutput関数の実装、そしてindex.goをジェネレータとして利用する新しいテストファイル群(index0.go, index1.go, index2.go)の導入です。

test/run.goにおけるerrorcheckoutputの追加:

	case "errorcheckoutput":
		useTmp = false
		out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...)
		if err != nil {
			t.err = err
			return
		}
		tfile := filepath.Join(t.tempDir, "tmp__.go")
		err = ioutil.WriteFile(tfile, out, 0666)
		if err != nil {
			t.err = fmt.Errorf("write tempfile:%s", err)
			return
		}
		cmdline := []string{"go", "tool", gc, "-e", "-o", "a." + letter}
		cmdline = append(cmdline, flags...)
		cmdline = append(cmdline, tfile)
		out, err = runcmd(cmdline...)
		if wantError {
			if err == nil {
				t.err = fmt.Errorf("compilation succeeded unexpectedly\\n%s", out)
				return
			}
		} else {
			if err != nil {
				t.err = err
				return
			}
		}
		t.err = t.errorCheck(string(out), tfile, "tmp__.go")
		return

このコードブロックは、// errorcheckoutputディレクティブを持つテストがどのように処理されるかを示しています。

  1. go runコマンドを実行し、t.goFileName()(例: ./index1.go)と追加の引数(args...)を渡します。index1.goindex.goを実行し、その結果としてGoコードが標準出力に出力されます。
  2. go runの出力(生成されたGoコード)を一時ファイル(tmp__.go)に書き込みます。
  3. go tool gc -eコマンドを使用して、この一時ファイルをコンパイルします。-eオプションは、コンパイルエラーをチェックするためのものです。
  4. wantErrorフラグ(// errorcheckoutputの場合、trueになる)に基づいて、コンパイルが成功したか、または期待通りにエラーが発生したかを検証します。
  5. 最後に、t.errorCheckを呼び出して、コンパイラの出力に期待されるエラーメッセージが含まれているかを詳細にチェックします。

test/testlibにおけるerrorcheckoutput関数の追加:

errorcheckoutput() {
	zero=""
	if [ "$1" = "-0" ]; then
		zero="-0"
		shift
	fi
	go run "$D/$F.go" "$@" > tmp.go
	errchk $zero $G -e tmp.go
}

これはシェルスクリプト側の実装で、test/run.gotestlibの関数を呼び出す際に使用されます。

  1. go run "$D/$F.go" "$@": $D/$F.go(例: test/index1.go)を実行し、その出力をtmp.goにリダイレクトします。"$@"は、test/run.goから渡された追加の引数(この場合はindex.goに渡されるpassの値など)をgo runに渡します。
  2. errchk $zero $G -e tmp.go: errchkユーティリティを使用して、tmp.goがコンパイルエラーを引き起こすことを検証します。$GはGoコンパイラへのパス、-eはエラーチェックモードを意味します。

これらの変更により、indexテストはGoの自動テストスイートに完全に統合され、Go言語のインデックスおよびスライス境界チェックの正確性が継続的に検証されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (特にテストに関するセクション)
  • Go言語のソースコードリポジトリ (特に src/cmd/go/testdata/test/ および src/cmd/go/testdata/test/testlib )
  • Go Issue Tracker (上記関連リンクの各Issueページ)
  • Go言語のコミット履歴