[インデックス 10259] ファイルの概要
コミット
commit 7f0622e66d5618acc7d2b9ab6e1cb96fd7c1e190
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Nov 4 14:12:35 2011 -0700
test: make closedchan.go exit with failure if something fails
R=golang-dev, rsc, iant
CC=golang-dev
https://golang.org/cl/5356042
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7f0622e66d5618acc7d2b9ab6e1cb96fd7c1e190
元コミット内容
このコミットは、Go言語のテストファイル test/closedchan.go を修正し、テストが失敗した場合にプログラムが非ゼロの終了コードで終了するように変更します。これにより、テストハーネスやCI/CDシステムがテストの失敗を正確に検出できるようになります。
変更の背景
Go言語のテストフレームワークでは、テストの成功・失敗は通常、testing パッケージの機能(例: t.Error(), t.Fail(), t.Fatal())を通じて報告されます。しかし、この closedchan.go のような特定のテストスクリプトは、独立したGoプログラムとして実行され、標準出力にメッセージを出力するだけでテストの失敗を通知していました。
このようなテストスクリプトがCI/CD環境や自動テストシステムで実行される場合、標準出力のメッセージを解析してテストの成否を判断するのは非効率的であり、エラーを見落とす可能性もあります。Unix系のシステムでは、プログラムの終了コード(exit code)が0であれば成功、非ゼロであれば失敗を示すのが一般的な慣習です。
このコミットは、closedchan.go がこの慣習に従い、テストが期待通りに動作しなかった場合に明示的に非ゼロの終了コード(1)を返すようにすることで、テストの信頼性と自動化の容易性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と一般的なプログラミングの知識が必要です。
-
Go言語のチャネル (Channels):
- チャネルは、Goルーチン間で値を送受信するための通信メカニズムです。Goルーチンは並行処理の単位であり、チャネルはそれらの間の安全なデータ交換を可能にします。
- チャネルは
make(chan Type)で作成され、ch <- valueで送信、value := <-chで受信します。 - クローズされたチャネル (Closed Channel):
close(ch)関数を使ってチャネルをクローズできます。クローズされたチャネルへの送信はパニック(ランタイムエラー)を引き起こしますが、受信は可能です。クローズされたチャネルからの受信は、チャネルにまだ値が残っていればその値を返し、値がなければ要素型のゼロ値と、チャネルがクローズされたことを示すfalseのブール値を返します。 closedchan.goは、クローズされたチャネルからの受信動作や、クローズされたチャネルへの送信がパニックを引き起こすことなどをテストするものです。
-
selectステートメント:selectステートメントは、複数のチャネル操作を待機するために使用されます。いずれかのチャネル操作が準備できるまでブロックし、準備できた操作を実行します。case <-ch:のように受信操作を記述し、case ch <- value:のように送信操作を記述します。defaultケースを持つこともでき、その場合、どのチャネル操作も準備できていなければすぐにdefaultケースが実行されます。
-
panicとrecover:- Go言語では、予期せぬエラーや回復不能な状況が発生した場合に
panicを発生させることができます。panicが発生すると、現在のGoルーチンの実行が停止し、遅延関数(deferで登録された関数)が実行され、コールスタックを遡ります。 recover関数は、defer関数内で呼び出すことで、panicから回復し、プログラムのクラッシュを防ぐことができます。shouldPanic関数は、特定のコードブロックがパニックを引き起こすことを期待するテストヘルパー関数です。
- Go言語では、予期せぬエラーや回復不能な状況が発生した場合に
-
osパッケージとos.Exit():osパッケージは、オペレーティングシステムとのインタフェースを提供します。os.Exit(code int)関数は、現在のプログラムを終了させ、指定された終了コードをオペレーティングシステムに返します。慣例として、0は成功、1以上の非ゼロ値はエラーを示します。
技術的詳細
test/closedchan.go は、Go言語のチャネルがクローズされた際の様々な挙動を検証するためのテストファイルです。具体的には、以下のようなシナリオをテストしています。
- クローズされたチャネルからの受信(値が残っている場合と残っていない場合)。
- クローズされたチャネルからの非ブロック受信 (
Nbrecv,Nbrecv2)。 - クローズされたチャネルへの送信がパニックを引き起こすこと。
selectステートメント内でのクローズされたチャネルの挙動。
このコミット以前は、これらのテストシナリオで期待と異なる結果が出た場合、println 関数を使って標準出力にエラーメッセージを出力するだけでした。例えば、println("test1: recv on closed:", x, c.Impl()) のように出力されます。
今回の変更では、このテストの失敗をより明確に外部に通知するために、以下の修正が加えられました。
import "os"の追加: プログラムの終了コードを制御するためにosパッケージをインポートします。var failed boolの導入: テスト中に一つでも失敗条件が検出された場合にtrueに設定されるグローバル変数failedを宣言します。- 失敗時の
failed = true設定: 各テスト関数 (test1,testasync1など) 内で、期待と異なる結果(つまりテスト失敗)が検出されたprintlnステートメントの直後にfailed = trueを追加します。 main関数での終了コード制御:main関数の最後に以下のコードブロックを追加します。
これにより、if failed { os.Exit(1) }failed変数がtrueであれば、プログラムは終了コード1で終了します。これは、テストが失敗したことを意味します。failedがfalseのまま(全てのテストが成功)であれば、プログラムはデフォルトで終了コード0で終了します。
この変更により、closedchan.go を実行するシェルスクリプトやCI/CDパイプラインは、プログラムの終了コードをチェックするだけでテストの成否を判断できるようになり、テストの自動化と信頼性が向上します。
コアとなるコードの変更箇所
変更は test/closedchan.go ファイルに集中しています。
--- a/test/closedchan.go
+++ b/test/closedchan.go
@@ -11,6 +11,10 @@
package main
+import "os"
+
+var failed bool
+
type Chan interface {
Send(int)
Nbsend(int) bool
@@ -225,19 +229,23 @@ func test1(c Chan) {
// recv a close signal (a zero value)
if x := c.Recv(); x != 0 {
println("test1: recv on closed:", x, c.Impl())
+ failed = true
}
if x, ok := c.Recv2(); x != 0 || ok {
println("test1: recv2 on closed:", x, ok, c.Impl())
+ failed = true
}
// should work with select: received a value without blocking, so selected == true.
x, selected := c.Nbrecv()
if x != 0 || !selected {
println("test1: recv on closed nb:", x, selected, c.Impl())
+ failed = true
}
x, ok, selected := c.Nbrecv2()
if x != 0 || ok || !selected {
println("test1: recv2 on closed nb:", x, ok, selected, c.Impl())
+ failed = true
}
}
@@ -247,12 +255,14 @@ func test1(c Chan) {
// the value should have been discarded.
if x := c.Recv(); x != 0 {
println("test1: recv on closed got non-zero after send on closed:", x, c.Impl())
+ failed = true
}
// similarly Send.
shouldPanic(func() { c.Send(2) })
if x := c.Recv(); x != 0 {
println("test1: recv on closed got non-zero after send on closed:", x, c.Impl())
+ failed = true
}
}
@@ -260,6 +270,7 @@ func testasync1(c Chan) {
// should be able to get the last value via Recv
if x := c.Recv(); x != 1 {
println("testasync1: Recv did not get 1:", x, c.Impl())
+ failed = true
}
test1(c)
@@ -269,6 +280,7 @@ func testasync2(c Chan) {
// should be able to get the last value via Recv2
if x, ok := c.Recv2(); x != 1 || !ok {
println("testasync1: Recv did not get 1, true:", x, ok, c.Impl())
+ failed = true
}
test1(c)
@@ -278,6 +290,7 @@ func testasync3(c Chan) {
// should be able to get the last value via Nbrecv
if x, selected := c.Nbrecv(); x != 1 || !selected {
println("testasync2: Nbrecv did not get 1, true:", x, selected, c.Impl())
+ failed = true
}
test1(c)
@@ -287,6 +300,7 @@ func testasync4(c Chan) {
// should be able to get the last value via Nbrecv2
if x, ok, selected := c.Nbrecv2(); x != 1 || !ok || !selected {
println("testasync2: Nbrecv did not get 1, true, true:", x, ok, selected, c.Impl())
+ failed = true
}
test1(c)
}
@@ -338,4 +352,8 @@ func main() {
shouldPanic(func() {
close(ch)
})
+
+ if failed {
+ os.Exit(1)
+ }
}
コアとなるコードの解説
-
import "os":mainパッケージの冒頭にosパッケージがインポートされています。これにより、os.Exit()関数を使用できるようになります。
-
var failed bool:mainパッケージのグローバルスコープにfailedという名前のブール型変数が宣言されています。Go言語では、グローバル変数は明示的に初期化されない場合、その型のゼロ値で初期化されます。ブール型のゼロ値はfalseなので、failedはデフォルトでfalseに設定されます。
-
failed = trueの追加:test1,testasync1,testasync2,testasync3,testasync4といったテスト関数内で、printlnを使ってエラーメッセージが出力されている箇所(つまり、テストの期待値と実際の値が異なっていた箇所)の直後にfailed = trueが追加されています。- これにより、テストのいずれかの部分で期待される動作と異なる結果が出た場合、
failed変数がtrueに設定され、そのテストが失敗したという状態が記録されます。
-
main関数でのos.Exit(1):main関数の最後に以下のコードブロックが追加されています。if failed { os.Exit(1) }main関数はプログラムのエントリポイントであり、全てのテスト関数が実行された後にこの部分が実行されます。- もし
failed変数がtrueであれば(つまり、テスト中に何らかの失敗が検出されていれば)、os.Exit(1)が呼び出され、プログラムは終了コード1で終了します。これは、テストが失敗したことを外部に明確に通知します。 - もし
failed変数がfalseのままであれば(つまり、全てのテストが成功していれば)、このifブロックは実行されず、main関数は正常に終了し、デフォルトで終了コード0を返します。
この一連の変更により、closedchan.go は単なる情報出力だけでなく、プログラムの終了コードを通じてテストの成否を明確に表現する、より堅牢なテストスクリプトとなりました。
関連リンク
- Go言語のチャネルに関する公式ドキュメント: https://go.dev/tour/concurrency/2
osパッケージのドキュメント: https://pkg.go.dev/ospanicとrecoverについて: https://go.dev/blog/defer-panic-and-recover
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(
test/closedchan.goの変更履歴) - 一般的なUnix系システムの終了コードの慣習に関する知識
- Go言語のテストに関する一般的なプラクティス