[インデックス 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ブログなど)