[インデックス 14184] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるパッケージリストの順序予測可能性を向上させるための変更と、cmd/go/test.bash
スクリプトにクリーンアップフェーズを追加するものです。特に、Goのマップ(map
)のイテレーション順序が不定であるという特性に起因する go list
コマンドの出力の不安定性を解消することを目的としています。
コミット
commit 6a3ad481cd495bc22aa4f892ad8f0c225acac1f3
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sat Oct 20 17:25:13 2012 +0800
cmd/go: make package list order predicable
also add a cleanup phase to cmd/go/test.bash.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6741050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6a3ad481cd495bc22aa4f892ad8f0c225acac1f3
元コミット内容
cmd/go: make package list order predicable
also add a cleanup phase to cmd/go/test.bash.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6741050
変更の背景
Go言語の map
型は、その設計上、要素のイテレーション(走査)順序が保証されていません。これは、パフォーマンス最適化のためにハッシュテーブルが内部的に使用されており、要素の追加や削除によってメモリ上の配置が変わり、結果としてイテレーション順序が実行ごとに、あるいは同じ実行内でも変化する可能性があるためです。Go 1以降では、この不定性を開発者が当てにしないように、意図的にイテレーション順序がランダム化されています。
cmd/go
ツール、特に go list
コマンドは、Goのパッケージ情報をリストアップする際に内部的にマップを使用している可能性がありました。このマップの不定なイテレーション順序が、go list
コマンドの出力順序に影響を与え、テストの再現性やスクリプトでの利用において問題を引き起こす可能性がありました。例えば、go list std
のようなコマンドを実行した際に、標準ライブラリのパッケージリストが実行ごとに異なる順序で出力されると、その出力を比較するテストが不安定になります。
このコミットは、このような go list
コマンドの出力順序の不安定性を解消し、予測可能な順序でパッケージがリストされるようにすることで、テストの信頼性を向上させ、ツールの挙動をより安定させることを目的としています。また、テストスクリプト test.bash
にクリーンアップ処理を追加することで、テスト実行後の環境をきれいに保ち、次のテスト実行に影響が出ないようにしています。
前提知識の解説
Go言語の map
のイテレーション順序
Go言語の map
はキーと値のペアを格納するデータ構造で、他の言語のハッシュマップや辞書に相当します。Goの仕様では、map
のイテレーション順序は保証されていません。これは、map
が内部的にハッシュテーブルとして実装されており、要素の追加、削除、リサイズなどによってメモリ上の配置が動的に変化するためです。Go 1からは、開発者がこの不定な順序に依存しないように、意図的にイテレーション順序がランダム化されるようになりました。これにより、もしコードが map
のイテレーション順序に依存している場合、その問題が早期に発見されるようになっています。
もし特定の順序で map
の要素を処理したい場合は、キーをスライスに抽出し、そのスライスをソートしてから、ソートされたキーを使って map
から値を取り出すという方法が一般的です。
go list
コマンド
go list
コマンドは、Goのパッケージやモジュールに関する情報を表示するための強力なツールです。指定されたパッケージのインポートパス、ディレクトリ、依存関係、ビルド情報など、多岐にわたる詳細情報を取得できます。このコマンドは、Goプロジェクトの構造を理解したり、ビルドスクリプトや自動化ツールでパッケージ情報を利用したりする際によく使われます。
例えば、go list std
はGoの標準ライブラリに含まれるすべてのパッケージのインポートパスをリストアップします。go list -f '{{.Dir}} {{.ImportPath}}'
のように -f
フラグとGoの text/template
構文を組み合わせることで、出力形式をカスタマイズし、より詳細な情報を抽出することも可能です。
test.bash
スクリプト
test.bash
は、Goプロジェクトのテストスイートを実行するためのシェルスクリプトです。Goの標準ライブラリやツールのテストは、このようなスクリプトを通じて自動化されており、継続的インテグレーション(CI)システムなどで利用されます。テストスクリプトは、テストの実行、結果の検証、そしてテストによって生成された一時ファイルのクリーンアップなど、一連の処理を自動で行います。
技術的詳細
このコミットの主要な変更点は、src/cmd/go/pkg.go
ファイル内の packagesAndErrors
関数におけるパッケージのロードロジックです。
変更前は、args
スライスから引数を受け取り、それを set
という map[string]bool
に格納していました。その後、この set
マップをイテレーションして loadPackage
を呼び出し、結果を pkgs
スライスに追加していました。
// 変更前 (簡略化)
var set = make(map[string]bool)
for _, arg := range args {
set[arg] = true // ここでマップに格納
}
for arg := range set { // マップのイテレーション順序は不定
pkgs = append(pkgs, loadPackage(arg, &stk))
}
Goの map
のイテレーション順序は不定であるため、for arg := range set
のループは、set
に格納されたキー(パッケージ名)を毎回異なる順序で返す可能性がありました。これが pkgs
スライスにパッケージが追加される順序に影響を与え、結果として go list
コマンドの出力順序が不安定になる原因となっていました。
変更後は、args
スライスを直接イテレーションし、set
マップは既に処理した引数を追跡するためにのみ使用されます。
// 変更後 (簡略化)
var set = make(map[string]bool)
for _, arg := range args { // args スライスのイテレーション順序は保証される
if !set[arg] { // 既に処理済みでなければ
pkgs = append(pkgs, loadPackage(arg, &stk))
set[arg] = true // 処理済みとしてマーク
}
}
この変更により、pkgs
スライスにパッケージが追加される順序は、入力 args
スライスの順序に厳密に従うようになります。set
マップは、重複するパッケージ引数が複数回ロードされないようにするためのチェックとして機能し、map
のイテレーション順序の不定性の影響を受けなくなります。これにより、go list
コマンドの出力順序が予測可能かつ安定したものになります。
また、src/cmd/go/test.bash
には、go list std
の出力が実行間で一貫していることを確認するための新しいテストが追加されました。このテストは、go list std
の出力をファイルに保存し、その後の実行で同じコマンドの出力と比較することで、順序の一貫性を検証します。さらに、テスト実行後に生成される一時ファイル (test_std.list
, testdata/bin
, testdata/bin1
, testgo
) を削除するクリーンアップフェーズが追加され、テスト環境の健全性が保たれるようになりました。
コアとなるコードの変更箇所
src/cmd/go/pkg.go
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -674,12 +674,11 @@ func packagesAndErrors(args []string) []*Package {
var set = make(map[string]bool)
for _, arg := range args {
-\t\tset[arg] = true
-\t}\n-\tfor arg := range set {\n-\t\tpkgs = append(pkgs, loadPackage(arg, &stk))\n+\t\tif !set[arg] {
+\t\t\tpkgs = append(pkgs, loadPackage(arg, &stk))
+\t\t\tset[arg] = true
+\t\t}
}\n-\n \tcomputeStale(pkgs...)\n \n \treturn pkgs
src/cmd/go/test.bash
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -142,6 +142,18 @@ if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
ok=false
fi
+# ensure that output of 'go list' is consistent between runs
+./testgo list std > test_std.list
+if ! ./testgo list std | cmp -s test_std.list - ; then
+\techo "go list std ordering is inconsistent"
+\tok=false
+fi
+rm -f test_std.list
+
+# clean up
+rm -rf testdata/bin testdata/bin1
+rm -f testgo
+
if $ok; then
\techo PASS
else
コアとなるコードの解説
src/cmd/go/pkg.go
の変更
packagesAndErrors
関数は、go
コマンドに渡されたパッケージ引数を処理し、対応する *Package
オブジェクトのリストを返す役割を担っています。
変更前は、まずすべての引数を set
マップのキーとして追加し、その後 set
マップをイテレーションして loadPackage
を呼び出していました。この for arg := range set
の部分が、Goのマップのイテレーション順序が不定であるという特性により、pkgs
スライスにパッケージが追加される順序を不安定にしていました。
変更後は、for _, arg := range args
ループで元の args
スライスを直接イテレーションします。args
スライスは順序が保証されているため、このループの順序は常に一定です。
if !set[arg]
の条件は、同じパッケージが複数回引数として渡された場合に、重複して loadPackage
が呼び出されるのを防ぐためのものです。set[arg] = true
で、そのパッケージが既に処理されたことを記録します。
この修正により、pkgs
スライスにパッケージが追加される順序は、args
スライスで指定された順序と一致するようになり、go list
コマンドの出力が予測可能になります。
src/cmd/go/test.bash
の変更
このシェルスクリプトの変更は、主に2つの部分からなります。
-
go list std
の出力順序の一貫性テスト:./testgo list std > test_std.list
:go list std
コマンドの出力をtest_std.list
というファイルにリダイレクトして保存します。if ! ./testgo list std | cmp -s test_std.list - ; then ... fi
: 再度go list std
を実行し、その出力をcmp -s
コマンドを使ってtest_std.list
の内容と比較します。cmp -s
は、2つのファイルが同じであれば何も出力せず、異なる場合に非ゼロの終了ステータスを返します。|
はパイプで、左側のコマンドの標準出力を右側のコマンドの標準入力に渡します。-
はcmp
コマンドにおいて標準入力を意味します。
- もし比較結果が異なれば(つまり、
go list std
の出力順序が不安定であれば)、エラーメッセージを出力し、テスト全体のok
フラグをfalse
に設定します。 rm -f test_std.list
: テストで使用した一時ファイルを削除します。
-
クリーンアップフェーズの追加:
rm -rf testdata/bin testdata/bin1
: テスト中に生成される可能性のあるtestdata/bin
およびtestdata/bin1
ディレクトリを再帰的に削除します。rm -f testgo
: テスト実行用のバイナリtestgo
を削除します。
これらの変更により、go list
コマンドの出力順序の安定性がテストによって保証されるようになり、またテスト実行後の環境が常にクリーンな状態に保たれることで、テストの信頼性と再現性が向上します。
関連リンク
- Go言語の
map
のイテレーション順序に関する公式ドキュメントやブログ記事 go list
コマンドの公式ドキュメント
参考にした情報源リンク
- Go言語の
map
のイテレーション順序に関する情報: go list
コマンドに関する情報:- Go CL 6741050 はウェブ検索では見つかりませんでした。