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

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

このコミットは、Go言語のnetパッケージにおけるDNS設定の読み込みに関するテストの追加と、それに伴う既存コードの修正を扱っています。具体的には、dnsconfig_unix.goで定義されているDNS設定読み込み関数dnsReadConfigが、設定ファイルパスを引数として受け取るように変更され、それに対応するテストがdnsconfig_unix_test.goとして新規追加されています。また、テスト用のresolv.confサンプルファイルもtestdata/resolv.confとして追加されています。

コミット

commit b3f38b463023e149f2906b85c2988854d10bcab4
Author: Anfernee Yongkun Gui <anfernee.gui@gmail.com>
Date:   Wed Dec 18 08:26:36 2013 -0800

    net: test dnsconfig_unix with sample resolv.conf
    
    R=golang-dev, adg, bradfitz, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/21580043
---
 src/pkg/net/dnsclient_unix.go      |  3 ++-
 src/pkg/net/dnsconfig_unix.go      |  5 ++--
 src/pkg/net/dnsconfig_unix_test.go | 48 ++++++++++++++++++++++++++++++++++++++
 src/pkg/net/testdata/resolv.conf   |  5 ++++\n 4 files changed, 57 insertions(+), 4 deletions(-)

diff --git a/src/pkg/net/dnsclient_unix.go b/src/pkg/net/dnsclient_unix.go
index 16cf420dcd..a30c9a73d7 100644
--- a/src/pkg/net/dnsclient_unix.go
+++ b/src/pkg/net/dnsclient_unix.go
@@ -159,7 +159,8 @@ func convertRR_AAAA(records []dnsRR) []IP {
 var cfg *dnsConfig
 var dnserr error
 
-func loadConfig() { cfg, dnserr = dnsReadConfig() }\n+// Assume dns config file is /etc/resolv.conf here
+func loadConfig() { cfg, dnserr = dnsReadConfig("/etc/resolv.conf") }\n 
 var onceLoadConfig sync.Once
 
 diff --git a/src/pkg/net/dnsconfig_unix.go b/src/pkg/net/dnsconfig_unix.go
index d10f099b12..7856ebc80d 100644
--- a/src/pkg/net/dnsconfig_unix.go
+++ b/src/pkg/net/dnsconfig_unix.go
@@ -20,9 +20,8 @@ type dnsConfig struct {
 // See resolv.conf(5) on a Linux machine.
 // TODO(rsc): Supposed to call uname() and chop the beginning
 // of the host name to get the default search domain.
-// We assume it's in resolv.conf anyway.\n-func dnsReadConfig() (*dnsConfig, error) {\n-\tfile, err := open("/etc/resolv.conf")\n+func dnsReadConfig(filename string) (*dnsConfig, error) {\n+\tfile, err := open(filename)\n  if err != nil {
  		return nil, &DNSConfigError{err}
  	}\ndiff --git a/src/pkg/net/dnsconfig_unix_test.go b/src/pkg/net/dnsconfig_unix_test.go
new file mode 100644
index 0000000000..b24291c1c2
--- /dev/null
+++ b/src/pkg/net/dnsconfig_unix_test.go
@@ -0,0 +1,48 @@
+// Copyright 2013 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 darwin dragonfly freebsd linux netbsd openbsd
+
+package net
+
+import (
+	"testing"
+)
+
+func TestReadConfig(t *testing.T) {
+	dnsConfig, err := dnsReadConfig("testdata/resolv.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(dnsConfig.servers) != 1 {
+		t.Errorf("len(dnsConfig.servers) = %d; want %d", len(dnsConfig.servers), 1)
+	}
+	if dnsConfig.servers[0] != "[192.168.1.1]" {
+		t.Errorf("dnsConfig.servers[0] = %s; want %s", dnsConfig.servers[0], "[192.168.1.1]")
+	}
+
+	if len(dnsConfig.search) != 1 {
+		t.Errorf("len(dnsConfig.search) = %d; want %d", len(dnsConfig.search), 1)
+	}
+	if dnsConfig.search[0] != "Home" {
+		t.Errorf("dnsConfig.search[0] = %s; want %s", dnsConfig.search[0], "Home")
+	}
+
+	if dnsConfig.ndots != 5 {
+		t.Errorf("dnsConfig.ndots = %d; want %d", dnsConfig.ndots, 5)
+	}
+
+	if dnsConfig.timeout != 10 {
+		t.Errorf("dnsConfig.timeout = %d; want %d", dnsConfig.timeout, 10)
+	}
+
+	if dnsConfig.attempts != 3 {
+		t.Errorf("dnsConfig.attempts = %d; want %d", dnsConfig.attempts, 3)
+	}
+
+	if dnsConfig.rotate != true {
+		t.Errorf("dnsConfig.rotate = %t; want %t", dnsConfig.rotate, true)
+	}
+}
diff --git a/src/pkg/net/testdata/resolv.conf b/src/pkg/net/testdata/resolv.conf
new file mode 100644
index 0000000000..b5972e09c9
--- /dev/null
+++ b/src/pkg/net/testdata/resolv.conf
@@ -0,0 +1,5 @@
+# /etc/resolv.conf
+
+domain Home
+nameserver 192.168.1.1
+options ndots:5 timeout:10 attempts:3 rotate

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

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

元コミット内容

このコミットの目的は、Go言語のnetパッケージにおけるDNS設定の読み込みロジック、特にUnix系システムにおけるresolv.confファイルのパース処理に対して、より堅牢なテストカバレッジを提供することです。

具体的には、以下の変更が行われています。

  1. dnsReadConfig関数の引数化: 以前は/etc/resolv.confという固定パスからDNS設定を読み込んでいたdnsReadConfig関数が、ファイルパスを引数として受け取るように変更されました。これにより、テスト時に任意のresolv.confファイルを指定して、そのパース結果を検証することが可能になります。
  2. loadConfig関数の修正: dnsReadConfigの変更に伴い、loadConfig関数も/etc/resolv.confを明示的に指定してdnsReadConfigを呼び出すように修正されました。これにより、本番環境での動作は維持されます。
  3. 新規テストファイルの追加: src/pkg/net/dnsconfig_unix_test.goが新規作成され、dnsReadConfig関数のテストケースが追加されました。このテストは、特定のresolv.confファイル(testdata/resolv.conf)を読み込み、その内容が正しくパースされているかを確認します。
  4. テスト用resolv.confの追加: src/pkg/net/testdata/resolv.confが新規作成されました。このファイルは、domain, nameserver, optionsといった一般的なresolv.confのエントリを含むサンプルデータであり、テストの入力として使用されます。

これらの変更により、Goのnetパッケージがresolv.confファイルをどのように解釈し、DNS設定を構築するかについて、より詳細なテストが可能になり、将来的なバグの混入を防ぐことに貢献します。

変更の背景

このコミットの背景には、Go言語のネットワークスタック、特にDNSリゾルバの堅牢性を向上させるという目的があります。resolv.confファイルはUnix系システムにおけるDNSクライアントの設定を司る重要なファイルであり、そのパースロジックにバグがあると、DNSルックアップが失敗したり、予期せぬ動作を引き起こしたりする可能性があります。

以前のdnsReadConfig関数は、/etc/resolv.confという固定パスから設定を読み込んでいました。これは実際の運用環境では問題ありませんが、テストの観点からは柔軟性に欠けていました。特定のresolv.confのパターン(例えば、異なるoptions設定や複数のnameserverエントリなど)に対するパースの正確性を検証するためには、テストごとに/etc/resolv.confを書き換える必要があり、これは非効率的で、テスト環境の汚染にもつながります。

このコミットは、dnsReadConfig関数を引数化することで、テスト時に任意のresolv.confファイルを指定できるようにし、これにより、様々なresolv.confのシナリオに対する網羅的なテストを容易にすることを目的としています。これにより、GoのDNSリゾルバが多様なシステム環境下で正しく動作することを保証し、信頼性を向上させることが可能になります。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1. DNS (Domain Name System)

DNSは、インターネット上のコンピュータやサービスを識別するための階層的な分散型命名システムです。人間が覚えやすいドメイン名(例: google.com)を、コンピュータが理解できるIPアドレス(例: 172.217.160.142)に変換する役割を担っています。

2. resolv.confファイル

Unix系オペレーティングシステム(Linux, macOS, BSDなど)において、DNSクライアント(リゾルバ)の設定を記述するファイルです。通常、/etc/resolv.confに配置されます。このファイルには、主に以下の情報が記述されます。

  • nameserver: DNSクエリを送信するDNSサーバーのIPアドレスを指定します。複数指定することも可能です。
  • domain: ローカルドメイン名を指定します。ホスト名が完全修飾ドメイン名(FQDN)でない場合に、このドメイン名が自動的に付加されます。
  • search: ドメイン名の検索リストを指定します。ホスト名がFQDNでない場合に、このリストに指定されたドメイン名が順番に付加されてDNSルックアップが試行されます。
  • options: リゾルバの動作に関する様々なオプションを指定します。例えば、
    • ndots: ドメイン名にピリオドがいくつ含まれていればFQDNとみなすか(それ以下の場合、searchリストが適用される)。
    • timeout: DNSクエリのタイムアウト時間(秒)。
    • attempts: DNSクエリの再試行回数。
    • rotate: 複数のnameserverが指定されている場合に、ラウンドロビン方式でDNSサーバーを使用するかどうか。

3. Go言語のnetパッケージ

Go言語の標準ライブラリであるnetパッケージは、ネットワークI/Oのプリミティブを提供します。これには、TCP/UDPソケット、IPアドレスの操作、DNSルックアップなどが含まれます。このコミットで変更されているのは、netパッケージ内のDNSリゾルバの実装の一部です。

4. ユニットテスト

ソフトウェア開発において、個々のプログラムの部品(関数やメソッドなど)が正しく動作するかを検証するテスト手法です。このコミットでは、dnsReadConfig関数がresolv.confファイルを正しくパースできるかを検証するためのユニットテストが追加されています。テストの際には、実際のシステムファイルではなく、テスト用に用意されたサンプルファイルを使用することで、テストの独立性と再現性を高めます。

5. sync.Once

Go言語のsyncパッケージに含まれる型で、特定の処理が一度だけ実行されることを保証するために使用されます。このコミットでは、loadConfig関数が一度だけ実行されるようにsync.Onceが使用されています。これは、DNS設定の読み込みがアプリケーションのライフサイクル中に一度だけ行われるべき処理であるためです。

技術的詳細

このコミットにおける技術的な変更点は、主にdnsReadConfig関数のインターフェース変更と、それに対応するテストの追加に集約されます。

dnsReadConfig関数の変更

変更前:

func dnsReadConfig() (*dnsConfig, error) {
	file, err := open("/etc/resolv.conf")
	// ...
}

変更後:

func dnsReadConfig(filename string) (*dnsConfig, error) {
	file, err := open(filename)
	// ...
}

この変更により、dnsReadConfig関数はハードコードされていた/etc/resolv.confのパスではなく、filenameという引数を通じて任意のファイルパスを受け取るようになりました。これにより、以下のメリットが生まれます。

  • テスト容易性の向上: テストコードから、実際の/etc/resolv.confファイルを変更することなく、様々な内容のresolv.confファイルを指定してdnsReadConfigの動作を検証できるようになります。これは、モックやスタブを使用せずに、実際のファイルI/Oを伴うテストを可能にする点で重要です。
  • 柔軟性の向上: 将来的に、resolv.conf以外の場所からDNS設定を読み込む必要がある場合や、複数のDNS設定ファイルを切り替えて使用するようなシナリオが発生した場合にも、この関数を再利用しやすくなります。

loadConfig関数の変更

変更前:

func loadConfig() { cfg, dnserr = dnsReadConfig() }

変更後:

// Assume dns config file is /etc/resolv.conf here
func loadConfig() { cfg, dnserr = dnsReadConfig("/etc/resolv.conf") }

dnsReadConfigのインターフェース変更に伴い、loadConfig関数も修正されました。loadConfigは、GoのnetパッケージがDNS設定を初期化する際に一度だけ呼び出される関数です。この関数内では、引き続き本番環境での標準的なresolv.confのパスである/etc/resolv.confが明示的に指定されています。これにより、dnsReadConfigの引数化が、既存のアプリケーションの動作に影響を与えないように配慮されています。

dnsconfig_unix_test.goの新規追加

このコミットの最も重要な部分の一つは、dnsconfig_unix_test.goという新しいテストファイルの追加です。このファイルには、TestReadConfigというテスト関数が含まれています。

func TestReadConfig(t *testing.T) {
	dnsConfig, err := dnsReadConfig("testdata/resolv.conf")
	if err != nil {
		t.Fatal(err)
	}

	// 各フィールドの検証
	if len(dnsConfig.servers) != 1 {
		t.Errorf("len(dnsConfig.servers) = %d; want %d", len(dnsConfig.servers), 1)
	}
	if dnsConfig.servers[0] != "[192.168.1.1]" {
		t.Errorf("dnsConfig.servers[0] = %s; want %s", dnsConfig.servers[0], "[192.168.1.1]")
	}
	// ... (他のフィールドの検証)
}

このテストは、以下の手順でdnsReadConfigの動作を検証します。

  1. dnsReadConfig("testdata/resolv.conf")を呼び出し、テスト用のresolv.confファイルを読み込みます。
  2. エラーが発生した場合は、テストを失敗させます。
  3. 返されたdnsConfig構造体の各フィールド(servers, search, ndots, timeout, attempts, rotate)が、testdata/resolv.confの内容と一致するかをアサートします。

このテストにより、dnsReadConfig関数がresolv.confの様々なエントリを正しくパースし、対応するdnsConfig構造体のフィールドにマッピングできることが保証されます。

testdata/resolv.confの新規追加

テストの入力として使用されるtestdata/resolv.confファイルは、以下の内容を含んでいます。

# /etc/resolv.conf

domain Home
nameserver 192.168.1.1
options ndots:5 timeout:10 attempts:3 rotate

このファイルは、resolv.confで一般的に使用されるdomain, nameserver, optionsディレクティブを含んでおり、dnsReadConfigがこれらの設定を正しく解釈できるかを検証するための具体的なシナリオを提供します。

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

このコミットにおけるコアとなるコードの変更箇所は以下の3つのファイルです。

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

    • loadConfig関数のシグネチャが変更され、dnsReadConfigを呼び出す際に/etc/resolv.confという固定パスを引数として渡すように修正されました。
  2. src/pkg/net/dnsconfig_unix.go:

    • dnsReadConfig関数のシグネチャが変更され、引数としてfilename stringを受け取るようになりました。これにより、読み込むresolv.confファイルのパスを外部から指定できるようになりました。
    • open関数に渡すファイルパスが、ハードコードされた/etc/resolv.confから引数filenameに変更されました。
  3. src/pkg/net/dnsconfig_unix_test.go:

    • 新規作成されたファイルです。
    • TestReadConfig関数が定義され、testdata/resolv.confを読み込み、そのパース結果を検証するユニットテストが含まれています。
  4. src/pkg/net/testdata/resolv.conf:

    • 新規作成されたファイルです。
    • TestReadConfigで使用されるサンプルresolv.confファイルの内容が記述されています。

コアとなるコードの解説

src/pkg/net/dnsconfig_unix.goにおけるdnsReadConfigの変更

// 変更前:
// func dnsReadConfig() (*dnsConfig, error) {
// 	file, err := open("/etc/resolv.conf")

// 変更後:
func dnsReadConfig(filename string) (*dnsConfig, error) {
	file, err := open(filename)
	if err != nil {
		return nil, &DNSConfigError{err}
	}
	// ... (以降のパースロジックは変更なし)
}

この変更は、dnsReadConfig関数の汎用性を高めるためのものです。以前は常に/etc/resolv.confを読み込んでいましたが、filename引数を導入することで、任意のパスにあるresolv.confファイルを読み込めるようになりました。これは、特にテストにおいて、様々なresolv.confのシナリオをシミュレートするために不可欠な変更です。

src/pkg/net/dnsclient_unix.goにおけるloadConfigの変更

// 変更前:
// func loadConfig() { cfg, dnserr = dnsReadConfig() }

// 変更後:
// Assume dns config file is /etc/resolv.conf here
func loadConfig() { cfg, dnserr = dnsReadConfig("/etc/resolv.conf") }

dnsReadConfigのインターフェース変更に伴い、loadConfig関数も更新されました。loadConfigは、GoのネットワークスタックがDNS設定を初期化する際に一度だけ呼び出される関数です。この変更により、本番環境では引き続き/etc/resolv.confがデフォルトのDNS設定ファイルとして使用されることが保証されます。コメントで「Assume dns config file is /etc/resolv.conf here」と明記されているのは、この関数の役割が/etc/resolv.confを読み込むことであることを示しています。

src/pkg/net/dnsconfig_unix_test.goの新規追加とTestReadConfig

// +build darwin dragonfly freebsd linux netbsd openbsd

package net

import (
	"testing"
)

func TestReadConfig(t *testing.T) {
	dnsConfig, err := dnsReadConfig("testdata/resolv.conf")
	if err != nil {
		t.Fatal(err)
	}

	// 以下、dnsConfigの各フィールドが期待値と一致するかを検証
	if len(dnsConfig.servers) != 1 {
		t.Errorf("len(dnsConfig.servers) = %d; want %d", len(dnsConfig.servers), 1)
	}
	if dnsConfig.servers[0] != "[192.168.1.1]" {
		t.Errorf("dnsConfig.servers[0] = %s; want %s", dnsConfig.servers[0], "[192.168.1.1]")
	}

	if len(dnsConfig.search) != 1 {
		t.Errorf("len(dnsConfig.search) = %d; want %d", len(dnsConfig.search), 1)
	}
	if dnsConfig.search[0] != "Home" {
		t.Errorf("dnsConfig.search[0] = %s; want %s", dnsConfig.search[0], "Home")
	}

	if dnsConfig.ndots != 5 {
		t.Errorf("dnsConfig.ndots = %d; want %d", dnsConfig.ndots, 5)
	}

	if dnsConfig.timeout != 10 {
		t.Errorf("dnsConfig.timeout = %d; want %d", dnsConfig.timeout, 10)
	}

	if dnsConfig.attempts != 3 {
		t.Errorf("dnsConfig.attempts = %d; want %d", dnsConfig.attempts, 3)
	}

	if dnsConfig.rotate != true {
		t.Errorf("dnsConfig.rotate = %t; want %t", dnsConfig.rotate, true)
	}
}

このテストファイルは、dnsReadConfig関数が正しく動作することを保証するためのものです。+buildディレクティブは、このテストがUnix系システム(Darwin, Dragonfly, FreeBSD, Linux, NetBSD, OpenBSD)でのみビルドおよび実行されることを示しています。

TestReadConfig関数は、dnsReadConfig"testdata/resolv.conf"というパスを渡して呼び出します。これにより、実際のシステムファイルではなく、テスト用に用意されたサンプルファイルが読み込まれます。その後、dnsConfig構造体の各フィールド(servers, search, ndots, timeout, attempts, rotate)が、testdata/resolv.confに記述された期待値と一致するかをt.Errorft.Fatalを使って検証しています。これにより、resolv.confのパースロジックにバグがないことを確認できます。

src/pkg/net/testdata/resolv.confの新規追加

# /etc/resolv.conf

domain Home
nameserver 192.168.1.1
options ndots:5 timeout:10 attempts:3 rotate

このファイルは、TestReadConfigの入力として使用されるサンプルresolv.confです。コメント行、domainnameserver、そして複数のオプションを含むoptions行が含まれており、dnsReadConfigがこれらの要素を正しく解釈できるかをテストするための具体的なデータを提供します。

関連リンク

参考にした情報源リンク