[インデックス 15638] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージ内のマルチキャストリスナーテストに関する修正です。具体的には、既存のマルチキャストテストをIPv4とIPv6のそれぞれに分割し、テストの安定性を向上させるための変更が含まれています。
コミット
commit 2ac799cfbcf212675d64ea0df835ca6c2e2aa368
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Fri Mar 8 06:51:06 2013 +0900
net: fix multicast listener tests
This CL splits multicast listener tests into two; for IPv4 and
for IPv6. It also removes redundant test inputs and makes sure
that assignment of multicast interface to stablize the tests.
Fixes #4059.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7565043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2ac799cfbcf212675d64ea0df835ca6c2e2aa368
元コミット内容
このコミットは、Go言語のnet
パッケージにおけるマルチキャストリスナーのテストに関するものです。元のコミットメッセージによると、以下の点が変更されています。
- マルチキャストリスナーのテストをIPv4用とIPv6用に分割。
- 冗長なテスト入力の削除。
- マルチキャストインターフェースの割り当てを確実に行い、テストの安定性を向上。
- Issue #4059 を修正。
変更の背景
このコミットの背景には、Go言語のnet
パッケージにおけるマルチキャスト機能のテストが不安定であったという問題があります。特に、IPv4とIPv6のマルチキャストテストが混在しており、特定の環境や実行タイミングによってテストが失敗する可能性がありました。
マルチキャスト通信は、特定のグループに属する複数のホストに対して同時にデータを送信する通信方式です。UDP(User Datagram Protocol)上で実装されることが多く、ストリーミング配信やサービスディスカバリなどで利用されます。しかし、マルチキャスト通信はネットワーク環境やOSの設定に依存する部分が多く、テストの安定性を確保するのが難しい側面があります。
元のテストコードでは、IPv4とIPv6の両方に対応するマルチキャストリスナーのテストが単一のテスト関数内で実行されていました。これにより、以下のような問題が発生していた可能性があります。
- 環境依存性: IPv4とIPv6のネットワーク設定が異なる環境では、一方のテストが成功してももう一方が失敗するといった問題が発生しやすくなります。特に、IPv6が完全にサポートされていない、または設定が不完全な環境では、IPv6関連のテストが不安定になる傾向があります。
- テストの複雑性: 単一のテスト関数内でIPv4とIPv6の両方を扱うことで、テストコードが複雑になり、デバッグやメンテナンスが困難になります。
- リソースの競合: マルチキャストグループへの参加やインターフェースのバインドといった操作は、システムのリソースを消費します。IPv4とIPv6のテストが同時に実行されることで、リソースの競合が発生し、テストの不安定化を招く可能性がありました。
- テストの再現性: テストが不安定であると、開発者がコード変更を行った際に、その変更が原因でテストが失敗したのか、それとも環境要因による一時的な失敗なのかを判断するのが難しくなります。これは開発効率を低下させ、バグの発見を遅らせる原因にもなります。
これらの問題を解決し、マルチキャスト機能の信頼性を高めるために、テストをIPv4とIPv6に明確に分割し、テスト環境の安定化を図る必要がありました。特に、マルチキャストインターフェースの明示的な割り当ては、テストが特定のネットワークインターフェースに依存しないようにし、より予測可能な結果を得るために重要です。
前提知識の解説
このコミットを理解するためには、以下のネットワークおよびGo言語に関する前提知識が必要です。
1. マルチキャスト通信
- ユニキャスト: 1対1の通信。特定の送信元から特定の宛先へデータを送信します。
- ブロードキャスト: 1対Nの通信。特定の送信元から同一ネットワーク内の全てのホストへデータを送信します。
- マルチキャスト: 1対多の通信。特定の送信元から特定のグループに属する複数のホストへデータを送信します。マルチキャストグループに参加しているホストのみがデータを受信します。IPマルチキャストでは、特定のIPアドレス(マルチキャストアドレス)がグループを識別するために使用されます。
2. IPv4とIPv6のマルチキャストアドレス
- IPv4マルチキャストアドレス:
224.0.0.0
から239.255.255.255
の範囲が予約されています。224.0.0.0/24
はローカルネットワーク制御ブロック(Link-Local Multicast)として予約されており、ルータを越えて転送されません。
- IPv6マルチキャストアドレス:
ff00::/8
の範囲が予約されています。IPv6マルチキャストアドレスは、スコープ(リンクローカル、サイトローカル、グローバルなど)やグループIDなどの情報を含みます。ff01::/16
: インターフェースローカルスコープff02::/16
: リンクローカルスコープff05::/16
: サイトローカルスコープff0e::/16
: グローバルスコープ
3. UDP (User Datagram Protocol)
マルチキャスト通信は通常、UDP上で実装されます。UDPはコネクションレス型のプロトコルであり、データの信頼性や順序保証は行いませんが、オーバーヘッドが少なく高速な通信が可能です。マルチキャストでは、送信元は特定のマルチキャストアドレスとポート番号にデータを送信し、受信側は同じマルチキャストアドレスとポート番号をリッスンすることでデータを受信します。
4. ネットワークインターフェース
コンピュータがネットワークに接続するための物理的または仮想的な装置です。各インターフェースにはIPアドレスが割り当てられ、データ送受信の経路となります。マルチキャスト通信では、どのインターフェースでマルチキャストグループに参加するか、またはどのインターフェースからマルチキャストパケットを送信するかが重要になります。
5. マルチキャストルーティング情報ベース (RIB: Routing Information Base)
RIBは、ルータやホストがネットワークのルーティング情報を格納するデータベースです。マルチキャストRIBは、特にマルチキャストルーティングに関する情報を管理し、マルチキャストグループのメンバーシップや、マルチキャストパケットの転送経路を決定するために使用されます。multicastRIBContains
関数は、特定のIPアドレスがマルチキャストRIBに含まれているか(つまり、そのIPアドレスのマルチキャストグループに参加しているか)を確認するために使用されます。
6. Go言語のnet
パッケージ
Go言語のnet
パッケージは、ネットワークI/Oのプリミティブを提供します。TCP/IP、UDP、IP、Unixドメインソケットなどのネットワークプロトコルをサポートし、ネットワーク接続の確立、データの送受信、アドレスの解決など、様々なネットワーク関連の機能を提供します。
net.ListenMulticastUDP
: 指定されたネットワークインターフェースとマルチキャストUDPアドレスでマルチキャストリスナーを確立します。net.UDPAddr
: UDPアドレス(IPアドレスとポート番号)を表す構造体です。net.Interface
: ネットワークインターフェースの情報を表す構造体です。net.Interfaces()
: システム上の全てのネットワークインターフェースのリストを返します。net.IP
: IPアドレスを表す型です。net.ParseIP
: 文字列からIPアドレスをパースします。net.IPv4
: IPv4アドレスを生成します。
7. テストフレームワーク (Go testing
パッケージ)
Go言語には標準でtesting
パッケージが用意されており、ユニットテストやベンチマークテストを記述できます。
testing.T
: テスト関数に渡される型で、テストの実行状態やエラー報告に使用されます。t.Fatalf()
: テストを失敗させ、すぐに終了します。t.Skipf()
: テストをスキップします。特定のOSやアーキテクチャでテストが実行できない場合などに使用されます。runtime.GOOS
,runtime.GOARCH
: 現在のOSとアーキテクチャを示す定数です。
技術的詳細
このコミットの技術的詳細は、主にGo言語のnet
パッケージにおけるマルチキャストリスナーのテストの改善に焦点を当てています。
1. テストファイルの分割
最も大きな変更点は、src/pkg/net/multicast_posix_test.go
が削除され、src/pkg/net/multicast_test.go
が新規作成されたことです。
multicast_posix_test.go
は、!plan9
ビルドタグを持つファイルであり、Plan 9以外のPOSIX互換システム向けのテストを含んでいました。このファイルはIPv4とIPv6のマルチキャストテストを混在させていました。- 新しく作成された
multicast_test.go
では、TestIPv4MulticastListener
とTestIPv6MulticastListener
という2つの独立したテスト関数が導入されました。これにより、IPv4とIPv6のテストが明確に分離され、それぞれのプロトコルに特化したテストロジックを記述できるようになりました。
2. テスト入力の冗長性排除
元のmulticastListenerTests
変数には、IPv4とIPv6のマルチキャストアドレスが混在しており、net
タイプ(udp
, udp4
, udp6
)とipv6
フラグによってテストの対象を切り替えていました。これはテストケースの定義を複雑にし、見通しを悪くしていました。
新しいテストファイルでは、ipv4MulticastListenerTests
とipv6MulticastListenerTests
という2つの独立したテストデータ構造が定義されました。
ipv4MulticastListenerTests
: IPv4マルチキャストアドレスのみを含みます。ipv6MulticastListenerTests
: IPv6マルチキャストアドレスのみを含みます。
これにより、テストケースの定義がより明確になり、各テスト関数が対象とするプロトコルに特化したデータのみを使用するようになりました。
3. マルチキャストインターフェースの明示的な割り当て
元のテストでは、availMulticastInterface
関数を使用して、FlagUp | FlagLoopback
などのフラグを持つ利用可能なマルチキャストインターフェースを検索していました。しかし、この方法ではシステムが自動的に選択するインターフェースに依存するため、テストの安定性が損なわれる可能性がありました。特に、複数のインターフェースが存在する場合や、ネットワーク設定が動的に変化する環境では、テストの再現性が低くなる原因となります。
新しいテストでは、loopbackInterface()
とnil
(システムによる自動選択)の両方を試すループが導入されました。
loopbackInterface()
: 明示的にループバックインターフェースを指定することで、テストが特定の物理インターフェースに依存せず、ローカル環境で安定して実行されることを保証します。ループバックインターフェースは、ネットワーク接続がなくても利用できるため、テスト環境の独立性を高めます。nil
: インターフェースを明示的に指定しない場合、システムが適切なマルチキャストインターフェースを自動的に選択します。これは、実際のアプリケーションがインターフェースを明示的に指定しない場合の挙動をテストするために重要です。ただし、コミットメッセージにもあるように、「システムによるマルチキャストインターフェースの割り当ては、通常、ネットワークとリンク層の両方の隣接性を含む適切なネクストホップを見つけるためのルーティングに依存するため、推奨されません」と注意書きがあります。これは、システムによる自動選択が不安定な結果をもたらす可能性があることを示唆しています。
この変更により、テストはより制御された環境で実行されるようになり、テストの安定性が向上しました。
4. checkMulticastListener
関数の改善
元のcheckMulticastListener
関数は、t *testing.T
を引数に取り、内部でt.Errorf
やt.Error
を呼び出してエラーを報告していました。これは、テスト関数内でエラーハンドリングを行う必要があり、コードの重複や複雑さを招く可能性がありました。
新しいcheckMulticastListener
関数は、error
を返すように変更されました。これにより、テスト関数側でエラーを適切に処理できるようになり、テストロジックがよりクリーンになりました。また、multicastRIBContains
関数もerror
を返すように変更され、より堅牢なエラーハンドリングが可能になりました。
5. multicastRIBContains
関数のプラットフォーム依存性
multicastRIBContains
関数は、特定のIPアドレスがマルチキャストRIBに含まれているかを確認する関数です。この関数は、netbsd
, openbsd
, plan9
, solaris
, windows
、およびlinux
のarm
またはalpha
アーキテクチャでは、常にtrue
を返すようにスキップされています。これは、これらのプラットフォームではマルチキャストRIBの情報を取得する機能がまだ実装されていないか、またはテスト環境の制約により正確なRIB情報を取得できないためと考えられます。このスキップにより、未実装のプラットフォームでのテスト失敗を防ぎつつ、実装されているプラットフォームでのみRIBのチェックを行うことができます。
6. closer
関数の導入
新しいテストでは、closer
という匿名関数が導入されました。これは、テスト中に開かれたUDPConn
インスタンスを確実にクローズするためのヘルパー関数です。これにより、リソースリークを防ぎ、テストのクリーンアップを簡素化しています。
これらの変更は、Go言語のnet
パッケージにおけるマルチキャスト機能のテストの信頼性と安定性を大幅に向上させることを目的としています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下の2つのファイルに集約されます。
-
src/pkg/net/multicast_posix_test.go
の削除- このファイルは完全に削除されました。元のマルチキャストリスナーテストのロジックとデータ定義が含まれていました。
-
src/pkg/net/multicast_test.go
の新規作成と内容の変更- このファイルが新しく作成され、IPv4とIPv6に分割されたマルチキャストリスナーテストが実装されました。
具体的な変更箇所は以下の通りです。
削除されたファイル: src/pkg/net/multicast_posix_test.go
--- a/src/pkg/net/multicast_posix_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2011 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.
-
-// +build !plan9
-
-package net
-
-import (
- "errors"
- "os"
- "runtime"
- "testing"
-)
-
-var multicastListenerTests = []struct {
- net string
- gaddr *UDPAddr
- flags Flags
- ipv6 bool // test with underlying AF_INET6 socket
-}{
- // cf. RFC 4727: Experimental Values in IPv4, IPv6, ICMPv4, ICMPv6, UDP, and TCP Headers
-
- {"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}, FlagUp | FlagLoopback, false},
- {"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}, 0, false},
- {"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}, 0, true},
-
- {"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}, FlagUp | FlagLoopback, false},
- {"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}, 0, false},
-
- {"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}, 0, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}, 0, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}, 0, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}, 0, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}, 0, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}, FlagUp | FlagLoopback, true},
- {"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}, 0, true},
-}
-
-// TestMulticastListener tests both single and double listen to a test
-// listener with same address family, same group address and same port.
-func TestMulticastListener(t *testing.T) {
- switch runtime.GOOS {
- case "netbsd", "openbsd", "plan9", "solaris", "windows":
- t.Skipf("skipping test on %q", runtime.GOOS)
- case "linux":
- if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" {
- t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
- }
- }
-
- for _, tt := range multicastListenerTests {
- if tt.ipv6 && (!*testIPv6 || !supportsIPv6 || os.Getuid() != 0) {
- continue
- }
- ifi, err := availMulticastInterface(t, tt.flags)
- if err != nil {
- continue
- }
- c1, err := ListenMulticastUDP(tt.net, ifi, tt.gaddr)
- if err != nil {
- t.Fatalf("First ListenMulticastUDP failed: %v", err)
- }
- checkMulticastListener(t, err, c1, tt.gaddr)
- c2, err := ListenMulticastUDP(tt.net, ifi, tt.gaddr)
- if err != nil {
- t.Fatalf("Second ListenMulticastUDP failed: %v", err)
- }
- checkMulticastListener(t, err, c2, tt.gaddr)
- c2.Close()
- c1.Close()
- }
-}
-
-func TestSimpleMulticastListener(t *testing.T) {
- switch runtime.GOOS {
- case "plan9":
- t.Skipf("skipping test on %q", runtime.GOOS)
- case "windows":
- if testing.Short() || !*testExternal {
- t.Skip("skipping test on windows to avoid firewall")
- }
- }
-
- for _, tt := range multicastListenerTests {
- if tt.ipv6 {
- continue
- }
- tt.flags = FlagUp | FlagMulticast // for windows testing
- ifi, err := availMulticastInterface(t, tt.flags)
- if err != nil {
- continue
- }
- c1, err := ListenMulticastUDP(tt.net, ifi, tt.gaddr)
- if err != nil {
- t.Fatalf("First ListenMulticastUDP failed: %v", err)
- }
- checkSimpleMulticastListener(t, err, c1, tt.gaddr)
- c2, err := ListenMulticastUDP(tt.net, ifi, tt.gaddr)
- if err != nil {
- t.Fatalf("Second ListenMulticastUDP failed: %v", err)
- }
- checkSimpleMulticastListener(t, err, c2, tt.gaddr)
- c2.Close()
- c1.Close()
- }
-}
-
-func checkMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) {
- if !multicastRIBContains(t, gaddr.IP) {
- t.Errorf("%q not found in RIB", gaddr.String())
- return
- }
- la := c.LocalAddr()
- if la == nil {
- t.Error("LocalAddr failed")
- return
- }
- if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
- t.Errorf("got %v; expected a proper address with non-zero port number", la)
- return
- }
-}
-
-func checkSimpleMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) {
- la := c.LocalAddr()
- if la == nil {
- t.Error("LocalAddr failed")
- return
- }
- if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
- t.Errorf("got %v; expected a proper address with non-zero port number", la)
- return
- }
-}
-
-func availMulticastInterface(t *testing.T, flags Flags) (*Interface, error) {
- var ifi *Interface
- if flags != Flags(0) {
- ift, err := Interfaces()
- if err != nil {
- t.Fatalf("Interfaces failed: %v", err)
- }
- for _, x := range ift {
- if x.Flags&flags == flags {
- ifi = &x
- break
- }
- }
- if ifi == nil {
- return nil, errors.New("an appropriate multicast interface not found")
- }
- }
- return ifi, nil
-}
-
-func multicastRIBContains(t *testing.T, ip IP) bool {
- ift, err := Interfaces()
- if err != nil {
- t.Fatalf("Interfaces failed: %v", err)
- }
- for _, ifi := range ift {
- ifmat, err := ifi.MulticastAddrs()
- if err != nil {
- t.Fatalf("MulticastAddrs failed: %v", err)
- }
- for _, ifma := range ifmat {
- if ifma.(*IPAddr).IP.Equal(ip) {
- return true
- }
- }
- }
- return false
-}
新規作成されたファイル: src/pkg/net/multicast_test.go
--- /dev/null
+++ b/src/pkg/net/multicast_test.go
@@ -0,0 +1,184 @@
+// Copyright 2011 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
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "testing"
+)
+
+var ipv4MulticastListenerTests = []struct {
+ net string
+ gaddr *UDPAddr // see RFC 4727
+}{
+ {"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},
+
+ {"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},
+}
+
+// TestIPv4MulticastListener tests both single and double listen to a
+// test listener with same address family, same group address and same
+// port.
+func TestIPv4MulticastListener(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("skipping test on %q", runtime.GOOS)
+ }
+
+ closer := func(cs []*UDPConn) {
+ for _, c := range cs {
+ if c != nil {
+ c.Close()
+ }
+ }
+ }
+
+ for _, ifi := range []*Interface{loopbackInterface(), nil} {
+ // Note that multicast interface assignment by system
+ // is not recommended because it usually relies on
+ // routing stuff for finding out an appropriate
+ // nexthop containing both network and link layer
+ // adjacencies.
+ if ifi == nil && !*testExternal {
+ continue
+ }
+ for _, tt := range ipv4MulticastListenerTests {
+ var err error
+ cs := make([]*UDPConn, 2)
+ if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
+ t.Fatalf("First ListenMulticastUDP on %v failed: %v", ifi, err)
+ }
+ if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil {
+ closer(cs)
+ t.Fatal(err)
+ }
+ if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
+ closer(cs)
+ t.Fatalf("Second ListenMulticastUDP on %v failed: %v", ifi, err)
+ }
+ if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil {
+ closer(cs)
+ t.Fatal(err)
+ }
+ closer(cs)
+ }
+ }
+}
+
+var ipv6MulticastListenerTests = []struct {
+ net string
+ gaddr *UDPAddr // see RFC 4727
+}{
+ {"udp", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}},
+ {"udp", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}},
+ {"udp", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}},
+ {"udp", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}},
+ {"udp", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}},
+ {"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}},
+
+ {"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}},
+ {"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}},
+ {"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}},
+ {"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}},
+ {"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}},
+ {"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}},
+}
+
+// TestIPv6MulticastListener tests both single and double listen to a
+// test listener with same address family, same group address and same
+// port.
+func TestIPv6MulticastListener(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9", "solaris", "windows":
+ t.Skipf("skipping test on %q", runtime.GOOS)
+ }
+ if !supportsIPv6 {
+ t.Skip("ipv6 is not supported")
+ }
+ if os.Getuid() != 0 {
+ t.Skip("skipping test; must be root")
+ }
+
+ closer := func(cs []*UDPConn) {
+ for _, c := range cs {
+ if c != nil {
+ c.Close()
+ }
+ }
+ }
+
+ for _, ifi := range []*Interface{loopbackInterface(), nil} {
+ // Note that multicast interface assignment by system
+ // is not recommended because it usually relies on
+ // routing stuff for finding out an appropriate
+ // nexthop containing both network and link layer
+ // adjacencies.
+ if ifi == nil && !*testExternal {
+ continue
+ }
+ for _, tt := range ipv6MulticastListenerTests {
+ var err error
+ cs := make([]*UDPConn, 2)
+ if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
+ t.Fatalf("First ListenMulticastUDP on %v failed: %v", ifi, err)
+ }
+ if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil {
+ closer(cs)
+ t.Fatal(err)
+ }
+ if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
+ closer(cs)
+ t.Fatalf("Second ListenMulticastUDP on %v failed: %v", ifi, err)
+ }
+ if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil {
+ closer(cs)
+ t.Fatal(err)
+ }
+ closer(cs)
+ }
+ }
+}
+
+func checkMulticastListener(c *UDPConn, ip IP) error {
+ if ok, err := multicastRIBContains(ip); err != nil {
+ return err
+ } else if !ok {
+ return fmt.Errorf("%q not found in multicast RIB", ip.String())
+ }
+ la := c.LocalAddr()
+ if la, ok := la.(*UDPAddr); !ok || la.Port == 0 {
+ return fmt.Errorf("got %v; expected a proper address with non-zero port number", la)
+ }
+ return nil
+}
+
+func multicastRIBContains(ip IP) (bool, error) {
+ switch runtime.GOOS {
+ case "netbsd", "openbsd", "plan9", "solaris", "windows":
+ return true, nil // not implemented yet
+ case "linux":
+ if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" {
+ return true, nil // not implemented yet
+ }
+ }
+ ift, err := Interfaces()
+ if err != nil {
+ return false, err
+ }
+ for _, ifi := range ift {
+ ifmat, err := ifi.MulticastAddrs()
+ if err != nil {
+ return false, err
+ }
+ for _, ifma := range ifmat {
+ if ifma.(*IPAddr).IP.Equal(ip) {
+ return true, nil
+ }
+ }
+ }
+ return false, nil
+}
コアとなるコードの解説
1. multicast_posix_test.go
の削除と multicast_test.go
の新規作成
この変更の最も根本的な部分は、既存のテストファイルmulticast_posix_test.go
を削除し、multicast_test.go
という新しいファイルを作成したことです。
multicast_posix_test.go
: このファイルは、+build !plan9
というビルドタグを持っていました。これは、Plan 9以外のPOSIX互換システム(Linux, macOSなど)でビルドされることを意味します。このファイルには、IPv4とIPv6のマルチキャストテストが混在しており、multicastListenerTests
という単一のテストデータセットを使用していました。multicast_test.go
: 新しいテストファイルでは、特定のOSに依存しないようにビルドタグが削除されました。これにより、より広範なプラットフォームでテストが実行されるようになります。また、このファイル内でIPv4とIPv6のテストが明確に分離されました。
2. テストデータ構造の分割
元のmulticastListenerTests
は、ipv6
というブール値フラグを使ってIPv4とIPv6のテストケースを区別していました。
var multicastListenerTests = []struct {
net string
gaddr *UDPAddr
flags Flags
ipv6 bool // test with underlying AF_INET6 socket
}{
// ... IPv4 and IPv6 test cases mixed ...
}
新しいテストファイルでは、これをipv4MulticastListenerTests
とipv6MulticastListenerTests
の2つの独立したデータ構造に分割しました。
var ipv4MulticastListenerTests = []struct {
net string
gaddr *UDPAddr // see RFC 4727
}{
{"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},
{"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},
}
var ipv6MulticastListenerTests = []struct {
net string
gaddr *UDPAddr // see RFC 4727
}{
{"udp", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}},
// ... other IPv6 test cases ...
{"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}},
}
この分割により、各テスト関数が対象とするIPバージョンに特化したテストデータのみを使用できるようになり、テストの意図がより明確になりました。
3. テスト関数の分割とインターフェースの明示的指定
元のテストファイルにはTestMulticastListener
とTestSimpleMulticastListener
という2つのテスト関数がありました。これらはmulticastListenerTests
をループし、ipv6
フラグに基づいて条件分岐していました。
新しいテストファイルでは、TestIPv4MulticastListener
とTestIPv6MulticastListener
という2つの独立したテスト関数が導入されました。
TestIPv4MulticastListener
func TestIPv4MulticastListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("skipping test on %q", runtime.GOOS)
}
closer := func(cs []*UDPConn) { /* ... */ } // ヘルパー関数
for _, ifi := range []*Interface{loopbackInterface(), nil} { // インターフェースのループ
// ...
for _, tt := range ipv4MulticastListenerTests { // IPv4テストデータのループ
// ... ListenMulticastUDP呼び出しとチェック ...
}
}
}
TestIPv6MulticastListener
func TestIPv6MulticastListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "solaris", "windows":
t.Skipf("skipping test on %q", runtime.GOOS)
}
if !supportsIPv6 {
t.Skip("ipv6 is not supported")
}
if os.Getuid() != 0 {
t.Skip("skipping test; must be root")
}
closer := func(cs []*UDPConn) { /* ... */ } // ヘルパー関数
for _, ifi := range []*Interface{loopbackInterface(), nil} { // インターフェースのループ
// ...
for _, tt := range ipv6MulticastListenerTests { // IPv6テストデータのループ
// ... ListenMulticastUDP呼び出しとチェック ...
}
}
}
両方のテスト関数で、for _, ifi := range []*Interface{loopbackInterface(), nil}
というループが導入されています。
loopbackInterface()
: システムのループバックインターフェースを明示的に取得します。これにより、ネットワーク接続に依存しない、安定したテスト環境を提供します。nil
: インターフェースを明示的に指定しない場合(システムが自動選択する場合)の挙動をテストします。ただし、コメントで「システムによるマルチキャストインターフェースの割り当ては推奨されない」と明記されており、不安定な結果をもたらす可能性があることが示唆されています。
このループにより、各テストケースがループバックインターフェースとシステム自動選択の両方でテストされるようになり、テストの網羅性と安定性が向上しました。
4. checkMulticastListener
関数の戻り値の変更
元のcheckMulticastListener
関数はt *testing.T
を引数に取り、内部でt.Errorf
を呼び出してエラーを報告していました。
func checkMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) {
if !multicastRIBContains(t, gaddr.IP) {
t.Errorf("%q not found in RIB", gaddr.String())
return
}
// ...
}
新しいcheckMulticastListener
関数は、エラーが発生した場合にerror
を返すように変更されました。
func checkMulticastListener(c *UDPConn, ip IP) error {
if ok, err := multicastRIBContains(ip); err != nil {
return err
} else if !ok {
return fmt.Errorf("%q not found in multicast RIB", ip.String())
}
la := c.LocalAddr()
if la, ok := la.(*UDPAddr); !ok || la.Port == 0 {
return fmt.Errorf("got %v; expected a proper address with non-zero port number", la)
}
return nil
}
これにより、テスト関数側でif err := checkMulticastListener(...)
のようにエラーを明示的にチェックし、t.Fatal(err)
でテストを終了させることができるようになり、テストロジックがよりクリーンで堅牢になりました。
5. multicastRIBContains
関数の改善とプラットフォーム依存性
multicastRIBContains
関数も、元のt *testing.T
を引数に取る形式から、error
を返す形式に変更されました。
func multicastRIBContains(ip IP) (bool, error) {
switch runtime.GOOS {
case "netbsd", "openbsd", "plan9", "solaris", "windows":
return true, nil // not implemented yet
case "linux":
if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" {
return true, nil // not implemented yet
}
}
// ... 実際のRIBチェックロジック ...
return false, nil
}
この関数は、特定のOSやアーキテクチャではマルチキャストRIBのチェックが実装されていないため、true, nil
を返すようにスキップされています。これにより、未実装のプラットフォームでのテスト失敗を防ぎつつ、実装されているプラットフォームでのみ正確なチェックが行われるようになります。
これらの変更は、Go言語のマルチキャストテストの安定性、可読性、および保守性を大幅に向上させるものです。
関連リンク
- Go Issue #4059: net: fix multicast listener tests
- Go CL 7565043: net: fix multicast listener tests
- Go
net
パッケージドキュメント: https://pkg.go.dev/net
参考にした情報源リンク
- RFC 4727: Experimental Values in IPv4, IPv6, ICMPv4, ICMPv6, UDP, and TCP Headers
- RFC 2365: Administratively Scoped IP Multicast
- RFC 4291: IP Version 6 Addressing Architecture
- Go言語
testing
パッケージ: https://pkg.go.dev/testing - Go言語
runtime
パッケージ: https://pkg.go.dev/runtime - マルチキャスト通信の基本: https://www.infraexpert.com/study/multicast.html (一般的なマルチキャストの概念理解のため)
- IPマルチキャストアドレス: https://ja.wikipedia.org/wiki/IP%E3%83%9E%E3%83%AB%E3%83%81%E3%82%AD%E3%83%A3%E3%82%B9%E3%83%88%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9 (IPv4/IPv6マルチキャストアドレスの理解のため)
- Go言語のテストに関する記事 (一般的なGoテストの書き方理解のため): https://go.dev/blog/testing
- Go言語のネットワークプログラミングに関する記事 (一般的なGoネットワークプログラミングの理解のため): https://go.dev/blog/go-and-the-network