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

[インデックス 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つの問題がありました。

  1. Accept メソッドの誤った呼び出し: startNewServer 関数内で、net.Listener からの接続を受け入れる Accept メソッドが、newServer.Accept(l) とすべきところを Accept(l) とグローバル関数として呼び出されていました。これは、rpc.Server インスタンス (newServer) に関連付けられた Accept メソッドではなく、おそらく別の(存在しないか、意図しない)Accept 関数を呼び出そうとしていたため、テストが正しく機能しない可能性がありました。rpc.ServerAccept メソッドは、特定のRPCサーバーインスタンスがリスナーからの接続を処理するために必要です。
  2. 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: リモートのサービスメソッドを同期的に呼び出します。serviceMethodServiceName.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 ファイルに対して以下の具体的な変更を加えています。

  1. startNewServer 関数の修正:

    • newServer.RegisterName("newServer.Arith", new(Arith)) の追加: startNewServer 関数は、新しいRPCサーバーインスタンス (newServer) をセットアップする役割を担っています。この変更により、Arith サービスが newServer.Arith というカスタム名で登録されるようになりました。これは、net/rpcRegisterName 機能のテストカバレッジを向上させるための準備です。
    • go Accept(l) から go newServer.Accept(l) への変更: これはコミットの主要なバグ修正点です。以前のコードでは、net.Listener l からの接続を受け入れるために、Accept(l) というグローバル関数が呼び出されていました。しかし、net/rpc パッケージにおいて、リスナーからの接続を受け入れてRPCリクエストを処理するのは、特定の rpc.Server インスタンスの Accept メソッドの役割です。したがって、この修正は、newServer インスタンスに紐づく Accept メソッドを正しく呼び出すように変更し、RPCサーバーが適切にクライアントからの接続を処理できるようにします。これにより、テスト環境におけるRPCサーバーの動作が意図通りになります。
  2. TestRPC 関数の修正:

    • testNewServerRPC(t, newServerAddr) の呼び出し追加: TestRPC 関数は、RPCサーバーの基本的な機能が正しく動作するかを検証する統合テスト関数です。この変更により、新しく追加された testNewServerRPC 関数が呼び出されるようになりました。これは、newServer.RegisterName で登録されたカスタム名のサービスが正しく機能するかどうかを検証するためのテストフローを組み込むものです。
  3. 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 パッケージのサーバー側の機能が正しく動作するかを検証するためのテストコードです。

  1. 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 メソッドをゴルーチンとして起動しています。これにより、newServerl でリッスンしているTCPポートからの新しい接続を非同期に受け入れ、それぞれの接続に対してRPCリクエストを処理するようになります。これは、RPCサーバーのテスト環境が正しく機能するために不可欠な修正です。
  2. TestRPC 関数内の変更:

    	testRPC(t, newServerAddr)
    +	testNewServerRPC(t, newServerAddr)
    

    TestRPC 関数は、RPCサーバーの基本的な機能テストをまとめたものです。この変更により、新しく追加された testNewServerRPC 関数が呼び出されるようになりました。これにより、newServer.RegisterName で登録されたカスタム名のサービスに対するテストが、メインのテストフローに組み込まれます。

  3. 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 に受け取ります。
    • エラーチェックと結果の検証: errnil であること(RPC呼び出しが成功したこと)と、reply.Cargs.A + args.B と等しいこと(計算結果が正しいこと)を確認しています。これにより、RegisterName を使用して登録されたサービスが正しくルーティングされ、期待通りの結果を返すことが保証されます。

これらの変更により、net/rpc パッケージのテストスイートはより包括的になり、特に RegisterName 機能の信頼性が向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に net/rpc パッケージ)
  • Go言語のテストに関する一般的なプラクティス
  • コミットメッセージと差分情報
  • https://golang.org/cl/10855043 (GoのコードレビューシステムGerritの変更リスト)