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

[インデックス 12402] ファイルの概要

このコミットでは、Go言語の標準ライブラリ net パッケージにおけるテストコードが大幅に改善されています。具体的には、以下の3つのファイルが変更されています。

  • src/pkg/net/file_test.go: 102行の追加と9行の削除
  • src/pkg/net/server_test.go: 490行の追加と192行の削除
  • src/pkg/net/timeout_test.go: 39行の追加と39行の削除

これらの変更は、ネットワークサーバーとクライアントのテストの堅牢性と網羅性を向上させることを目的としています。

コミット

commit d4e138328525341c9893f51255add19276960bb9
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Tue Mar 6 09:43:45 2012 +0900

    net: improve server and file tests
    
    * Splits into three server tests.
      - TestStreamConnServer for tcp, tcp4, tcp6 and unix networks
      - TestSeqpacketConnServer for unixpacket networks
      - TestDatagramPacketConnServer for udp, udp4, udp6 and unixgram networks
    * Adds both PacketConn and Conn test clients to datagram packet conn tests.
    * Fixes wildcard listen test cases on dual IP stack platform.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5701066

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d4e138328525341c9893f51255add19276960bb9

元コミット内容

net: improve server and file tests

  • サーバーテストを3つに分割:
    • TestStreamConnServer: TCP、TCP4、TCP6、Unixネットワーク用
    • TestSeqpacketConnServer: Unixpacketネットワーク用
    • TestDatagramPacketConnServer: UDP、UDP4、UDP6、Unixgramネットワーク用
  • データグラムパケットコネクションテストに、PacketConnConn の両方のテストクライアントを追加。
  • デュアルIPスタックプラットフォームでのワイルドカードリスニングテストケースを修正。

変更の背景

Go言語の net パッケージは、ネットワークプログラミングの基盤を提供する非常に重要なコンポーネントです。このパッケージの安定性と正確性は、Goで開発されるあらゆるネットワークアプリケーションにとって不可欠です。

このコミットが行われた2012年当時、Go言語はまだ比較的新しい言語であり、標準ライブラリも活発に開発・改善されていました。ネットワーク機能は特に複雑であり、様々なOS、ネットワークプロトコル、アドレス設定に対応する必要があるため、テストの網羅性と堅牢性が非常に重要になります。

以前のテストコードは、異なるネットワークタイプ(TCP、UDP、Unixドメインソケットなど)やアドレス設定(特定のIPアドレス、ワイルドカードアドレスなど)に対して、十分に分離されていなかったり、特定のプラットフォームでの挙動を考慮していなかった可能性があります。特に、デュアルIPスタック環境(IPv4とIPv6の両方が有効な環境)でのワイルドカードリスニング(例: 0.0.0.0:: でリッスンする)は、実装が複雑で、予期せぬ挙動を引き起こす可能性があるため、厳密なテストが求められます。

このコミットの目的は、これらの問題を解決し、net パッケージのネットワークテストをより構造化され、網羅的で、信頼性の高いものにすることにありました。テストを細分化し、異なるネットワークタイプやクライアントタイプに対応することで、将来的なバグの発見や機能追加の際のデバッグが容易になります。

前提知識の解説

このコミットを理解するためには、以下のネットワークプログラミングとGo言語のテストに関する基本的な知識が必要です。

  1. ネットワークプロトコル:

    • TCP (Transmission Control Protocol): 信頼性の高い、コネクション指向のプロトコル。データの順序保証、再送制御、フロー制御などが行われる。ストリーム指向の通信に適している。
    • UDP (User Datagram Protocol): コネクションレスなプロトコル。データの信頼性や順序は保証されないが、オーバーヘッドが少なく高速。データグラム指向の通信に適している。
    • Unixドメインソケット (Unix Domain Sockets): 同じホスト上のプロセス間通信 (IPC) に使用されるソケット。ネットワークスタックを介さないため、TCP/IPよりも高速。ファイルシステム上のパス名に関連付けられることが多い。
      • Unix (Stream): TCPと同様に信頼性の高いストリーム通信。
      • Unixpacket (Sequenced Packet): データグラムの境界を保持しつつ、信頼性と順序を保証する通信。
      • Unixgram (Datagram): UDPと同様に信頼性のないデータグラム通信。
    • IPv4/IPv6: インターネットプロトコルのバージョン。IPv4は32ビットアドレス、IPv6は128ビットアドレスを使用する。
    • IPv4-mapped IPv6アドレス: IPv6アドレス空間内でIPv4アドレスを表現するための形式(例: ::ffff:192.0.2.1)。デュアルスタック環境でIPv6ソケットがIPv4通信も処理できるようにするために使用されることがある。
  2. Go言語の net パッケージ:

    • net.Listen(network, address string): 指定されたネットワークとアドレスで着信接続をリッスンする net.Listener を返す。TCPなどのコネクション指向プロトコルで使用。
    • net.ListenPacket(network, address string): 指定されたネットワークとアドレスでパケットをリッスンする net.PacketConn を返す。UDPなどのデータグラム指向プロトコルで使用。
    • net.Dial(network, address string): 指定されたネットワークとアドレスへの接続を確立する net.Conn を返す。
    • net.Conn インターフェース: コネクション指向のネットワーク接続を表すインターフェース。Read, Write, Close, LocalAddr, RemoteAddr, SetDeadline などのメソッドを持つ。
    • net.PacketConn インターフェース: データグラム指向のネットワーク接続を表すインターフェース。ReadFrom, WriteTo, Close, LocalAddr, SetDeadline などのメソッドを持つ。ReadFrom は送信元アドレスも返す。WriteTo は指定されたアドレスにデータを送信する。
    • ワイルドカードリスニング: サーバーが特定のアドレスではなく、利用可能なすべてのアドレス(例: IPv4の 0.0.0.0 や IPv6の ::)で接続を受け入れる設定。
  3. Go言語のテスト:

    • testing パッケージ: Goの標準テストフレームワーク。go test コマンドで実行される。
    • func TestXxx(t *testing.T): テスト関数は Test で始まり、*testing.T 型の引数を取る。
    • t.Errorf(...), t.Fatalf(...): テスト失敗を報告するメソッド。Fatalf はテストを即座に終了させる。
    • go func() { ... }(): ゴルーチン(軽量スレッド)を起動し、並行処理を行う。ネットワークテストでは、サーバーとクライアントを並行して実行するためによく使われる。
    • chan (チャネル): ゴルーチン間でデータを安全にやり取りするためのGoの機能。このコミットでは、サーバーがリッスンを開始したことをクライアントに通知したり、サーバーが終了したことを待機したりするために使用されている。

技術的詳細

このコミットの主要な変更点は、src/pkg/net/server_test.go におけるテストの構造化と、src/pkg/net/file_test.go および src/pkg/net/timeout_test.go におけるテストケースの拡張です。

src/pkg/net/server_test.go の変更

  • テストの分割: 以前は doTestdoTestPacket といった汎用的な関数で様々なネットワークタイプを扱っていましたが、これを以下の3つの専用テスト関数に分割しました。
    • TestStreamConnServer: TCP (tcp, tcp4, tcp6) および Unixストリーム (unix) ネットワークのテスト。コネクション指向の通信を検証します。
    • TestSeqpacketConnServer: Unixシーケンスパケット (unixpacket) ネットワークのテスト。これはLinux固有の機能であり、信頼性のあるデータグラム通信を検証します。
    • TestDatagramPacketConnServer: UDP (udp, udp4, udp6) および Unixデータグラム (unixgram) ネットワークのテスト。コネクションレスなデータグラム通信を検証します。
  • テストケースの構造化: 各テスト関数は、streamConnServerTests, seqpacketConnServerTests, datagramPacketConnServerTests という構造体のスライスをイテレートして、多数のテストシナリオを実行します。これにより、異なるネットワークタイプ、アドレス、IPv6/IPv4マッピングの有無、空のデータグラムのテストなど、多様な組み合わせを網羅的にテストできるようになりました。
    • 各テスト構造体には、サーバー側のネットワーク (snet), サーバーアドレス (saddr), クライアント側のネットワーク (cnet), クライアントアドレス (caddr), IPv6サポートの有無 (ipv6), IPv4マッピングの有無 (ipv4map), 空のデータテストの有無 (empty), Linux固有のテスト (linux) などのフィールドが含まれています。
  • skipServerTest 関数の導入: 特定のプラットフォームや設定でサポートされていないテストケースをスキップするためのヘルパー関数 skipServerTest が導入されました。これにより、テストの実行環境に依存しない柔軟なテストが可能になります。例えば、WindowsやPlan 9ではUnixソケットがサポートされていないため、関連するテストはスキップされます。また、OSXのファイアウォールダイアログポップアップを避けるためのロジックも含まれています。
  • クライアントテストの強化: TestDatagramPacketConnServer では、データグラム通信のクライアントとして net.Conn インターフェースを使用する runDatagramConnClient と、net.PacketConn インターフェースを使用する runDatagramPacketConnClient の両方をテストするようにしました。これにより、異なるAPIパスからのデータグラム送受信の挙動を検証できます。
  • ワイルドカードリスニングの修正: デュアルIPスタックプラットフォームでのワイルドカードリスニング(例: 0.0.0.0::)に関するテストケースが修正されました。これは、net.Listen が返すアドレスが、クライアントが接続する際に使用するアドレスと一致しない場合に問題が発生する可能性があるためです。新しいテスト構造では、サーバーがリッスンを開始した後に実際にリッスンしているアドレスを取得し、それに基づいてクライアントが接続するようにしています。

src/pkg/net/file_test.go の変更

  • testFileListenertestFilePacketConnListen の改善: switch ステートメントを使用して、tcp, tcp4, tcp6 および udp, udp4, udp6 の各ネットワークタイプに対して、ポート番号の自動割り当て (:0) を行うように変更されました。
  • テストケースの構造化: fileListenerTestsfilePacketConnTests という構造体のスライスが導入され、TestFileListenerTestFilePacketConn がこれらのスライスをイテレートしてテストを実行するように変更されました。これにより、ファイルディスクリプタの引き渡しに関するテストがより網羅的になりました。特に、IPv6アドレスや抽象Unixドメインソケット(Linux固有)のテストケースが追加されています。
  • runtime.GOOS の削除: 以前は runtime.GOOS を直接使用して特定のOSでのみテストを実行していましたが、skipServerTest 関数を呼び出すことで、より汎用的なスキップロジックに置き換えられました。

src/pkg/net/timeout_test.go の変更

  • testTimeout 関数の引数変更: networknet に、addraddr に変更され、より一般的な命名になりました。
  • テストサーバーの変更: TestTimeoutUDPTestTimeoutTCP で、タイムアウトテストのために使用されるサーバーが、それぞれ runDatagramPacketConnServerrunStreamConnServer に変更されました。これにより、より実際のサーバーの挙動に近い形でタイムアウトをテストできるようになりました。
  • エラーメッセージの改善: タイムアウトエラーメッセージが、より詳細で分かりやすいものに改善されました。

これらの変更により、Goの net パッケージのテストスイートは、よりモジュール化され、網羅的で、異なるプラットフォームやネットワーク設定に対する堅牢性が向上しました。

コアとなるコードの変更箇所

src/pkg/net/server_test.go の主要な変更点

--- a/src/pkg/net/server_test.go
+++ b/src/pkg/net/server_test.go
@@ -9,229 +9,460 @@ import (
 	"io"
 	"os"
 	"runtime"
-	"strings"
 	"testing"
 	"time"
 )
 
-// Do not test empty datagrams by default.\n
-// It causes unexplained timeouts on some systems,\n
-// including Snow Leopard.  I think that the kernel\n
-// doesn\'t quite expect them.\n
-var testUDP = flag.Bool(\"udp\", false, \"whether to test UDP datagrams\")\n
+func skipServerTest(net, unixsotype, addr string, ipv6, ipv4map, linuxonly bool) bool {
+	switch runtime.GOOS {
+	case "linux":
+	case "plan9", "windows":
+		// "unix" sockets are not supported on Windows and Plan 9.
+		if net == unixsotype {
+			return true
+		}
+	default:
+		if net == unixsotype && linuxonly {
+			return true
+		}
+	}
+	switch addr {
+	case "", "0.0.0.0", "[::ffff:0.0.0.0]", "[::]":
+		if avoidOSXFirewallDialogPopup() {
+			return true
+		}
+	}
+	if ipv6 && !supportsIPv6 {
+		return true
+	}
+	if ipv4map && !supportsIPv4map {
+		return true
+	}
+	return false
+}
 
-func runEcho(fd io.ReadWriter, done chan<- int) {\n
-	var buf [1024]byte\n
+var streamConnServerTests = []struct {
+	snet    string // server side
+	saddr   string
+	cnet    string // client side
+	caddr   string
+	ipv6    bool // test with underlying AF_INET6 socket
+	ipv4map bool // test with IPv6 IPv4-mapping functionality
+	empty   bool // test with empty data
+	linux   bool // test with abstract unix domain socket, a Linux-ism
+}{
+	{snet: "tcp", saddr: "", cnet: "tcp", caddr: "127.0.0.1"},
+	// ... (多数のテストケースが続く) ...
+}
+
+func TestStreamConnServer(t *testing.T) {
+	for _, tt := range streamConnServerTests {
+		if skipServerTest(tt.snet, "unix", tt.saddr, tt.ipv6, tt.ipv4map, tt.linux) {
+			continue
+		}
+
+		listening := make(chan string)
+		done := make(chan int)
+		switch tt.snet {
+		case "tcp", "tcp4", "tcp6":
+			tt.saddr += ":0"
+		case "unix":
+			os.Remove(tt.saddr)
+			os.Remove(tt.caddr)
+		}
+
+		go runStreamConnServer(t, tt.snet, tt.saddr, listening, done)
+		taddr := <-listening // wait for server to start
+
+		switch tt.cnet {
+		case "tcp", "tcp4", "tcp6":
+			_, port, err := SplitHostPort(taddr)
+			if err != nil {
+				t.Errorf("SplitHostPort(%q) failed: %v", taddr, err)
+				return
+			}
+			taddr = tt.caddr + ":" + port
+		}
+
+		runStreamConnClient(t, tt.cnet, taddr, tt.empty)
+		<-done // make sure server stopped
+
+		switch tt.snet {
+		case "unix":
+			os.Remove(tt.saddr)
+			os.Remove(tt.caddr)
+		}
+	}
+}
+
+// runStreamConnServer, runStreamConnClient, TestSeqpacketConnServer, TestDatagramPacketConnServer
+// などの新しい関数とテストケース定義が追加・変更されている。

src/pkg/net/file_test.go の主要な変更点

--- a/src/pkg/net/file_test.go
+++ b/src/pkg/net/file_test.go
@@ -7,7 +7,6 @@ package net
 import (
 	"os"
 	"reflect"
-	"runtime"
 	"testing"
 )
 
@@ -27,7 +26,8 @@ type connFile interface {
 }
 
 func testFileListener(t *testing.T, net, laddr string) {
-	if net == "tcp" {
+	switch net {
+	case "tcp", "tcp4", "tcp6":
 		laddr += ":0" // any available port
 	}
 	l, err := Listen(net, laddr)
@@ -55,15 +55,46 @@ func testFileListener(t *testing.T, net, laddr string) {
 	}
 }
 
+var fileListenerTests = []struct {
+	net   string
+	laddr string
+	ipv6  bool // test with underlying AF_INET6 socket
+	linux bool // test with abstract unix domain socket, a Linux-ism
+}{
+	{net: "tcp", laddr: ""},
+	// ... (多数のテストケースが続く) ...
+}
+
 func TestFileListener(t *testing.T) {
-	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
-		return
-	}
-	testFileListener(t, "tcp", "127.0.0.1")
-	testFileListener(t, "tcp", "[::ffff:127.0.0.1]")
-	if runtime.GOOS == "linux" {
-		testFileListener(t, "unix", "@gotest/net")
-		testFileListener(t, "unixpacket", "@gotest/net")
+	for _, tt := range fileListenerTests {
+		if skipServerTest(tt.net, "unix", tt.laddr, tt.ipv6, false, tt.linux) {
+			continue
+		}
+		if skipServerTest(tt.net, "unixpacket", tt.laddr, tt.ipv6, false, tt.linux) {
+			continue
+		}
+		testFileListener(t, tt.net, tt.laddr)
 	}
 }
 
 // ... (testFilePacketConnListen, testFilePacketConnDial, filePacketConnTests, TestFilePacketConn も同様に変更) ...

コアとなるコードの解説

skipServerTest 関数

この関数は、特定のテストケースをスキップすべきかどうかを判断します。

  • runtime.GOOS をチェックし、WindowsやPlan 9などのOSでUnixソケットがサポートされていない場合に true を返します。
  • Linux固有の抽象Unixドメインソケットのテストを、Linux以外のOSでスキップします。
  • ワイルドカードアドレス("", "0.0.0.0", "[::ffff:0.0.0.0]", "[::]")でリッスンするテストにおいて、avoidOSXFirewallDialogPopup()true を返す場合にスキップします。これは、OSXでワイルドカードアドレスを使用するとファイアウォールのダイアログが表示されるのを避けるためのものです。
  • IPv6またはIPv4マッピングが必要なテストで、システムがそれらをサポートしていない場合にスキップします。

この関数により、テストスイートは様々な環境で適切に動作し、不要なエラーやユーザーインタラクションを避けることができます。

streamConnServerTests および TestStreamConnServer

streamConnServerTests は、TCPおよびUnixストリームソケットのテストシナリオを定義する構造体のスライスです。各要素は、サーバーとクライアントのネットワークタイプ、アドレス、IPv6/IPv4マッピングの有無、空のデータテストの有無、Linux固有のテストの有無などを指定します。

TestStreamConnServer 関数は、この streamConnServerTests スライスをループで処理します。

  1. skipServerTest を呼び出して、現在のテストケースをスキップすべきか判断します。
  2. サーバーがリッスンを開始したことを通知するためのチャネル (listening) と、サーバーが終了したことを通知するためのチャネル (done) を作成します。
  3. TCP系のネットワークの場合、サーバーアドレスに :0 を追加して、利用可能な任意のポートを割り当てます。Unixソケットの場合、テスト前にソケットファイルを削除します。
  4. runStreamConnServer をゴルーチンとして起動し、サーバーをバックグラウンドで実行します。
  5. <-listening でサーバーがリッスンを開始するのを待ち、実際にリッスンしているアドレス (taddr) を取得します。
  6. クライアントのネットワークタイプに応じて、taddr を調整します(例: TCPの場合、クライアントアドレスにサーバーのポート番号を追加)。
  7. runStreamConnClient を呼び出してクライアントを実行し、サーバーとの通信をテストします。
  8. <-done でサーバーが終了するのを待ちます。
  9. Unixソケットの場合、テスト後にソケットファイルを削除します。

この構造により、多数の異なるネットワーク設定とシナリオに対して、自動的かつ網羅的にテストを実行できるようになります。

fileListenerTests および TestFileListener

fileListenerTests は、ファイルディスクリプタの引き渡しに関するリスナーテストのシナリオを定義する構造体のスライスです。

TestFileListener 関数は、この fileListenerTests スライスをループで処理します。

  1. skipServerTest を呼び出して、UnixおよびUnixpacketソケットのテストをスキップすべきか判断します。
  2. testFileListener 関数を呼び出し、各テストケースを実行します。

testFileListener 関数内では、switch net を使用して、tcp, tcp4, tcp6 ネットワークに対してポート番号の自動割り当てを行うように変更されています。これにより、テストの柔軟性が向上し、特定のポートが利用可能であるかどうかに依存しなくなります。

これらの変更は、Goの net パッケージのテストの品質と網羅性を大幅に向上させ、将来的な開発とメンテナンスを容易にするための重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語の net パッケージに関する公式ドキュメント: https://pkg.go.dev/net
  • Go言語のテストに関する公式ドキュメント: https://pkg.go.dev/testing
  • Unixドメインソケットに関する一般的な情報 (例: Wikipediaなど)
  • IPv4-mapped IPv6アドレスに関する一般的な情報 (例: RFC 4291など)
  • Go言語のネットワークプログラミングに関するチュートリアルや記事 (一般的な知識として)
  • Go言語のソースコード (特に src/pkg/net ディレクトリ)
  • Go言語のIssue TrackerやCode Reviewシステム (当時の議論を追うため)