[インデックス 16731] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/rpc
パッケージ内のテストコード server_test.go
におけるバグ修正を目的としています。具体的には、RPCサーバーのテストにおいて、Accept
メソッドの呼び出し方が誤っていた点と、特定の名前で登録されたサービスに対するテストケースが不足していた点を修正しています。
コミット
commit 735cf529833c3600a9518505977c2a25f32bb901
Author: ChaiShushan <chaishushan@gmail.com>
Date: Thu Jul 11 15:55:08 2013 -0400
net/rpc: fix a test bug
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/10855043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/735cf529833c3600a9518505977c2a25f32bb901
元コミット内容
net/rpc: fix a test bug
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/10855043
変更の背景
このコミットは、net/rpc
パッケージのテストスイートにおける既存のバグを修正するために行われました。Goの net/rpc
パッケージは、ネットワーク経由でリモートプロシージャコール(RPC)を可能にするための機能を提供します。このパッケージの健全性を保証するためには、堅牢なテストが不可欠です。
元のテストコード server_test.go
には、以下の2つの問題がありました。
Accept
メソッドの誤った呼び出し:startNewServer
関数内で、net.Listener
からの接続を受け入れるAccept
メソッドが、newServer.Accept(l)
とすべきところをAccept(l)
とグローバル関数として呼び出されていました。これは、rpc.Server
インスタンス (newServer
) に関連付けられたAccept
メソッドではなく、おそらく別の(存在しないか、意図しない)Accept
関数を呼び出そうとしていたため、テストが正しく機能しない可能性がありました。rpc.Server
のAccept
メソッドは、特定のRPCサーバーインスタンスがリスナーからの接続を処理するために必要です。RegisterName
で登録されたサービスに対するテストの不足:net/rpc
パッケージでは、Register
メソッドの他にRegisterName
メソッドを使用して、サービスにカスタム名を付けて登録することができます。このコミットでは、newServer.RegisterName("newServer.Arith", new(Arith))
という行が追加されており、これはnewServer.Arith
という名前でArith
サービスを登録しています。しかし、このカスタム名で登録されたサービスが正しく機能するかどうかを検証するテストケースが既存のTestRPC
関数には含まれていませんでした。これにより、RegisterName
の機能が意図通りに動作しない場合に、テストスイートがそれを検出できないという問題がありました。
これらの問題は、net/rpc
パッケージの信頼性に影響を与える可能性があり、特に RegisterName
の機能が正しくテストされていないことは、将来的なバグの温床となり得ました。そのため、これらのテストバグを修正し、パッケージの堅牢性を高めることが変更の背景にあります。
前提知識の解説
Go言語の net/rpc
パッケージ
net/rpc
パッケージは、Go言語でRPC(Remote Procedure Call)を実装するための標準ライブラリです。これにより、異なるプロセスや異なるマシン上で実行されているプログラム間で、関数呼び出しのように通信を行うことができます。
- RPCの基本: クライアントがリモートのサーバーにある関数を呼び出すと、その呼び出しはネットワーク経由でサーバーに送信され、サーバーで実行された後、結果がクライアントに返されます。クライアントは、あたかもローカルの関数を呼び出しているかのように感じます。
- サービスとメソッドの登録: サーバー側では、RPCで公開したい構造体(サービス)とそのメソッドを
rpc.Register
またはrpc.RegisterName
を使って登録します。rpc.Register(rcvr interface{})
:rcvr
の型名がサービス名として使われます。rpc.RegisterName(name string, rcvr interface{})
:name
で指定した文字列がサービス名として使われます。これにより、型名とは異なるカスタム名をサービスに割り当てることができます。
- サーバーの起動と接続の受け入れ:
rpc.NewServer()
: 新しいRPCサーバーインスタンスを作成します。server.Accept(lis net.Listener)
: 指定されたnet.Listener
からの新しい接続を継続的に受け入れ、それぞれの接続に対してRPCリクエストを処理します。これは通常、ゴルーチン内で実行されます。
- クライアントの接続と呼び出し:
rpc.Dial(network, address string)
: 指定されたネットワークアドレスに接続し、RPCクライアントを確立します。client.Call(serviceMethod string, args interface{}, reply interface{}) error
: リモートのサービスメソッドを同期的に呼び出します。serviceMethod
はServiceName.MethodName
の形式で指定します。
Go言語のテストフレームワーク
Go言語には、標準で強力なテストフレームワークが組み込まれています。
testing
パッケージ: テストコードを記述するための基本的な機能を提供します。- テスト関数の命名規則: テスト関数は
TestXxx
の形式で命名され、*testing.T
型の引数を1つ取ります。 t.Fatal
/t.Errorf
: テスト中にエラーが発生した場合に、テストを失敗させるために使用します。t.Fatal
はテストを即座に終了させ、t.Errorf
はテストを継続しながらエラーを報告します。once.Do
:sync.Once
型のDo
メソッドは、複数のゴルーチンから呼び出されても、引数に渡された関数が一度だけ実行されることを保証します。これは、テスト環境のセットアップなど、一度だけ実行したい初期化処理に非常に便利です。
net.Listener
net.Listener
インターフェースは、ネットワーク接続をリッスンするための汎用的なインターフェースです。Accept()
メソッドを持ち、新しい接続が確立されるたびに net.Conn
インターフェースを実装するオブジェクトを返します。
技術的詳細
このコミットは、src/pkg/net/rpc/server_test.go
ファイルに対して以下の具体的な変更を加えています。
-
startNewServer
関数の修正:newServer.RegisterName("newServer.Arith", new(Arith))
の追加:startNewServer
関数は、新しいRPCサーバーインスタンス (newServer
) をセットアップする役割を担っています。この変更により、Arith
サービスがnewServer.Arith
というカスタム名で登録されるようになりました。これは、net/rpc
のRegisterName
機能のテストカバレッジを向上させるための準備です。go Accept(l)
からgo newServer.Accept(l)
への変更: これはコミットの主要なバグ修正点です。以前のコードでは、net.Listener
l
からの接続を受け入れるために、Accept(l)
というグローバル関数が呼び出されていました。しかし、net/rpc
パッケージにおいて、リスナーからの接続を受け入れてRPCリクエストを処理するのは、特定のrpc.Server
インスタンスのAccept
メソッドの役割です。したがって、この修正は、newServer
インスタンスに紐づくAccept
メソッドを正しく呼び出すように変更し、RPCサーバーが適切にクライアントからの接続を処理できるようにします。これにより、テスト環境におけるRPCサーバーの動作が意図通りになります。
-
TestRPC
関数の修正:testNewServerRPC(t, newServerAddr)
の呼び出し追加:TestRPC
関数は、RPCサーバーの基本的な機能が正しく動作するかを検証する統合テスト関数です。この変更により、新しく追加されたtestNewServerRPC
関数が呼び出されるようになりました。これは、newServer.RegisterName
で登録されたカスタム名のサービスが正しく機能するかどうかを検証するためのテストフローを組み込むものです。
-
testNewServerRPC
関数の新規追加:- この関数は、
newServer.RegisterName
で登録されたnewServer.Arith
サービスに対するRPC呼び出しをテストするために特別に作成されました。 - クライアントの確立:
Dial("tcp", addr)
を使用して、テスト対象のRPCサーバーへのTCP接続を確立し、RPCクライアントを作成します。 - 同期呼び出しの実行:
client.Call("newServer.Arith.Add", args, reply)
を使用して、newServer.Arith
サービス内のAdd
メソッドを同期的に呼び出します。ここで重要なのは、サービス名がnewServer.Arith
と、RegisterName
で登録されたカスタム名を使用している点です。 - 結果の検証: 呼び出しが成功したか(エラーがないか)、そして返された結果 (
reply.C
) が期待される値 (args.A + args.B
) と一致するかを検証します。これにより、カスタム名で登録されたサービスが正しくルーティングされ、メソッドが実行されることが保証されます。
- この関数は、
これらの変更は、net/rpc
パッケージのテストカバレッジと信頼性を向上させ、特に RegisterName
機能の正しい動作を保証するために不可欠です。
コアとなるコードの変更箇所
--- a/src/pkg/net/rpc/server_test.go
+++ b/src/pkg/net/rpc/server_test.go
@@ -99,11 +99,12 @@ func startNewServer() {
newServer = NewServer()
newServer.Register(new(Arith))
newServer.RegisterName("net.rpc.Arith", new(Arith))
+ newServer.RegisterName("newServer.Arith", new(Arith))
var l net.Listener
l, newServerAddr = listenTCP()
log.Println("NewServer test RPC server listening on", newServerAddr)
- go Accept(l)
+ go newServer.Accept(l)
newServer.HandleHTTP(newHttpPath, "/bar")
httpOnce.Do(startHttpServer)
@@ -120,6 +121,7 @@ func TestRPC(t *testing.T) {
testRPC(t, serverAddr)
newOnce.Do(startNewServer)
testRPC(t, newServerAddr)
+ testNewServerRPC(t, newServerAddr)
}
func testRPC(t *testing.T, addr string) {
@@ -249,6 +251,25 @@ func testRPC(t *testing.T, addr string) {
}
}
+func testNewServerRPC(t *testing.T, addr string) {
+ client, err := Dial("tcp", addr)
+ if err != nil {
+ t.Fatal("dialing", err)
+ }
+ defer client.Close()
+
+ // Synchronous calls
+ args := &Args{7, 8}
+ reply := new(Reply)
+ err = client.Call("newServer.Arith.Add", args, reply)
+ if err != nil {
+ t.Errorf("Add: expected no error but got string %q", err.Error())
+ }
+ if reply.C != args.A+args.B {
+ t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
+ }
+}
+
func TestHTTP(t *testing.T) {
once.Do(startServer)
testHTTPRPC(t, "")
コアとなるコードの解説
src/pkg/net/rpc/server_test.go
このファイルは、net/rpc
パッケージのサーバー側の機能が正しく動作するかを検証するためのテストコードです。
-
startNewServer
関数内の変更:newServer.RegisterName("newServer.Arith", new(Arith))
この行は、
newServer
というRPCサーバーインスタンスに、Arith
サービスを"newServer.Arith"
という名前で登録しています。これにより、クライアントはこのカスタム名を使ってArith
サービスのメソッドを呼び出すことができるようになります。これは、RegisterName
機能のテストを可能にするための準備です。- go Accept(l) + go newServer.Accept(l)
この変更は、テストサーバーがクライアントからの接続を正しく受け入れるための重要な修正です。
- 変更前 (
go Accept(l)
): これは、net/rpc
パッケージのグローバルなAccept
関数(もし存在すれば)を呼び出そうとしていました。しかし、RPCサーバーがリスナーからの接続を処理するためには、特定のrpc.Server
インスタンスに紐づくAccept
メソッドを呼び出す必要があります。 - 変更後 (
go newServer.Accept(l)
):newServer
インスタンスのAccept
メソッドをゴルーチンとして起動しています。これにより、newServer
がl
でリッスンしているTCPポートからの新しい接続を非同期に受け入れ、それぞれの接続に対してRPCリクエストを処理するようになります。これは、RPCサーバーのテスト環境が正しく機能するために不可欠な修正です。
- 変更前 (
-
TestRPC
関数内の変更:testRPC(t, newServerAddr) + testNewServerRPC(t, newServerAddr)
TestRPC
関数は、RPCサーバーの基本的な機能テストをまとめたものです。この変更により、新しく追加されたtestNewServerRPC
関数が呼び出されるようになりました。これにより、newServer.RegisterName
で登録されたカスタム名のサービスに対するテストが、メインのテストフローに組み込まれます。 -
testNewServerRPC
関数の新規追加:func testNewServerRPC(t *testing.T, addr string) { client, err := Dial("tcp", addr) if err != nil { t.Fatal("dialing", err) } defer client.Close() // Synchronous calls args := &Args{7, 8} reply := new(Reply) err = client.Call("newServer.Arith.Add", args, reply) if err != nil { t.Errorf("Add: expected no error but got string %q", err.Error()) } if reply.C != args.A+args.B { t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B) } }
この新しいテスト関数は、
newServer.RegisterName
で登録されたサービスが正しく機能するかを検証します。Dial("tcp", addr)
: 指定されたアドレスにTCP接続を確立し、RPCクライアントを作成します。テスト対象のサーバーに接続します。client.Call("newServer.Arith.Add", args, reply)
: ここがこのテストの核心です。"newServer.Arith.Add"
という文字列は、RegisterName
で登録されたサービス名 (newServer.Arith
) と、そのサービス内のメソッド名 (Add
) を結合したものです。この呼び出しにより、クライアントはリモートのAdd
メソッドを呼び出し、args
を引数として渡し、結果をreply
に受け取ります。- エラーチェックと結果の検証:
err
がnil
であること(RPC呼び出しが成功したこと)と、reply.C
がargs.A + args.B
と等しいこと(計算結果が正しいこと)を確認しています。これにより、RegisterName
を使用して登録されたサービスが正しくルーティングされ、期待通りの結果を返すことが保証されます。
これらの変更により、net/rpc
パッケージのテストスイートはより包括的になり、特に RegisterName
機能の信頼性が向上しました。
関連リンク
- Go言語
net/rpc
パッケージのドキュメント: https://pkg.go.dev/net/rpc - Go言語
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語
net
パッケージのドキュメント: https://pkg.go.dev/net
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
net/rpc
パッケージ) - Go言語のテストに関する一般的なプラクティス
- コミットメッセージと差分情報
- https://golang.org/cl/10855043 (GoのコードレビューシステムGerritの変更リスト)