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

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

このコミットは、Go言語の初期開発段階における重要なマイルストーンを示しています。Goコンパイラおよびランタイムの堅牢性を向上させるため、多数の新規バグテストケースと、既に修正されたバグのテストケースが追加されています。これにより、言語仕様の解釈、型チェック、コード生成における既知の問題が体系的に捕捉され、将来の回帰を防ぐための基盤が構築されました。

コミット

commit 094ee44b32d1f459534c3f187da16619f4909d7a
Author: Rob Pike <r@golang.org>
Date:   Fri Jun 6 16:56:18 2008 -0700

    check in the bugs and fixed bugs
    
    SVN=121543

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

https://github.com/golang/go/commit/094ee44b32d1f459534c3f187da16619f4909d7a

元コミット内容

check in the bugs and fixed bugs

この簡潔なコミットメッセージは、Go言語の初期開発における実用的なアプローチを反映しています。これは、新たなバグのテストケースと、既に修正されたバグのテストケースをバージョン管理システムに登録したことを示しています。SVN=121543という記述は、当時のGoプロジェクトがSubversion(SVN)を使用しており、このコミットがSVNリポジトリの特定のリビジョン121543に対応することを示唆しています。

変更の背景

Go言語は2007年からGoogle社内で開発が始まり、2009年にオープンソースとして公開されました。このコミットの日付は2008年6月であり、Go言語がまだ活発な開発段階にあったことを示しています。この時期の言語開発では、言語仕様の策定と同時に、それを実装するコンパイラやランタイムのバグ修正が最優先事項でした。

このコミットの背景には、以下の目的があったと考えられます。

  1. コンパイラの堅牢性向上: 初期段階のコンパイラは多くのバグを含んでいるのが一般的です。これらのバグを特定し、修正し、そしてその修正が将来の変更によって再発しないようにするためのテストケースを整備する必要がありました。
  2. 言語仕様の明確化: バグの発見と修正のプロセスは、しばしば言語仕様の曖昧な点や未定義の動作を浮き彫りにします。テストケースとしてバグを記録することで、言語設計者はこれらのエッジケースを考慮し、仕様をより明確に定義する機会を得ます。
  3. 回帰テストの確立: 一度修正されたバグが、その後のコード変更によって再び発生する「回帰(regression)」はソフトウェア開発において一般的な問題です。test/fixedbugsディレクトリに修正済みバグのテストケースを追加することで、将来の回帰を自動的に検出できるメカニズムを構築しました。
  4. 開発プロセスの標準化: バグをtest/bugsに、修正済みバグをtest/fixedbugsに分類して管理することは、バグ報告、再現、修正、検証という一連の開発ワークフローを標準化する上で役立ちます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびソフトウェアテストに関する基本的な知識が役立ちます。

  • Go言語の初期開発: Go言語は、C++やJavaのような既存の言語の欠点を克服し、シンプルさ、効率性、並行処理の容易さを追求して設計されました。初期のGoコンパイラは、C言語で書かれたgc(Go Compiler)という名称で知られ、その後のGoツールチェインの基盤となりました。
  • コンパイラの役割: コンパイラは、人間が書いたソースコード(Go言語)を、コンピュータが直接実行できる機械語に変換するソフトウェアです。この変換プロセスには、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成といった複数の段階があります。これらの各段階でバグが発生する可能性があります。
  • テスト駆動開発 (TDD) の概念: TDDは、コードを書く前にテストケースを書く開発手法です。このコミットのように、バグを再現するテストケースを先に作成し、そのテストがパスするようにコードを修正するアプローチは、TDDの原則と共通しています。
  • 回帰テスト: ソフトウェアの変更(新機能の追加、バグ修正など)が、既存の機能に悪影響を与えていないことを確認するためのテストです。test/fixedbugsディレクトリのテストケースは、まさに回帰テストの目的で追加されています。
  • iota: Go言語のconst宣言で使用される特殊な識別子で、連続する定数に自動的に値を割り当てます。iotaは0から始まり、constブロック内で新しいconst宣言ごとに1ずつ増加します。
  • エスケープシーケンス: 文字列リテラルや文字リテラル内で、特殊な意味を持つ文字を表現するために使用される記号の組み合わせです。例えば、\nは改行、\tはタブを表します。Goでは、\uXXXX(Unicodeコードポイント)や\xXX(バイト値)のようなエスケープシーケンスもサポートしています。
  • panic: Go言語におけるランタイムエラーの一種で、プログラムの異常終了を引き起こします。通常、回復不可能なエラーやプログラマの論理的誤りを示すために使用されます。
  • exportキーワード (初期Go言語): 初期Go言語には、現在のGo言語には存在しないexportキーワードがありました。これは、パッケージ外に公開する識別子を明示的に指定するために使用されていました。現在のGo言語では、識別子の最初の文字が大文字であるかどうかに基づいて公開/非公開が決定されます。

技術的詳細

このコミットは、Go言語のコンパイラとランタイムの初期のバグを浮き彫りにし、それらをテストケースとして体系的に管理しようとする試みです。

test/bugs ディレクトリのファイル: これらのファイルは、当時のGoコンパイラが誤ってコンパイルしてしまったり、誤ったエラーメッセージを出力したり、あるいはコンパイラがクラッシュしたりするような、特定のバグを再現するためのコードを含んでいます。各ファイルのコメントには、期待されるエラーメッセージや、なぜそれがバグであるかの説明が記述されています。

例:

  • bug001.go: if {} のような構文的に不完全なif文がコンパイルされてしまうバグ。Goのif文の条件は式である必要があります。
  • bug003.go: switch ; {} のような構文的に不完全なswitch文がコンパイルされてしまうバグ。switch文のセミコロンの前にはsimplevardecl(単純な変数宣言)が必要です。
  • bug006.go: const ( g float = 4.5 * iota; ); のようなiotaと浮動小数点数の組み合わせに関するコンパイルエラーの不適切さ。複数の誤ったエラーメッセージが出力される問題。
  • bug014.go: 文字リテラルのエスケープシーケンス(例: \0, \x) の構文チェックの不備。特に、オクタルエスケープには3桁、16進エスケープには2桁の数字が必要ですが、それが守られていない場合に適切なエラーが出ない、あるいはコンパイルされてしまうバグ。
  • bug015.go: (1<<64) -1 のようなuint64の最大値を超える定数がコンパイル時にエラーとならないバグ。
  • bug016.go: 負のシフト量(例: i << -3)がコンパイラをクラッシュさせるバグ。
  • bug023.go: インターフェース型とnilの代入に関するコンパイラのクラッシュ。
  • bug024.go: 文字列リテラル内の不正なエスケープシーケンス(例: \', \\, \")が適切に処理されないバグ。
  • bug025.go: export Foo のようなexportキーワードの使用に関するコンパイラのクラッシュ。
  • bug026.go: ローカルスコープでの型定義(例: type I struct { val int; };)がグローバルに定義された場合にのみ動作するバグ。
  • bug027.go: インターフェース型から具象型への型アサーション(例: I(v.At(i)).val)が正しく機能しないバグ。
  • bug028.go: switch文のdefaultケースの後に到達不能なステートメントがある場合に、コンパイラがそれを検出できないバグ。
  • bug029.go: 関数ポインタの型定義(例: f *func(int))が誤ってコンパイルされてしまうバグ。
  • bug030.go: 変数の再宣言(例: var x int; x := 0;)がエラーとならないバグ。

test/fixedbugs ディレクトリのファイル: これらのファイルは、既に修正されたバグのテストケースを含んでいます。これらのテストは、コンパイラが正しく動作することを確認し、将来の回帰を防ぐためのものです。

例:

  • bug000.go: switch文のcaseステートメントが誤った位置にある場合に、コンパイラが正しくエラーを報告するようになったことを確認するテスト。
  • bug005.go: goto文とラベルの定義に関するコンパイラのクラッシュが修正されたことを確認するテスト。
  • bug007.go: 型の再定義に関するコンパイラのエラー報告が改善されたことを確認するテスト。
  • bug008.go: switch文の条件が省略された場合にコンパイラがクラッシュするバグが修正されたことを確認するテスト。
  • bug009.go: bool型の初期化に関するコンパイラのクラッシュが修正されたことを確認するテスト。
  • bug011.go: メソッド呼び出しに関するコンパイラのクラッシュが修正されたことを確認するテスト。
  • bug012.go: uint64型の定数オーバーフローが正しく検出されるようになったことを確認するテスト。
  • bug013.go: Unicodeエスケープシーケンス(\uXXXX, \UXXXXXXXX)の構文チェックが改善されたことを確認するテスト。
  • bug017.go: 文字列リテラル内のエスケープシーケンス(特に\r)の処理に関するコンパイラのバグが修正されたことを確認するテスト。
  • bug020.go: 文字列のインデックスアクセスに関するコンパイラのバグが修正されたことを確認するテスト。
  • bug021.go: 文字列の結合(+=)が正しく動作することを確認するテスト。
  • bug031.go: 非常に長い文字列リテラルがコンパイラをクラッシュさせるバグが修正されたことを確認するテスト。

test/golden.out の変更: このファイルは、テスト実行時の標準出力(コンパイラのエラーメッセージなど)の「ゴールデン」リファレンスを保持しています。このコミットでは、多くのバグテストケースが追加されたため、golden.outも更新され、これらの新しいテストケースが生成するエラーメッセージや出力が記録されています。これにより、テストの実行結果が期待通りであるかを自動的に検証できます。

test/ken/robliteral.go の変更: このファイルは、リテラル(数値、文字列、ブール値など)の正しい動作を検証するためのテストファイルです。変更点として、assert関数にcode = 1;が追加され、main関数がintを返すようになりました。これは、テストが失敗した場合に非ゼロの終了コードを返すように変更され、テストハーネスがテストの成功/失敗をより容易に判断できるようにするための改善と考えられます。

test/run の変更: このシェルスクリプトは、Go言語のテストスイートを実行するためのものです。このコミットでは、for dir in . ken の行が for dir in . ken bugs fixedbugs に変更されています。これは、新しく追加されたtest/bugstest/fixedbugsディレクトリ内のテストファイルも、全体のテスト実行プロセスに含めるようにテストランナーが更新されたことを意味します。これにより、追加されたすべてのバグテストと修正済みバグテストが自動的に実行されるようになります。また、echo 2>&1 $(grep -c '^BUG' run.out) tests are failing incorrectlyecho 2>&1 $(grep -c '^BUG' run.out) tests are behaving incorrectly に変更されており、テストの振る舞いに関するメッセージがより一般的な表現に修正されています。

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

このコミット自体は、Go言語のコンパイラやランタイムの「コア」となる実装コードを直接変更するものではありません。代わりに、Go言語のテストスイートに以下の新しいテストファイルを追加し、既存のテスト実行スクリプトを更新しています。

  • test/bugs/bug001.go から test/bugs/bug030.go までの新規ファイル(計18ファイル)
  • test/fixedbugs/bug000.go から test/fixedbugs/bug031.go までの新規ファイル(計12ファイル)
  • test/golden.out の更新
  • test/ken/robliteral.go の更新
  • test/run の更新

これらの変更は、Go言語のコンパイラとランタイムの品質保証プロセスを強化するためのものです。

コアとなるコードの解説

このコミットで追加された各.goファイルは、Go言語の特定のバグを再現するための最小限のコードスニペットを含んでいます。これらのファイルは、Goコンパイラ($G)とリンカ($L)を使用してコンパイルされ、実行可能ファイル(./$A.out)として実行されます。

bugXXX.goファイルには、通常、以下の要素が含まれています。

  1. コンパイル/実行コマンド: // $G $D/$F.go && $L $F.$A && ./$A.out これは、Goコンパイラ($G)、リンカ($L)、そして生成された実行可能ファイル(./$A.out)を使ってテストを実行するためのシェルコマンドです。$Dは現在のディレクトリ、$Fはファイル名、$Aはアーキテクチャ(例: 6 for amd64)を表す変数です。 // errchk $G $D/$F.go のように、コンパイルエラーが期待されるテストでは、実行ステップが省略され、errchkキーワードが使用されます。

  2. 著作権表示とライセンス: // Copyright 2009 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. Goプロジェクトの標準的な著作権表示とBSDスタイルのライセンス情報です。

  3. パッケージ宣言とmain関数: package main func main() { ... } ほとんどのテストファイルは、独立して実行可能なプログラムとしてmainパッケージとmain関数を含んでいます。

  4. バグを再現するコード: これが各テストファイルの核心です。Go言語の特定の構文、型システム、ランタイムの振る舞いにおいて、当時バグがあった部分を意図的に記述しています。

  5. 期待されるエラーメッセージやコメント: コードブロックの後に、/* ... */ の形式で、そのバグが修正されていない場合にコンパイラが出力するであろうエラーメッセージや、バグの簡単な説明がコメントとして記述されています。これは、テストが期待通りに失敗するか、あるいは修正後に期待通りに成功するかを検証するための重要な情報源となります。

例えば、test/bugs/bug001.go のコアとなるコードは以下の通りです。

package main

func main() {
	if {}  // compiles; should be an error (must be an expression)
}

このコードは、if文の条件部分が空であるという構文エラーを含んでいます。当時のコンパイラはこれを誤ってコンパイルしてしまっていたため、このテストケースが追加されました。コメントには「コンパイルされるが、エラーであるべき(式である必要がある)」と明記されています。

test/run スクリプトの変更は、これらのテストファイルがGo言語のビルドシステムに統合され、自動的に実行されるようになったことを示しています。これにより、Go言語の進化に伴う回帰バグの発生を早期に検出し、品質を維持するための重要なステップが踏み出されました。

関連リンク

参考にした情報源リンク