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

[インデックス 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呼び出しのレスポンスは以下のいずれかの形式を取ります。

  1. 成功レスポンス:

    {
        "jsonrpc": "2.0",
        "result": <結果の値>,
        "id": <リクエストID>
    }
    

    この場合、error メンバーは存在してはなりません。

  2. エラーレスポンス:

    {
        "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クライアントと通信できるようになります。NewServerCodecNewClientCodec などの関数を提供し、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.Bufferstrings.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 の追加です。

テストのシナリオは以下の通りです。

  1. サーバーコーデックのセットアップ: jsonrpc.NewServerCodec を使用してサーバー側のコーデック (sc) を作成します。このコーデックは、io.Readerio.Writerio.Closer の組み合わせを引数として取ります。

    • Reader: strings.NewReader を使用して、RPCリクエストのJSON文字列 {"method": "Arith.Add", "id": "123", "params": []} を提供します。これは、サーバーが処理する架空のRPC呼び出しをシミュレートします。
    • Writer: bytes.Buffer (out) を使用します。サーバーからのJSONレスポンスはこのバッファに書き込まれ、後で検証されます。
    • Closer: ioutil.NopCloser(nil) を使用します。これは、テスト中にクローズ操作が不要なためです。
  2. リクエストヘッダの読み込み: sc.ReadRequestHeader(r) を呼び出して、クライアントからのリクエストヘッダを読み込みます。これにより、サーバーは受信したリクエストのメソッド名、IDなどを解析します。

  3. エラーレスポンスの書き込み: sc.WriteResponse を呼び出して、サーバーからのレスポンスを書き込みます。

    • rpc.Response オブジェクトを作成し、Error フィールドに errorText ("some error") を設定します。これは、RPC呼び出しがサーバー側でエラーを発生させたことをシミュレートします。
    • valueText ("the value we don't want to see") を結果として渡します。この値は、エラーレスポンスの場合にはJSON出力に含まれるべきではありません。
  4. レスポンスの検証: 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 関数は、以下の重要な側面をテストしています。

  1. bytes.Bufferstrings.NewReader の利用: テストは実際のネットワーク接続を使用せず、bytes.Buffer を出力 (io.Writer) として、strings.NewReader を入力 (io.Reader) として使用することで、メモリ内でJSON-RPCの通信をシミュレートしています。これにより、テストの実行が高速になり、外部依存性がなくなります。

  2. NewServerCodec の使用: jsonrpc.NewServerCodec は、net/rpc のサーバー側でJSON-RPCプロトコルを処理するためのコーデックを作成します。テストでは、このコーデックがエラーレスポンスを正しく生成するかどうかを検証します。

  3. rpc.ResponseError フィールド: sc.WriteResponse に渡される rpc.Response オブジェクトの Error フィールドに文字列を設定することで、サーバーがエラーを返した状況をシミュレートします。

  4. valueText の存在チェック: 最も重要なのは、if strings.Contains(out.String(), valueText) の部分です。valueText は、エラーが発生したにもかかわらず、JSONレスポンスの result フィールドに誤って含まれてしまう可能性のある値です。JSON-RPC 2.0の仕様では、エラーレスポンスには result フィールドが含まれるべきではない(または null であるべき)ため、この valueText が出力に含まれていないことを確認することが、テストの目的です。もし含まれていれば、それはバグであり、テストは t.Errorf を呼び出して失敗します。

  5. t.Skip による一時的な無効化: t.Skip("Known failing test; Issue 7442") は、このテストが意図的にスキップされることを示します。これは、このテストが現在の実装では失敗することが既知であり、このコミットの目的がバグの修正ではなく、バグの存在を明確にするためのテストケースの追加であるためです。このテストは、関連するバグが修正された後に有効化される予定です。

このテストは、net/rpc/jsonrpc パッケージがJSON-RPC 2.0の厳密な仕様に準拠していることを保証するための重要なステップです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • JSON-RPC 2.0 Specification
  • GitHubのgolang/goリポジトリのコミット履歴
  • Go言語のイシュートラッカー (ただし、Issue 7442の直接的な公開情報は見つからなかったため、内部的な追跡番号である可能性が高い)