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

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

このコミットは、Go言語の標準ライブラリosパッケージ内のテストTestOpenErrorがPlan 9環境で失敗する問題を修正するものです。具体的には、Plan 9のファイルサーバーがディレクトリを書き込みモードで開こうとした際に、EISDIR(Is a directory)ではなくEACCES(Permission denied)を誤って返すという挙動に対応するための変更が加えられています。これにより、テストが期待通りのエラーハンドリングを検証できるようになります。

コミット

  • コミットハッシュ: a6cc6347669408a2d72779743d32588c9a528315
  • Author: Aram Hăvărneanu aram@mgk.ro
  • Date: Wed Jul 9 12:31:20 2014 +0200

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

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

元コミット内容

os: fix failing test on Plan 9
    
LGTM=0intro, r
R=0intro, r
CC=ality, dave, golang-codereviews, jas, mischief, rsc
https://golang.org/cl/105580044

変更の背景

Go言語のosパッケージは、ファイルシステム操作のためのクロスプラットフォームなインターフェースを提供します。しかし、異なるオペレーティングシステム(OS)間では、同じ操作に対するエラーの挙動が微妙に異なることがあります。

このコミットの背景にある問題は、osパッケージのテストスイートに含まれるTestOpenError関数が、Plan 9オペレーティングシステム上で失敗していたことです。TestOpenErrorは、特定の条件下でファイルを開こうとした際に、期待されるエラーが返されることを検証するテストです。

具体的には、ディレクトリを書き込みモードで開こうとした場合、通常は「それがディレクトリであるため開けない」という趣旨のsyscall.EISDIRエラーが返されることが期待されます。しかし、Plan 9の一部のファイルサーバーでは、このシナリオで誤って「アクセス権がない」という趣旨のsyscall.EACCESエラーを返していました。

このOS固有の挙動の違いにより、Goのテストフレームワークはsyscall.EISDIRを期待しているにもかかわらずsyscall.EACCESが返されるため、テストが失敗していました。このコミットは、Plan 9のこの特殊な挙動を考慮に入れ、テストが正しくパスするように修正することを目的としています。

前提知識の解説

Plan 9 from Bell Labs

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。Unixの設計思想をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルとして表現し、それらをファイルシステムを通じてアクセスするという徹底したアプローチを取っています。Go言語の開発者の一部はPlan 9の設計に深く関わっており、Go言語自体もPlan 9の思想から影響を受けている部分があります。

システムコールとエラーコード

オペレーティングシステムは、アプリケーションがハードウェアやOSの機能にアクセスするためのインターフェースとして「システムコール」を提供します。ファイルを開く、読み書きする、プロセスを生成するといった操作は、システムコールを通じて行われます。

システムコールが失敗した場合、OSはエラーコードを返します。これらのエラーコードは通常、errnoのようなグローバル変数や、システムコール関数の戻り値として提供されます。Go言語では、syscallパッケージを通じてこれらのOS固有のエラーコードや定数にアクセスできます。

  • syscall.EISDIR (Is a directory): このエラーコードは、ファイル操作がディレクトリに対して行われた場合に返されます。例えば、通常のファイルを期待する操作(書き込みモードで開くなど)をディレクトリに対して実行しようとした場合に発生します。これは、操作の対象がファイルではなくディレクトリであることを示します。

  • syscall.EACCES (Permission denied): このエラーコードは、操作を実行するための適切なアクセス権がない場合に返されます。例えば、読み取り専用のファイルに書き込もうとしたり、実行権限のないプログラムを実行しようとしたりした場合に発生します。

このコミットの文脈では、Plan 9のファイルサーバーが、ディレクトリを書き込みモードで開こうとした際に、本来EISDIRを返すところをEACCESを返してしまうという「誤った」挙動を示していたことが問題でした。これは、OSの実装の詳細が、一般的なUnix系の挙動と異なっていたためです。

Go言語のテストフレームワーク

Go言語には、標準で強力なテストフレームワークが組み込まれています。go testコマンドを使用してテストを実行し、testingパッケージを利用してテスト関数を記述します。テスト関数は通常、Testで始まる名前を持ち、*testing.T型の引数を取ります。テスト内でエラーを報告するには、t.Errorft.Fatalfなどのメソッドを使用します。

技術的詳細

src/pkg/os/os_test.go内のTestOpenError関数は、様々な無効なファイル操作に対してos.Openos.OpenFileが正しいエラーを返すことを検証します。このテストは、testCasesという構造体のスライスをイテレートし、各テストケースで指定されたパスとモードでファイルを開き、返されるエラーが期待されるエラーと一致するかどうかを確認します。

問題となっていたのは、tt.error == syscall.EISDIRとなるテストケース、つまりディレクトリを書き込みモードで開こうとした際にEISDIRが期待されるケースでした。Plan 9では、このときにsyscall.EACCESが返されるため、テストの比較ロジック!strings.HasSuffix(syscallErrStr, expectedErrStr)trueとなり、テストが失敗していました。

このコミットでは、このPlan 9特有の挙動を許容するための条件分岐が追加されました。具体的には、期待されるエラーがsyscall.EISDIRであり、かつ実際に返されたシステムコールエラー文字列がsyscall.EACCESのエラー文字列で終わる場合、そのテストケースはスキップ(continue)されるようになります。これにより、Plan 9上でのテストの誤検出が回避され、テストスイート全体が正常にパスするようになります。

この修正は、テストのロバスト性を高め、特定のOSの癖に対応しつつ、他のOSでの厳密なエラーチェックは維持するという、Goのクロスプラットフォーム開発における一般的なアプローチを示しています。

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

変更はsrc/pkg/os/os_test.goファイルに集中しています。

--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -926,6 +926,12 @@ func TestOpenError(t *testing.T) {
 			syscallErrStr := perr.Err.Error()
 			expectedErrStr := strings.Replace(tt.error.Error(), "file ", "", 1)
 			if !strings.HasSuffix(syscallErrStr, expectedErrStr) {
+				// Some Plan 9 file servers incorrectly return
+				// EACCES rather than EISDIR when a directory is
+				// opened for write.
+				if tt.error == syscall.EISDIR && strings.HasSuffix(syscallErrStr, syscall.EACCES.Error()) {
+					continue
+				}
 				t.Errorf("Open(%q, %d) = _, %q; want suffix %q", tt.path, tt.mode, syscallErrStr, expectedErrStr)
 			}
 			continue

コアとなるコードの解説

追加されたコードブロックは以下の通りです。

				// Some Plan 9 file servers incorrectly return
				// EACCES rather than EISDIR when a directory is
				// opened for write.
				if tt.error == syscall.EISDIR && strings.HasSuffix(syscallErrStr, syscall.EACCES.Error()) {
					continue
				}

このコードは、TestOpenError関数内のエラーチェックロジックの一部として挿入されています。

  1. if !strings.HasSuffix(syscallErrStr, expectedErrStr): この外側のif文は、実際に返されたシステムコールエラー文字列(syscallErrStr)が、期待されるエラー文字列(expectedErrStr)で終わっていない場合に実行されます。これは、エラーが期待通りでなかったことを意味します。

  2. tt.error == syscall.EISDIR: 追加されたif文の最初の条件は、現在のテストケースで期待されているエラーがsyscall.EISDIRであるかどうかをチェックします。これは、ディレクトリをファイルとして開こうとしたシナリオに特化したものです。

  3. strings.HasSuffix(syscallErrStr, syscall.EACCES.Error()): 追加されたif文の2番目の条件は、実際に返されたシステムコールエラー文字列がsyscall.EACCESのエラー文字列で終わっているかどうかをチェックします。これは、Plan 9のファイルサーバーがEISDIRの代わりにEACCESを返しているという特定のケースを検出します。

  4. continue: 上記の2つの条件が両方とも真である場合(つまり、EISDIRが期待されているのにEACCESが返された場合)、continueステートメントが実行されます。これは、現在のテストケースの残りの部分をスキップし、forループの次のイテレーションに進むことを意味します。これにより、この特定のPlan 9固有の挙動が原因でテストが失敗するのを防ぎます。

この修正は、Goのテストが異なるOSの微妙な挙動の違いに対応できるようにするための、実用的なアプローチを示しています。これにより、Goのクロスプラットフォーム互換性が維持され、特定の環境でのテストの誤検出が回避されます。

関連リンク

参考にした情報源リンク