[インデックス 14344] ファイルの概要
このコミットは、Go言語のテストスイートにおけるindex
テストの実行方法を変更し、デフォルトで実行されるようにしたものです。具体的には、test/index.go
が生成するテストコードを、test/index0.go
、test/index1.go
、test/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.bash
やrun.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
が利用するシェルスクリプト関数群を定義したファイルです。runoutput
やerrorcheck
といったテストアクションに対応する具体的なコマンド実行ロジックが含まれています。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
に統合した点にあります。
-
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
)に対応します。
- 以前の
-
新しいテストエントリポイントの導入:
test/index0.go
、test/index1.go
、test/index2.go
という3つの新しいファイルが導入されました。- これらのファイルはそれぞれ、
const pass = 0
、const pass = 1
、const pass = 2
という定数を定義しています。 - 各ファイルの先頭には、
test/run.go
が解釈する特別なコメントが追加されました。test/index0.go
:// runoutput ./index.go
- これは、./index.go
を実行し、その標準出力が期待される出力と一致するかを検証するテストであることを示します。この場合、index.go
はpass = 0
の動的チェックコードを生成し、そのコードが実行されます。test/index1.go
およびtest/index2.go
:// errorcheckoutput ./index.go
- これは、./index.go
を実行し、その標準出力(生成されたGoコード)をコンパイルした際に、期待されるコンパイルエラーが発生するかを検証するテストであることを示します。index.go
はそれぞれpass = 1
とpass = 2
の静的チェックコードを生成し、それらがコンパイルエラーを引き起こすことを確認します。
-
test/run.go
とtest/testlib
の拡張:test/run.go
には、新しいテストアクションタイプerrorcheckoutput
が追加されました。このアクションは、指定されたGoファイルをgo run
で実行し、その出力を一時ファイルに保存します。その後、その一時ファイルをgo tool gc -e
でコンパイルし、コンパイルエラーの有無をチェックします。これにより、index1.go
やindex2.go
のような、生成されたコードがコンパイルエラーを引き起こすことを期待するテストを自動化できます。test/run.go
のrunoutput
ケースも修正され、go run
コマンドに追加の引数(args...
)を渡せるようになりました。これは、index0.go
がindex.go
にpass
の値を渡すために利用されます。test/testlib
にも、errorcheckoutput
という新しいシェル関数が追加されました。これはtest/run.go
のerrorcheckoutput
アクションに対応するシェルスクリプト側の実装であり、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/testlib
のerrorcheckoutput
関数の実装、そして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
ディレクティブを持つテストがどのように処理されるかを示しています。
go run
コマンドを実行し、t.goFileName()
(例:./index1.go
)と追加の引数(args...
)を渡します。index1.go
はindex.go
を実行し、その結果としてGoコードが標準出力に出力されます。go run
の出力(生成されたGoコード)を一時ファイル(tmp__.go
)に書き込みます。go tool gc -e
コマンドを使用して、この一時ファイルをコンパイルします。-e
オプションは、コンパイルエラーをチェックするためのものです。wantError
フラグ(// errorcheckoutput
の場合、true
になる)に基づいて、コンパイルが成功したか、または期待通りにエラーが発生したかを検証します。- 最後に、
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.go
がtestlib
の関数を呼び出す際に使用されます。
go run "$D/$F.go" "$@"
:$D/$F.go
(例:test/index1.go
)を実行し、その出力をtmp.go
にリダイレクトします。"$@"
は、test/run.go
から渡された追加の引数(この場合はindex.go
に渡されるpass
の値など)をgo run
に渡します。errchk $zero $G -e tmp.go
:errchk
ユーティリティを使用して、tmp.go
がコンパイルエラーを引き起こすことを検証します。$G
はGoコンパイラへのパス、-e
はエラーチェックモードを意味します。
これらの変更により、index
テストはGoの自動テストスイートに完全に統合され、Go言語のインデックスおよびスライス境界チェックの正確性が継続的に検証されるようになりました。
関連リンク
- Go Issue 4344: https://go.dev/issue/4344 (または
https://golang.org/issue/4344
) - Go Issue 4348: https://go.dev/issue/4348 (または
https://golang.org/issue/4348
) - Go Issue 4353: https://go.dev/issue/4353 (または
https://golang.org/issue/4353
) - Go Code Review 6832043: https://golang.org/cl/6832043
参考にした情報源リンク
- Go言語の公式ドキュメント (特にテストに関するセクション)
- Go言語のソースコードリポジトリ (特に
src/cmd/go/testdata/test/
およびsrc/cmd/go/testdata/test/testlib
) - Go Issue Tracker (上記関連リンクの各Issueページ)
- Go言語のコミット履歴