[インデックス 15001] ファイルの概要
このコミットは、Go言語のテストスイートにおいて、特定のテストファイルの実行を制御するためにGoのビルドタグ(+build
ディレクティブ)をサポートするように変更を加えるものです。これにより、これまでシェルスクリプトの条件分岐で制御されていたアーキテクチャ固有またはOS固有のテストが、Goの標準的なビルドタグメカニズムによってより適切に管理されるようになります。
コミット
commit 4f6a2b9840f7894086a04e42ebf497b2d8fdbd33
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Mon Jan 28 21:29:45 2013 +0100
test: add support for build tags.
This enables a few tests that were only executed
unconditionnally.
R=rsc, minux.ma, bradfitz
CC=golang-dev
https://golang.org/cl/7103051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4f6a2b9840f7894086a04e42ebf497b2d8fdbd33
元コミット内容
test: add support for build tags.
This enables a few tests that were only executed
unconditionnally.
変更の背景
Go言語のプロジェクトでは、特定のオペレーティングシステム(OS)やCPUアーキテクチャに依存するコードやテストが存在します。これまでのGoのテストスイートでは、このような条件付きのテスト実行は、テストファイル内のコメントに記述されたシェルスクリプトの条件式(例: [ $A == 6 ]
や [ "$GOOS" == windows ]
)を用いて制御されていました。しかし、これはGoの標準的なビルドシステムが提供するビルドタグの仕組みとは異なり、テストの実行ロジックがGoコードとシェルスクリプトに分散している状態でした。
このコミットの背景には、以下の課題がありました。
- 非Goイディオムなテスト制御: Goには
+build
ディレクティブという、ファイルを含めるかどうかを制御する標準的なメカニズムがあります。テストスイートがこの標準メカニズムを利用していないことは、一貫性の欠如と理解の複雑さにつながっていました。 - テスト実行の不確実性: シェルスクリプトによる条件分岐は、
go test
コマンドのようなGoのツールチェーンから直接テストを実行する際に、期待通りに機能しない可能性がありました。特に、all.bash
のようなトップレベルのスクリプトを介さずに個別のテストを実行する場合に問題が生じやすかったです。 - メンテナンスの複雑さ: シェルスクリプトとGoコードの両方で条件ロジックを管理することは、メンテナンスの負担を増やし、エラーの温床となる可能性がありました。
このコミットは、これらの課題を解決し、GoのテストスイートをよりGoらしく、堅牢で、メンテナンスしやすいものにすることを目的としています。具体的には、シェルスクリプトによる条件分岐をGoのビルドタグに置き換え、test/run.go
内のテスト実行ロジックがこれらのビルドタグを適切に解釈するように拡張されています。これにより、特定の環境でのみ実行されるべきテストが、Goのビルドシステムと同じロジックで自動的にスキップまたは実行されるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連する概念についての知識が必要です。
-
Goのビルドタグ(Build Tags): Go言語では、ソースファイルの先頭に
// +build tag
形式のコメントを記述することで、そのファイルを特定のビルド条件に基づいてコンパイルに含めるか除外するかを制御できます。例えば、// +build linux,amd64
と書かれたファイルは、LinuxかつAMD64アーキテクチャの場合にのみコンパイルされます。!
を前置することで否定条件も指定できます(例:// +build !windows
)。これは、クロスプラットフォーム開発において、OSやアーキテクチャ固有のコードを適切に分離するために広く利用されます。 -
GOOS
とGOARCH
環境変数: Goのビルドシステムは、GOOS
(ターゲットOS、例:linux
,windows
,darwin
)とGOARCH
(ターゲットアーキテクチャ、例:amd64
,386
,arm
)という環境変数に基づいて動作します。これらの変数は、Goプログラムがどのプラットフォーム向けにビルドされるかを決定します。ビルドタグはこれらの値と照合され、ファイルの含める/含めないが決定されます。 -
Goのテストフレームワーク: Goには標準で
testing
パッケージが提供されており、go test
コマンドを使ってテストを実行します。Goのテストファイルは通常、_test.go
というサフィックスを持ち、TestXxx
という形式の関数を定義します。Goのテストスイートは、これらのテストファイルを検出し、実行します。 -
Goプロジェクトのテストスイートの構造: Go言語自体のリポジトリには、
test/
ディレクトリ以下に膨大な数のテストファイルが含まれています。これらのテストは、Goコンパイラ、ランタイム、標準ライブラリの各機能が正しく動作することを検証するために使用されます。test/run.go
は、これらのテストを管理・実行するための内部ツールであり、all.bash
などのスクリプトから呼び出されます。 -
シェルスクリプトの条件分岐: Unix系システムで広く使われるシェルスクリプト(Bashなど)では、
[ ]
や[[ ]]
構文を用いて条件式を評価し、それに基づいてコマンドの実行を制御します。例えば、[ "$GOOS" == windows ]
はGOOS
変数がwindows
と等しい場合に真となります。
このコミットは、Goのビルドタグの概念をGo自身のテストスイートに適用し、シェルスクリプトによるテストの条件付き実行をGoのビルドタグに置き換えることで、テストの管理と実行をよりGoのイディオムに沿ったものにしています。
技術的詳細
このコミットの技術的詳細は、主にtest/run.go
と、いくつかのテストファイルの変更に集約されます。
-
test/run.go
におけるshouldTest
関数の導入: このコミットの最も重要な変更は、test/run.go
にshouldTest(src string, goos, goarch string) (ok bool, whyNot string)
という新しい関数が追加されたことです。- この関数は、Goのソースコード(
src
)の先頭部分を解析し、そこに記述されている// +build
ディレクティブを読み取ります。 strings.Split(src, "\\n")
でソースコードを行ごとに分割し、各行が// +build
で始まるかどうかをチェックします。+build
行が見つかった場合、その行に含まれるタグ(例:linux
,amd64
,!windows
など)を解析します。- 現在の実行環境の
GOOS
とGOARCH
(runtime.GOOS
,runtime.GOARCH
から取得)と照合し、テストを実行すべきかどうか(ok
)と、スキップする場合の理由(whyNot
)を返します。 - タグが
goos
またはgoarch
に一致する場合、そのテストは実行されます。 - タグが
!goos
または!goarch
に一致する場合、そのテストはスキップされます。 +build
タグが存在しない場合は、デフォルトでテストが実行されると判断されます。
- この関数は、Goのソースコード(
-
test.run()
メソッドの変更:test/run.go
内のtest
構造体のrun()
メソッドが修正され、テストの実行前にshouldTest
関数を呼び出すようになりました。if ok, why := shouldTest(t.src, runtime.GOOS, runtime.GOARCH); !ok { ... }
という条件が追加されました。shouldTest
がfalse
を返した場合、t.action
が"skip"
に設定され、テストがスキップされます。showSkips
フラグが有効な場合、スキップされた理由も出力されます。- また、
action
変数の処理において、+build
行がアクションの先頭に含まれている場合に、その行をスキップするロジックが追加されました。これは、+build
行がテストの実行コマンドの一部として誤って解釈されるのを防ぐためです。
-
test/fixedbugs/bug385_32.go
、test/fixedbugs/bug385_64.go
、test/sigchld.go
の変更: これらのファイルは、これまでシェルスクリプトのコメントで条件付き実行を制御していました。test/fixedbugs/bug385_32.go
:// [ $A == 6 ] || errchk $G -e $D/$F.go
のような行が削除され、代わりに// +build 386 arm
と// errorcheck
が追加されました。これにより、このテストは32ビットアーキテクチャ(386またはARM)でのみ実行されるようになります。test/fixedbugs/bug385_64.go
: 同様に、// [ $A != 6 ] || errchk $G -e $D/$F.go
のような行が削除され、// +build amd64
と// errorcheck
が追加されました。これにより、このテストはAMD64アーキテクチャでのみ実行されるようになります。test/sigchld.go
:// [ "$GOOS" == windows ] || ...
のような行が削除され、// +build !windows
と// cmpout
が追加されました。これにより、このテストはWindows以外のOSでのみ実行されるようになります。
-
skipOkay
マップの更新:test/run.go
内のskipOkay
マップは、run.go
によって直接実行されないテストファイルをリストアップしていました。ビルドタグのサポートにより、bug385_32.go
、bug385_64.go
、sigchld.go
はもはやこのマップで明示的にスキップする必要がなくなったため、これらのエントリが削除されました。 -
test/testlib
の変更:test/testlib
は、Goのテストスイート内で使用されるシェルスクリプトのヘルパー関数を定義するファイルです。+build()
という新しいシェル関数が追加されました。この関数は、引数として与えられたタグを現在のGOOS
とGOARCH
と比較し、一致しない場合はスクリプトの実行を中断(exit 0
)します。これは、Goのビルドタグのロジックをシェルスクリプトのコンテキストで模倣するためのもので、まだシェルスクリプトに依存しているテストの互換性を保つために導入されました。
-
checkShouldTest
関数の追加:test/run.go
には、shouldTest
関数の動作を検証するためのcheckShouldTest
という内部的なテスト関数が追加されました。これは、shouldTest
が正しくビルドタグを解釈するかどうかを確認するための健全性チェックです。
これらの変更により、Goのテストスイートは、Goのビルドシステムが提供するビルドタグの機能を活用し、より一貫性のある方法でプラットフォーム固有のテストを管理できるようになりました。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主にtest/run.go
ファイルに集中しています。
test/run.go
--- a/test/run.go
+++ b/test/run.go
@@ -299,6 +299,50 @@ func goDirPackages(longdir string) ([][]string, error) {
return pkgs, nil
}
+// shouldTest looks for build tags in a source file and returns
+// whether the file should be used according to the tags.
+func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
+ if idx := strings.Index(src, "\\npackage"); idx >= 0 {
+ src = src[:idx]
+ }
+ notgoos := "!" + goos
+ notgoarch := "!" + goarch
+ for _, line := range strings.Split(src, "\\n") {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "//") {
+ line = line[2:]
+ } else {
+ continue
+ }
+ line = strings.TrimSpace(line)
+ if len(line) == 0 || line[0] != '+' {
+ continue
+ }
+ words := strings.Fields(line)
+ if words[0] == "+build" {
+ for _, word := range words {
+ switch word {
+ case goos, goarch:
+ return true, ""
+ case notgoos, notgoarch:
+ continue
+ default:
+ if word[0] == '!' {
+ // NOT something-else
+ return true, ""
+ }
+ }
+ }
+ // no matching tag found.
+ return false, line
+ }
+ }
+ // no build tags.
+ return true, ""
+}
+
+func init() { checkShouldTest() }
+
// run runs a test.
func (t *test) run() {
defer close(t.donec)
@@ -318,7 +362,18 @@ func (t *test) run() {
t.err = errors.New("double newline not found")
return
}\n+ if ok, why := shouldTest(t.src, runtime.GOOS, runtime.GOARCH); !ok {
+ t.action = "skip"
+ if *showSkips {
+ fmt.Printf("%-20s %-20s: %s\\n", t.action, t.goFileName(), why)
+ }
+ return
+ }
action := t.src[:pos]
+\tif nl := strings.Index(action, "\\n"); nl >= 0 && strings.Contains(action[:nl], "+build") {
+\t\t// skip first line
+\t\taction = action[nl+1:]
+\t}
if strings.HasPrefix(action, "//") {
action = action[2:]
}
@@ -732,17 +787,14 @@ func (t *test) wantedErrors(file, short string) (errs []wantedError) {
}
var skipOkay = map[string]bool{
- "linkx.go": true,
- "sigchld.go": true,
- "sinit.go": true,
- "fixedbugs/bug248.go": true, // combines errorcheckdir and rundir in the same dir.
- "fixedbugs/bug302.go": true, // tests both .$O and .a imports.
- "fixedbugs/bug345.go": true, // needs the appropriate flags in gc invocation.
- "fixedbugs/bug369.go": true, // needs compiler flags.
- "fixedbugs/bug385_32.go": true, // arch-specific errors.
- "fixedbugs/bug385_64.go": true, // arch-specific errors.
- "fixedbugs/bug429.go": true,
- "bugs/bug395.go": true,
+ "linkx.go": true, // like "run" but wants linker flags
+ "sinit.go": true,
+ "fixedbugs/bug248.go": true, // combines errorcheckdir and rundir in the same dir.
+ "fixedbugs/bug302.go": true, // tests both .$O and .a imports.
+ "fixedbugs/bug345.go": true, // needs the appropriate flags in gc invocation.
+ "fixedbugs/bug369.go": true, // needs compiler flags.
+ "fixedbugs/bug429.go": true, // like "run" but program should fail
+ "bugs/bug395.go": true,
}
// defaultRunOutputLimit returns the number of runoutput tests that
@@ -756,3 +808,18 @@ func defaultRunOutputLimit() int {
}\n return cpu
}\n+\n+// checkShouldTest runs canity checks on the shouldTest function.\n+func checkShouldTest() {\n+\tassert := func(ok bool, _ string) {\n+\t\tif !ok {\n+\t\t\tpanic("fail")\n+\t\t}\n+\t}\n+\tassertNot := func(ok bool, _ string) { assert(!ok, "") }\n+\tassert(shouldTest("// +build linux", "linux", "arm"))\n+\tassert(shouldTest("// +build !windows", "linux", "arm"))\n+\tassertNot(shouldTest("// +build !windows", "windows", "amd64"))\n+\tassertNot(shouldTest("// +build arm 386", "linux", "amd64"))\n+\tassert(shouldTest("// This is a test.", "os", "arch"))\n+}\n
test/fixedbugs/bug385_32.go
--- a/test/fixedbugs/bug385_32.go
+++ b/test/fixedbugs/bug385_32.go
@@ -1,7 +1,5 @@
-// [ $A == 6 ] || errchk $G -e $D/$F.go
-// NOTE: This test is not run by 'run.go' and so not run by all.bash.
-// To run this test you must use the ./run shell script.
+// +build 386 arm
+// errorcheck
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@@ -14,4 +12,4 @@ func main() {
var arr [1000200030]int // ERROR "type .* too large"
arr_bkup := arr
_ = arr_bkup
-}\n\\ No newline at end of file
+}\n
test/fixedbugs/bug385_64.go
--- a/test/fixedbugs/bug385_64.go
+++ b/test/fixedbugs/bug385_64.go
@@ -1,7 +1,5 @@
-// [ $A != 6 ] || errchk $G -e $D/$F.go
-// NOTE: This test is not run by 'run.go' and so not run by all.bash.
-// To run this test you must use the ./run shell script.
+// +build amd64
+// errorcheck
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
test/sigchld.go
--- a/test/sigchld.go
+++ b/test/sigchld.go
@@ -1,8 +1,5 @@
-// [ "$GOOS" == windows ] ||
-// ($G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out)
-// NOTE: This test is not run by 'run.go' and so not run by all.bash.
-// To run this test you must use the ./run shell script.
+// +build !windows
+// cmpout
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
test/testlib
--- a/test/testlib
+++ b/test/testlib
@@ -16,6 +16,31 @@ pkgs() {\n done | sort
}\n
+# +build aborts execution if the supplied tags don't match,\n+# i.e. none of the tags (x or !x) matches GOARCH or GOOS.\n++build() {\n+\tif (( $# == 0 )); then\n+\t\treturn\n+\tfi\n+\tfor tag; do\n+\t\tcase $tag in\n+\t\t$GOARCH|$GOOS)\n+\t\t\t#echo >&2 \"match $tag in $1\"\n+\t\t\treturn # don't exclude.\n+\t\t\t;;\n+\t\t'!'$GOARCH|'!'$GOOS)\n+\t\t\t;;\n+\t\t'!'*)\n+\t\t\t# not x where x is neither GOOS nor GOARCH.\n+\t\t\t#echo >&2 \"match $tag in $1\"\n+\t\t\treturn # don't exclude\n+\t\t\t;;\n+\t\tesac\n+\tdone\n+\t# no match.\n+\texit 0\n+}\n+\n compile() {\n $G $D/$F.go
}\n```
## コアとなるコードの解説
### `test/run.go`
1. **`shouldTest`関数**:
この関数は、Goのソースファイルの内容(`src`)と現在のOS (`goos`) およびアーキテクチャ (`goarch`) を引数として受け取ります。
* まず、ソースコードの先頭から`package`宣言までの部分を抽出します。これは、ビルドタグが通常ファイルの先頭に記述されるためです。
* `notgoos`と`notgoarch`という否定形のタグ文字列(例: `!windows`, `!amd64`)を準備します。
* ソースコードを行ごとに走査し、`// +build`で始まる行を探します。
* `+build`行が見つかった場合、その行の各単語(タグ)をループで処理します。
* `switch`文を使って、各タグが現在の`goos`または`goarch`に一致するかどうかをチェックします。
* もしタグが現在の`goos`または`goarch`と直接一致する場合(例: `+build linux`で`goos`が`linux`)、そのテストは実行されるべきであると判断し、`true`を返します。
* もしタグが現在の`goos`または`goarch`の否定形と一致する場合(例: `+build !windows`で`goos`が`windows`)、そのタグは現在の環境ではマッチしないため、次のタグのチェックに進みます(`continue`)。
* その他の否定タグ(例: `+build !tag_name`で`tag_name`が`goos`でも`goarch`でもない場合)は、そのタグが現在の環境に影響を与えないため、テストは実行されるべきであると判断し、`true`を返します。
* `+build`行内のどのタグも現在の環境にマッチしなかった場合(つまり、すべてのタグが現在の環境を排除する条件であった場合)、そのテストはスキップされるべきであると判断し、`false`を返します。
* ソースファイルに`+build`タグが全く見つからない場合は、デフォルトでテストが実行されるべきであると判断し、`true`を返します。
2. **`test.run()`メソッドの変更**:
`test`構造体の`run`メソッドは、個々のテストを実行するロジックを含んでいます。
* 追加された`if ok, why := shouldTest(...)`ブロックは、テストの実行前に`shouldTest`関数を呼び出し、その結果に基づいてテストをスキップするかどうかを決定します。
* `!ok`の場合(テストがスキップされるべき場合)、`t.action`を`"skip"`に設定し、必要に応じてスキップ理由を出力して、関数を早期に終了します。
* `action`変数を設定する部分に、`+build`行がアクションの先頭に含まれている場合に、その行をスキップするロジックが追加されました。これは、テストの実行コマンドとして解釈されるべきではないビルドタグ行を適切に無視するためです。
3. **`skipOkay`マップの更新**:
`skipOkay`マップは、`run.go`が直接実行しない特定のテストファイルをリストしていました。`bug385_32.go`、`bug385_64.go`、`sigchld.go`は、ビルドタグによって実行が制御されるようになったため、このマップから削除されました。これにより、これらのテストはビルドタグのロジックに従って自動的にスキップまたは実行されるようになります。
4. **`checkShouldTest`関数**:
これは`shouldTest`関数の内部的な単体テストのようなもので、様々なビルドタグの組み合わせに対して`shouldTest`が期待通りの結果を返すことを確認します。例えば、`// +build linux`が`linux`OSで`true`を返すこと、`// +build !windows`が`windows`OSで`false`を返すことなどを検証しています。
### `test/fixedbugs/bug385_32.go`, `test/fixedbugs/bug385_64.go`, `test/sigchld.go`
これらのファイルでは、従来のシェルスクリプトによる条件分岐のコメントが削除され、Goの標準的なビルドタグに置き換えられました。
* `bug385_32.go`: `// +build 386 arm` - 32ビットアーキテクチャ(386またはARM)でのみ実行。
* `bug385_64.go`: `// +build amd64` - AMD64アーキテクチャでのみ実行。
* `sigchld.go`: `// +build !windows` - Windows以外のOSでのみ実行。
これにより、これらのテストはGoのビルドシステムとテストランナーによって、より自然に、かつ正確に制御されるようになります。
### `test/testlib`
`testlib`に追加された`+build()`シェル関数は、Goのビルドタグのロジックをシェルスクリプトのコンテキストで模倣します。これは、Goのテストスイート内にまだ存在するシェルスクリプトベースのテストが、新しいビルドタグの概念と互換性を持つようにするためのものです。この関数は、引数として与えられたタグを現在の`GOOS`と`GOARCH`と比較し、一致するタグがない場合はスクリプトを終了させます。これにより、シェルスクリプトで書かれたテストも、Goのビルドタグと同様の条件付き実行ロジックを持つことができます。
これらの変更全体として、Goのテストスイートの内部的な仕組みが、Go言語自体のビルドシステムとより密接に連携するように進化し、テストの管理と実行がより効率的かつ一貫性のあるものになっています。
## 関連リンク
* Go言語のビルドタグに関する公式ドキュメント(Go 1.18以降の新しい構文を含むが、基本的な概念は同じ):
[https://go.dev/cmd/go/#hdr-Build_constraints](https://go.dev/cmd/go/#hdr-Build_constraints)
* Goの`testing`パッケージのドキュメント:
[https://pkg.go.dev/testing](https://pkg.go.dev/testing)
* Goの`runtime`パッケージのドキュメント(`GOOS`や`GOARCH`に相当する情報を提供する):
[https://pkg.go.dev/runtime](https://pkg.go.dev/runtime)
## 参考にした情報源リンク
* Goの公式ドキュメント(上記「関連リンク」に記載)
* Goのソースコード(特に`cmd/go/internal/load/pkg.go`や`go/build/build.go`など、ビルドタグの解析に関連する部分)
* Goのコミット履歴とGerritレビュー(`https://golang.org/cl/7103051`)
* Goのテストスイートの構造と実行方法に関する一般的な知識
* シェルスクリプトの条件分岐に関する一般的な知識