[インデックス 14774] ファイルの概要
このコミットは、Go言語のテストスイートにおけるcompiledir
およびerrorcheckdir
テストモードが、複数のファイルで構成されるパッケージを正しく処理できるようにするための修正です。Go言語では、同じパッケージに属する複数の.go
ファイルは、単一のパッケージとしてまとめてコンパイルされます。このコミット以前は、テストランナーがこれらのファイルを個別に処理しようとしていたため、問題が発生していました。
コミット
commit 8850d14fe9c38482ee64e21f6d837c267dbe528e
Author: Russ Cox <rsc@golang.org>
Date: Wed Jan 2 15:31:49 2013 -0500
test/run: handle compiledir and errorcheckdir with multi-file packages
Multiple files with the same package all get compiled together.
R=golang-dev, iant, dave
CC=golang-dev
https://golang.org/cl/7005053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8850d14fe9c38482ee64e21f6d837c267dbe528e
元コミット内容
test/run: handle compiledir and errorcheckdir with multi-file packages
Multiple files with the same package all get compiled together.
R=golang-dev, iant, dave
CC=golang-dev
https://golang.org/cl/7005053
変更の背景
Go言語の設計において、同じディレクトリ内に存在する複数の.go
ファイルが同じpackage
宣言を持つ場合、それらは単一のパッケージの一部として扱われ、まとめてコンパイルされます。これは、パッケージ内の機能が複数のファイルに分割されている場合に、コードの整理とモジュール化を可能にするための重要な特性です。
しかし、Goのテストスイート、特にcompiledir
(ディレクトリ内のすべてのファイルをコンパイルする)およびerrorcheckdir
(ディレクトリ内のすべてのファイルをコンパイルし、エラーチェックを行う)モードでは、この「複数ファイルで構成される単一パッケージ」という概念が十分に考慮されていませんでした。以前の実装では、これらのモードがディレクトリ内の各.go
ファイルを個別にコンパイルしようとしていた可能性があります。
この問題は、例えばパッケージ内で定義された型や関数が別のファイルで参照されている場合、個別のコンパイルでは未定義エラーが発生するなど、コンパイルエラーを引き起こす原因となります。このコミットは、このようなシナリオでテストが正しく実行されるように、テストランナーの挙動を修正することを目的としています。
具体的には、test/fixedbugs/issue4326.dir/z.go
の変更が示唆するように、package main
からpackage z
への変更が行われています。これは、このテストケースが元々単一の実行可能ファイルとして扱われていたものを、複数ファイルで構成される可能性のある通常のパッケージとしてテストする必要があることを示しています。
前提知識の解説
Go言語のパッケージとコンパイル
Go言語では、コードは「パッケージ」という単位で整理されます。
- パッケージの定義: 各
.go
ファイルは、ファイルの先頭にpackage <パッケージ名>
という宣言を持ちます。 - ディレクトリとパッケージ: 通常、同じディレクトリ内のすべての
.go
ファイルは同じパッケージに属します。 - 複数ファイルのパッケージ: 1つの論理的なパッケージは、複数の物理的な
.go
ファイルに分割して記述することができます。これらのファイルはすべて同じパッケージ名を持ち、コンパイル時にはまとめて処理され、単一のパッケージバイナリ(またはライブラリ)が生成されます。これにより、パッケージ内の異なる機能や型定義を別々のファイルに整理できます。 main
パッケージ:package main
と宣言されたファイルは、実行可能なプログラムのエントリポイント(main
関数)を含み、コンパイルされると実行可能バイナリが生成されます。それ以外のパッケージはライブラリとしてコンパイルされ、他のプログラムからインポートされて利用されます。
Goテストスイートのcompiledir
とerrorcheckdir
Go言語の標準テストスイート(go test
コマンドとは異なる、Goプロジェクト自体の内部テスト)には、特定のテストモードが存在します。
compiledir
: このテストモードは、指定されたディレクトリ内のすべてのGoソースファイルをコンパイルすることを目的としています。これは、構文エラーや基本的な型チェックエラーがないことを確認するために使用されます。errorcheckdir
: このモードはcompiledir
に似ていますが、コンパイル時のエラーメッセージの内容もチェックします。特定のコードが意図的にエラーを発生させるべきである場合(例えば、不正なAPI使用のテストなど)、このモードはそのエラーメッセージが期待通りであるかを確認します。
これらのモードは、Goコンパイラ自体のテストや、特定の言語機能が正しく動作するかを確認するために重要です。
技術的詳細
このコミットの主要な変更点は、Goテストランナーがディレクトリ内のGoファイルを処理する際に、個々のファイルではなく「パッケージ」単位で処理するように変更されたことです。
-
test/fixedbugs/issue4326.dir/z.go
の変更:package main
からpackage z
へ変更されました。これにより、このテストケースが単一の実行可能ファイルではなく、通常のGoパッケージとして扱われるようになります。これは、複数ファイルパッケージのテストシナリオをより適切にシミュレートするためです。
-
test/run.go
の変更:compileInDir
関数のシグネチャ変更:- 変更前:
func compileInDir(runcmd runCmd, dir, name string) (out []byte, err error)
- 変更後:
func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error)
- これにより、
compileInDir
は単一のファイル名ではなく、可変長引数として複数のファイル名(同じパッケージに属するファイル群)を受け取れるようになりました。これは、go tool gc
コマンドが複数のソースファイルを一度に受け取ってコンパイルできるという特性を利用しています。
- 変更前:
goDirPackages
関数の追加:- この新しい関数は、指定されたディレクトリ内のGoファイルを読み込み、それらをパッケージ名に基づいてグループ化します。
- 正規表現
regexp.MustCompile(
(?m)^package (\w+))
を使用して、各ファイルのpackage
宣言からパッケージ名を抽出します。 - 結果として、
[][]string
(string
のスライスを要素とするスライス)を返します。各内部スライスは、同じパッケージに属するファイル名のリストを含みます。
compiledir
およびerrorcheckdir
のロジック変更:- これらのテストモードは、
goDirFiles
(個々のファイルをリストする)の代わりにgoDirPackages
を呼び出すようになりました。 - これにより、ループは個々のファイルではなく、パッケージごとにグループ化されたファイルリスト(
gofiles
)を処理します。 compileInDir
の呼び出しも、gofile.Name()
の代わりにgofiles...
(スライスを展開して複数の引数として渡す)を使用するように変更されました。これにより、同じパッケージのすべてのファイルが一度にコンパイラに渡されます。
- これらのテストモードは、
errorCheck
関数のシグネチャ変更とロジック修正:- 変更前:
func (t *test) errorCheck(outStr string, full, short string) (err error)
- 変更後:
func (t *test) errorCheck(outStr string, fullshort ...string) (err error)
errorCheck
関数は、エラーメッセージのパスを短縮する際に、単一のfull
パスとshort
パスのペアではなく、複数のパスのペアを受け取れるようになりました。これは、複数ファイルパッケージの場合に、エラーメッセージが複数のファイルパスを参照する可能性があるためです。wantedErrors
の呼び出しも、すべてのファイルパスに対して期待されるエラーを収集するように変更されました。
- 変更前:
-
test/testlib
の変更:- これはシェルスクリプトであり、Goテストランナーのヘルパー関数を定義しています。
pkgs()
ヘルパー関数の追加:- このシェル関数は、引数として渡されたGoファイルのリストから、パッケージごとにファイルをグループ化し、カンマ区切りのファイル名のリストとして出力します。
grep -h '^package ' $* | awk '{print $2}' | sort -u
でユニークなパッケージ名を抽出し、grep -l "^package $p\\$"
で各パッケージに属するファイルを特定しています。
compiledir
,errorcheckdir
,rundir
,rundircmpout
の変更:- これらの関数は、個々のファイルをループする代わりに、新しく追加された
pkgs()
関数を使用してパッケージ単位でファイルを処理するように変更されました。 $G -I . $(echo $pkg | tr , ' ')
のように、go tool gc
コマンドにカンマ区切りのファイル名をスペース区切りに変換して渡すことで、複数ファイルを一度にコンパイルするようにしています。errorcheckdir
では、最後のパッケージのみがlastzero
フラグ(最終的なエラーチェックを行うためのもの)を適用するようにロジックが調整されました。
- これらの関数は、個々のファイルをループする代わりに、新しく追加された
これらの変更により、Goテストスイートは、複数ファイルで構成されるGoパッケージを単一のコンパイル単位として正しく扱い、より堅牢なテストが可能になりました。
コアとなるコードの変更箇所
test/run.go
compileInDir
関数のシグネチャと実装の変更(複数ファイル対応)goDirPackages
関数の新規追加(ディレクトリ内のファイルをパッケージごとにグループ化)test.run()
メソッド内のcompiledir
およびerrorcheckdir
ケースのロジック変更(goDirFiles
からgoDirPackages
への移行、compileInDir
への引数渡し方法の変更)errorCheck
関数のシグネチャと実装の変更(複数ファイルパス対応)
test/testlib
pkgs()
シェルヘルパー関数の新規追加compiledir
,errorcheckdir
,rundir
,rundircmpout
シェル関数のロジック変更(pkgs()
の利用と、go tool gc
への複数ファイル引数渡し)
test/fixedbugs/issue4326.dir/z.go
package main
からpackage z
への変更
コアとなるコードの解説
test/run.go
におけるgoDirPackages
関数
var packageRE = regexp.MustCompile(`(?m)^package (\w+)`)
func goDirPackages(longdir string) ([][]string, error) {
files, err := goDirFiles(longdir) // ディレクトリ内の全Goファイルを取得
if err != nil {
return nil, err
}
var pkgs [][]string // パッケージごとのファイルリストを格納するスライス
m := make(map[string]int) // パッケージ名とpkgsスライス内のインデックスのマッピング
for _, file := range files {
name := file.Name()
data, err := ioutil.ReadFile(filepath.Join(longdir, name)) // ファイル内容を読み込み
if err != nil {
return nil, err
}
pkgname := packageRE.FindStringSubmatch(string(data)) // package宣言からパッケージ名を抽出
if pkgname == nil {
return nil, fmt.Errorf("cannot find package name in %s", name)
}
i, ok := m[pkgname[1]] // 既にそのパッケージ名がマップに存在するか確認
if !ok {
i = len(pkgs) // 存在しない場合、新しいパッケージとして追加
pkgs = append(pkgs, nil)
m[pkgname[1]] = i
}
pkgs[i] = append(pkgs[i], name) // 該当パッケージのファイルリストに現在のファイルを追加
}
return pkgs, nil
}
この関数は、指定されたディレクトリ内のすべてのGoファイルを読み込み、それぞれのファイルが属するパッケージ名に基づいてファイルをグループ化します。これにより、テストランナーは個々のファイルではなく、パッケージ全体をコンパイル単位として扱えるようになります。
test/run.go
におけるcompiledir
の変更
case "compiledir":
longdir := filepath.Join(cwd, t.goDirName())
pkgs, err := goDirPackages(longdir) // ここでパッケージごとにファイルをグループ化
if err != nil {
t.err = err
return
}
for _, gofiles := range pkgs { // パッケージごとのファイルリストをループ
_, t.err = compileInDir(runcmd, longdir, gofiles...) // 複数ファイルをまとめてコンパイル
if t.err != nil {
return
}
}
compiledir
モードでは、以前はgoDirFiles
で取得した個々のファイルをループしていましたが、この変更によりgoDirPackages
で取得したパッケージごとのファイルリストをループするようになりました。そして、compileInDir
にgofiles...
として複数のファイル名を渡すことで、同じパッケージに属するすべてのファイルを一度にコンパイルするようにしています。
test/testlib
におけるpkgs()
関数
# helper (not known to run.go)
# group file list by packages and return list of packages
# each package is a comma-separated list of go files.
pkgs() {
pkglist=$(grep -h '^package ' $* | awk '{print $2}' | sort -u) # ユニークなパッケージ名を抽出
for p in $pkglist
do
echo $(grep -l "^package $p\\$" $* | tr ' ' ,) # 各パッケージに属するファイルをカンマ区切りで出力
done | sort
}
このシェル関数は、test/run.go
のgoDirPackages
と同様の目的を果たしますが、シェルスクリプト環境で動作します。Goファイルのリストを受け取り、それらをパッケージ名でグループ化し、各パッケージに属するファイルをカンマ区切りで出力します。これにより、シェルスクリプトベースのテストでも複数ファイルパッケージを正しく扱えるようになります。
関連リンク
- Go言語のパッケージに関する公式ドキュメント: https://go.dev/doc/code
- Go言語のテストに関する公式ドキュメント: https://go.dev/doc/tutorial/add-a-test
参考にした情報源リンク
- https://golang.org/cl/7005053 (元のGerrit変更リスト)
- Go言語のソースコード(
test/run.go
,test/testlib
) - Go言語のパッケージとコンパイルに関する一般的な知識