Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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テストスイートのcompiledirerrorcheckdir

Go言語の標準テストスイート(go testコマンドとは異なる、Goプロジェクト自体の内部テスト)には、特定のテストモードが存在します。

  • compiledir: このテストモードは、指定されたディレクトリ内のすべてのGoソースファイルをコンパイルすることを目的としています。これは、構文エラーや基本的な型チェックエラーがないことを確認するために使用されます。
  • errorcheckdir: このモードはcompiledirに似ていますが、コンパイル時のエラーメッセージの内容もチェックします。特定のコードが意図的にエラーを発生させるべきである場合(例えば、不正なAPI使用のテストなど)、このモードはそのエラーメッセージが期待通りであるかを確認します。

これらのモードは、Goコンパイラ自体のテストや、特定の言語機能が正しく動作するかを確認するために重要です。

技術的詳細

このコミットの主要な変更点は、Goテストランナーがディレクトリ内のGoファイルを処理する際に、個々のファイルではなく「パッケージ」単位で処理するように変更されたことです。

  1. test/fixedbugs/issue4326.dir/z.goの変更:

    • package mainからpackage zへ変更されました。これにより、このテストケースが単一の実行可能ファイルではなく、通常のGoパッケージとして扱われるようになります。これは、複数ファイルパッケージのテストシナリオをより適切にシミュレートするためです。
  2. 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宣言からパッケージ名を抽出します。
      • 結果として、[][]stringstringのスライスを要素とするスライス)を返します。各内部スライスは、同じパッケージに属するファイル名のリストを含みます。
    • 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の呼び出しも、すべてのファイルパスに対して期待されるエラーを収集するように変更されました。
  3. 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で取得したパッケージごとのファイルリストをループするようになりました。そして、compileInDirgofiles...として複数のファイル名を渡すことで、同じパッケージに属するすべてのファイルを一度にコンパイルするようにしています。

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.gogoDirPackagesと同様の目的を果たしますが、シェルスクリプト環境で動作します。Goファイルのリストを受け取り、それらをパッケージ名でグループ化し、各パッケージに属するファイルをカンマ区切りで出力します。これにより、シェルスクリプトベースのテストでも複数ファイルパッケージを正しく扱えるようになります。

関連リンク

参考にした情報源リンク

  • https://golang.org/cl/7005053 (元のGerrit変更リスト)
  • Go言語のソースコード(test/run.go, test/testlib
  • Go言語のパッケージとコンパイルに関する一般的な知識