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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおけるテストファイルの再編成に関するものです。具体的には、既存のnet_test.goファイルに集約されていたテストコードの一部を、より専門的なdial_test.golookup_test.goに分割・移動することで、テストコードの構造を改善し、保守性と可読性を向上させています。

コミット

commit 705ebf1144b6326bd8298119dc1979e384c21c64
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri Feb 24 11:58:30 2012 +0900

    net: reorganize test files
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5694063

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

https://github.com/golang/go/commit/705ebf1144b6326bd8298119dc1979e384c21c64

元コミット内容

net: reorganize test files

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5694063

変更の背景

このコミットの主な背景は、Go言語のnetパッケージのテストコードの整理と構造化です。初期のGoプロジェクトでは、テストコードが単一の大きなファイル(net_test.goなど)に集約される傾向がありました。しかし、プロジェクトの規模が拡大し、機能が追加されるにつれて、単一ファイル内のテストコードが肥大化し、以下の問題が発生していました。

  • 可読性の低下: 関連性の低いテストケースが混在することで、特定の機能に関するテストを見つけにくくなる。
  • 保守性の低下: コードの変更が他のテストに意図しない影響を与えやすくなる。
  • 開発効率の低下: 開発者が特定のテストを探したり、新しいテストを追加したりする際に、ファイル全体を把握する必要があり、手間が増える。

これらの問題を解決するため、netパッケージのテストを機能ごとに分割し、より論理的なファイル構造に再編成する必要がありました。このコミットでは、特にネットワーク接続(Dial)と名前解決(lookup)に関するテストをそれぞれ専用のファイルに移動することで、テストコードのモジュール性を高め、将来的な拡張やメンテナンスを容易にすることを目指しています。

前提知識の解説

Go言語のテストフレームワーク (testingパッケージ)

Go言語には、標準ライブラリとしてtestingパッケージが提供されており、これを用いてユニットテストやベンチマークテストを記述します。

  • テストファイルの命名規則: テストファイルは通常、テスト対象のソースファイルと同じディレクトリに配置され、ファイル名の末尾に_test.goを付けます(例: net.goに対するnet_test.go)。
  • テスト関数の命名規則: テスト関数はTestで始まり、その後に続く名前の最初の文字は大文字である必要があります(例: func TestSomething(t *testing.T))。
  • *testing.T: テスト関数に渡される*testing.T型の引数は、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します(例: t.Error(), t.Errorf(), t.Log(), t.Logf())。
  • flagパッケージ: コマンドライン引数を解析するためのパッケージです。テストにおいて、特定のテストの実行を制御するためのフラグ(例: --run_error_test)を定義するのに使われます。
  • regexpパッケージ: 正規表現を扱うためのパッケージです。このコミットでは、エラーメッセージが特定のパターンに一致するかどうかを検証するために使用されています。

Go言語のnetパッケージ

netパッケージは、ネットワークI/Oの基本的なインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うための機能が含まれています。

  • Dial関数: ネットワークアドレスに接続するための汎用的な関数です。Dial(network, address string)の形式で呼び出され、指定されたネットワーク(例: "tcp", "udp", "unix")とアドレスに接続を試みます。
  • 名前解決 (Lookup): ドメイン名からIPアドレスを解決したり(DNSルックアップ)、IPアドレスからドメイン名を解決したり(逆引きルックアップ)する機能です。netパッケージには、これらの操作を行うための関数が含まれています。
  • DNSError: netパッケージで発生するDNS関連のエラーを表す型です。

ネットワークプロトコルとアドレス

  • TCP (Transmission Control Protocol): 信頼性の高いコネクション指向のプロトコル。"tcp", "tcp4" (IPv4), "tcp6" (IPv6) などのネットワークタイプで指定されます。アドレスは通常 ホスト:ポート の形式です(例: 127.0.0.1:80)。
  • UDP (User Datagram Protocol): コネクションレスのプロトコル。"udp", "udp4", "udp6" などのネットワークタイプで指定されます。
  • Unixドメインソケット (Unix Domain Socket): 同じホスト上のプロセス間通信に使用されるソケット。ファイルシステム上のパスで識別されます(例: /tmp/mysocket)。"unix", "unixgram", "unixpacket" などのネットワークタイプで指定されます。

IPアドレスの逆引き (Reverse DNS Lookup)

IPアドレスから対応するドメイン名を検索するプロセスです。IPv4アドレスの場合、in-addr.arpa.ドメインを使用し、IPアドレスのオクテットを逆順にした形式でクエリが発行されます(例: 1.2.3.44.3.2.1.in-addr.arpa.)。IPv6アドレスの場合はip6.arpa.ドメインを使用します。

技術的詳細

このコミットは、netパッケージのテストコードを以下の3つのファイルに再編成しています。

  1. src/pkg/net/net_test.go:

    • このファイルは、netパッケージの一般的なテストや、他の特定のカテゴリに属さないテストを保持する役割を担います。
    • 変更前はTestDialErrorTestReverseAddressという2つの主要なテスト関数とその関連データ構造(dialErrorTests, revAddrTests)が含まれていましたが、これらはそれぞれdial_test.golookup_test.goに移動されました。
    • 結果として、このファイルからはflagregexpパッケージのインポートが削除され、大幅にコードが削減されています。
  2. src/pkg/net/dial_test.go:

    • このファイルは、net.Dial関数および関連する接続確立エラーのテストに特化しています。
    • net_test.goからTestDialError関数と、そのテストデータであるdialErrorTestsスライス、そしてエラーパターンマッチングに使用するduplicateErrorPattern変数が移動されました。
    • flagパッケージのrunErrorTestフラグ(--run_error_testコマンドライン引数で制御)もこのファイルに移動され、DNSエラーチェックのテストをオプションで有効にできるようにしています。
    • regexpパッケージがインポートされ、エラーメッセージの正規表現マッチングに使用されます。
    • dialErrorTestsは、datakitのような未知のネットワークタイプ、不正なポート番号()、存在しないドメイン名、不正なドメイン名、Unixドメインソケットのパスに関する様々なエラーケースを網羅しています。これにより、Dial関数が予期されるエラーを正しく返すことを検証します。
  3. src/pkg/net/lookup_test.go:

    • このファイルは、IPアドレスの名前解決(ルックアップ)に関するテストに特化しています。
    • net_test.goからTestReverseAddress関数と、そのテストデータであるrevAddrTestsスライスが移動されました。
    • revAddrTestsは、IPv4およびIPv6アドレスの逆引き変換が正しく行われるか、また不正なアドレスが与えられた場合に適切なエラーが返されるかを検証します。特に、in-addr.arpa.ip6.arpa.形式への変換ロジックがテストされています。

この再編成により、各テストファイルが特定の機能領域に焦点を当てるようになり、テストコードの構造がより明確になりました。これにより、開発者は特定のネットワーク機能のテストを探しやすくなり、新しいテストを追加する際にも適切な場所を判断しやすくなります。

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

src/pkg/net/dial_test.go

追加されたコード:

import (
	"flag" // 追加
	"regexp" // 追加
	"runtime"
	"testing"
	"time"
)

var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors") // 追加

type DialErrorTest struct { // 追加
	Net     string
	Raddr   string
	Pattern string
}

var dialErrorTests = []DialErrorTest{ // 追加
	{
		"datakit", "mh/astro/r70",
		"dial datakit mh/astro/r70: unknown network datakit",
	},
	{
		"tcp", "127.0.0.1:☺",
		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
	},
	{
		"tcp", "no-such-name.google.com.:80",
		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name.no-such-top-level-domain.:80",
		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name:80",
		"`dial tcp no-such-name:80: lookup no-such-name\\.(.*\\.)?( on .*)?: no (.*)`",
	},
	{
		"tcp", "mh/astro/r70:http",
		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
	},
	{
		"unix", "/etc/file-not-found",
		"dial unix /etc/file-not-found: no such file or directory",
	},
	{
		"unix", "/etc/",
		"dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
	{
		"unixpacket", "/etc/file-not-found",
		"dial unixpacket /etc/file-not-found: no such file or directory",
	},
	{
		"unixpacket", "/etc/",
		"dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
}

var duplicateErrorPattern = `dial (.*) dial (.*)` // 追加

func TestDialError(t *testing.T) { // 追加
	if !*runErrorTest {
		t.Logf("test disabled; use --run_error_test to enable")
		return
	}
	for i, tt := range dialErrorTests {
		c, err := Dial(tt.Net, tt.Raddr)
		if c != nil {
			c.Close()
		}
		if err == nil {
			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern)
			continue
		}
		s := err.Error()
		match, _ := regexp.MatchString(tt.Pattern, s)
		if !match {
			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern)
		}
		match, _ = regexp.MatchString(duplicateErrorPattern, s)
		if match {
			t.Errorf("#%d: %q, duplicate error return from Dial", i, s)
		}
	}
}

src/pkg/net/lookup_test.go

追加されたコード:

var revAddrTests = []struct { // 追加
	Addr      string
	Reverse   string
	ErrPrefix string
}{
	{"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""},
	{"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""},
	{"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""},
	{"::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", ""},
	{"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""},
	{"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
	{"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
	{"1.2.3", "", "unrecognized address"},
	{"1.2.3.4.5", "", "unrecognized address"},
	{"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
	{"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
}

func TestReverseAddress(t *testing.T) { // 追加
	for i, tt := range revAddrTests {
		a, err := reverseaddr(tt.Addr)
		if len(tt.ErrPrefix) > 0 && err == nil {
			t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix)
			continue
		}
		if len(tt.ErrPrefix) == 0 && err != nil {
			t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
		}
		if err != nil && err.(*DNSError).Err != tt.ErrPrefix {
			t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err)
		}
		if a != tt.Reverse {
			t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
		}
	}
}

src/pkg/net/net_test.go

削除されたコード:

-var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors")
-
-type DialErrorTest struct {
-	Net     string
-	Raddr   string
-	Pattern string
-}
-
-var dialErrorTests = []DialErrorTest{
-	{
-		"datakit", "mh/astro/r70",
-		"dial datakit mh/astro/r70: unknown network datakit",
-	},
-	{
-		"tcp", "127.0.0.1:☺",
-		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
-	},
-	{
-		"tcp", "no-such-name.google.com.:80",
-		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
-	},
-	{
-		"tcp", "no-such-name.no-such-top-level-domain.:80",
-		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
-	},
-	{
-		"tcp", "no-such-name:80",
-		`dial tcp no-such-name:80: lookup no-such-name\.(.*\\.)?( on .*)?: no (.*)`,
-	},
-	{
-		"tcp", "mh/astro/r70:http",
-		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
-	},
-	{
-		"unix", "/etc/file-not-found",
-		"dial unix /etc/file-not-found: no such file or directory",
-	},
-	{
-		"unix", "/etc/",
-		"dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)",
-	},
-	{
-		"unixpacket", "/etc/file-not-found",
-		"dial unixpacket /etc/file-not-found: no such file or directory",
-	},
-	{
-		"unixpacket", "/etc/",
-		"dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)",
-	},
-}
-
-var duplicateErrorPattern = `dial (.*) dial (.*)`
-
-func TestDialError(t *testing.T) {
-	if !*runErrorTest {
-		t.Logf("test disabled; use --run_error_test to enable")
-		return
-	}
-	for i, tt := range dialErrorTests {
-		c, err := Dial(tt.Net, tt.Raddr)
-		if c != nil {
-			c.Close()
-		}
-		if err == nil {
-			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern)
-			continue
-		}
-		s := err.Error()
-		match, _ := regexp.MatchString(tt.Pattern, s)
-		if !match {
-			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern)
-		}
-		match, _ = regexp.MatchString(duplicateErrorPattern, s)
-		if match {
-			t.Errorf("#%d: %q, duplicate error return from Dial", i, s)
-		}
-	}
-}
-
-var revAddrTests = []struct {
-	Addr      string
-	Reverse   string
-	ErrPrefix string
-}{
-	{"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""},
-	{"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""},
-	{"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""},
-	{"::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", ""},
-	{"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""},
-	{"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
-	{"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
-	{"1.2.3", "", "unrecognized address"},
-	{"1.2.3.4.5", "", "unrecognized address"},
-	{"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
-	{"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
-}
-
-func TestReverseAddress(t *testing.T) {
-	for i, tt := range revAddrTests {
-		a, err := reverseaddr(tt.Addr)
-		if len(tt.ErrPrefix) > 0 && err == nil {
-			t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix)
-			continue
-		}
-		if len(tt.ErrPrefix) == 0 && err != nil {
-			t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
-		}
-		if err != nil && err.(*DNSError).Err != tt.ErrPrefix {
-			t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err)
-		}
-		if a != tt.Reverse {
-			t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
-		}
-	}
-}

コアとなるコードの解説

dial_test.goTestDialError

このテスト関数は、net.Dial関数が様々な不正な入力に対して適切にエラーを返すことを検証します。

  • runErrorTestフラグ: flag.Boolで定義されたrunErrorTestは、コマンドライン引数--run_error_testが指定された場合にのみテストを実行するように制御します。これは、DNSルックアップエラーなど、外部ネットワーク環境に依存する可能性のあるテストを、通常時にはスキップできるようにするための一般的なプラクティスです。
  • DialErrorTest構造体とdialErrorTestsスライス:
    • DialErrorTestは、テストケースの入力(NetRaddr)と期待されるエラーメッセージの正規表現パターン(Pattern)を定義します。
    • dialErrorTestsは、具体的なテストケースの集合です。これには、未知のネットワークタイプ、不正なポート、存在しないドメイン名、不正なドメイン名、Unixドメインソケットのファイルパスに関するエラーケースが含まれています。
  • テストロジック:
    • 各テストケースについてDial関数を呼び出し、返されたエラーがnilでないことを確認します。
    • エラーがnilの場合、テストは失敗とマークされます。
    • エラーメッセージの文字列表現(err.Error())が、regexp.MatchStringを使って期待されるPatternに一致するかどうかを検証します。これにより、エラーメッセージの内容が正しいことを確認します。
    • duplicateErrorPatternは、エラーメッセージが重複して表示されていないか(例: "dial tcp dial tcp...")をチェックするための正規表現です。これは、エラー処理ロジックのバグを防ぐための追加の検証です。

lookup_test.goTestReverseAddress

このテスト関数は、IPアドレスの逆引き変換(reverseaddr関数、おそらく内部関数)が正しく機能するかを検証します。

  • revAddrTestsスライス:
    • 各要素は、入力IPアドレス(Addr)、期待される逆引きアドレス(Reverse)、および期待されるエラーメッセージのプレフィックス(ErrPrefix)を定義します。
    • テストケースには、有効なIPv4/IPv6アドレスの逆引き変換と、不正な形式のIPアドレスが与えられた場合にunrecognized addressエラーが返されるケースが含まれています。
  • テストロジック:
    • 各テストケースについてreverseaddr関数を呼び出し、返された逆引きアドレスとエラーを検証します。
    • ErrPrefixが設定されている場合(エラーが期待される場合)、エラーがnilでないこと、およびエラーメッセージが期待されるプレフィックスと一致することを確認します。
    • ErrPrefixが設定されていない場合(エラーが期待されない場合)、エラーがnilであることを確認します。
    • 最終的に、返された逆引きアドレスが期待されるReverse値と一致するかどうかを検証します。

これらのテストは、netパッケージの堅牢性と正確性を保証するために不可欠であり、テストファイルの再編成によって、これらの重要なテストがより見つけやすく、管理しやすくなりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なネットワークプロトコルに関する知識 (TCP/IP, UDP, Unix Domain Sockets, DNS)
  • 正規表現に関する一般的な知識