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

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

このコミットは、Go言語の初期開発段階における浮動小数点リテラルのテストファイル test/float_lit.go と、そのテストの期待出力が記録されている test/golden.out に関連する変更です。

test/float_lit.go は、様々な形式の浮動小数点リテラル(例: 0., +10., .0, 0.0, 0E+1, 0.E1 など)が正しくパースされ、期待される値になるかを検証するためのテストスイートです。テストは close 関数を用いて、計算された浮動小数点値が期待値に十分に「近い」かどうかを判定しています。

test/golden.out は、Go言語のテストフレームワークやビルドシステムが、特定のテストスクリプトの標準出力や標準エラー出力をキャプチャし、その内容を比較するために使用する「ゴールデンファイル」の一種と考えられます。これにより、テストの実行結果が意図せず変更されていないかを確認できます。

コミット

commit 33f7637d6a6370a580401cef9a80b18af3cb6680
Author: Rob Pike <r@golang.org>
Date:   Sun Jun 8 19:33:54 2008 -0700

    fix float lit to return 1, print error
    
    SVN=121627

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

https://github.com/golang/go/commit/33f7637d6a6370a580401cef9a80b18af3cb6680

元コミット内容

fix float lit to return 1, print error

変更の背景

このコミットは、Go言語の非常に初期の段階(2008年6月)に行われたものです。当時のGo言語のテストフレームワークやエラーハンドリングの慣習がまだ確立されていなかったことが背景にあります。

以前の test/float_lit.go では、浮動小数点リテラルのテストが失敗した場合に panic 関数を呼び出してプログラムを異常終了させていました。しかし、panic は予期せぬエラーや回復不能な状態を示すために使われることが多く、テストの失敗を報告するメカニズムとしては適切ではありません。テストの自動化やCI/CDパイプラインにおいては、プログラムがクラッシュするのではなく、テストが失敗したことを示す明確な終了コードを返すことが一般的です。

このコミットの目的は、テストの失敗をより標準的な方法で報告するように変更することです。具体的には、panic の代わりにエラーメッセージを print で標準出力に出力し、main 関数が非ゼロの終了コード(この場合は 1)を返すようにすることで、テストが失敗したことを外部のテストランナーやシェルスクリプトに明確に伝えるようにしています。これにより、テストの自動実行と結果の判定が容易になります。

また、test/golden.out の変更は、この新しいエラー報告メカニズムによって出力されるメッセージを、テストの期待出力として正式に記録するためのものです。これにより、将来の変更でテストの出力が変わってしまった場合に、それが意図的なものかどうかの判断が可能になります。

前提知識の解説

Go言語の main 関数と終了コード

Go言語において、main パッケージの main 関数はプログラムのエントリポイントです。

  • func main(): 通常の main 関数は戻り値を持ちません。プログラムは main 関数の実行が完了すると、自動的に終了コード 0(成功)で終了します。
  • func main() int: Go言語の初期のバージョンでは、main 関数が int 型の戻り値を持つことが許容されており、その戻り値がプログラムの終了コードとして使用されました。非ゼロの値は通常、エラーを示します。このコミットが行われた2008年時点では、この形式がテストスクリプトなどで利用されていた可能性があります。現在のGo言語では、main 関数は戻り値を持たず、プログラムを非ゼロの終了コードで終了させるには os.Exit(code) を明示的に呼び出す必要があります。

panicprint の違い

  • panic: Go言語の組み込み関数で、現在のゴルーチンを停止させ、パニック状態に入ります。パニックは通常、回復不能なエラーやプログラマーの論理的誤りを示すために使用されます。パニックが発生すると、defer関数が実行された後、プログラムは異常終了します。テストにおいては、予期せぬ状態や致命的なバグを示すために使われることがありますが、テストの失敗を意図的に報告する目的ではあまり使われません。
  • print: Go言語の組み込み関数で、引数を標準出力に表示します。これはデバッグや簡単な情報出力によく使われます。print はプログラムの実行フローを中断せず、単にメッセージを出力するだけです。テストの失敗メッセージを出力するのに適しています。

テストにおける終了コードの重要性

自動化されたテスト環境(CI/CDパイプライン、シェルスクリプトなど)では、テストスクリプトの実行結果を判断するために、そのスクリプトの終了コードが利用されます。

  • 終了コード 0: 成功(テスト合格)
  • 終了コード 非ゼロ: 失敗(テスト不合格、エラー発生) これにより、テストランナーは個々のテストの合否をプログラム的に判断し、次のステップに進むか、ビルドを中断するかなどを決定できます。panic によるプログラムのクラッシュは、テストランナーが終了コードを適切に解釈できない場合があり、テスト結果の自動判定を困難にする可能性があります。

ゴールデンファイル (golden.out)

ゴールデンファイルは、テストの期待される出力を事前に記録しておくファイルです。テスト実行時に生成される実際の出力とゴールデンファイルの内容を比較することで、テストの合否を判定します。

  • 利点:
    • テストの期待値を明確に定義できる。
    • 出力が複雑な場合でも、手動での検証なしに自動で比較できる。
    • 意図しない出力の変更(リグレッション)を検出できる。
  • 運用: テストのロジックや期待される出力が変更された場合、ゴールデンファイルも更新する必要があります。

技術的詳細

このコミットは、test/float_lit.go のテストロジックにおけるエラー報告メカニズムを根本的に変更しています。

  1. main 関数のシグネチャ変更: func main() { から func main() int { へと変更されました。これにより、main 関数が整数値を返すようになり、その値がプログラムの終了コードとして利用されます。

  2. エラー報告の変更: 各テストケースで !close(...) が真(つまりテスト失敗)の場合の処理が、panic から printreturn 1 の組み合わせに変更されました。

    • 変更前: panic "..."
    • 変更後: print "..." ; return 1; これにより、テストが失敗した場合でもプログラムはクラッシュせず、エラーメッセージが標準出力に表示され、その後 main 関数が 1 を返して終了します。これは、テストが失敗したことを示す標準的なUnix系の慣習に沿った動作です。
  3. test/golden.out の更新: test/float_lit.go の変更に伴い、テストが失敗した場合に標準出力に出力されるメッセージが test/golden.out に追加されました。具体的には、+10. is printfloat should be printfloat という行が追加されています。これは、このテストが特定の条件下で失敗し、その際にこのメッセージが出力されることを期待していることを示唆しています。また、既存の BUG: known to fail incorrectly というコメントは、このテストがまだ完全に修正されていない、あるいは特定の既知の問題を抱えていることを示しています。

この変更は、Go言語のテストインフラストラクチャが成熟していく過程の一部であり、より堅牢で自動化に適したテスト環境を構築するための重要なステップでした。

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

test/float_lit.gomain 関数内の変更がコアとなります。

--- a/test/float_lit.go
+++ b/test/float_lit.go
@@ -29,65 +29,65 @@ close(a, b double) bool
 	return false;
 }
 
-func main() {
+func main() int {
 
-	if !close(0., 0.) { panic "0. is ", 0., " should be ", 0., "\n"; }
-	if !close(+10., +10.) { panic "+10. is ", +10., " should be ", +10., "\n"; }
-	if !close(-210., -210.) { panic "-210. is ", -210., " should be ", -210., "\n"; }
+	if !close(0., 0.) { print "0. is ", 0., " should be ", 0., "\n"; return 1; }
+	if !close(+10., +10.) { print "+10. is ", +10., " should be ", +10., "\n"; return 1; }
+	if !close(-210., -210.) { print "-210. is ", -210., " should be ", -210., "\n"; return 1; }
 
-	if !close(.0, .0) { panic ".0 is ", .0, " should be ", .0, "\n"; }
-	if !close(+.01, +.01) { panic "+.01 is ", +.01, " should be ", +.01, "\n"; }
-	if !close(-.012, -.012) { panic "-.012 is ", -.012, " should be ", -.012, "\n"; }
+	if !close(.0, .0) { print ".0 is ", .0, " should be ", .0, "\n"; return 1; }
+	if !close(+.01, +.01) { print "+.01 is ", +.01, " should be ", +.01, "\n"; return 1; }
+	if !close(-.012, -.012) { print "-.012 is ", -.012, " should be ", -.012, "\n"; return 1; }
 
-	if !close(0.0, 0.0) { panic "0.0 is ", 0.0, " should be ", 0.0, "\n"; }
-	if !close(+10.01, +10.01) { panic "+10.01 is ", +10.01, " should be ", +10.01, "\n"; }
-	if !close(-210.012, -210.012) { panic "-210.012 is ", -210.012, " should be ", -210.012, "\n"; }
+	if !close(0.0, 0.0) { print "0.0 is ", 0.0, " should be ", 0.0, "\n"; return 1; }
+	if !close(+10.01, +10.01) { print "+10.01 is ", +10.01, " should be ", +10.01, "\n"; return 1; }
+	if !close(-210.012, -210.012) { print "-210.012 is ", -210.012, " should be ", -210.012, "\n"; return 1; }
 
-	if !close(0E+1, 0E+1) { panic "0E+1 is ", 0E+1, " should be ", 0E+1, "\n"; }
-	if !close(+10e2, +10e2) { panic "+10e2 is ", +10e2, " should be ", +10e2, "\n"; }
-	if !close(-210e3, -210e3) { panic "-210e3 is ", -210e3, " should be ", -210e3, "\n"; }
+	if !close(0E+1, 0E+1) { print "0E+1 is ", 0E+1, " should be ", 0E+1, "\n"; return 1; }
+	if !close(+10e2, +10e2) { print "+10e2 is ", +10e2, " should be ", +10e2, "\n"; return 1; }
+	if !close(-210e3, -210e3) { print "-210e3 is ", -210e3, " should be ", -210e3, "\n"; return 1; }
 
-	if !close(0E-1, 0E-1) { panic "0E-1 is ", 0E-1, " should be ", 0E-1, "\n"; }
-	if !close(+0e23, +0e23) { panic "+0e23 is ", +0e23, " should be ", +0e23, "\n"; }
-	if !close(-0e345, -0e345) { panic "-0e345 is ", -0e345, " should be ", -0e345, "\n"; }
+	if !close(0E-1, 0E-1) { print "0E-1 is ", 0E-1, " should be ", 0E-1, "\n"; return 1; }
+	if !close(+0e23, +0e23) { print "+0e23 is ", +0e23, " should be ", +0e23, "\n"; return 1; }
+	if !close(-0e345, -0e345) { print "-0e345 is ", -0e345, " should be ", -0e345, "\n"; return 1; }
 
-	if !close(0E1, 0E1) { panic "0E1 is ", 0E1, " should be ", 0E1, "\n"; }
-	if !close(+10e23, +10e23) { panic "+10e23 is ", +10e23, " should be ", +10e23, "\n"; }
-//	if !close(-210e345, -210e345) { panic "-210e345 is ", -210e345, " should be ", -210e345, "\n"; }
+	if !close(0E1, 0E1) { print "0E1 is ", 0E1, " should be ", 0E1, "\n"; return 1; }
+	if !close(+10e23, +10e23) { print "+10e23 is ", +10e23, " should be ", +10e23, "\n"; return 1; }
+//	if !close(-210e345, -210e345) { print "-210e345 is ", -210e345, " should be ", -210e345, "\n"; return 1; }
 
-	if !close(0.E1, 0.E1) { panic "0.E1 is ", 0.E1, " should be ", 0.E1, "\n"; }
-	if !close(+10.e+2, +10.e+2) { panic "+10.e+2 is ", +10.e+2, " should be ", +10.e+2, "\n"; }
-	if !close(-210.e-3, -210.e-3) { panic "-210.e-3 is ", -210.e-3, " should be ", -210.e-3, "\n"; }
+	if !close(0.E1, 0.E1) { print "0.E1 is ", 0.E1, " should be ", 0.E1, "\n"; return 1; }
+	if !close(+10.e+2, +10.e+2) { print "+10.e+2 is ", +10.e+2, " should be ", +10.e+2, "\n"; return 1; }
+	if !close(-210.e-3, -210.e-3) { print "-210.e-3 is ", -210.e-3, " should be ", -210.e-3, "\n"; return 1; }
 
-	if !close(.0E1, .0E1) { panic ".0E1 is ", .0E1, " should be ", .0E1, "\n"; }
-	if !close(+.01e2, +.01e2) { panic "+.01e2 is ", +.01e2, " should be ", +.01e2, "\n"; }
-	if !close(-.012e3, -.012e3) { panic "-.012e3 is ", -.012e3, " should be ", -.012e3, "\n"; }
+	if !close(.0E1, .0E1) { print ".0E1 is ", .0E1, " should be ", .0E1, "\n"; return 1; }
+	if !close(+.01e2, +.01e2) { print "+.01e2 is ", +.01e2, " should be ", +.01e2, "\n"; return 1; }
+	if !close(-.012e3, -.012e3) { print "-.012e3 is ", -.012e3, " should be ", -.012e3, "\n"; return 1; }
 
-	if !close(0.0E1, 0.0E1) { panic "0.0E1 is ", 0.0E1, " should be ", 0.0E1, "\n"; }
-	if !close(+10.01e2, +10.01e2) { panic "+10.01e2 is ", +10.01e2, " should be ", +10.01e2, "\n"; }
-	if !close(-210.012e3, -210.012e3) { panic "-210.012e3 is ", -210.012e3, " should be ", -210.012e3, "\n"; }
+	if !close(0.0E1, 0.0E1) { print "0.0E1 is ", 0.0E1, " should be ", 0.0E1, "\n"; return 1; }
+	if !close(+10.01e2, +10.01e2) { print "+10.01e2 is ", +10.01e2, " should be ", +10.01e2, "\n"; return 1; }
+	if !close(-210.012e3, -210.012e3) { print "-210.012e3 is ", -210.012e3, " should be ", -210.012e3, "\n"; return 1; }
 
-	if !close(0.E+12, 0.E+12) { panic "0.E+12 is ", 0.E+12, " should be ", 0.E+12, "\n"; }
-	if !close(+10.e23, +10.e23) { panic "+10.e23 is ", +10.e23, " should be ", +10.e23, "\n"; }
-	if !close(-210.e34, -210.e34) { panic "-210.e34 is ", -210.e34, " should be ", -210.e34, "\n"; }
+	if !close(0.E+12, 0.E+12) { print "0.E+12 is ", 0.E+12, " should be ", 0.E+12, "\n"; return 1; }
+	if !close(+10.e23, +10.e23) { print "+10.e23 is ", +10.e23, " should be ", +10.e23, "\n"; return 1; }
+	if !close(-210.e34, -210.e34) { print "-210.e34 is ", -210.e34, " should be ", -210.e34, "\n"; return 1; }
 
-	if !close(.0E-12, .0E-12) { panic ".0E-12 is ", .0E-12, " should be ", .0E-12, "\n"; }
-	if !close(+.01e23, +.01e23) { panic "+.01e23 is ", +.01e23, " should be ", +.01e23, "\n"; }
-	if !close(-.012e34, -.012e34) { panic "-.012e34 is ", -.012e34, " should be ", -.012e34, "\n"; }
+	if !close(.0E-12, .0E-12) { print ".0E-12 is ", .0E-12, " should be ", .0E-12, "\n"; return 1; }
+	if !close(+.01e23, +.01e23) { print "+.01e23 is ", +.01e23, " should be ", +.01e23, "\n"; return 1; }
+	if !close(-.012e34, -.012e34) { print "-.012e34 is ", -.012e34, " should be ", -.012e34, "\n"; return 1; }
 
-	if !close(0.0E12, 0.0E12) { panic "0.0E12 is ", 0.0E12, " should be ", 0.0E12, "\n"; }
-	if !close(+10.01e23, +10.01e23) { panic "+10.01e23 is ", +10.01e23, " should be ", +10.01e23, "\n"; }
-	if !close(-210.012e34, -210.012e34) { panic "-210.012e34 is ", -210.012e34, " should be ", -210.012e34, "\n"; }
+	if !close(0.0E12, 0.0E12) { print "0.0E12 is ", 0.0E12, " should be ", 0.0E12, "\n"; return 1; }
+	if !close(+10.01e23, +10.01e23) { print "+10.01e23 is ", +10.01e23, " should be ", +10.01e23, "\n"; return 1; }
+	if !close(-210.012e34, -210.012e34) { print "-210.012e34 is ", -210.012e34, " should be ", -210.012e34, "\n"; return 1; }
 
-	if !close(0.E123, 0.E123) { panic "0.E123 is ", 0.E123, " should be ", 0.E123, "\n"; }
-	if !close(+10.e+234, +10.e+234) { panic "+10.e+234 is ", +10.e+234, " should be ", +10.e+234, "\n"; }
-//	if !close(-210.e-345, -210.e-345) { panic "-210.e-345 is ", -210.e-345, " should be ", -210.e-345, "\n"; }
+	if !close(0.E123, 0.E123) { print "0.E123 is ", 0.E123, " should be ", 0.E123, "\n"; return 1; }
+	if !close(+10.e+234, +10.e+234) { print "+10.e+234 is ", +10.e+234, " should be ", +10.e+234, "\n"; return 1; }
+//	if !close(-210.e-345, -210.e-345) { print "-210.e-345 is ", -210.e-345, " should be ", -210.e-345, "\n"; return 1; }
 
-	if !close(.0E123, .0E123) { panic ".0E123 is ", .0E123, " should be ", .0E123, "\n"; }
-//	if !close(+.01e234, +.01e234) { panic "+.01e234 is ", +.01e234, " should be ", +.01e234, "\n"; }
-//	if !close(-.012e345, -.012e345) { panic "-.012e345 is ", -.012e345, " should be ", -.012e345, "\n"; }
+	if !close(.0E123, .0E123) { print ".0E123 is ", .0E123, " should be ", .0E123, "\n"; return 1; }
+//	if !close(+.01e234, +.01e234) { print "+.01e234 is ", +.01e234, " should be ", +.01e234, "\n"; return 1; }
+//	if !close(-.012e345, -.012e345) { print "-.012e345 is ", -.012e345, " should be ", -.012e345, "\n"; return 1; }
 
-	if !close(0.0E123, 0.0E123) { panic "0.0E123 is ", 0.0E123, " should be ", 0.0E123, "\n"; }
-//	if !close(+10.01e234, +10.01e234) { panic "+10.01e234 is ", +10.01e234, " should be ", +10.01e234, "\n"; }
-//	if !close(-210.012e345, -210.012e345) { panic "-210.012e345 is ", -210.012e345, " should be ", -210.012e345, "\n"; }
+	if !close(0.0E123, 0.0E123) { print "0.0E123 is ", 0.0E123, " should be ", 0.0E123, "\n"; return 1; }
+//	if !close(+10.01e234, +10.01e234) { print "+10.01e234 is ", +10.01e234, " should be ", +10.01e234, "\n"; return 1; }
+//	if !close(-210.012e345, -210.012e345) { print "-210.012e345 is ", -210.012e345, " should be ", -210.012e345, "\n"; return 1; }

test/golden.out の変更も含まれます。

--- a/test/golden.out
+++ b/test/golden.out
@@ -2,6 +2,7 @@
 =========== ./char_lit.go
 
 =========== ./float_lit.go
++10. is printfloat should be printfloat
 BUG: known to fail incorrectly
 
 =========== ./for.go

コアとなるコードの解説

test/float_lit.go の変更

  1. main 関数の戻り値型の追加: func main() {func main() int { に変更されました。これにより、main 関数が整数値を返すことが可能になり、その値がプログラムの終了コードとして利用されます。テストが失敗した際に 1 を返すことで、テストランナーに失敗を通知します。

  2. エラーハンドリングの変更: 各 if !close(...) ブロック内で、テストが失敗した場合の処理が変更されました。

    • 変更前は panic "..." を使用しており、テスト失敗時にプログラムがクラッシュしていました。
    • 変更後は print "..." ; return 1; となっています。これにより、エラーメッセージが標準出力に表示され、その後 main 関数が 1 を返して正常に終了します(ただし、終了コードはエラーを示します)。この方法は、テストの自動化において、テストの合否を終了コードで判断する標準的なアプローチです。

これらの変更により、test/float_lit.go は、テスト失敗時にプログラムをクラッシュさせるのではなく、エラーメッセージを出力し、非ゼロの終了コードを返すことで、より自動テストに適した振る舞いをするようになりました。

test/golden.out の変更

test/golden.out+10. is printfloat should be printfloat という行が追加されました。これは、test/float_lit.go の変更によって、特定のテストケースが失敗した際にこのメッセージが標準出力に表示されることを期待していることを示しています。この行は、テストの出力が変更されたことを反映し、将来のテスト実行でこの出力が期待されることを保証するためのものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Gitの差分表示
  • 一般的なプログラミングにおけるテストの終了コードに関する慣習
  • ゴールデンファイルテストの概念