[インデックス 17955] ファイルの概要
このコミットは、Go言語のテストスイートにおけるbug191
という特定のテストケースの修正に関するものです。具体的には、パッケージのインポート順序に依存しないようにテストのロジックを変更し、異なるコンパイラ(特にgccgo)での挙動の違いによってテストが失敗する問題を解決しています。
コミット
commit 6ae378050356715a0f8a91f317030a728a89647b
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Dec 10 12:05:37 2013 -0800
test: don't rely on order of unrelated imports in bug191
There is no necessary relationship between the imports of the
packages a and b, and gccgo happens to import them in a
different order, leading to different output. This ordering
is not the purpose of the test in any case.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/40400043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ae378050356715a0f8a91f317030a728a89647b
元コミット内容
元のコミットメッセージは以下の通りです。
test: don't rely on order of unrelated imports in bug191
There is no necessary relationship between the imports of the packages a and b, and gccgo happens to import them in a different order, leading to different output. This ordering is not the purpose of the test in any case.
R=golang-dev, rsc CC=golang-dev https://golang.org/cl/40400043
変更の背景
この変更の背景には、Go言語のパッケージ初期化順序に関する特定のテストケースの脆弱性がありました。Go言語では、パッケージの初期化(init
関数の実行など)は、そのパッケージがインポートする他のパッケージがすべて初期化された後に行われます。しかし、複数のパッケージが互いに独立してインポートされている場合、それらのパッケージが初期化される「相対的な順序」はGo言語の仕様によって厳密には定義されていません。
bug191
というテストは、おそらくこの初期化順序に暗黙的に依存していたと考えられます。具体的には、a
とb
という2つのパッケージがあり、それぞれがinit
関数内で何らかの副作用(例えば、標準出力への文字列出力)を起こしていました。オリジナルのGoコンパイラ(gc)では特定の順序でこれらのパッケージが初期化され、それによってテストが期待する出力が得られていた可能性があります。
しかし、gccgo
(GCCベースのGoコンパイラ)では、これらの独立したパッケージのインポートおよび初期化順序がgc
とは異なっていたため、テストの出力が期待と異なり、テストが失敗していました。このテストの本来の目的は、パッケージの初期化順序を検証することではなく、別のバグ(おそらくはGo言語の初期化メカニズム自体に関連する何か)を検出することだったため、インポート順序に依存する現在のテストの挙動は不適切であると判断されました。
このコミットは、テストが特定のコンパイラのインポート順序の挙動に依存しないように修正し、テストの堅牢性と移植性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念が重要です。
-
Go言語のパッケージ初期化:
- Goプログラムは、
main
パッケージのmain
関数から実行が開始されます。 - プログラムの実行に先立ち、インポートされたすべてのパッケージが初期化されます。
- パッケージの初期化は、そのパッケージがインポートする他のすべてのパッケージが初期化された後に実行されます。
- 各パッケージは、グローバル変数の初期化と、
init
関数(複数定義可能)の実行によって初期化されます。 init
関数は、main
関数が呼び出される前に、そのパッケージ内のすべてのinit
関数が宣言順に実行されます。- 重要な点: 複数のパッケージが互いに依存関係を持たない場合、それらのパッケージが初期化される相対的な順序はGo言語の仕様によって保証されていません。コンパイラの実装やビルド環境によって順序が異なる可能性があります。
- Goプログラムは、
-
go test
コマンドとテストの種類:- Go言語には、標準でテストフレームワークが組み込まれています。
go test
コマンドを使用してテストを実行します。 - Goのテストは、通常、
_test.go
というサフィックスを持つファイルに記述されます。 - このコミットで言及されている
test/fixedbugs/bug191.go
のようなファイルは、Goのテストスイートの一部であり、特定のバグの回帰テストとして機能します。 rundircmpout
とrundir
は、Goのテストフレームワーク内で使用される特別なディレクティブ(またはテストの種類)です。rundircmpout
: 指定されたディレクトリ内のGoソースファイルをコンパイル・実行し、その標準出力(stdout)を特定の.out
ファイルの内容と比較するテストです。出力の厳密な一致が求められます。rundir
: 指定されたディレクトリ内のGoソースファイルをコンパイル・実行するテストです。rundircmpout
とは異なり、標準出力の比較は行いません。通常、プログラムがパニックを起こさずに正常終了するか、特定の終了コードを返すかなどを検証します。
- Go言語には、標準でテストフレームワークが組み込まれています。
-
Goコンパイラ:
- gc: Go言語の公式コンパイラであり、Goプロジェクトによって開発されています。
- gccgo: GCC(GNU Compiler Collection)をバックエンドとして使用するGoコンパイラです。
gc
とは異なる実装であり、Go言語の仕様に準拠しつつも、内部的な挙動(例えば、最適化や特定の非決定的な処理の順序)がgc
と異なる場合があります。
技術的詳細
このコミットの技術的な核心は、Go言語のパッケージ初期化順序の非決定性に対処することです。
元のbug191
テストは、a.go
とb.go
という2つのパッケージがそれぞれinit
関数内でprintln
を使って文字列を出力していました。bug191.out
ファイルには、これらのprintln
の出力が特定の順序で記述されており、rundircmpout
ディレクティブによってその順序が厳密に比較されていました。
しかし、Go言語の仕様では、互いに依存関係のないパッケージのinit
関数の実行順序は保証されていません。gc
コンパイラではたまたまb
パッケージのinit
がa
パッケージのinit
より先に実行されるような順序になっていたため、bug191.out
の内容(b
の出力、a
の出力)と一致していました。
一方、gccgo
コンパイラでは、この順序が逆になることがあり、a
の出力がb
の出力より先に出る可能性がありました。これにより、gccgo
でテストを実行すると、bug191.out
との比較が失敗し、テストが落ちていました。
この問題に対処するため、コミットでは以下の変更が行われました。
- テストの目的の変更: テストの目的が、
init
関数の出力順序の検証から、init
関数が正しく実行され、グローバル変数が期待通りに初期化されることを検証することに変わりました。 println
の削除と変数による検証:a.go
とb.go
のinit
関数からprintln
呼び出しを削除し、代わりにパッケージレベルの変数(a.A
とb.B
)を初期化するように変更しました。a.go
のinit
関数でa.A = 1
を設定。b.go
のinit
関数でb.B = 2
を設定。
main.go
での検証:main.go
のmain
関数内で、これらの変数が期待通りの値(A == 1
かつB == 2
)になっているかをチェックするpanic
文を追加しました。if A != 1 || B != 2 { panic("wrong vars") }
- テストディレクティブの変更:
bug191.go
のテストディレクティブを// rundircmpout
から// rundir
に変更しました。これにより、テストは標準出力の厳密な比較を行わず、プログラムがパニックを起こさずに正常終了するかどうか(つまり、A
とB
が正しく初期化されるか)のみを検証するようになりました。 .out
ファイルの削除:bug191.out
ファイルは不要になったため削除されました。
この修正により、a
とb
のinit
関数の実行順序がどちらであっても、最終的にA
とB
が正しく初期化されていればテストは成功するようになり、gccgo
を含むすべてのGoコンパイラでテストが安定してパスするようになりました。これは、Go言語のテストが、仕様で保証されていない挙動に依存すべきではないという原則に従った修正と言えます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
-
test/fixedbugs/bug191.dir/a.go
:--- a/test/fixedbugs/bug191.dir/a.go +++ b/test/fixedbugs/bug191.dir/a.go @@ -4,8 +4,10 @@ package a +var A int + func init() { - println("a"); + A = 1 } type T int;
-
test/fixedbugs/bug191.dir/b.go
:--- a/test/fixedbugs/bug191.dir/b.go +++ b/test/fixedbugs/bug191.dir/b.go @@ -4,8 +4,10 @@ package b +var B int + func init() { - println("b"); + B = 2 } type V int;
-
test/fixedbugs/bug191.dir/main.go
:--- a/test/fixedbugs/bug191.dir/main.go +++ b/test/fixedbugs/bug191.dir/main.go @@ -11,4 +11,7 @@ var _ T var _ V func main() { + if A != 1 || B != 2 { + panic("wrong vars") + } }
-
test/fixedbugs/bug191.go
:--- a/test/fixedbugs/bug191.go +++ b/test/fixedbugs/bug191.go @@ -1,4 +1,4 @@ -// rundircmpout +// rundir // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style
-
test/fixedbugs/bug191.out
:--- a/test/fixedbugs/bug191.out +++ /dev/null @@ -1,2 +0,0 @@ -b -a
このファイルは削除されました。
コアとなるコードの解説
-
a.go
とb.go
の変更:- 元のコードでは、
init
関数内でprintln("a")
やprintln("b")
を呼び出し、標準出力に文字列を出力していました。これは、rundircmpout
テストディレクティブと組み合わせて、init
関数の実行順序を間接的に検証するためのものでした。 - 修正後、
println
の呼び出しは削除され、代わりにパッケージレベルの変数A
とB
が導入されました。init
関数内でそれぞれA = 1
とB = 2
が設定されます。これにより、init
関数が実行されたことを、副作用のある出力ではなく、変数の状態変化として表現するようになりました。この変更は、init
関数の実行順序が非決定であっても、最終的な変数の状態は決定論的であることを利用しています。
- 元のコードでは、
-
main.go
の変更:main
関数内にif A != 1 || B != 2 { panic("wrong vars") }
という行が追加されました。- このコードは、
main
関数が実行される時点で、パッケージa
とb
のinit
関数がそれぞれ実行され、変数A
とB
が期待通りの値(1と2)に初期化されていることを検証します。もしどちらかの変数が期待通りの値でなければ、プログラムはpanic
を起こし、テストは失敗します。 - この検証方法は、
init
関数の実行順序には依存せず、単にそれらが「実行されたこと」と「変数が正しく初期化されたこと」のみをチェックします。
-
bug191.go
の変更:- テストディレクティブが
// rundircmpout
から// rundir
に変更されました。 rundircmpout
は、テスト対象のプログラムの標準出力と、対応する.out
ファイルの内容を厳密に比較します。println
を使用していた元のテストでは、この比較が重要でした。rundir
は、テスト対象のプログラムを実行するだけで、その標準出力の内容は比較しません。プログラムが正常に終了するか、あるいは特定の条件でパニックを起こすかなどを検証します。- この変更は、テストの目的が標準出力の順序の検証から、変数の初期化状態の検証に変わったことに対応しています。
main.go
でpanic
による検証を行うため、標準出力の比較は不要になりました。
- テストディレクティブが
-
bug191.out
の削除:rundircmpout
ディレクティブが削除されたため、標準出力と比較するための.out
ファイルも不要になり、削除されました。
これらの変更により、bug191
テストは、Go言語の仕様で保証されていないパッケージ初期化の相対的な順序に依存することなく、init
関数が正しく機能し、パッケージレベルの変数が期待通りに初期化されることを堅牢に検証できるようになりました。これにより、異なるGoコンパイラ(gc
とgccgo
など)間でのテスト結果の不一致が解消され、テストスイートの信頼性が向上しました。
関連リンク
- Go言語のパッケージ初期化に関する公式ドキュメント(Go言語仕様):
- Go言語のテストに関する公式ドキュメント:
- Go言語のテストディレクティブ(
rundir
など)に関する情報(Goソースコード内のテストヘルパー):- https://github.com/golang/go/blob/master/src/cmd/go/test.go (Goのテスト実行ロジックの一部としてこれらのディレクティブが処理されます)
参考にした情報源リンク
- Go言語の公式ドキュメントと仕様書
- Go言語のソースコード(特に
test
ディレクトリ内の既存のテストケース) - Go言語のコンパイラ(
gc
とgccgo
)に関する一般的な知識 - Go言語の
init
関数とパッケージ初期化順序に関するコミュニティの議論(Stack Overflow, Goブログなど)
[インデックス 17955] ファイルの概要
このコミットは、Go言語のテストスイートにおけるbug191
という特定のテストケースの修正に関するものです。具体的には、パッケージのインポート順序に依存しないようにテストのロジックを変更し、異なるコンパイラ(特にgccgo)での挙動の違いによってテストが失敗する問題を解決しています。
コミット
commit 6ae378050356715a0f8a91f317030a728a89647b
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Dec 10 12:05:37 2013 -0800
test: don't rely on order of unrelated imports in bug191
There is no necessary relationship between the imports of the
packages a and b, and gccgo happens to import them in a
different order, leading to different output. This ordering
is not the purpose of the test in any case.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/40400043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ae378050356715a0f8a91f317030a728a89647b
元コミット内容
元のコミットメッセージは以下の通りです。
test: don't rely on order of unrelated imports in bug191
There is no necessary relationship between the imports of the packages a and b, and gccgo happens to import them in a different order, leading to different output. This ordering is not the purpose of the test in any case.
R=golang-dev, rsc CC=golang-dev https://golang.org/cl/40400043
変更の背景
この変更の背景には、Go言語のテストスイートにおける特定のテストケースの脆弱性がありました。Go言語では、パッケージの初期化(init
関数の実行など)は、そのパッケージがインポートする他のパッケージがすべて初期化された後に行われます。しかし、複数のパッケージが互いに独立してインポートされている場合、それらのパッケージが初期化される「相対的な順序」はGo言語の仕様によって厳密には定義されていません。
bug191
というテストは、おそらくこの初期化順序に暗黙的に依存していたと考えられます。具体的には、a
とb
という2つのパッケージがあり、それぞれがinit
関数内で何らかの副作用(例えば、標準出力への文字列出力)を起こしていました。オリジナルのGoコンパイラ(gc)では特定の順序でこれらのパッケージが初期化され、それによってテストが期待する出力が得られていた可能性があります。
一方、gccgo
(GCCベースのGoコンパイラ)では、これらの独立したパッケージのインポートおよび初期化順序がgc
とは異なっていたため、テストの出力が期待と異なり、テストが失敗していました。このテストの本来の目的は、パッケージの初期化順序を検証することではなく、別のバグ(おそらくはGo言語の初期化メカニズム自体に関連する何か)を検出することだったため、インポート順序に依存する現在のテストの挙動は不適切であると判断されました。
このコミットは、テストが特定のコンパイラのインポート順序の挙動に依存しないように修正し、テストの堅牢性と移植性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念が重要です。
-
Go言語のパッケージ初期化:
- Goプログラムは、
main
パッケージのmain
関数から実行が開始されます。 - プログラムの実行に先立ち、インポートされたすべてのパッケージが初期化されます。
- パッケージの初期化は、そのパッケージがインポートする他のすべてのパッケージが初期化された後に実行されます。
- 各パッケージは、グローバル変数の初期化と、
init
関数(複数定義可能)の実行によって初期化されます。 init
関数は、main
関数が呼び出される前に、そのパッケージ内のすべてのinit
関数が宣言順に実行されます。- 重要な点: 複数のパッケージが互いに依存関係を持たない場合、それらのパッケージが初期化される相対的な順序はGo言語の仕様によって保証されていません。コンパイラの実装やビルド環境によって順序が異なる可能性があります。
- Goプログラムは、
-
go test
コマンドとテストの種類:- Go言語には、標準でテストフレームワークが組み込まれています。
go test
コマンドを使用してテストを実行します。 - Goのテストは、通常、
_test.go
というサフィックスを持つファイルに記述されます。 - このコミットで言及されている
test/fixedbugs/bug191.go
のようなファイルは、Goのテストスイートの一部であり、特定のバグの回帰テストとして機能します。 rundircmpout
とrundir
は、Goのテストフレームワーク内で使用される特別なディレクティブ(またはテストの種類)です。rundircmpout
: 指定されたディレクトリ内のGoソースファイルをコンパイル・実行し、その標準出力(stdout)を特定の.out
ファイルの内容と比較するテストです。出力の厳密な一致が求められます。rundir
: 指定されたディレクトリ内のGoソースファイルをコンパイル・実行するテストです。rundircmpout
とは異なり、標準出力の比較は行いません。通常、プログラムがパニックを起こさずに正常終了するか、特定の終了コードを返すかなどを検証します。
- Go言語には、標準でテストフレームワークが組み込まれています。
-
Goコンパイラ:
- gc: Go言語の公式コンパイラであり、Goプロジェクトによって開発されています。
- gccgo: GCC(GNU Compiler Collection)をバックエンドとして使用するGoコンパイラです。
gc
とは異なる実装であり、Go言語の仕様に準拠しつつも、内部的な挙動(例えば、最適化や特定の非決定的な処理の順序)がgc
と異なる場合があります。
技術的詳細
このコミットの技術的な核心は、Go言語のパッケージ初期化順序の非決定性に対処することです。
元のbug191
テストは、a.go
とb.go
という2つのパッケージがそれぞれinit
関数内でprintln
を使って文字列を出力していました。bug191.out
ファイルには、これらのprintln
の出力が特定の順序で記述されており、rundircmpout
ディレクティブによってその順序が厳密に比較されていました。
しかし、Go言語の仕様では、互いに依存関係のないパッケージのinit
関数の実行順序は保証されていません。gc
コンパイラではたまたまb
パッケージのinit
がa
パッケージのinit
より先に実行されるような順序になっていたため、bug191.out
の内容(b
の出力、a
の出力)と一致していました。
一方、gccgo
コンパイラでは、この順序が逆になることがあり、a
の出力がb
の出力より先に出る可能性がありました。これにより、gccgo
でテストを実行すると、bug191.out
との比較が失敗し、テストが落ちていました。
この問題に対処するため、コミットでは以下の変更が行われました。
- テストの目的の変更: テストの目的が、
init
関数の出力順序の検証から、init
関数が正しく実行され、グローバル変数が期待通りに初期化されることを検証することに変わりました。 println
の削除と変数による検証:a.go
とb.go
のinit
関数からprintln
呼び出しを削除し、代わりにパッケージレベルの変数(a.A
とb.B
)を初期化するように変更しました。a.go
のinit
関数でa.A = 1
を設定。b.go
のinit
関数でb.B = 2
を設定。
main.go
での検証:main.go
のmain
関数内で、これらの変数が期待通りの値(A == 1
かつB == 2
)になっているかをチェックするpanic
文を追加しました。if A != 1 || B != 2 { panic("wrong vars") }
- テストディレクティブの変更:
bug191.go
のテストディレクティブを// rundircmpout
から// rundir
に変更しました。これにより、テストは標準出力の厳密な比較を行わず、プログラムがパニックを起こさずに正常終了するかどうか(つまり、A
とB
が正しく初期化されるか)のみを検証するようになりました。 .out
ファイルの削除:bug191.out
ファイルは不要になったため削除されました。
この修正により、a
とb
のinit
関数の実行順序がどちらであっても、最終的にA
とB
が正しく初期化されていればテストは成功するようになり、gccgo
を含むすべてのGoコンパイラでテストが安定してパスするようになりました。これは、Go言語のテストが、仕様で保証されていない挙動に依存すべきではないという原則に従った修正と言えます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
-
test/fixedbugs/bug191.dir/a.go
:--- a/test/fixedbugs/bug191.dir/a.go +++ b/test/fixedbugs/bug191.dir/a.go @@ -4,8 +4,10 @@ package a +var A int + func init() { - println("a"); + A = 1 } type T int;
-
test/fixedbugs/bug191.dir/b.go
:--- a/test/fixedbugs/bug191.dir/b.go +++ b/test/fixedbugs/bug191.dir/b.go @@ -4,8 +4,10 @@ package b +var B int + func init() { - println("b"); + B = 2 } type V int;
-
test/fixedbugs/bug191.dir/main.go
:--- a/test/fixedbugs/bug191.dir/main.go +++ b/test/fixedbugs/bug191.dir/main.go @@ -11,4 +11,7 @@ var _ T var _ V func main() { + if A != 1 || B != 2 { + panic("wrong vars") + } }
-
test/fixedbugs/bug191.go
:--- a/test/fixedbugs/bug191.go +++ b/test/fixedbugs/bug191.go @@ -1,4 +1,4 @@ -// rundircmpout +// rundir // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style
-
test/fixedbugs/bug191.out
:--- a/test/fixedbugs/bug191.out +++ /dev/null @@ -1,2 +0,0 @@ -b -a
このファイルは削除されました。
コアとなるコードの解説
-
a.go
とb.go
の変更:- 元のコードでは、
init
関数内でprintln("a")
やprintln("b")
を呼び出し、標準出力に文字列を出力していました。これは、rundircmpout
テストディレクティブと組み合わせて、init
関数の実行順序を間接的に検証するためのものでした。 - 修正後、
println
の呼び出しは削除され、代わりにパッケージレベルの変数A
とB
が導入されました。init
関数内でそれぞれA = 1
とB = 2
が設定されます。これにより、init
関数が実行されたことを、副作用のある出力ではなく、変数の状態変化として表現するようになりました。この変更は、init
関数の実行順序が非決定であっても、最終的な変数の状態は決定論的であることを利用しています。
- 元のコードでは、
-
main.go
の変更:main
関数内にif A != 1 || B != 2 { panic("wrong vars") }
という行が追加されました。- このコードは、
main
関数が実行される時点で、パッケージa
とb
のinit
関数がそれぞれ実行され、変数A
とB
が期待通りの値(1と2)に初期化されていることを検証します。もしどちらかの変数が期待通りの値でなければ、プログラムはpanic
を起こし、テストは失敗します。 - この検証方法は、
init
関数の実行順序には依存せず、単にそれらが「実行されたこと」と「変数が正しく初期化されたこと」のみをチェックします。
-
bug191.go
の変更:- テストディレクティブが
// rundircmpout
から// rundir
に変更されました。 rundircmpout
は、テスト対象のプログラムの標準出力と、対応する.out
ファイルの内容を厳密に比較します。println
を使用していた元のテストでは、この比較が重要でした。rundir
は、テスト対象のプログラムを実行するだけで、その標準出力の内容は比較しません。プログラムが正常に終了するか、あるいは特定の条件でパニックを起こすかなどを検証します。- この変更は、テストの目的が標準出力の順序の検証から、変数の初期化状態の検証に変わったことに対応しています。
main.go
でpanic
による検証を行うため、標準出力の比較は不要になりました。
- テストディレクティブが
-
bug191.out
の削除:rundircmpout
ディレクティブが削除されたため、標準出力と比較するための.out
ファイルも不要になり、削除されました。
これらの変更により、bug191
テストは、Go言語の仕様で保証されていないパッケージ初期化の相対的な順序に依存することなく、init
関数が正しく機能し、パッケージレベルの変数が期待通りに初期化されることを堅牢に検証できるようになりました。これにより、異なるGoコンパイラ(gc
とgccgo
など)間でのテスト結果の不一致が解消され、テストスイートの信頼性が向上しました。
関連リンク
- Go言語のパッケージ初期化に関する公式ドキュメント(Go言語仕様):
- Go言語のテストに関する公式ドキュメント:
- Go言語のテストディレクティブ(
rundir
など)に関する情報(Goソースコード内のテストヘルパー):- https://github.com/golang/go/blob/master/src/cmd/go/test.go (Goのテスト実行ロジックの一部としてこれらのディレクティブが処理されます)
参考にした情報源リンク
- Go言語の公式ドキュメントと仕様書
- Go言語のソースコード(特に
test
ディレクトリ内の既存のテストケース) - Go言語のコンパイラ(
gc
とgccgo
)に関する一般的な知識 - Go言語の
init
関数とパッケージ初期化順序に関するコミュニティの議論(Stack Overflow, Goブログなど)