[インデックス 18770] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/rpc/jsonrpc
パッケージに、一時的に無効化された失敗するテストケースを追加するものです。このテストは、JSON-RPCサーバーがエラーを返した場合に、結果フィールドが null
であるべきという挙動を検証するために書かれました。これは、既存のバグまたは予期せぬ動作を浮き彫りにすることを目的としています。
コミット
commit 31731b27cd9001d399e0e3c38c149c51dacb22ac
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Mar 5 16:01:37 2014 -0800
net/rpc/jsonrpc: add temporarily-disabled failing test
To be enabled by https://golang.org/cl/71230045/
Update #7442
LGTM=adg
R=adg
CC=golang-codereviews
https://golang.org/cl/69860056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/31731b27cd9001d399e0e3c38c149c51dacb22ac
元コミット内容
net/rpc/jsonrpc: add temporarily-disabled failing test
To be enabled by https://golang.org/cl/71230045/
Update #7442
変更の背景
このコミットは、Goの net/rpc/jsonrpc
パッケージにおける特定の挙動、具体的にはRPC呼び出しがエラーを返した場合のJSONレスポンスの構造に関する問題を修正するための準備として行われました。JSON-RPC 2.0の仕様では、RPC呼び出し中にエラーが発生した場合、レスポンスオブジェクトの result
メンバーは存在してはならず、代わりに error
メンバーが存在する必要があります。しかし、当時の net/rpc/jsonrpc
の実装では、エラーが発生した場合でも result
フィールドに予期せぬ値が含まれてしまう可能性がありました。
このコミットで追加されたテスト TestServerErrorHasNullResult
は、この仕様違反を検出するために設計されました。テストは一時的に無効化されています (t.Skip
)。これは、テストが現在の実装では失敗することが既知であり、このコミットの目的がバグの修正ではなく、バグの存在を明確にするためのテストケースの追加であるためです。コミットメッセージにある https://golang.org/cl/71230045/
は、このテストを有効化し、関連するバグ修正を適用する別の変更リスト(Change List)を指しています。また、Update #7442
は、この問題がGoのイシュートラッカーで追跡されていることを示唆しています。
前提知識の解説
JSON-RPC
JSON-RPCは、リモートプロシージャコール(RPC)プロトコルの一種で、データフォーマットとしてJSONを使用します。クライアントがサーバー上のメソッドを呼び出し、その結果をJSON形式で受け取るための軽量なプロトコルです。
JSON-RPC 2.0の仕様では、RPC呼び出しのレスポンスは以下のいずれかの形式を取ります。
-
成功レスポンス:
{ "jsonrpc": "2.0", "result": <結果の値>, "id": <リクエストID> }
この場合、
error
メンバーは存在してはなりません。 -
エラーレスポンス:
{ "jsonrpc": "2.0", "error": { "code": <エラーコード>, "message": <エラーメッセージ>, "data": <オプションのエラーデータ> }, "id": <リクエストID> }
この場合、
result
メンバーは存在してはなりません。仕様では、result
メンバーが存在しないか、あるいはnull
であるべきとされています。特に、エラーが発生した場合はresult
フィールドがnull
であることが推奨されます。
Go言語の net/rpc
および net/rpc/jsonrpc
パッケージ
net/rpc
: Go言語の標準ライブラリで提供されるRPC(Remote Procedure Call)フレームワークです。これは、ネットワーク経由でGoプログラム間でメソッドを呼び出すための汎用的なメカニズムを提供します。エンコーディングには依存せず、様々なプロトコルをサポートできます。net/rpc/jsonrpc
:net/rpc
パッケージのサブパッケージで、JSON-RPC 2.0プロトコルをnet/rpc
フレームワーク上で実装するためのものです。これにより、GoのRPCサーバーがJSON-RPCクライアントと通信できるようになります。NewServerCodec
やNewClientCodec
などの関数を提供し、JSON形式でのリクエストとレスポンスのエンコード/デコードを扱います。
io.Reader
, io.Writer
, io.Closer
インターフェース
Go言語の標準ライブラリ io
パッケージで定義されている基本的なインターフェースです。
io.Reader
:Read(p []byte) (n int, err error)
メソッドを持つインターフェース。データを読み込むための抽象化を提供します。io.Writer
:Write(p []byte) (n int, err error)
メソッドを持つインターフェース。データを書き込むための抽象化を提供します。io.Closer
:Close() error
メソッドを持つインターフェース。リソースをクリーンアップするための抽象化を提供します。
これらのインターフェースは、ネットワーク接続、ファイル、メモリバッファなど、様々なデータストリームを統一的に扱うために広く利用されます。
bytes.Buffer
と strings.NewReader
bytes.Buffer
:bytes
パッケージで提供される可変長のバイトバッファです。io.Writer
インターフェースを実装しており、メモリ内でデータを効率的に構築するために使用されます。strings.NewReader
:strings
パッケージで提供される関数で、文字列からio.Reader
を作成します。これにより、文字列をファイルやネットワーク接続のように読み込むことができます。
ioutil.NopCloser
io/ioutil
パッケージで提供される関数で、io.Closer
インターフェースを実装するが、Close
メソッドが何もしない(no-operation)ラッパーを返します。テストなどで io.Closer
が必要な場合に、実際のクローズ処理が不要なダミーとして使用されます。
技術的詳細
このコミットの核心は、net/rpc/jsonrpc
パッケージがJSON-RPC 2.0仕様に準拠していることを確認するためのテストケース TestServerErrorHasNullResult
の追加です。
テストのシナリオは以下の通りです。
-
サーバーコーデックのセットアップ:
jsonrpc.NewServerCodec
を使用してサーバー側のコーデック (sc
) を作成します。このコーデックは、io.Reader
、io.Writer
、io.Closer
の組み合わせを引数として取ります。Reader
:strings.NewReader
を使用して、RPCリクエストのJSON文字列{"method": "Arith.Add", "id": "123", "params": []}
を提供します。これは、サーバーが処理する架空のRPC呼び出しをシミュレートします。Writer
:bytes.Buffer
(out
) を使用します。サーバーからのJSONレスポンスはこのバッファに書き込まれ、後で検証されます。Closer
:ioutil.NopCloser(nil)
を使用します。これは、テスト中にクローズ操作が不要なためです。
-
リクエストヘッダの読み込み:
sc.ReadRequestHeader(r)
を呼び出して、クライアントからのリクエストヘッダを読み込みます。これにより、サーバーは受信したリクエストのメソッド名、IDなどを解析します。 -
エラーレスポンスの書き込み:
sc.WriteResponse
を呼び出して、サーバーからのレスポンスを書き込みます。rpc.Response
オブジェクトを作成し、Error
フィールドにerrorText
("some error"
) を設定します。これは、RPC呼び出しがサーバー側でエラーを発生させたことをシミュレートします。valueText
("the value we don't want to see"
) を結果として渡します。この値は、エラーレスポンスの場合にはJSON出力に含まれるべきではありません。
-
レスポンスの検証:
out.String()
に書き込まれたJSONレスポンスを検証します。!strings.Contains(out.String(), errorText)
: レスポンスにエラーメッセージ ("some error"
) が含まれていることを確認します。これは、エラーレスポンスが正しく生成されたことを示します。strings.Contains(out.String(), valueText)
: これがテストの主要なアサーションです。 レスポンスにvalueText
("the value we don't want to see"
) が含まれていないことを確認します。JSON-RPC 2.0の仕様では、エラーレスポンスの場合、result
フィールドは存在しないか、null
であるべきであり、実際の成功時の結果値が含まれていてはなりません。もしvalueText
が含まれていれば、それは仕様違反であり、テストは失敗します。
このテストは、t.Skip("Known failing test; Issue 7442")
によって一時的に無効化されています。これは、このコミットが作成された時点では、net/rpc/jsonrpc
がこの特定のシナリオでJSON-RPC 2.0の仕様に完全に準拠していなかったことを示しています。テストを追加することで、開発者はこの問題を認識し、将来のコミットで修正を適用できるようになります。
コアとなるコードの変更箇所
変更は src/pkg/net/rpc/jsonrpc/all_test.go
ファイルに集中しています。
--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -5,6 +5,7 @@
package jsonrpc
import (
+ "bytes"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +13,7 @@ import (
"io/ioutil"
"net"
"net/rpc"
+ "strings"
"testing"
)
@@ -202,6 +204,40 @@ func TestMalformedOutput(t *testing.T) {
}
}
+func TestServerErrorHasNullResult(t *testing.T) {
+ t.Skip("Known failing test; Issue 7442")
+ var out bytes.Buffer
+ sc := NewServerCodec(struct {
+ io.Reader
+ io.Writer
+ io.Closer
+ }{
+ Reader: strings.NewReader(`{"method": "Arith.Add", "id": "123", "params": []}`),
+ Writer: &out,
+ Closer: ioutil.NopCloser(nil),
+ })
+ r := new(rpc.Request)
+ if err := sc.ReadRequestHeader(r); err != nil {
+ t.Fatal(err)
+ }
+ const valueText = "the value we don't want to see"
+ const errorText = "some error"
+ err := sc.WriteResponse(&rpc.Response{
+ ServiceMethod: "Method",
+ Seq: 1,
+ Error: errorText,
+ }, valueText)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(out.String(), errorText) {
+ t.Fatalf("Response didn't contain expected error %q: %s", errorText, &out)
+ }
+ if strings.Contains(out.String(), valueText) {
+ t.Errorf("Response contains both an error and value: %s", &out)
+ }
+}
+
func TestUnexpectedError(t *testing.T) {
cli, srv := myPipe()
go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error
コアとなるコードの解説
追加された TestServerErrorHasNullResult
関数は、以下の重要な側面をテストしています。
-
bytes.Buffer
とstrings.NewReader
の利用: テストは実際のネットワーク接続を使用せず、bytes.Buffer
を出力 (io.Writer
) として、strings.NewReader
を入力 (io.Reader
) として使用することで、メモリ内でJSON-RPCの通信をシミュレートしています。これにより、テストの実行が高速になり、外部依存性がなくなります。 -
NewServerCodec
の使用:jsonrpc.NewServerCodec
は、net/rpc
のサーバー側でJSON-RPCプロトコルを処理するためのコーデックを作成します。テストでは、このコーデックがエラーレスポンスを正しく生成するかどうかを検証します。 -
rpc.Response
のError
フィールド:sc.WriteResponse
に渡されるrpc.Response
オブジェクトのError
フィールドに文字列を設定することで、サーバーがエラーを返した状況をシミュレートします。 -
valueText
の存在チェック: 最も重要なのは、if strings.Contains(out.String(), valueText)
の部分です。valueText
は、エラーが発生したにもかかわらず、JSONレスポンスのresult
フィールドに誤って含まれてしまう可能性のある値です。JSON-RPC 2.0の仕様では、エラーレスポンスにはresult
フィールドが含まれるべきではない(またはnull
であるべき)ため、このvalueText
が出力に含まれていないことを確認することが、テストの目的です。もし含まれていれば、それはバグであり、テストはt.Errorf
を呼び出して失敗します。 -
t.Skip
による一時的な無効化:t.Skip("Known failing test; Issue 7442")
は、このテストが意図的にスキップされることを示します。これは、このテストが現在の実装では失敗することが既知であり、このコミットの目的がバグの修正ではなく、バグの存在を明確にするためのテストケースの追加であるためです。このテストは、関連するバグが修正された後に有効化される予定です。
このテストは、net/rpc/jsonrpc
パッケージがJSON-RPC 2.0の厳密な仕様に準拠していることを保証するための重要なステップです。
関連リンク
- Go言語の
net/rpc
パッケージのドキュメント: https://pkg.go.dev/net/rpc - Go言語の
net/rpc/jsonrpc
パッケージのドキュメント: https://pkg.go.dev/net/rpc/jsonrpc - JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
参考にした情報源リンク
- Go言語の公式ドキュメント
- JSON-RPC 2.0 Specification
- GitHubのgolang/goリポジトリのコミット履歴
- Go言語のイシュートラッカー (ただし、Issue 7442の直接的な公開情報は見つからなかったため、内部的な追跡番号である可能性が高い)