[インデックス 12187] ファイルの概要
このコミットは、Go言語のテストスイートにおけるテスト実行方法を簡素化し、標準出力の比較をより効率的に行うためのcmpout
という新しいテストディレクティブを導入するものです。これにより、テストファイルの先頭に記述されていた複雑なシェルコマンドが// cmpout
という簡潔な記述に置き換えられ、テストの可読性と保守性が向上しています。
コミット
commit e014cf0e545ca16abfd2a80d541750c6a3809082
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 24 13:17:26 2012 +1100
test: add cmpout to testlib
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5699060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e014cf0e545ca16abfd2a80d541750c6a3809082
元コミット内容
test: add cmpout to testlib
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5699060
変更の背景
Go言語のテストスイートでは、特定のテストケースが生成する標準出力(stdout)を、期待される出力ファイルと比較することで、そのテストの正しさを検証するパターンが頻繁に用いられていました。これまでの方法では、各テストファイルの先頭に以下のようなシェルコマンドがコメントとして記述されていました。
// $G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out
このコマンドは、Goプログラムのコンパイル、リンク、実行、そしてその標準出力と期待される出力ファイルとの比較という一連の複雑な処理を記述しています。しかし、この記述は冗長であり、多くのテストファイルで繰り返し現れるため、テストコードの可読性を損ね、またテスト実行ロジックの変更があった場合に多数のファイルを修正する必要があるという保守性の問題がありました。
このコミットは、このような共通のテストパターンをcmpout
という新しいディレクティブとして抽象化し、testlib
とtest/run.go
にそのロジックを組み込むことで、テストファイルの記述を簡素化し、テストインフラの保守性を向上させることを目的としています。
前提知識の解説
この変更を理解するためには、以下の概念について知っておく必要があります。
-
Go言語のテストインフラ: Go言語のプロジェクトでは、
test
ディレクトリ以下に様々なテストケースが配置されています。これらのテストは、Goの標準テストフレームワーク(go test
)だけでなく、カスタムのテストスクリプトやユーティリティ(例:test/run.go
、test/testlib
)によって実行されることがあります。これは、コンパイラやランタイムの低レベルな挙動を検証するためなど、go test
だけではカバーしきれない特殊なテスト要件に対応するためです。 -
test/run.go
: これはGo言語のテストスイート内で使用されるカスタムテストランナーの一つです。テストファイルの先頭に記述された特定のコメント行(ディレクティブ)を解析し、それに基づいてテストのコンパイル、実行、検証などのアクションを決定します。例えば、// run
、// compile
、// errorcheck
といったディレクティブが存在します。 -
test/testlib
: これはシェルスクリプトの関数定義を含むファイルで、test/run.go
などのテストランナーから呼び出される共通のヘルパー関数を提供します。Goテストのコンパイル、リンク、実行、出力比較など、繰り返し使用されるシェルコマンドのロジックがここにカプセル化されています。 -
シェルコマンドとリダイレクト:
$G
,$D
,$F
,$L
,$A
: これらはシェル変数であり、Goのテストインフラ内で特定のパスやファイル名、アーキテクチャ情報などを表すために使用されます。$G
: Goコンパイラへのパス。$D
: テストファイルのディレクトリパス。$F
: テストファイルのベース名(拡張子なし)。$L
: Goリンカへのパス。$A
: 実行可能ファイルのアーキテクチャサフィックス(例:.exe
)。
2>&1
: これはシェルにおけるファイルディスクリプタのリダイレクトです。2
は標準エラー出力(stderr)、1
は標準出力(stdout)を表します。2>&1
は、標準エラー出力を標準出力にリダイレクトすることを意味します。これにより、プログラムの標準出力と標準エラー出力の両方が、後続のパイプ(|
)に渡されます。|
: パイプ。左側のコマンドの標準出力を、右側のコマンドの標準入力に接続します。cmp - file
:cmp
コマンドは2つのファイルをバイト単位で比較します。-
は標準入力を意味します。したがって、cmp - $D/$F.out
は、パイプで渡された入力(プログラムの標準出力と標準エラー出力)を、$D/$F.out
というパスにある期待される出力ファイルと比較します。
技術的詳細
このコミットの技術的な核心は、Goのテストインフラにおけるテスト実行フローの抽象化と効率化にあります。
-
cmpout
ディレクティブの導入:- これまでテストファイルの先頭に直接記述されていた複雑なシェルコマンド(
$G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out
)が、// cmpout
という簡潔なコメントに置き換えられました。 - これにより、テストの意図がより明確になり、テストファイルの可読性が大幅に向上しました。
- これまでテストファイルの先頭に直接記述されていた複雑なシェルコマンド(
-
test/run.go
の変更:test/run.go
内のrun()
関数に、新しいcmpout
ケースが追加されました。case "cmpout": action = "run" // the run case already looks for <dir>/<test>.out files fallthrough
- このコードは、テストファイルのディレクティブが
cmpout
である場合、内部的にaction
変数を"run"
に設定し、fallthrough
キーワードによって次のcase "compile", "build", "run", "errorcheck":
ブロックに処理を継続させます。 - これは、
cmpout
が本質的に「テストを実行し、その出力を比較する」というrun
アクションの特殊なバリエーションであることを示しています。run
アクションは既に<dir>/<test>.out
ファイルを探すロジックを含んでいるため、このfallthrough
によって既存のrun
ロジックを再利用しつつ、cmpout
固有の比較処理をtestlib
に委譲する設計になっています。
-
test/testlib
の変更:testlib
ファイルに新しいシェル関数cmpout()
が追加されました。cmpout() { $G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out }
- この関数は、以前テストファイルの先頭に直接記述されていたシェルコマンドと全く同じロジックを含んでいます。
test/run.go
がcmpout
ディレクティブを検出すると、最終的にこのcmpout()
シェル関数が呼び出され、Goプログラムのコンパイル、リンク、実行、そしてその標準出力と期待される出力ファイル($D/$F.out
)との比較が実行されます。
この変更により、テストの実行ロジックがtest/run.go
とtest/testlib
に一元化され、テストファイルの記述が簡素化されました。将来的に出力比較のロジックに変更が必要になった場合でも、testlib
内のcmpout()
関数を修正するだけで済み、多数のテストファイルを個別に修正する必要がなくなります。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルに集中しています。
-
test/run.go
:--- a/test/run.go +++ b/test/run.go @@ -238,6 +238,9 @@ func (t *test) run() { action = strings.TrimSpace(action) switch action { + case "cmpout": + action = "run" // the run case already looks for <dir>/<test>.out files + fallthrough case "compile", "build", "run", "errorcheck": t.action = action default:
-
test/testlib
:--- a/test/testlib +++ b/test/testlib @@ -17,6 +17,10 @@ run() { $G $D/$F.go && $L $F.$A && ./$A.out "$@" } +cmpout() { + $G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out +} + errorcheck() { errchk $G -e $D/$F.go }
また、以下の複数のテストファイルで、先頭のコメント行が従来の複雑なシェルコマンドから// cmpout
に置き換えられています。
test/deferprint.go
test/fixedbugs/bug328.go
test/fixedbugs/bug409.go
test/goprint.go
test/helloworld.go
test/ken/cplx0.go
test/ken/string.go
test/printbig.go
コアとなるコードの解説
test/run.go
の変更
test/run.go
の変更は、test
構造体のrun()
メソッド内で行われています。このメソッドは、テストファイルの先頭に記述されたディレクティブ(コメント行)を解析し、それに応じたアクションを実行します。
switch action {
case "cmpout":
action = "run" // the run case already looks for <dir>/<test>.out files
fallthrough
case "compile", "build", "run", "errorcheck":
t.action = action
default:
// ... (既存の処理)
}
case "cmpout":
: 新しく追加されたケースです。テストファイルのディレクティブが"cmpout"
である場合にこのブロックが実行されます。action = "run"
:cmpout
ディレクティブが検出された場合、内部的に実行されるアクションを"run"
に設定します。これは、cmpout
が「プログラムを実行する」という基本的な動作を含むためです。// the run case already looks for <dir>/<test>.out files
: このコメントは、run
アクションの既存のロジックが、テスト対象のGoプログラムの出力と比較するための期待される出力ファイル(例:test/deferprint.out
)を自動的に探すことを示唆しています。fallthrough
: このキーワードは、現在のcase
ブロックの処理が完了した後、次のcase
ブロックの条件を評価せずに、そのまま次のcase
ブロックの処理を実行することを指示します。これにより、cmpout
ディレクティブはrun
アクションのロジック(プログラムの実行など)を再利用しつつ、cmpout
固有の出力比較ロジックはtestlib
に委譲される形になります。
このfallthrough
の利用は、Goのswitch
文の強力な機能の一つであり、共通の処理を複数のケースで共有しつつ、特定のケースで追加の処理を行う場合に有効です。
test/testlib
の変更
test/testlib
はシェルスクリプトであり、Goテストの実行に必要な共通のシェル関数を定義しています。
cmpout() {
$G $D/$F.go && $L $F.$A && ./$A.out 2>&1 | cmp - $D/$F.out
}
cmpout()
: 新しく定義されたシェル関数です。$G $D/$F.go
: Goコンパイラ($G
)を使用して、現在のテストファイル($D/$F.go
)をコンパイルします。&&
: 論理AND演算子。左側のコマンドが成功した場合(終了コードが0の場合)にのみ、右側のコマンドを実行します。$L $F.$A
: Goリンカ($L
)を使用して、コンパイルされたオブジェクトファイル($F.$A
)をリンクし、実行可能ファイルを生成します。./$A.out
: 生成された実行可能ファイルを実行します。2>&1
: 実行可能ファイルの標準エラー出力(stderr)を標準出力(stdout)にリダイレクトします。これにより、プログラムのすべての出力(stdoutとstderr)がパイプに渡されます。|
: パイプ。左側のコマンド(プログラムの実行と出力リダイレクト)の標準出力を、右側のコマンド(cmp
)の標準入力に渡します。cmp - $D/$F.out
:cmp
コマンドは、標準入力(-
)として受け取ったプログラムの出力と、期待される出力ファイル($D/$F.out
)の内容を比較します。両者が一致すればcmp
は成功し、そうでなければ失敗します。
このcmpout()
関数は、Goテストインフラの外部から直接呼び出されることは少なく、主にtest/run.go
のようなテストランナーによって、cmpout
ディレクティブが指定されたテストに対して実行されます。これにより、テストの出力比較ロジックが一箇所に集約され、管理が容易になります。
関連リンク
- Go CL (Change List): https://golang.org/cl/5699060
参考にした情報源リンク
- Go言語のソースコード(特に
test
ディレクトリ内のファイル) - シェルスクリプトのリダイレクトとパイプに関する一般的なドキュメント
cmp
コマンドのmanページまたはドキュメント- Go言語のテストに関する公式ドキュメントやブログ記事(一般的なGoテストの概念理解のため)
- Goの
switch
文におけるfallthrough
キーワードの挙動に関するドキュメント