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

[インデックス 1157] ファイルの概要

このコミットは、Go言語の初期開発段階において、ユニットテストの自動化を目的とした新しいシェルスクリプト gotest を導入するものです。これは、現在の go test コマンドの原型となる非常に初期の実装であり、テストファイルのコンパイル、テスト実行用のメイン関数の動的生成、そしてテストの実行という一連のプロセスを自動化します。

コミット

commit d4953725099792e625decc1a812bff44356dce37
Author: Rob Pike <r@golang.org>
Date:   Tue Nov 18 14:12:14 2008 -0800

    new gotest shell script (will be a proper command some day, probably)
    automates construction and execution of unit tests.
    
    R=rsc
    DELTA=60  (58 added, 0 deleted, 2 changed)
    OCL=19482
    CL=19484

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d4953725099792e625decc1a812bff44356dce37

元コミット内容

新しい gotest シェルスクリプト(おそらく将来的に適切なコマンドになるだろう)は、ユニットテストの構築と実行を自動化する。

変更の背景

このコミットは2008年11月に行われており、Go言語がまだ公開される前の非常に初期の段階に当たります。当時のGo言語には、現在のような統合された go test コマンドは存在せず、テストの実行は手動で行うか、個別のスクリプトで対応する必要がありました。

Go言語の設計思想の一つに、テストの容易さがあります。標準ライブラリに testing パッケージが提供され、go test コマンドによってテストが自動的に発見・実行される仕組みは、Go言語の大きな特徴です。このコミットは、その自動テスト実行の基盤を築くための最初の一歩として、gotest というシェルスクリプトを導入しました。コミットメッセージにある「will be a proper command some day, probably」という記述は、このスクリプトが将来的にGoツールチェインの一部として統合されることを示唆しており、現在の go test コマンドへの進化を予見させるものです。

当時のGo言語のビルドシステムは、6g (Goコンパイラ for AMD64), 6l (Goリンカ for AMD64), 6nm (シンボルリストユーティリティ for AMD64) といった、Plan 9由来のアーキテクチャ固有のコマンドに依存していました。この gotest スクリプトは、これらの低レベルなツールを直接呼び出すことで、テストのコンパイルと実行を実現しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の初期のツールチェインとテストに関する概念を理解しておく必要があります。

  • 6g, 6l, 6nm: これらはGo言語の初期のビルドツールチェーンの一部でした。
    • 6g: Goコンパイラ。Goのソースコードをオブジェクトファイル(.6 拡張子)にコンパイルします。6 はAMD64アーキテクチャを指します。
    • 6l: Goリンカ。オブジェクトファイルを結合し、実行可能ファイルを生成します。
    • 6nm: オブジェクトファイルや実行可能ファイルからシンボルをリストアップするユーティリティ。特に、テスト関数を識別するために使用されます。 これらのツールは、Go 1.0以降の go buildgo test といった統合コマンドに置き換えられましたが、Goのビルドプロセスの根幹をなすものでした。
  • testing パッケージ: Go言語の標準ライブラリに含まれる、ユニットテスト、ベンチマーク、サンプルコードテストを記述するためのパッケージです。テスト関数は TestXxx という命名規則に従い、*testing.T 型の引数を取ります。
  • testing.Main 関数: testing パッケージが提供する関数で、テストの実行をオーケストレーションします。通常、テスト実行可能ファイルの main 関数から呼び出され、発見されたすべてのテスト関数を実行します。
  • シェルスクリプト: このコミットで導入される gotest は、Bashシェルスクリプトとして実装されています。これは、Go言語自体でテストツールがまだ十分に成熟していなかったため、既存のシェル機能を利用してテストプロセスを自動化したものです。

技術的詳細

このコミットの主要な技術的詳細は、src/cmd/gotest/gotest シェルスクリプトに集約されています。このスクリプトは、以下のステップでユニットテストを自動化します。

  1. テストファイルの特定: カレントディレクトリ内の test*.go というパターンにマッチするすべてのGoファイルをテストファイルとして識別します。
  2. 個別のコンパイル: 識別された各 test*.go ファイルを 6g コンパイラを使用して個別にコンパイルします。これにより、各テストファイルに対応するオブジェクトファイル(.6 拡張子)が生成されます。
  3. _testmain.go の動的生成: ここがこのスクリプトの最も重要な部分です。Goのテストフレームワークは、すべてのテスト関数を呼び出す単一の main 関数を持つ実行可能ファイルを必要とします。このスクリプトは、以下の内容を持つ _testmain.go というGoソースファイルを動的に生成します。
    • package main: 実行可能ファイルのエントリポイントとなる main パッケージを宣言します。
    • テストファイルのインポート: 各テストファイル(例: test_foo.go)を ./test_foo のようにパッケージとしてインポートします。これにより、各テストファイル内で定義されたテスト関数が _testmain.go から参照可能になります。
    • import "testing": Goの標準テストパッケージをインポートします。
    • テスト関数の発見とリスト化: 6nm コマンドを使用して、コンパイル済みのオブジェクトファイル(.6 ファイル)からテスト関数を抽出します。具体的には、6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./' というコマンドチェーンが使用されます。
      • 6nm $ofiles: すべてのオブジェクトファイルからシンボルをリストアップします。
      • grep ' T .*·Test': グローバルなテキストセクション (T) にあり、Goの内部的なシンボル名で ·Test を含むもの(例: main·TestMyFunction)をフィルタリングします。これはGoのテスト関数の命名規則と内部表現に基づいています。
      • sed 's/.* //; s/·/./': シンボル名から不要な部分を削除し、main·TestMyFunction のような形式を main.TestMyFunction のような、Goのコードで直接参照できる形式に変換します。
    • testing.Test 構造体の配列生成: 抽出された各テスト関数に対して、testing.Test 構造体のインスタンスを生成し、それらを var tests = &[]testing.Test { ... } という配列に格納します。この配列は testing.Main 関数に渡されます。
    • main 関数の定義: func main() { testing.Main(tests) } という main 関数を定義します。この関数が実行されると、testing.Maintests 配列内のすべてのテスト関数を順次実行します。
  4. _testmain.go のコンパイルとリンク: 生成された _testmain.go6g でコンパイルし、6l でリンクして実行可能ファイルを生成します。このリンクプロセスでは、ステップ2でコンパイルされた個々のテストファイルのオブジェクトファイルも自動的に結合されます。
  5. テストの実行: 最後に、生成された実行可能ファイル(6.out)を実行します。これにより、すべてのユニットテストが実行され、結果が標準出力に表示されます。

この一連のプロセスは、現在の go test コマンドが内部的に行っていることと非常に似ており、Go言語のテストフレームワークの設計思想が初期段階から確立されていたことを示しています。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。

  1. src/cmd/gotest/Makefile (新規追加):

    # Copyright 2009 The Go Authors. All rights reserved.
    # Use of this source code is governed by a BSD-style
    # license that can be found in the LICENSE file.
    
    include ../../Make.conf
    
    TARG=gotest
    
    clean:
    	@true
    
    install: $(TARG)
    	cp $(TARG) $(BIN)/$(TARG)
    

    このMakefileは、gotest コマンドのビルドとインストール方法を定義しています。clean ターゲットは @true で何もしませんが、これは src/cmd/clean.bashgotest のクリーンアップが処理されるためです。

  2. src/cmd/gotest/gotest (新規追加):

    #!/bin/bash
    # Copyright 2009 The Go Authors. All rights reserved.
    # Use of this source code is governed by a BSD-style
    # license that can be found in the LICENSE file.
    
    # Using all the test*.go files in the current directory, write out a file
    # _testmain.go that runs all its tests. Compile everything and run the
    # tests.
    
    set -e
    
    gofiles=$(echo test*.go)
    ofiles=$(echo $gofiles | sed 's/\.go/.6/g')
    files=$(echo $gofiles | sed 's/\.go//g')
    echo $ofiles
    
    for i in $gofiles
    do
    	6g $i
    done
    
    # They all compile; now generate the code to call them.
    
    >{
    	# package spec
    	echo 'package main'
    	echo
    	# imports
    	for i in $files
    	do
    		echo 'import "./'$i'"'
    	done
    	echo 'import "testing"'
    	# test array
    	echo
    	echo 'var tests = &[]testing.Test {'
    	for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
    	do
    		echo '	testing.Test{ "'$i'", &'$i' },'
    	done
    	echo '}'
    	# body
    	echo
    	echo 'func main() {'
    	echo '	testing.Main(tests)'
    	echo '}'
    }>_testmain.go
    
    6g _testmain.go
    6l _testmain.6
    6.out
    

    このシェルスクリプトが、テストの自動化ロジックの全てを実装しています。

  3. src/cmd/clean.bash:

    --- a/src/cmd/clean.bash
    +++ b/src/cmd/clean.bash
    @@ -3,7 +3,7 @@
     # Use of this source code is governed by a BSD-style
     # license that can be found in the LICENSE file.
    
    -for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof
    +for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof gotest
     do
     	cd $i
     	make clean
    

    gotest がクリーンアップ対象のコマンドリストに追加されました。

  4. src/cmd/make.bash:

    --- a/src/cmd/make.bash
    +++ b/src/cmd/make.bash
    @@ -12,7 +12,7 @@ bash mkenam
     make enam.o
     cd ..
    
    -for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof
    +for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof gotest
     do
     	echo; echo; echo %%%% making $i %%%%; echo
     	cd $i
    

    gotest がビルド対象のコマンドリストに追加されました。

  5. src/lib/make.bash:

    --- a/src/lib/make.bash
    +++ b/src/lib/make.bash
    @@ -48,6 +48,7 @@ buildfiles	flag.go\
     		bufio.go\
     		once.go\
     		bignum.go\
    +\t\ttesting.go\
     	
     builddirs	net\
     		time\
    

    testing.go が標準ライブラリのビルド対象ファイルに追加されました。これは、gotesttesting パッケージに依存するため、そのパッケージがビルドシステムによって適切に処理されるようにするための変更です。

コアとなるコードの解説

src/cmd/gotest/gotest シェルスクリプトは、Go言語のテスト実行の基本的なメカニズムを初期段階でどのように実現していたかを示す貴重な例です。

スクリプトの冒頭では、カレントディレクトリ内の test*.go ファイルを特定し、それらのファイル名からオブジェクトファイル名(.6)とパッケージ名(.go 拡張子なし)を生成しています。

gofiles=$(echo test*.go)
ofiles=$(echo $gofiles | sed 's/\.go/.6/g')
files=$(echo $gofiles | sed 's/\.go//g')

次に、各テストGoファイルを 6g コンパイラで個別にコンパイルします。

for i in $gofiles
do
	6g $i
done

この部分が最も重要で、Goのテスト実行可能ファイルのエントリポイントとなる _testmain.go を動的に生成しています。

>{
	# package spec
	echo 'package main'
	echo
	# imports
	for i in $files
	do
		echo 'import "./'$i'"'
	done
	echo 'import "testing"'
	# test array
	echo
	echo 'var tests = &[]testing.Test {'
	for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
	do
		echo '	testing.Test{ "'$i'", &'$i' },'
	done
	echo '}'
	# body
	echo
	echo 'func main() {'
	echo '	testing.Main(tests)'
	echo '}'
}>_testmain.go

このブロックでは、echo コマンドとシェルスクリプトのループ、パイプを組み合わせて、Goのソースコードを文字列として出力し、それを _testmain.go ファイルにリダイレクトしています。

  • package mainimport "testing" は、テスト実行可能ファイルの基本的な構造を定義します。
  • for i in $files; do echo 'import "./'$i'"'; done の部分は、各テストファイル(例: test_my_feature.go)を ./test_my_feature というローカルパッケージとしてインポートします。これにより、_testmain.go から各テストファイル内の関数にアクセスできるようになります。
  • for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./') の部分は、6nm コマンドと grepsed を組み合わせて、コンパイル済みのオブジェクトファイルからテスト関数(TestXxx という命名規則に従う関数)のシンボル名を抽出し、Goのコードで参照可能な形式に整形しています。
  • 抽出されたテスト関数名を使って、testing.Test 構造体の配列 tests を動的に構築します。各要素は testing.Test{ "テスト関数名", &テスト関数名 } の形式を取ります。
  • 最後に、func main() { testing.Main(tests) } という main 関数を定義し、生成された tests 配列を testing.Main 関数に渡してテスト実行を委ねます。

_testmain.go が生成された後、スクリプトはそれを 6g でコンパイルし、6l でリンクして実行可能ファイル 6.out を作成します。

6g _testmain.go
6l _testmain.6
6.out

そして、最後に 6.out を実行することで、すべてのテストが実行されます。

この一連の処理は、現在の go test コマンドが内部的に行っていることと本質的に同じであり、Go言語のテストフレームワークが、テストコードを通常のGoプログラムとしてコンパイル・リンク・実行するというシンプルな設計原則に基づいていることを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の初期のツールチェインに関する情報 (6g, 6l, 6nm):
  • Go言語のテストの歴史と哲学に関する一般的な情報:
  • Web検索結果 (Google Search): "Go language early testing framework history gotest 6g 6l 6nm"
    • この検索結果は、go testtesting パッケージがGoのテストの核であり、6g, 6l, 6nm が初期のビルドシステムの一部であったことを確認するのに役立ちました。# [インデックス 1157] ファイルの概要

このコミットは、Go言語の初期開発段階において、ユニットテストの自動化を目的とした新しいシェルスクリプト gotest を導入するものです。これは、現在の go test コマンドの原型となる非常に初期の実装であり、テストファイルのコンパイル、テスト実行用のメイン関数の動的生成、そしてテストの実行という一連のプロセスを自動化します。

コミット

commit d4953725099792e625decc1a812bff44356dce37
Author: Rob Pike <r@golang.org>
Date:   Tue Nov 18 14:12:14 2008 -0800

    new gotest shell script (will be a proper command some day, probably)
    automates construction and execution of unit tests.
    
    R=rsc
    DELTA=60  (58 added, 0 deleted, 2 changed)
    OCL=19482
    CL=19484

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d4953725099792e625decc1a812bff44356dce37

元コミット内容

新しい gotest シェルスクリプト(おそらく将来的に適切なコマンドになるだろう)は、ユニットテストの構築と実行を自動化する。

変更の背景

このコミットは2008年11月に行われており、Go言語がまだ公開される前の非常に初期の段階に当たります。当時のGo言語には、現在のような統合された go test コマンドは存在せず、テストの実行は手動で行うか、個別のスクリプトで対応する必要がありました。

Go言語の設計思想の一つに、テストの容易さがあります。標準ライブラリに testing パッケージが提供され、go test コマンドによってテストが自動的に発見・実行される仕組みは、Go言語の大きな特徴です。このコミットは、その自動テスト実行の基盤を築くための最初の一歩として、gotest というシェルスクリプトを導入しました。コミットメッセージにある「will be a proper command some day, probably」という記述は、このスクリプトが将来的にGoツールチェインの一部として統合されることを示唆しており、現在の go test コマンドへの進化を予見させるものです。

当時のGo言語のビルドシステムは、6g (Goコンパイラ for AMD64), 6l (Goリンカ for AMD64), 6nm (シンボルリストユーティリティ for AMD64) といった、Plan 9由来のアーキテクチャ固有のコマンドに依存していました。この gotest スクリプトは、これらの低レベルなツールを直接呼び出すことで、テストのコンパイルと実行を実現しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の初期のツールチェインとテストに関する概念を理解しておく必要があります。

  • 6g, 6l, 6nm: これらはGo言語の初期のビルドツールチェーンの一部でした。
    • 6g: Goコンパイラ。Goのソースコードをオブジェクトファイル(.6 拡張子)にコンパイルします。6 はAMD64アーキテクチャを指します。
    • 6l: Goリンカ。オブジェクトファイルを結合し、実行可能ファイルを生成します。
    • 6nm: オブジェクトファイルや実行可能ファイルからシンボルをリストアップするユーティリティ。特に、テスト関数を識別するために使用されます。 これらのツールは、Go 1.0以降の go buildgo test といった統合コマンドに置き換えられましたが、Goのビルドプロセスの根幹をなすものでした。
  • testing パッケージ: Go言語の標準ライブラリに含まれる、ユニットテスト、ベンチマーク、サンプルコードテストを記述するためのパッケージです。テスト関数は TestXxx という命名規則に従い、*testing.T 型の引数を取ります。
  • testing.Main 関数: testing パッケージが提供する関数で、テストの実行をオーケストレーションします。通常、テスト実行可能ファイルの main 関数から呼び出され、発見されたすべてのテスト関数を実行します。
  • シェルスクリプト: このコミットで導入される gotest は、Bashシェルスクリプトとして実装されています。これは、Go言語自体でテストツールがまだ十分に成熟していなかったため、既存のシェル機能を利用してテストプロセスを自動化したものです。

技術的詳細

このコミットの主要な技術的詳細は、src/cmd/gotest/gotest シェルスクリプトに集約されています。このスクリプトは、以下のステップでユニットテストを自動化します。

  1. テストファイルの特定: カレントディレクトリ内の test*.go というパターンにマッチするすべてのGoファイルをテストファイルとして識別します。
  2. 個別のコンパイル: 識別された各 test*.go ファイルを 6g コンパイラを使用して個別にコンパイルします。これにより、各テストファイルに対応するオブジェクトファイル(.6 拡張子)が生成されます。
  3. _testmain.go の動的生成: ここがこのスクリプトの最も重要な部分です。Goのテストフレームワークは、すべてのテスト関数を呼び出す単一の main 関数を持つ実行可能ファイルを必要とします。このスクリプトは、以下の内容を持つ _testmain.go というGoソースファイルを動的に生成します。
    • package main: 実行可能ファイルのエントリポイントとなる main パッケージを宣言します。
    • テストファイルのインポート: 各テストファイル(例: test_foo.go)を ./test_foo のようにパッケージとしてインポートします。これにより、各テストファイル内で定義されたテスト関数が _testmain.go から参照可能になります。
    • import "testing": Goの標準テストパッケージをインポートします。
    • テスト関数の発見とリスト化: 6nm コマンドを使用して、コンパイル済みのオブジェクトファイル(.6 ファイル)からテスト関数を抽出します。具体的には、6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./' というコマンドチェーンが使用されます。
      • 6nm $ofiles: すべてのオブジェクトファイルからシンボルをリストアップします。
      • grep ' T .*·Test': グローバルなテキストセクション (T) にあり、Goの内部的なシンボル名で ·Test を含むもの(例: main·TestMyFunction)をフィルタリングします。これはGoのテスト関数の命名規則と内部表現に基づいています。
      • sed 's/.* //; s/·/./': シンボル名から不要な部分を削除し、main·TestMyFunction のような形式を main.TestMyFunction のような、Goのコードで直接参照できる形式に変換します。
    • testing.Test 構造体の配列生成: 抽出された各テスト関数に対して、testing.Test 構造体のインスタンスを生成し、それらを var tests = &[]testing.Test { ... } という配列に格納します。この配列は testing.Main 関数に渡されます。
    • main 関数の定義: func main() { testing.Main(tests) } という main 関数を定義します。この関数が実行されると、testing.Maintests 配列内のすべてのテスト関数を順次実行します。
  4. _testmain.go のコンパイルとリンク: 生成された _testmain.go6g でコンパイルし、6l でリンクして実行可能ファイルを生成します。このリンクプロセスでは、ステップ2でコンパイルされた個々のテストファイルのオブジェクトファイルも自動的に結合されます。
  5. テストの実行: 最後に、生成された実行可能ファイル(6.out)を実行します。これにより、すべてのユニットテストが実行され、結果が標準出力に表示されます。

この一連のプロセスは、現在の go test コマンドが内部的に行っていることと非常に似ており、Go言語のテストフレームワークの設計思想が初期段階から確立されていたことを示しています。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。

  1. src/cmd/gotest/Makefile (新規追加):

    # Copyright 2009 The Go Authors. All rights reserved.
    # Use of this source code is governed by a BSD-style
    # license that can be found in the LICENSE file.
    
    include ../../Make.conf
    
    TARG=gotest
    
    clean:
    	@true
    
    install: $(TARG)
    	cp $(TARG) $(BIN)/$(TARG)
    

    このMakefileは、gotest コマンドのビルドとインストール方法を定義しています。clean ターゲットは @true で何もしませんが、これは src/cmd/clean.bashgotest のクリーンアップが処理されるためです。

  2. src/cmd/gotest/gotest (新規追加):

    #!/bin/bash
    # Copyright 2009 The Go Authors. All rights reserved.
    # Use of this source code is governed by a BSD-style
    # license that can be found in the LICENSE file.
    
    # Using all the test*.go files in the current directory, write out a file
    # _testmain.go that runs all its tests. Compile everything and run the
    # tests.
    
    set -e
    
    gofiles=$(echo test*.go)
    ofiles=$(echo $gofiles | sed 's/\.go/.6/g')
    files=$(echo $gofiles | sed 's/\.go//g')
    echo $ofiles
    
    for i in $gofiles
    do
    	6g $i
    done
    
    # They all compile; now generate the code to call them.
    
    >{
    	# package spec
    	echo 'package main'
    	echo
    	# imports
    	for i in $files
    	do
    		echo 'import "./'$i'"'
    	done
    	echo 'import "testing"'
    	# test array
    	echo
    	echo 'var tests = &[]testing.Test {'
    	for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
    	do
    		echo '	testing.Test{ "'$i'", &'$i' },'
    	done
    	echo '}'
    	# body
    	echo
    	echo 'func main() {'
    	echo '	testing.Main(tests)'
    	echo '}'
    

}>_testmain.go

6g _testmain.go
6l _testmain.6
6.out
```
このシェルスクリプトが、テストの自動化ロジックの全てを実装しています。
  1. src/cmd/clean.bash:

    --- a/src/cmd/clean.bash
    +++ b/src/cmd/clean.bash
    @@ -3,7 +3,7 @@
     # Use of this source code is governed by a BSD-style
     # license that can be found in the LICENSE file.
    
    -for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof
    +for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof gotest
     do
     	cd $i
     	make clean
    

    gotest がクリーンアップ対象のコマンドリストに追加されました。

  2. src/cmd/make.bash:

    --- a/src/cmd/make.bash
    +++ b/src/cmd/make.bash
    @@ -12,7 +12,7 @@ bash mkenam
     make enam.o
     cd ..
    
    -for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof
    +for i in cc 6l 6a 6c gc 6g ar db nm blyacc acid cov gobuild prof gotest
     do
     	echo; echo; echo %%%% making $i %%%%; echo
     	cd $i
    

    gotest がビルド対象のコマンドリストに追加されました。

  3. src/lib/make.bash:

    --- a/src/lib/make.bash
    +++ b/src/lib/make.bash
    @@ -48,6 +48,7 @@ buildfiles	flag.go\
     		bufio.go\
     		once.go\
     		bignum.go\
    +\t\ttesting.go\
     	
     builddirs	net\
     		time\
    

    testing.go が標準ライブラリのビルド対象ファイルに追加されました。これは、gotesttesting パッケージに依存するため、そのパッケージがビルドシステムによって適切に処理されるようにするための変更です。

コアとなるコードの解説

src/cmd/gotest/gotest シェルスクリプトは、Go言語のテスト実行の基本的なメカニズムを初期段階でどのように実現していたかを示す貴重な例です。

スクリプトの冒頭では、カレントディレクトリ内の test*.go ファイルを特定し、それらのファイル名からオブジェクトファイル名(.6)とパッケージ名(.go 拡張子なし)を生成しています。

gofiles=$(echo test*.go)
ofiles=$(echo $gofiles | sed 's/\.go/.6/g')
files=$(echo $gofiles | sed 's/\.go//g')

次に、各テストGoファイルを 6g コンパイラで個別にコンパイルします。

for i in $gofiles
do
	6g $i
done

この部分が最も重要で、Goのテスト実行可能ファイルのエントリポイントとなる _testmain.go を動的に生成しています。

>{
	# package spec
	echo 'package main'
	echo
	# imports
	for i in $files
	do
		echo 'import "./'$i'"'
	done
	echo 'import "testing"'
	# test array
	echo
	echo 'var tests = &[]testing.Test {'
	for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
	do
		echo '	testing.Test{ "'$i'", &'$i' },'
	done
	echo '}'
	# body
	echo
	echo 'func main() {'
	echo '	testing.Main(tests)'
	echo '}'
}>_testmain.go

このブロックでは、echo コマンドとシェルスクリプトのループ、パイプを組み合わせて、Goのソースコードを文字列として出力し、それを _testmain.go ファイルにリダイレクトしています。

  • package mainimport "testing" は、テスト実行可能ファイルの基本的な構造を定義します。
  • for i in $files; do echo 'import "./'$i'"'; done の部分は、各テストファイル(例: test_my_feature.go)を ./test_my_feature というローカルパッケージとしてインポートします。これにより、_testmain.go から各テストファイル内の関数にアクセスできるようになります。
  • for i in $(6nm $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./') の部分は、6nm コマンドと grepsed を組み合わせて、コンパイル済みのオブジェクトファイルからテスト関数(TestXxx という命名規則に従う関数)のシンボル名を抽出し、Goのコードで参照可能な形式に整形しています。
  • 抽出されたテスト関数名を使って、testing.Test 構造体の配列 tests を動的に構築します。各要素は testing.Test{ "テスト関数名", &テスト関数名 } の形式を取ります。
  • 最後に、func main() { testing.Main(tests) } という main 関数を定義し、生成された tests 配列を testing.Main 関数に渡してテスト実行を委ねます。

_testmain.go が生成された後、スクリプトはそれを 6g でコンパイルし、6l でリンクして実行可能ファイル 6.out を作成します。

6g _testmain.go
6l _testmain.6
6.out

そして、最後に 6.out を実行することで、すべてのテストが実行されます。

この一連の処理は、現在の go test コマンドが内部的に行っていることと本質的に同じであり、Go言語のテストフレームワークが、テストコードを通常のGoプログラムとしてコンパイル・リンク・実行するというシンプルな設計原則に基づいていることを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の初期のツールチェインに関する情報 (6g, 6l, 6nm):
  • Go言語のテストの歴史と哲学に関する一般的な情報:
  • Web検索結果 (Google Search): "Go language early testing framework history gotest 6g 6l 6nm"
    • この検索結果は、go testtesting パッケージがGoのテストの核であり、6g, 6l, 6nm が初期のビルドシステムの一部であったことを確認するのに役立ちました。