[インデックス 15746] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージのテストコードにおける、WindowsおよびPlan 9ビルドの問題を修正するものです。具体的には、テストの初期化段階でUnix系OS専用の関数が呼び出されることによって発生していたビルドエラーを解消します。
コミット
commit 1fdb3e2ed66dda36f98937f9570d007dbc7300bc
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Mar 13 07:42:55 2013 -0700
net: fix windows and plan9 build
Don't call unix-only function in test init.
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/7778043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1fdb3e2ed66dda36f98937f9570d007dbc7300bc
元コミット内容
net: fix windows and plan9 build
Don't call unix-only function in test init.
このコミットは、net
パッケージのテストコードがWindowsおよびPlan 9環境でビルドできない問題を修正します。問題の原因は、テストの初期化時にUnix系OS専用の関数が呼び出されていたことにあります。
変更の背景
Go言語はクロスプラットフォーム対応を重視しており、単一のコードベースから様々なOS向けのバイナリを生成できることが大きな特徴です。しかし、特定のOSに依存する機能(例えばUnixドメインソケットなど)を使用する場合、その依存性を適切に管理しないと、非対応OSでのビルドエラーや実行時エラーが発生します。
このコミット以前のnet
パッケージのテストコードでは、connTests
やpacketConnTests
といったテストケースを定義する際に、Unixドメインソケットのアドレスを生成するtestUnixAddr()
関数が直接呼び出されていました。Go言語では、グローバル変数や構造体の初期化はプログラムの起動時、特にinit
関数が実行される前に行われます。testUnixAddr()
関数はUnix系OSのファイルシステムに一時ファイルを作成するなどの操作を行うため、WindowsやPlan 9のようなUnixドメインソケットをサポートしないOSでは、この初期化処理自体が失敗し、結果としてビルドエラーが発生していました。
開発者は、テストコードが特定のOSでのみ動作する関数を、そのOSがサポートされていない環境で初期化時に呼び出すことによって、クロスプラットフォームビルドが妨げられるという問題に直面していました。このコミットは、この問題を解決し、net
パッケージのテストがより広範なプラットフォームで実行可能になるようにするためのものです。
前提知識の解説
Go言語のnet
パッケージ
Go言語の標準ライブラリであるnet
パッケージは、ネットワークI/Oプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための機能が含まれています。ソケットの作成、接続、データの送受信、リスナーの構築などが可能です。
Unixドメインソケット (Unix Domain Sockets / UDS)
Unixドメインソケットは、同じホストマシン上のプロセス間通信(IPC)のためのメカニズムです。ネットワークソケット(TCP/IPなど)とは異なり、ファイルシステム上のパス名(例: /tmp/mysocket
)をアドレスとして使用します。これにより、ファイルシステム権限によるアクセス制御が可能になり、ネットワークスタックを介さないため、TCP/IPソケットよりも高速な通信が期待できます。ただし、Unix系OS(Linux, macOS, BSDなど)に特有の機能であり、Windowsではネイティブにはサポートされていません(WSLなどを介して利用できる場合を除く)。Plan 9もUnix系OSとは異なる設計思想を持つため、直接的な互換性はありません。
Go言語のinit
関数と変数初期化
Go言語では、パッケージの初期化は特別なinit
関数によって行われます。init
関数は、パッケージ内のすべての変数が宣言され、初期化された後に、main
関数が実行される前に自動的に呼び出されます。また、グローバル変数や構造体のフィールドの初期化式は、コンパイル時またはプログラム起動時に評価されます。
この挙動が今回の問題の根源でした。connTests
やpacketConnTests
のようなグローバルなテストデータ構造体に含まれるフィールドの初期化式が、プログラム起動時に評価されてしまい、その中に含まれるtestUnixAddr()
の呼び出しが、まだUnixドメインソケットが利用できない環境で実行されてしまったのです。
クロスプラットフォーム開発における課題
Go言語はビルドタグ(build tags)などを用いて、特定のOSやアーキテクチャに依存するコードを条件付きでコンパイルする仕組みを提供しています。しかし、今回のケースのように、テストデータ構造体の初期化式でOS依存の関数を直接呼び出すと、ビルドタグによる制御が及ばない初期化フェーズで問題が発生する可能性があります。
技術的詳細
このコミットの核心は、テストデータ構造体connTests
とpacketConnTests
のaddr
フィールド(およびaddr1
, addr2
フィールド)の型を変更することにあります。
変更前:
var connTests = []struct {
net string
addr string
}{
{"tcp", "127.0.0.1:0"},
{"unix", testUnixAddr()}, // ここでtestUnixAddr()が初期化時に呼び出される
{"unixpacket", testUnixAddr()}, // ここでtestUnixAddr()が初期化時に呼び出される
}
変更後:
var connTests = []struct {
net string
addr func() string // 型がstringからfunc() stringに変更
}{
{"tcp", func() string { return "127.0.0.1:0" }}, // リテラルも関数でラップ
{"unix", testUnixAddr}, // 関数そのものを代入(呼び出しはしない)
{"unixpacket", testUnixAddr}, // 関数そのものを代入(呼び出しはしない)
}
この変更により、testUnixAddr()
関数はテストデータ構造体の初期化時には呼び出されず、その代わりにfunc() string
型の関数リテラルまたは関数ポインタがフィールドに格納されます。実際にアドレスが必要になるのは、テストケースが実行され、Listen
やListenPacket
が呼び出される時点です。その時点で初めてtt.addr()
(またはtt.addr1()
, tt.addr2()
)が評価され、testUnixAddr()
が実行されます。
これにより、WindowsやPlan 9のようなUnixドメインソケットをサポートしない環境では、unix
やunixpacket
のテストケースがスキップされるか、またはListen
呼び出し時にエラーとなるだけで、プログラムの初期化段階でビルドが失敗することがなくなります。
また、packetconn_test.go
には、文字列リテラルをfunc() string
型に変換するためのヘルパー関数strfunc
が追加されています。
func strfunc(s string) func() string {
return func() string {
return s
}
}
これは、"127.0.0.1:0"
のような固定アドレスもfunc() string
型に合わせるために導入されました。
コアとなるコードの変更箇所
このコミットで変更されたファイルは以下の2つです。
src/pkg/net/conn_test.go
src/pkg/net/packetconn_test.go
src/pkg/net/conn_test.go
の変更点
connTests
構造体のaddr
フィールドの型がstring
からfunc() string
に変更されました。connTests
の初期化において、testUnixAddr()
の直接呼び出しがtestUnixAddr
(関数ポインタ)に変更されました。"127.0.0.1:0"
のような文字列リテラルもfunc() string { return "127.0.0.1:0" }
のように関数リテラルでラップされました。Listen
関数を呼び出す際に、tt.addr
が関数として呼び出されるようにtt.addr()
に変更されました。defer closer
の引数も、実際に評価されたaddr
変数を使用するように変更されました。
src/pkg/net/packetconn_test.go
の変更点
strfunc
ヘルパー関数が追加されました。packetConnTests
構造体のaddr1
とaddr2
フィールドの型がstring
からfunc() string
に変更されました。packetConnTests
の初期化において、testUnixAddr()
の直接呼び出しがtestUnixAddr
(関数ポインタ)に変更されました。"127.0.0.1:0"
や"127.0.0.1"
のような文字列リテラルもstrfunc
関数でラップされるように変更されました。ListenPacket
関数を呼び出す際に、tt.addr1
とtt.addr2
が関数として呼び出されるようにtt.addr1()
とtt.addr2()
に変更されました。defer closer
の引数も、実際に評価されたaddr1
とaddr2
変数を使用するように変更されました。
コアとなるコードの解説
src/pkg/net/conn_test.go
--- a/src/pkg/net/conn_test.go
+++ b/src/pkg/net/conn_test.go
@@ -16,11 +16,11 @@ import (
var connTests = []struct {
net string
- addr string
+ addr func() string // 変更点1: addrフィールドの型をfunc() stringに変更
}{
- {"tcp", "127.0.0.1:0"},
- {"unix", testUnixAddr()}, // 変更前: 初期化時に呼び出される
- {"unixpacket", testUnixAddr()}, // 変更前: 初期化時に呼び出される
+ {"tcp", func() string { return "127.0.0.1:0" }}, // 変更点2: 文字列リテラルも関数でラップ
+ {"unix", testUnixAddr}, // 変更点3: 関数そのものを代入(呼び出しを遅延)
+ {"unixpacket", testUnixAddr}, // 変更点3: 関数そのものを代入(呼び出しを遅延)
}
// someTimeout is used just to test that net.Conn implementations
@@ -41,12 +41,12 @@ func TestConnAndListener(t *testing.T) {
}
}
- ln, err := Listen(tt.net, tt.addr) // 変更前: tt.addrはstring
+ addr := tt.addr() // 変更点4: 関数を呼び出してアドレスを取得
+ ln, err := Listen(tt.net, addr) // 変更点5: 取得したアドレスを使用
if err != nil {
t.Fatalf("Listen failed: %v", err)
}
defer func(ln Listener, net, addr string) {
ln.Close()
switch net {
case "unix", "unixpacket":
- os.Remove(addr)
+ os.Remove(addr) // 変更点6: 取得したアドレスを使用
}
- }(ln, tt.net, tt.addr)
+ }(ln, tt.net, addr) // 変更点7: 取得したアドレスをdeferに渡す
ln.Addr()
done := make(chan int)
src/pkg/net/packetconn_test.go
--- a/src/pkg/net/packetconn_test.go
+++ b/src/pkg/net/packetconn_test.go
@@ -15,14 +15,20 @@ import (
"time"
)
+// 変更点1: 文字列をfunc() stringに変換するヘルパー関数
+func strfunc(s string) func() string {
+ return func() string {
+ return s
+ }
+}
+
var packetConnTests = []struct {
net string
- addr1 string
- addr2 string
+ addr1 func() string // 変更点2: addr1フィールドの型をfunc() stringに変更
+ addr2 func() string // 変更点3: addr2フィールドの型をfunc() stringに変更
}{
- {"udp", "127.0.0.1:0", "127.0.0.1:0"},
- {"ip:icmp", "127.0.0.1", "127.0.0.1"},
- {"unixgram", testUnixAddr(), testUnixAddr()},
+ {"udp", strfunc("127.0.0.1:0"), strfunc("127.0.0.1:0")}, // 変更点4: strfuncを使用
+ {"ip:icmp", strfunc("127.0.0.1"), strfunc("127.0.0.1")}, // 変更点5: strfuncを使用
+ {"unixgram", testUnixAddr, testUnixAddr}, // 変更点6: 関数そのものを代入
}
func TestPacketConn(t *testing.T) {
@@ -70,21 +76,22 @@ func TestPacketConn(t *testing.T) {
continue
}
- c1, err := ListenPacket(tt.net, tt.addr1) // 変更前: tt.addr1はstring
+ addr1, addr2 := tt.addr1(), tt.addr2() // 変更点7: 関数を呼び出してアドレスを取得
+ c1, err := ListenPacket(tt.net, addr1) // 変更点8: 取得したアドレスを使用
if err != nil {
t.Fatalf("ListenPacket failed: %v", err)
}
- defer closer(c1, netstr[0], tt.addr1, tt.addr2) // 変更前: tt.addr1, tt.addr2はstring
+ defer closer(c1, netstr[0], addr1, addr2) // 変更点9: 取得したアドレスを使用
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))
- c2, err := ListenPacket(tt.net, tt.addr2) // 変更前: tt.addr2はstring
+ c2, err := ListenPacket(tt.net, addr2) // 変更点10: 取得したアドレスを使用
if err != nil {
t.Fatalf("ListenPacket failed: %v", err)
}
- defer closer(c2, netstr[0], tt.addr1, tt.addr2) // 変更前: tt.addr1, tt.addr2はstring
+ defer closer(c2, netstr[0], addr1, addr2) // 変更点11: 取得したアドレスを使用
c2.LocalAddr()
c2.SetDeadline(time.Now().Add(100 * time.Millisecond))
c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
@@ -152,11 +159,12 @@ func TestConnAndPacketConn(t *testing.T) {
continue
}
- c1, err := ListenPacket(tt.net, tt.addr1) // 変更前: tt.addr1はstring
+ addr1, addr2 := tt.addr1(), tt.addr2() // 変更点12: 関数を呼び出してアドレスを取得
+ c1, err := ListenPacket(tt.net, addr1) // 変更点13: 取得したアドレスを使用
if err != nil {
t.Fatalf("ListenPacket failed: %v", err)
}
- defer closer(c1, netstr[0], tt.addr1, tt.addr2) // 変更前: tt.addr1, tt.addr2はstring
+ defer closer(c1, netstr[0], addr1, addr2) // 変更点14: 取得したアドレスを使用
c1.LocalAddr()
c1.SetDeadline(time.Now().Add(100 * time.Millisecond))
c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
これらの変更により、testUnixAddr()
のようなOS依存の関数が、そのOSがサポートされていない環境でテストデータ構造体の初期化時に呼び出されることがなくなりました。代わりに、アドレスが必要になった時点で初めて関数が実行されるため、クロスプラットフォームでのビルドとテストの互換性が向上しました。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os - Unixドメインソケットに関する一般的な情報: https://ja.wikipedia.org/wiki/Unix%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88
参考にした情報源リンク
- Go CL (Change List) 7778043: https://golang.org/cl/7778043
- Go言語の
init
関数に関する公式ドキュメント: https://go.dev/doc/effective_go#initialization - Go言語のビルドタグに関する公式ドキュメント: https://go.dev/pkg/go/build/#hdr-Build_Constraints