[インデックス 11235] ファイルの概要
このコミットは、Go言語のテストスイートにおける複数のテストファイルに対して行われた変更を記録しています。主な目的は、テストの出力を標準出力に直接行うのではなく、内部的に文字列として構築し、期待される結果と比較する形式に修正することです。これにより、gccgo
のような並列テスト実行環境や、golden.out
のような出力比較メカニズムを持たない環境でのテストの信頼性と互換性を向上させています。また、重複するテストファイルtest/ken/simpprint.go
の削除も含まれています。
コミット
commit f2030938522fae7c6b65569a20a7b9ed1431b8f8
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Jan 18 14:31:31 2012 -0800
test: change several tests to not print
This will make these tests more meaningful for gccgo, which
runs tests in parallel and has no equivalent to golden.out.
Remove ken/simpprint.go since it duplicates helloworld.go.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5536058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2030938522fae7c6b65569a20a7b9ed1431b8f8
元コミット内容
test: change several tests to not print
This will make these tests more meaningful for gccgo, which
runs tests in parallel and has no equivalent to golden.out.
Remove ken/simpprint.go since it duplicates helloworld.go.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5536058
変更の背景
このコミットの主な背景には、Go言語のテストフレームワークと、異なるGoコンパイラ実装(特にgccgo
)におけるテスト実行環境の特性があります。
- 並列テスト実行への対応: 従来のGoテストの一部は、テスト結果を標準出力(
stdout
)に直接print
関数で出力し、その出力をgolden.out
というファイルに保存された「ゴールデン」な出力と比較することで検証していました。しかし、gccgo
のようなコンパイラはテストを並列で実行する可能性があり、複数のテストが同時に標準出力に書き込むと、出力が混ざり合い、golden.out
との比較が不安定になる問題がありました。 golden.out
の代替:gccgo
には、Goの標準テストツールが提供するgolden.out
のような出力比較メカニズムがありませんでした。そのため、テストが標準出力に依存していると、gccgo
環境でのテストの信頼性が低下するか、そもそもテストが実行できないという問題が生じます。- テストの自己完結性: テストが自身の内部で結果を検証し、
panic
やエラーコードで成功/失敗を示すようにすることで、外部の出力比較メカニズムに依存せず、より堅牢で移植性の高いテストになります。 - コードの重複排除:
test/ken/simpprint.go
がhelloworld.go
と機能的に重複していたため、コードベースの整理の一環として削除されました。
これらの理由から、テストの出力を標準出力から切り離し、テストコード内で直接結果を検証する形式に変更する必要がありました。
前提知識の解説
Go言語のprint
組み込み関数とfmt
パッケージ
Go言語には、print
とprintln
という組み込み関数が存在します。これらは主にデバッグ用途や、非常にシンプルな出力のために提供されています。しかし、これらはfmt
パッケージの関数(例: fmt.Print
, fmt.Println
, fmt.Sprintf
)とは異なり、以下の点で制約があります。
- 型安全性と書式設定:
print
は型安全ではなく、書式設定の機能も限定的です。一方、fmt
パッケージはGoの型システムと統合されており、多様な書式設定オプション(%v
,%d
,%s
など)を提供します。 - 出力先:
print
は常に標準エラー出力(stderr
)に出力されることが保証されています(ただし、実装依存の部分もあります)。fmt.Print
などはデフォルトで標準出力(stdout
)に出力されますが、fmt.Fprint
などを使えば任意のio.Writer
に出力先を変更できます。 - 戻り値:
print
は戻り値がありません。fmt
パッケージの関数は、書き込まれたバイト数とエラーを返します。 - 並列実行時の問題: 複数のゴルーチンが同時に
print
を呼び出すと、出力がインターリーブ(混ざり合う)する可能性があり、特にテストの出力比較においては問題となります。
このコミットでは、print
関数による直接的な標準出力への書き込みを避け、fmt.Sprintln
や文字列結合を用いて期待される出力をメモリ上の文字列として構築し、その文字列を直接比較することで、並列実行時の出力の競合を防ぎ、テストの自己完結性を高めています。
gccgo
とGoコンパイラ
Go言語には複数のコンパイラ実装が存在します。
- gc (Go Compiler): Goプロジェクトの公式コンパイラであり、Go言語のソースコードをネイティブバイナリにコンパイルします。ほとんどのGo開発者が日常的に使用しているものです。
- gccgo: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。GCCの最適化パスやバックエンドを利用できるため、特定の環境や既存のGCCツールチェーンとの統合が必要な場合に利用されます。
gccgo
はgc
とは異なるテスト実行環境や並列処理の特性を持つことがあります。
このコミットは、gccgo
のような代替コンパイラ環境でのテストの互換性と信頼性を向上させることを目的としています。
golden.out
ファイル
Goのテストフレームワークにおいて、golden.out
はテストの期待される出力(「ゴールデン」な出力)を記録するファイルとして使われることがあります。テスト実行時にプログラムの出力をこのgolden.out
と比較し、一致すればテスト成功、不一致であればテスト失敗と判断します。これは、特にテキストベースの出力を持つテストにおいて、出力内容の正確性を検証する一般的な手法です。しかし、この手法は出力が常に予測可能で、並列実行によって乱されない場合にのみ有効です。
技術的詳細
このコミットで行われた技術的な変更は、主に以下のパターンに従っています。
print
関数の置き換え: 既存のテストコードでprint
関数が直接標準出力に文字列や変数の値を出力している箇所を特定します。- 文字列バッファの導入: テスト関数のスコープ内で、
var r string
のような形で文字列変数を宣言し、これを「出力バッファ」として使用します。 fmt.Sprintln
または文字列結合による出力のキャプチャ:print
の代わりに、fmt.Sprintln
(またはfmt.Sprintf
)を使用して、出力したい内容を文字列としてフォーマットし、その結果を先ほど導入した文字列バッファr
に+=
演算子で追記していきます。これにより、実際の出力はメモリ上の文字列r
に蓄積されます。- 例:
print("hello\\n")
はr += "hello\\n"
に。 - 例:
print(i, " ", x.val, "\\n")
はr += fmt.Sprintln(i, x.val)
に。
- 例:
- 期待値との比較: テストの最後に、構築された文字列バッファ
r
が、事前に定義された期待される文字列expect
と完全に一致するかどうかを比較します。 - テスト失敗時の
panic
: 比較の結果、r != expect
であれば、panic(r)
(またはpanic(0)
など)を呼び出してテストを失敗させます。これにより、テストフレームワークはテストの失敗を検知できます。 golden.out
からの関連エントリの削除: 変更されたテストがもはや標準出力に依存しないため、test/golden.out
ファイルからこれらのテストに関連する期待出力のエントリが削除されています。- 重複ファイルの削除:
test/ken/simpprint.go
がhelloworld.go
と重複していたため、削除されました。これはコードベースのクリーンアップと保守性の向上に貢献します。
このアプローチにより、テストは外部の出力比較メカニズムに依存せず、自己完結的に結果を検証できるようになり、並列実行環境での信頼性が向上します。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
test/fixedbugs/bug027.go
:print
をfmt.Sprintln
と文字列結合に置き換え、期待値との比較を追加。test/fixedbugs/bug070.go
:print
をfmt.Sprintln
と文字列結合に置き換え、期待値との比較を追加。test/golden.out
: 複数のテスト(peano.go
,turing.go
,cplx4.go
,label.go
,rob1.go
,rob2.go
,simpprint.go
,simpswitch.go
,bug027.go
,bug070.go
)に関連する期待出力が削除。test/ken/cplx4.go
:fmt.Printf
をfmt.Sprintf
とwant
関数(内部で文字列比較を行うヘルパー関数)に置き換え。test/ken/label.go
:print
呼び出しを削除。test/ken/rob1.go
:Item
インターフェースのPrint
メソッドの戻り値をstring
に変更し、print
を文字列結合に置き換え、期待値との比較を追加。test/ken/rob2.go
:Slist
のPrintOne
およびPrint
メソッドの戻り値をstring
に変更し、print
を文字列結合に置き換え、期待値との比較を追加。test/ken/simpprint.go
: ファイル自体が削除。test/ken/simpswitch.go
:print
を文字列結合に置き換え、期待値との比較を追加。test/peano.go
:print
を削除し、計算結果を直接results
配列の期待値と比較するロジックを追加。test/turing.go
:print
を文字列結合に置き換え、期待値との比較を追加。
コアとなるコードの解説
test/fixedbugs/bug027.go
の変更例
--- a/test/fixedbugs/bug027.go
+++ b/test/fixedbugs/bug027.go
@@ -6,6 +6,8 @@
package main
+import "fmt"
+
type Element interface {
}
@@ -43,7 +45,7 @@ func main() {
i4 := new(I)
i4.val = 44444
v := New()
- print("hi\\n")
+ r := "hi\\n"
v.Insert(i4)
v.Insert(i3)
v.Insert(i2)
@@ -52,10 +54,25 @@ func main() {
for i := 0; i < v.nelem; i++ {
var x *I
x = v.At(i).(*I)
- print(i, " ", x.val, "\\n") // prints correct list
+ r += fmt.Sprintln(i, x.val) // prints correct list
}
for i := 0; i < v.nelem; i++ {
- print(i, " ", v.At(i).(*I).val, "\\n")
+ r += fmt.Sprintln(i, v.At(i).(*I).val)
+ }
+ expect := `hi
+0 44444
+1 3333
+2 222
+3 11
+4 0
+0 44444
+1 3333
+2 222
+3 11
+4 0
+`
+ if r != expect {
+ panic(r)
}
}
この変更では、まずfmt
パッケージをインポートしています。次に、print("hi\\n")
のような直接的な出力の代わりに、r := "hi\\n"
という文字列変数r
を導入し、そこに初期値を代入しています。ループ内のprint
呼び出しもr += fmt.Sprintln(i, x.val)
のように変更され、fmt.Sprintln
でフォーマットされた文字列がr
に追記されます。最後に、構築された文字列r
が、複数行文字列リテラルで定義されたexpect
変数と一致するかを検証し、一致しない場合はpanic(r)
でテストを失敗させています。
test/ken/rob1.go
の変更例
--- a/test/ken/rob1.go
+++ b/test/ken/rob1.go
@@ -7,7 +7,7 @@
package main
type Item interface {
- Print()
+ Print() string
}
type ListItem struct {
@@ -30,12 +30,14 @@ func (list *List) Insert(i Item) {
list.head = item
}
-func (list *List) Print() {
+func (list *List) Print() string {
+\tr := ""
i := list.head
for i != nil {
- i.item.Print()
+ r += i.item.Print()
i = i.next
}
+\treturn r
}
// Something to put in a list
@@ -48,8 +50,8 @@ func (this *Integer) Init(i int) *Integer {
return this
}
-func (this *Integer) Print() {
- print(this.val)
+func (this *Integer) Print() string {
+ return string(this.val + '0')
}
func main() {\
@@ -61,6 +63,8 @@ func main() {\
list.Insert(integer)
}
- list.Print()
- print("\\n")
+\tr := list.Print()
+\tif r != "9876543210" {
+\t\tpanic(r)
+\t}
}
この例では、Item
インターフェースのPrint()
メソッドがPrint() string
に変更され、文字列を返すようになりました。List
構造体のPrint()
メソッドも同様に文字列を返すように変更され、内部で各Item
のPrint()
結果をr
に結合しています。Integer
構造体のPrint()
メソッドも、print(this.val)
からreturn string(this.val + '0')
に変更され、文字列表現を返すようになっています。main
関数では、list.Print()
の戻り値を変数r
に格納し、期待される文字列"9876543210"
と比較しています。
これらの変更は、Goのテストがより堅牢で、異なるコンパイラや実行環境においても一貫した結果を提供できるようにするための重要なステップです。
関連リンク
- Go言語の
fmt
パッケージ: https://pkg.go.dev/fmt - Go言語のテスト: https://go.dev/doc/code#Testing
- GCCGo: https://gcc.gnu.org/onlinedocs/gccgo/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ(特に
test
ディレクトリ) - Go言語のIssueトラッカーやメーリングリスト(
golang.org/cl/5536058
など) - GCCGoのドキュメント