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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおける主要なネットワークインターフェースであるnet.Connnet.PacketConn、およびnet.Listenerに対する新しいテストを追加します。既存のテストが複数の要素(サーバーの振る舞い、プラットフォーム固有の挙動、プロトコル、API)を同時に検証しており複雑であったため、APIに焦点を当てた、より単一責任の原則に基づいたテストの必要性から導入されました。これにより、各インターフェースの基本的な機能とAPIの振る舞いが独立して検証され、コードの堅牢性と保守性が向上します。

コミット

commit 4545dc6a6953b2be6d0d50719ad165d46278d9bf
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sun Oct 21 17:17:51 2012 -0400

    net: add test for Conn, PacketConn and Listener
    
    I just realized that there is no good place for adding
    exposed function or method tests because server, unicast
    and multicast_test.go do test complicated multiple test
    objects, platform behaviros, protocol behaviors and API,
    at the same time. Perhaps splitting them into per test
    object might be better, so this CL provides tests focused
    on API.
    
    R=rsc
    CC=gobot, golang-dev
    https://golang.org/cl/6501057

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

https://github.com/golang/go/commit/4545dc6a6953b2be6d0d50719ad165d46278d9bf

元コミット内容

このコミットは、Go言語のnetパッケージにConnPacketConn、およびListenerインターフェースのためのテストを追加します。既存のテストがサーバー、ユニキャスト、マルチキャストといった複雑なシナリオを同時にテストしており、プラットフォームの挙動、プロトコルの挙動、APIの挙動が混在しているため、APIに特化したテストを追加することで、テストの分離と明確化を図ることを目的としています。

変更の背景

Go言語のnetパッケージは、ネットワークプログラミングの基盤を提供する非常に重要なコンポーネントです。しかし、コミットメッセージが示唆するように、当時のテストスイートは、個々のネットワークインターフェースのAPIの振る舞いを独立して検証するよりも、より広範で複雑なシナリオ(例: サーバーの動作、ユニキャスト/マルチキャスト通信、プラットフォーム固有の挙動)を統合的にテストする傾向がありました。

このような統合テストは、システム全体の機能を確認する上では有用ですが、特定のAPIメソッドが期待通りに動作するかどうか、あるいは特定のネットワークインターフェースの振る舞いが仕様に準拠しているかを詳細に検証するには不向きです。複数の要素が絡み合うため、テストが失敗した場合にどの部分に問題があるのかを特定するのが困難になるという課題がありました。

このコミットは、この課題に対処するために導入されました。net.Connnet.PacketConnnet.Listenerといった基本的なインターフェースに焦点を当てた、より粒度の細かい単体テストを追加することで、以下の目的を達成しようとしています。

  1. APIの正確性の検証: 各インターフェースのメソッドが、期待される入力に対して正しい出力を返し、適切なエラーを処理するかを厳密に確認します。
  2. 回帰テストの強化: 将来の変更が既存のAPIの振る舞いを壊さないことを保証するための安全網を構築します。
  3. テストの分離と理解の容易さ: 複雑なシナリオからAPIテストを分離することで、テストコード自体が読みやすくなり、何がテストされているのかが明確になります。これにより、開発者がテストの意図を理解しやすくなり、デバッグや機能追加の際に役立ちます。
  4. プラットフォーム固有の挙動の考慮: unixソケットやip:icmpなど、特定のOSや権限に依存するネットワーク機能についても、適切な条件分岐を設けてテストを記述することで、クロスプラットフォームでの堅牢性を確保します。

この変更は、Goの標準ライブラリの品質と信頼性を向上させるための重要なステップであり、テスト駆動開発(TDD)や堅牢なソフトウェアエンジニアリングの実践に沿ったものです。

前提知識の解説

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

Go言語のnetパッケージ

netパッケージは、Go言語でネットワーク通信を行うための主要なパッケージです。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルをサポートしています。

  • net.Connインターフェース:

    • ストリーム指向のネットワーク接続(例: TCP接続)を表す汎用インターフェースです。
    • ReadWriteCloseLocalAddrRemoteAddrSetDeadlineSetReadDeadlineSetWriteDeadlineなどのメソッドを持ちます。
    • net.Dial関数によって確立された接続がこのインターフェースを実装します。
  • net.PacketConnインターフェース:

    • パケット指向のネットワーク接続(例: UDP接続)を表す汎用インターフェースです。
    • ReadFromWriteToCloseLocalAddrSetDeadlineSetReadDeadlineSetWriteDeadlineなどのメソッドを持ちます。
    • net.ListenPacket関数によって確立された接続がこのインターフェースを実装します。
  • net.Listenerインターフェース:

    • 着信ネットワーク接続をリッスンするための汎用インターフェースです。
    • AcceptCloseAddrなどのメソッドを持ちます。
    • net.Listen関数によって作成されたリスナーがこのインターフェースを実装します。
  • net.UDPConn: UDP接続を表す具体的な型で、net.PacketConnを実装し、WriteToUDPReadFromUDPなどのUDP固有のメソッドを提供します。

  • net.IPConn: IPプロトコル(例: ICMP)接続を表す具体的な型で、net.PacketConnを実装し、WriteToIPReadFromIPなどのIP固有のメソッドを提供します。

  • net.UnixConn: Unixドメインソケット接続を表す具体的な型で、net.Connnet.PacketConnを実装し、WriteMsgUnixReadMsgUnixなどのUnixドメインソケット固有のメソッドを提供します。

ネットワークプロトコル

  • TCP (Transmission Control Protocol): 信頼性のある、コネクション指向のストリーム通信プロトコル。データの順序保証と再送制御を行います。
  • UDP (User Datagram Protocol): 信頼性のない、コネクションレスなデータグラム通信プロトコル。高速ですが、データの到達保証や順序保証はありません。
  • Unixドメインソケット (Unix Domain Sockets): 同じホスト上のプロセス間通信(IPC)に使用されるソケット。ネットワークスタックを介さないため、TCP/IPよりも高速です。ストリーム型(unix)とデータグラム型(unixgram)があります。
  • ICMP (Internet Control Message Protocol): IPネットワーク上でエラーメッセージや運用情報を送るために使われるプロトコル。pingコマンドなどで利用されます。生IPソケット(ip:icmp)を扱うには、通常、root権限が必要です。

Go言語のテスト

Go言語には、標準でテストフレームワークが組み込まれています。

  • testingパッケージ: Goのテスト機能を提供します。
  • テストファイルの命名規則: テストファイルは、テスト対象のソースファイルと同じディレクトリに配置され、ファイル名の末尾が_test.goである必要があります(例: conn_test.go)。
  • テスト関数の命名規則: テスト関数はTestで始まり、その後に続く名前の最初の文字が大文字である必要があります(例: func TestConnAndListener(t *testing.T))。
  • *testing.T: テスト関数に渡される引数で、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します(例: t.Errorf, t.Fatalf, t.Logf)。
  • t.Fatalf: テストを即座に終了させ、失敗としてマークします。
  • t.Errorf: テストを失敗としてマークしますが、テストの実行は続行します。
  • t.Logf: テスト中に情報をログに出力します。
  • defer: 関数がリターンする直前に実行される処理をスケジュールします。テストでは、リソースのクリーンアップ(例: Close()の呼び出し、一時ファイルの削除)によく使用されます。
  • os.Remove: ファイルシステムからファイルやディレクトリを削除します。Unixドメインソケットのテストでは、ソケットファイルが残らないようにクリーンアップするために使用されます。
  • runtime.GOOS: 現在のオペレーティングシステムを示す文字列(例: "linux", "windows", "plan9")。プラットフォーム固有のテストロジックを記述する際に使用されます。
  • os.Getuid(): 現在のプロセスの実効ユーザーIDを返します。特権が必要な操作(例: 生ソケットの作成)のテストで、root権限があるかを確認するために使用されます。

これらの前提知識を理解することで、コミットが追加するテストコードの意図と実装の詳細を深く把握することができます。

技術的詳細

このコミットによって追加されたテストは、netパッケージの主要なインターフェースであるnet.Connnet.PacketConn、およびnet.Listenerの基本的な機能とAPIの振る舞いを検証することに特化しています。それぞれのテストファイルは、異なる種類のネットワーク接続とそれに対応するインターフェースに焦点を当てています。

src/pkg/net/conn_test.go

このファイルは、net.Connnet.Listenerインターフェースのテストを含んでいます。

  • connTests: tcpunixunixpacketの3種類のネットワークタイプとアドレスの組み合わせを定義しています。
    • tcp: 標準的なTCP/IPソケット。
    • unix: Unixドメインストリームソケット。
    • unixpacket: Unixドメインデータグラムソケット(Linuxのみ)。
  • TestConnAndListener関数:
    • connTestsの各エントリに対してループを実行します。
    • プラットフォーム固有のスキップ: unixおよびunixpacketソケットは、plan9windowsではサポートされていないため、これらのOSではテストをスキップします。また、unixpacketはLinux固有の機能であるため、Linux以外ではスキップされます。
    • ソケットファイルのクリーンアップ: unixおよびunixpacketの場合、テスト実行前にソケットファイルが存在すれば削除し、テスト終了後にも削除します。これは、ソケットファイルが残っていると次のテスト実行時にエラーになる可能性があるためです。
    • net.Listennet.Accept: net.Listenでリスナーを作成し、transponderゴルーチン内でln.Accept()を呼び出して接続を待ち受けます。
    • net.Dial: クライアント側でnet.Dialを使用してリスナーに接続します。
    • net.Connメソッドのテスト:
      • LocalAddr()RemoteAddr(): ローカルおよびリモートのアドレスが取得できることを確認します。
      • SetDeadline()SetReadDeadline()SetWriteDeadline(): タイムアウト設定が機能することを確認します。
      • Write()Read(): データの送受信が正しく行われることを確認します。transponderゴルーチンが受け取ったデータをそのままクライアントに送り返す(エコーバック)ことで、送受信の整合性を検証します。
  • transponder関数:
    • net.Listenerから接続を受け入れ、受け取ったデータをそのままクライアントに送り返すヘルパーゴルーチンです。
    • SetDeadlineなどのnet.Connメソッドも呼び出し、それらの基本的な動作を確認します。

src/pkg/net/packetconn_test.go

このファイルは、net.PacketConnインターフェースのテストを含んでいます。

  • packetConnTests: udpip:icmpunixgramの3種類のネットワークタイプとアドレスの組み合わせを定義しています。
    • udp: 標準的なUDPソケット。
    • ip:icmp: 生IPソケット(ICMPプロトコル)。
    • unixgram: Unixドメインデータグラムソケット。
  • TestPacketConn関数:
    • packetConnTestsの各エントリに対してループを実行します。
    • プラットフォーム固有のスキップと権限チェック: ip:icmpplan9ではスキップされ、またroot権限が必要なため、os.Getuid() != 0の場合はスキップされます。unixgramplan9windowsではスキップされます。
    • ICMPエコーリクエストの生成: ip:icmpの場合、newICMPEchoRequest関数を使用してICMPエコーリクエストパケットを生成します。
    • ソケットファイルのクリーンアップ: unixgramの場合、テスト実行前後でソケットファイルを削除します。
    • net.ListenPacket: 2つのnet.PacketConn (c1, c2) を作成します。
    • net.PacketConnメソッドのテスト:
      • LocalAddr(): ローカルアドレスが取得できることを確認します。
      • SetDeadline()SetReadDeadline()SetWriteDeadline(): タイムアウト設定が機能することを確認します。
      • WriteTo()ReadFrom(): パケットの送受信が正しく行われることを確認します。c1からc2へ、そしてc2からc1へパケットを送信し、それぞれが正しく受信できることを検証します。
  • TestConnAndPacketConn関数:
    • packetConnTestsudpip:icmpのケースに限定してテストを実行します。
    • net.ListenPacketc1PacketConn)を作成し、net.Dialc2Conn)を作成します。
    • c2.Write()でデータを送信し、c1.ReadFrom()で受信できることを確認します。
    • c1.WriteTo()でデータを送信し、c2.Read()で受信できることを確認します。
    • これにより、net.Connnet.PacketConnが相互運用できることを検証します。

src/pkg/net/protoconn_test.go

このファイルは、net.UDPConnnet.IPConnnet.UnixConnといった具体的なコネクション型の固有メソッドのテストを含んでいます。

  • TestUDPConnSpecificMethods関数:
    • net.ListenUDPでUDP接続を作成します。
    • File(): ファイルディスクリプタが取得できることを確認します。
    • LocalAddr()RemoteAddr(): アドレスが取得できることを確認します。
    • SetDeadline()SetReadDeadline()SetWriteDeadline(): タイムアウト設定が機能することを確認します。
    • SetReadBuffer()SetWriteBuffer(): ソケットバッファサイズの設定が機能することを確認します。
    • WriteToUDP()ReadFromUDP(): UDP固有の送受信メソッドが機能することを確認します。
  • TestIPConnSpecificMethods関数:
    • ip:icmpプロトコルを使用し、net.ListenIPでIP接続を作成します。
    • plan9ではスキップされ、root権限が必要なため、権限チェックが行われます。
    • File()LocalAddr()RemoteAddr()SetDeadline()SetReadDeadline()SetWriteDeadline()SetReadBuffer()SetWriteBuffer(): UDPConnと同様の汎用メソッドのテストを行います。
    • WriteToIP()ReadFromIP(): IP固有の送受信メソッドが機能することを確認します。
    • newICMPEchoRequestを使用してICMPエコーリクエストパケットを生成し、送受信をテストします。
  • TestUnixConnSpecificMethods関数:
    • unixgramプロトコルを使用し、net.DialUnixでUnixドメインデータグラム接続を作成します。
    • plan9windowsではスキップされます。
    • ソケットファイルのクリーンアップが行われます。
    • File()LocalAddr()RemoteAddr()SetDeadline()SetReadDeadline()SetWriteDeadline()SetReadBuffer()SetWriteBuffer(): 汎用メソッドのテストを行います。
    • WriteMsgUnix()ReadMsgUnix(): Unixドメインソケット固有のメッセージ送受信メソッドが機能することを確認します。
    • WriteToUnix()ReadFromUnix(): Unixドメインソケット固有のデータグラム送受信メソッドが機能することを確認します。
  • newICMPEchoRequest関数とnewICMPInfoMessage関数:
    • ICMPエコーリクエストパケットを生成するためのヘルパー関数です。
    • ICMPヘッダの構造(タイプ、コード、チェックサム、識別子、シーケンス番号)を理解し、それに基づいてバイトスライスを構築します。
    • 特に、ICMPチェックサムの計算ロジックが含まれています。これは、ネットワークプロトコルレベルでの正確なパケット生成に不可欠な部分です。

これらのテストは、Goのnetパッケージが提供する様々なネットワークインターフェースとプロトコルに対して、基本的なAPIの振る舞いが期待通りであることを保証するための包括的なカバレッジを提供します。プラットフォーム固有の挙動や特権の必要性も考慮されており、堅牢なテストスイートとなっています。

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

このコミットでは、以下の3つの新しいテストファイルが追加されました。

  • src/pkg/net/conn_test.go: 103行の追加
  • src/pkg/net/packetconn_test.go: 161行の追加
  • src/pkg/net/protoconn_test.go: 185行の追加

合計で449行のコードが追加されています。既存のファイルへの変更はありません。

コアとなるコードの解説

追加されたテストコードは、Goのnetパッケージが提供する主要なネットワークインターフェースの基本的な機能とAPIの振る舞いを検証することに重点を置いています。以下に、各テストファイルの主要な部分と、その設計思想について解説します。

src/pkg/net/conn_test.go

このファイルは、ストリーム指向の接続であるnet.Connと、接続を待ち受けるnet.Listenerのテストを行います。

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net_test

import (
	"net"
	"os"
	"runtime"
	"testing"
	"time"
)

var connTests = []struct {
	net  string
	addr string
}{
	{"tcp", "127.0.0.1:0"},
	{"unix", "/tmp/gotest.net"},
	{"unixpacket", "/tmp/gotest.net"},
}

func TestConnAndListener(t *testing.T) {
	for _, tt := range connTests {
		// ... (プラットフォーム固有のスキップとソケットファイルのクリーンアップ) ...

		ln, err := net.Listen(tt.net, tt.addr)
		if err != nil {
			t.Errorf("net.Listen failed: %v", err)
			return
		}
		ln.Addr() // アドレス取得のテスト
		defer ln.Close()

		done := make(chan int)
		go transponder(t, ln, done) // 別ゴルーチンで接続を待ち受け、エコーバック

		c, err := net.Dial(tt.net, ln.Addr().String())
		if err != nil {
			t.Errorf("net.Dial failed: %v", err)
			return
		}
		c.LocalAddr()  // ローカルアドレス取得のテスト
		c.RemoteAddr() // リモートアドレス取得のテスト
		c.SetDeadline(time.Now().Add(100 * time.Millisecond))     // デッドライン設定のテスト
		c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) // 読み込みデッドライン設定のテスト
		c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) // 書き込みデッドライン設定のテスト
		defer c.Close()

		if _, err := c.Write([]byte("CONN TEST")); err != nil {
			t.Errorf("net.Conn.Write failed: %v", err)
			return
		}
		rb := make([]byte, 128)
		if _, err := c.Read(rb); err != nil {
			t.Errorf("net.Conn.Read failed: %v", err)
		}

		<-done // transponderゴルーチンの終了を待つ
		// ... (ソケットファイルのクリーンアップ) ...
	}
}

func transponder(t *testing.T, ln net.Listener, done chan<- int) {
	defer func() { done <- 1 }() // 終了時にチャネルに通知

	c, err := ln.Accept() // 接続を受け入れる
	if err != nil {
		t.Errorf("net.Listener.Accept failed: %v", err)
		return
	}
	c.LocalAddr()
	c.RemoteAddr()
	c.SetDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
	defer c.Close()

	b := make([]byte, 128)
	n, err := c.Read(b) // データを受信
	if err != nil {
		t.Errorf("net.Conn.Read failed: %v", err)
		return
	}
	if _, err := c.Write(b[:n]); err != nil { // 受信したデータをそのまま送り返す
		t.Errorf("net.Conn.Write failed: %v", err)
		return
	}
}
  • テストの構造: connTestsという構造体のスライスを使って、テスト対象のネットワークタイプとアドレスをパラメータ化しています。これにより、TCP、Unixドメインストリーム、Unixドメインデータグラムソケットに対して同じテストロジックを適用できます。
  • 並行処理: transponder関数を別のゴルーチンで実行することで、クライアントとサーバーの役割を同時にシミュレートし、実際のネットワーク通信に近い形でテストを行っています。doneチャネルを使って、transponderゴルーチンの完了を待機しています。
  • デッドラインのテスト: SetDeadline, SetReadDeadline, SetWriteDeadlineメソッドを呼び出すことで、タイムアウト機能が正しく設定できることを確認しています。これは、ネットワークアプリケーションにおける応答性とリソース管理において重要な機能です。

src/pkg/net/packetconn_test.go

このファイルは、パケット指向の接続であるnet.PacketConnのテストを行います。

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net_test

import (
	"net"
	"os"
	"runtime"
	"strings"
	"testing"
	"time"
)

var packetConnTests = []struct {
	net   string
	addr1 string
	addr2 string
}{
	{"udp", "127.0.0.1:0", "127.0.0.1:0"},
	{"ip:icmp", "127.0.0.1", "127.0.0.1"},
	{"unixgram", "/tmp/gotest.net1", "/tmp/gotest.net2"},
}

func TestPacketConn(t *testing.T) {
	for _, tt := range packetConnTests {
		// ... (プラットフォーム固有のスキップ、権限チェック、ICMPパケット生成、ソケットファイルのクリーンアップ) ...

		c1, err := net.ListenPacket(tt.net, tt.addr1)
		if err != nil {
			t.Fatalf("net.ListenPacket failed: %v", err)
		}
		c1.LocalAddr()
		c1.SetDeadline(time.Now().Add(100 * time.Millisecond))
		c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
		defer c1.Close()

		c2, err := net.ListenPacket(tt.net, tt.addr2)
		if err != nil {
			t.Fatalf("net.ListenPacket failed: %v", err)
		}
		c2.LocalAddr()
		c2.SetDeadline(time.Now().Add(100 * time.Millisecond))
		c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
		defer c2.Close()

		if _, err := c1.WriteTo(wb, c2.LocalAddr()); err != nil { // c1からc2へ書き込み
			t.Fatalf("net.PacketConn.WriteTo failed: %v", err)
		}
		rb2 := make([]byte, 128)
		if _, _, err := c2.ReadFrom(rb2); err != nil { // c2で読み込み
			t.Fatalf("net.PacketConn.ReadFrom failed: %v", err)
		}
		if _, err := c2.WriteTo(wb, c1.LocalAddr()); err != nil { // c2からc1へ書き込み
			t.Fatalf("net.PacketConn.WriteTo failed: %v", err)
		}
		rb1 := make([]byte, 128)
		if _, _, err := c1.ReadFrom(rb1); err != nil { // c1で読み込み
			t.Fatalf("net.PacketConn.ReadFrom failed: %v", err)
		}

		// ... (ソケットファイルのクリーンアップ) ...
	}
}

func TestConnAndPacketConn(t *testing.T) {
	for _, tt := range packetConnTests {
		// ... (プラットフォーム固有のスキップ、権限チェック、ICMPパケット生成) ...

		c1, err := net.ListenPacket(tt.net, tt.addr1) // PacketConn
		if err != nil {
			t.Fatalf("net.ListenPacket failed: %v", err)
		}
		c1.LocalAddr()
		c1.SetDeadline(time.Now().Add(100 * time.Millisecond))
		c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
		defer c1.Close()

		c2, err := net.Dial(tt.net, c1.LocalAddr().String()) // Conn
		if err != nil {
			t.Fatalf("net.Dial failed: %v", err)
		}
		c2.LocalAddr()
		c2.RemoteAddr()
		c2.SetDeadline(time.Now().Add(100 * time.Millisecond))
		c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
		defer c2.Close()

		if _, err := c2.Write(wb); err != nil { // ConnからPacketConnへ書き込み
			t.Fatalf("net.Conn.Write failed: %v", err)
		}
		rb1 := make([]byte, 128)
		if _, _, err := c1.ReadFrom(rb1); err != nil { // PacketConnで読み込み
			t.Fatalf("net.PacetConn.ReadFrom failed: %v", err)
		}
		var dst net.Addr
		if netstr[0] == "ip" {
			dst = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
		} else {
			dst = c2.LocalAddr()
		}
		if _, err := c1.WriteTo(wb, dst); err != nil { // PacketConnからConnへ書き込み
			t.Fatalf("net.PacketConn.WriteTo failed: %v", err)
		}
		rb2 := make([]byte, 128)
		if _, err := c2.Read(rb2); err != nil { // Connで読み込み
			t.Fatalf("net.Conn.Read failed: %v", err)
		}
	}
}
  • WriteToReadFrom: net.PacketConnの主要なメソッドであるWriteToReadFromの動作を検証しています。これらのメソッドは、データグラム通信において送信先アドレスを指定し、受信元アドレスを取得するために使用されます。
  • TestConnAndPacketConn: net.Connnet.PacketConnが異なるタイプのソケットであっても、特定のプロトコル(UDP、IP)においては相互に通信できることを示しています。これは、GoのネットワークAPIの柔軟性を示唆しています。

src/pkg/net/protoconn_test.go

このファイルは、net.UDPConnnet.IPConnnet.UnixConnといった具体的なコネクション型が持つ、より特化したメソッドのテストを行います。

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net_test

import (
	"bytes"
	"net"
	"os"
	"runtime"
	"testing"
	"time"
)

func TestUDPConnSpecificMethods(t *testing.T) {
	la, err := net.ResolveUDPAddr("udp4", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("net.ResolveUDPAddr failed: %v", err)
	}
	c, err := net.ListenUDP("udp4", la)
	if err != nil {
		t.Fatalf("net.ListenUDP failed: %v", err)
	}
	c.File() // ファイルディスクリプタ取得のテスト
	c.LocalAddr()
	c.RemoteAddr()
	c.SetDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetReadBuffer(2048)  // 読み込みバッファサイズ設定のテスト
	c.SetWriteBuffer(2048) // 書き込みバッファサイズ設定のテスト
	defer c.Close()

	wb := []byte("UDPCONN TEST")
	if _, err := c.WriteToUDP(wb, c.LocalAddr().(*net.UDPAddr)); err != nil { // UDP固有の書き込み
		t.Fatalf("net.UDPConn.WriteToUDP failed: %v", err)
	}
	rb := make([]byte, 128)
	if _, _, err := c.ReadFromUDP(rb); err != nil { // UDP固有の読み込み
		t.Fatalf("net.UDPConn.ReadFromUDP failed: %v", err)
	}
}

func TestIPConnSpecificMethods(t *testing.T) {
	// ... (プラットフォーム固有のスキップと権限チェック) ...

	la, err := net.ResolveIPAddr("ip4", "127.0.0.1")
	if err != nil {
		t.Fatalf("net.ResolveIPAddr failed: %v", err)
	}
	c, err := net.ListenIP("ip4:icmp", la)
	if err != nil {
		t.Fatalf("net.ListenIP failed: %v", err)
	}
	c.File()
	c.LocalAddr()
	c.RemoteAddr()
	c.SetDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
	c.SetReadBuffer(2048)
	c.SetWriteBuffer(2048)
	defer c.Close()

	id := os.Getpid() & 0xffff
	wb := newICMPEchoRequest(id, 1, 128, []byte("IPCONN TEST ")) // ICMPエコーリクエスト生成
	if _, err := c.WriteToIP(wb, c.LocalAddr().(*net.IPAddr)); err != nil { // IP固有の書き込み
		t.Fatalf("net.IPConn.WriteToIP failed: %v", err)
	}
	rb := make([]byte, 20+128)
	if _, _, err := c.ReadFromIP(rb); err != nil { // IP固有の読み込み
		t.Fatalf("net.IPConn.ReadFromIP failed: %v", err)
	}
}

// ... (TestUnixConnSpecificMethods関数) ...

func newICMPEchoRequest(id, seqnum, msglen int, filler []byte) []byte {
	b := newICMPInfoMessage(id, seqnum, msglen, filler)
	b[0] = 8 // ICMP Echo Request type
	// ... (ICMPチェックサム計算ロジック) ...
	return b
}

func newICMPInfoMessage(id, seqnum, msglen int, filler []byte) []byte {
	b := make([]byte, msglen)
	copy(b[8:], bytes.Repeat(filler, (msglen-8)/len(filler)+1))
	b[0] = 0                   // type
	b[1] = 0                   // code
	b[2] = 0                   // checksum (placeholder)
	b[3] = 0                   // checksum (placeholder)
	b[4] = byte(id >> 8)       // identifier
	b[5] = byte(id & 0xff)     // identifier
	b[6] = byte(seqnum >> 8)   // sequence number
	b[7] = byte(seqnum & 0xff) // sequence number
	return b
}
  • 固有メソッドのテスト: WriteToUDP, ReadFromUDP, WriteToIP, ReadFromIP, WriteMsgUnix, ReadMsgUnix, WriteToUnix, ReadFromUnixといった、各コネクション型に特有のメソッドの動作を検証しています。これにより、汎用インターフェースではカバーできない、より詳細なプロトコル固有の振る舞いがテストされます。
  • File()メソッドのテスト: File()メソッドは、ネットワーク接続に対応するファイルディスクリプタ(またはファイル)を取得するために使用されます。このテストは、Goのネットワーク接続が基盤となるOSのリソースとどのように関連しているかを示しています。
  • バッファサイズの設定: SetReadBufferSetWriteBufferメソッドのテストは、ネットワークI/Oのパフォーマンスチューニングに関連する機能が正しく動作することを確認します。
  • ICMPパケットの生成: newICMPEchoRequestnewICMPInfoMessage関数は、ICMPエコーリクエストパケットを手動で構築するためのものです。これには、ICMPヘッダのフィールド(タイプ、コード、識別子、シーケンス番号)の設定と、特に重要なICMPチェックサムの計算が含まれます。チェックサムは、パケットの整合性を保証するために使用されるため、その正確な計算は生ソケットプログラミングにおいて不可欠です。

これらのテストは、Goのnetパッケージの堅牢性を高めるために、基本的なAPIの振る舞いから、特定のプロトコルやプラットフォームに依存する詳細な機能まで、幅広いカバレッジを提供しています。テストコード自体も、Goのテストのベストプラクティス(パラメータ化されたテスト、ヘルパー関数、プラットフォーム固有の条件分岐)を示しています。

関連リンク

  • Go言語 net パッケージのドキュメント: https://pkg.go.dev/net
  • Go言語 testing パッケージのドキュメント: https://pkg.go.dev/testing
  • Go言語におけるネットワークプログラミングの基本(公式ブログ記事などがあれば追加)
  • Unixドメインソケットに関する情報(一般的な概念)
  • ICMPプロトコルに関する情報(一般的な概念)

参考にした情報源リンク