[インデックス 1164] ファイルの概要
このコミットは、Go言語の標準ライブラリであるstrconvパッケージ(文字列と数値の変換を扱う)のテストを強化し、当時のGo言語の新しいテストフレームワークであるgotestへの移行を行ったものです。具体的には、数値変換関数(Atoi, Atouiなど)に対するより網羅的なテストケースが追加され、テストの実行方法がシェルスクリプトベースからGo言語ネイティブのgotestフレームワークへと変更されました。これにより、テストの記述と実行がよりGo言語のイディオムに沿った形になり、将来的なテストインフラの発展の基礎が築かれました。
コミット
commit 92a4fe1dd5e6db1b65ab837098ad90c312070166
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 18 17:12:14 2008 -0800
more tests for strconv; convert to gotest.
R=r
DELTA=219 (186 added, 32 deleted, 1 changed)
OCL=19508
CL=19523
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/92a4fe1dd5e6db1b65ab837098ad90c312070166
元コミット内容
more tests for strconv; convert to gotest.
変更の背景
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。当時のGo言語のテストは、シェルスクリプト(test.bashなど)と、Go言語で書かれたシンプルなテストヘルパー(src/lib/strconv/testing.goのようなファイル)を組み合わせて実行されていました。しかし、より大規模で複雑なプロジェクトに対応するためには、より堅牢でGo言語に統合されたテストフレームワークが必要とされていました。
このコミットの背景には、Go言語の標準的なテストツールであるgotest(現在のgo testコマンドの原型)の導入と成熟があります。gotestは、Go言語のソースコード内に直接テストを記述し、専用のコマンドで実行できる仕組みを提供することで、開発者がより簡単にテストを作成・実行できるようにすることを目指していました。
strconvパッケージは、文字列と数値の変換という、多くのアプリケーションで基本的な機能を提供する重要なパッケージです。そのため、このパッケージの正確性と堅牢性を保証することは非常に重要でした。既存のテストに加えて、特に数値のオーバーフローや不正な入力に対する挙動を検証するための、より詳細なテストケースが必要とされていました。
このコミットは、Go言語のテストインフラがシェルスクリプトベースの手動実行から、Go言語ネイティブの自動化されたフレームワークへと移行する過渡期における重要な一歩を示しています。
前提知識の解説
Go言語の初期のテスト慣習
Go言語の初期(2008年頃)には、現在のgo testコマンドのような洗練されたテストランナーはまだ存在しませんでした。テストはしばしばシェルスクリプト(例: test.bash)によってオーケストレーションされ、Go言語で書かれたテスト関数は、特定の命名規則(例: TestXxx)に従い、ブール値を返すことでテストの成功/失敗を示していました。テストの実行結果は、printlnやfmt.printfで標準出力に出力され、シェルスクリプトがその出力を解析して全体のテスト結果を判断していました。
strconvパッケージ
strconvパッケージは、Go言語の標準ライブラリの一部であり、文字列と数値の相互変換機能を提供します。例えば、文字列"123"を整数123に変換したり、その逆を行ったりする際に使用されます。主な関数には以下のようなものがあります。
Atoi(s string) (i int, err os.Error): 文字列sを符号付き整数(int)に変換します。Atoui(s string) (u uint, err os.Error): 文字列sを符号なし整数(uint)に変換します。ParseInt(s string, base int, bitSize int) (i int64, err os.Error): 指定された基数(base)とビットサイズ(bitSize)で文字列をint64に変換します。ParseUint(s string, base int, bitSize int) (u uint64, err os.Error): 指定された基数とビットサイズで文字列をuint64に変換します。
エラーハンドリング (os.Error, os.EINVAL, os.ERANGE)
Go言語では、エラーは関数の戻り値として明示的に返されます。このコミットの時点では、エラーを表すためにos.Errorインターフェースが使用されていました(後にerrorインターフェースに置き換わります)。
nil: エラーがないことを示します。os.EINVAL: 不正な引数(Invalid argument)エラーを示します。例えば、数値として解析できない文字列が入力された場合などです。os.ERANGE: 範囲外(Out of range)エラーを示します。例えば、変換結果が対象の数値型の最大値や最小値を超えてしまう場合などです。
数値型とビットサイズ
Go言語には、様々なサイズの整数型があります。
int: 符号付き整数。プラットフォームに依存し、32ビットまたは64ビットのいずれかです。uint: 符号なし整数。プラットフォームに依存し、32ビットまたは64ビットのいずれかです。int32: 32ビット符号付き整数。uint32: 32ビット符号なし整数。int64: 64ビット符号付き整数。uint64: 64ビット符号なし整数。
これらの型にはそれぞれ表現できる数値の範囲が決まっており、その範囲を超える値を変換しようとするとos.ERANGEエラーが発生します。
uint64の最大値:1<<64 - 1(約1.84 x 10^19)int64の最大値:1<<63 - 1(約9.22 x 10^18)int64の最小値:-1<<63(約-9.22 x 10^18)uint32の最大値:1<<32 - 1(約4.29 x 10^9)int32の最大値:1<<31 - 1(約2.14 x 10^9)int32の最小値:-1<<31(約-2.14 x 10^9)
makeコマンド
makeは、プログラムのコンパイルやビルド、テストなどのタスクを自動化するためのツールです。Makefileというファイルに定義されたルールに基づいてコマンドを実行します。このコミットでは、run.bashスクリプト内でmake testというコマンドが呼び出されるように変更されており、テストの実行がmakeシステムに統合されたことを示しています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
シェルスクリプトベースのテストの廃止:
src/lib/strconv/test.bashファイルが削除されました。これは、strconvパッケージのテストを実行するためのシェルスクリプトでした。このスクリプトは、make clean、make、make test(コメントアウトされているが、おそらく以前は使用されていた)といったコマンドを実行し、Goプログラムのビルドとテストの実行をオーケストレーションしていました。- この削除は、テストの実行がシェルスクリプトに依存するのではなく、Go言語のネイティブなテストフレームワークに移行したことを意味します。
-
Go言語ネイティブのテストフレームワーク (
gotest) への移行:src/lib/strconv/testing.goファイルが削除されました。このファイルは、Go言語のテストヘルパー関数や構造体を定義していたものと思われますが、gotestの導入により不要となりました。- 新しく
src/lib/strconv/testatoi.goが追加されました。このファイルには、strconvパッケージのAtoiおよびAtoui関数に対する新しいテストケースがGo言語で直接記述されています。 - テスト関数は
export func TestXxx() boolという形式で定義されており、これは当時のgotestが認識するテスト関数のシグネチャでした。これらの関数はブール値を返し、trueなら成功、falseなら失敗を示します。
-
strconv関数のテスト強化:testatoi.goには、Uint64Test,Int64Test,Uint32Test,Int32Testという構造体が定義され、それぞれstrconv.atoui64,strconv.atoi64,strconv.atoui,strconv.atoi関数のテストデータ(入力文字列、期待される出力値、期待されるエラー)を保持しています。- これらのテストデータには、通常の数値、ゼロ、負の数、最大値・最小値、オーバーフローする値、不正な形式の文字列(例: "012345"のような先頭にゼロがある数値、"12345x"のような非数値文字を含む文字列)など、様々なエッジケースが含まれています。
- 特に、
1<<64-1(uint64の最大値)、1<<63-1(int64の最大値)、-1<<63(int64の最小値)といったビット演算子を用いた数値リテラルが使われており、Go言語の数値型の限界を正確にテストしようとしていることがわかります。 - テストロジックは、各テストデータに対して対象の関数を呼び出し、結果(出力値とエラー)が期待値と一致するかどうかを比較します。不一致の場合には
fmt.printfで詳細なエラーメッセージを出力し、okフラグをfalseに設定します。
-
プラットフォーム依存のテスト (
IntSize1()関数):testatoi.goにはIntSize1()というヘルパー関数が定義されています。この関数は、現在の実行環境のint型が32ビットか64ビットかを判定します。tmp := 1; if tmp<<16<<16 == 0 { return 32; }というロジックは、1を32ビット左シフト(1<<32)した結果がゼロになるかどうかで判定しています。32ビット環境では1<<32はオーバーフローしてゼロになりますが、64ビット環境ではゼロになりません。- この関数を利用して、
TestAtoui()とTestAtoi()は、実行環境のintおよびuintのサイズに応じて、32ビット用のテストデータ(uint32tests,int32tests)または64ビット用のテストデータ(uint64tests,int64tests)のいずれかを選択して実行します。これにより、異なるアーキテクチャ上でも適切なテストが実行されるようになっています。
-
run.bashの変更:src/run.bashファイルが変更され、bash test.bashの呼び出しがmake testに置き換えられました。これは、strconvパッケージのテスト実行が、シェルスクリプトからmakeシステムを介したgotestの実行へと完全に移行したことを示しています。
これらの変更は、Go言語のテストインフラがより洗練され、自動化され、Go言語のイディオムに沿ったものへと進化していく過程を示しています。
コアとなるコードの変更箇所
src/lib/strconv/test.bash (削除)
--- a/src/lib/strconv/test.bash
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-# 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.
-
-set -e
-set -x
-
-make clean
-make
-# make test
-# ./test
-# rm -f *.6 6.out test
src/lib/strconv/testatoi.go (新規追加)
// 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.
package strconv
import (
"os";
"fmt";
"strconv"
)
type Uint64Test struct {
in string;
out uint64;
err *os.Error;
}
var uint64tests = []Uint64Test {
Uint64Test{ "0", 0, nil },
Uint64Test{ "1", 1, nil },
Uint64Test{ "12345", 12345, nil },
Uint64Test{ "012345", 0, os.EINVAL },
Uint64Test{ "12345x", 0, os.EINVAL },
Uint64Test{ "98765432100", 98765432100, nil },
Uint64Test{ "18446744073709551615", 1<<64-1, nil },
Uint64Test{ "18446744073709551616", 1<<64-1, os.ERANGE },
}
type Int64Test struct {
in string;
out int64;
err *os.Error;
}
var int64tests = []Int64Test {
Int64Test{ "0", 0, nil },
Int64Test{ "-0", 0, nil },
Int64Test{ "1", 1, nil },
Int64Test{ "-1", -1, nil },
Int64Test{ "12345", 12345, nil },
Int64Test{ "-12345", -12345, nil },
Int64Test{ "012345", 0, os.EINVAL },
Int64Test{ "-012345", 0, os.EINVAL },
Int64Test{ "12345x", 0, os.EINVAL },
Int64Test{ "-12345x", 0, os.EINVAL },
Int64Test{ "98765432100", 98765432100, nil },
Int64Test{ "-98765432100", -98765432100, nil },
Int64Test{ "9223372036854775807", 1<<63-1, nil },
Int64Test{ "-9223372036854775807", -(1<<63-1), nil },
Int64Test{ "9223372036854775808", 1<<63-1, os.ERANGE },
Int64Test{ "-9223372036854775808", -1<<63, nil },
Int64Test{ "9223372036854775809", 1<<63-1, os.ERANGE },
Int64Test{ "-9223372036854775809", -1<<63, os.ERANGE },
}
type Uint32Test struct {
in string;
out uint32;
err *os.Error;
}
var uint32tests = []Uint32Test {
Uint32Test{ "0", 0, nil },
Uint32Test{ "1", 1, nil },
Uint32Test{ "12345", 12345, nil },
Uint32Test{ "012345", 0, os.EINVAL },
Uint32Test{ "12345x", 0, os.EINVAL },
Uint32Test{ "987654321", 987654321, nil },
Uint32Test{ "4294967295", 1<<32-1, nil },
Uint32Test{ "4294967296", 1<<32-1, os.ERANGE },
}
type Int32Test struct {
in string;
out int32;
err *os.Error;
}
var int32tests = []Int32Test {
Int32Test{ "0", 0, nil },
Int32Test{ "-0", 0, nil },
Int32Test{ "1", 1, nil },
Int32Test{ "-1", -1, nil },
Int32Test{ "12345", 12345, nil },
Int32Test{ "-12345", -12345, nil },
Int32Test{ "012345", 0, os.EINVAL },
Int32Test{ "-012345", 0, os.EINVAL },
Int32Test{ "12345x", 0, os.EINVAL },
Int32Test{ "-12345x", 0, os.EINVAL },
Int32Test{ "987654321", 987654321, nil },
Int32Test{ "-987654321", -987654321, nil },
Int32Test{ "2147483647", 1<<31-1, nil },
Int32Test{ "-2147483647", -(1<<31-1), nil },
Int32Test{ "2147483648", 1<<31-1, os.ERANGE },
Int32Test{ "-2147483648", -1<<31, nil },
Int32Test{ "2147483649", 1<<31-1, os.ERANGE },
Int32Test{ "-2147483649", -1<<31, os.ERANGE },
}
export func TestAtoui64() bool {
ok := true;
for i := 0; i < len(uint64tests); i++ {
t := &uint64tests[i];
out, err := strconv.atoui64(t.in);
if t.out != out || t.err != err {
fmt.printf("strconv.atoui64(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
return ok;
}
export func TestAtoi64() bool {
ok := true;
for i := 0; i < len(int64tests); i++ {
t := &int64tests[i];
out, err := strconv.atoi64(t.in);
if t.out != out || t.err != err {
fmt.printf("strconv.atoi64(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
return ok;
}
func IntSize1() uint {
tmp := 1;
if tmp<<16<<16 == 0 {
return 32;
}
println("tmp<<32 = ", tmp<<32);
return 64;
}
export func TestAtoui() bool {
ok := true;
switch IntSize1() {
case 32:
for i := 0; i < len(uint32tests); i++ {
t := &uint32tests[i];
out, err := strconv.atoui(t.in);
if t.out != uint32(out) || t.err != err {
fmt.printf("strconv.atoui(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
case 64:
for i := 0; i < len(uint64tests); i++ {
t := &uint64tests[i];
out, err := strconv.atoui(t.in);
if t.out != uint64(out) || t.err != err {
fmt.printf("strconv.atoui(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
}
return ok;
}
export func TestAtoi() bool {
ok := true;
switch IntSize1() {
case 32:
for i := 0; i < len(int32tests); i++ {
t := &int32tests[i];
out, err := strconv.atoi(t.in);
if t.out != int32(out) || t.err != err {
fmt.printf("strconv.atoi(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
case 64:
for i := 0; i < len(int64tests); i++ {
t := &int64tests[i];
out, err := strconv.atoi(t.in);
if t.out != int64(out) || t.err != err {
fmt.printf("strconv.atoi(%v) = %v, %v want %v, %v\n",
t.in, out, err, t.out, t.err);
ok = false;
}
}
}
return ok;
}
src/lib/strconv/testing.go (削除)
--- a/src/lib/strconv/testing.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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.
-
-package testing
-
-export type Test struct {
- name string;
- f *() bool;
-}
-
-export func Main(tests *[]Test) {
- ok := true;
- for i := 0; i < len(tests); i++ {
- ok1 := tests[i].f();
- status := "FAIL";
- if ok1 {
- status = "PASS"
- }
- ok = ok && ok1;
- println(status, tests[i].name);
- }
- if !ok {
- sys.exit(1);
- }
-}
src/run.bash (変更)
--- a/src/run.bash
+++ b/src/run.bash
@@ -14,7 +14,7 @@ xcd() {
(xcd lib/strconv
make clean
time make
-bash test.bash
+make test
) || exit $?
(xcd lib/reflect
コアとなるコードの解説
src/lib/strconv/testatoi.go
このファイルは、strconvパッケージの文字列から数値への変換関数(atoui64, atoi64, atoui, atoi)のテストケースを定義しています。
-
テストデータ構造体:
Uint64Test,Int64Test,Uint32Test,Int32Testという構造体が定義されています。これらはそれぞれ、テスト対象の入力文字列(in)、期待される出力数値(out)、期待されるエラー(err)を保持します。- これらの構造体のスライス(例:
uint64tests,int64tests)として、具体的なテストケースが初期化されています。各テストケースは、正常な変換、境界値、オーバーフロー、不正な入力など、様々なシナリオをカバーしています。
-
テスト関数:
export func TestAtoui64() bool,export func TestAtoi64() bool,export func TestAtoui() bool,export func TestAtoi() boolという関数が定義されています。これらは当時のgotestフレームワークが自動的に発見して実行するテスト関数です。- 各テスト関数は、対応するテストデータスライスをループし、各テストケースに対して
strconvパッケージの変換関数を呼び出します。 - 変換結果(
outとerr)が期待値(t.outとt.err)と一致するかどうかを比較します。 - もし結果が期待値と異なる場合、
fmt.printfを使って詳細なエラーメッセージ(入力、実際の結果、期待される結果)を標準出力に出力し、テスト全体の成功を示すokフラグをfalseに設定します。 - 最終的に、すべてのテストケースが成功した場合は
trueを、一つでも失敗した場合はfalseを返します。
-
IntSize1()関数:- このヘルパー関数は、Goが実行されている環境の
int型が32ビットか64ビットかを判定します。 tmp := 1; if tmp<<16<<16 == 0 { return 32; }というロジックは、1を32ビット左シフト(1 << 32)した結果がゼロになるかどうかをチェックしています。- 32ビットシステムでは、
1 << 32はオーバーフローして結果がゼロになります。 - 64ビットシステムでは、
1 << 32はオーバーフローせず、2^32という値になります。
- 32ビットシステムでは、
- この判定結果に基づいて、
TestAtoui()とTestAtoi()は、32ビット環境ではuint32testsやint32testsを、64ビット環境ではuint64testsやint64testsを使用するように切り替えます。これにより、異なるアーキテクチャ上でも適切なサイズの数値変換テストが実行されるようになっています。
- このヘルパー関数は、Goが実行されている環境の
src/run.bash
このシェルスクリプトは、Go言語プロジェクト全体のビルドとテストの実行を管理するスクリプトの一部です。
- 変更点として、
strconvライブラリのディレクトリ(lib/strconv)に移動した後、以前はbash test.bashを実行していた箇所がmake testに置き換えられました。 - これは、
strconvパッケージのテスト実行が、シェルスクリプトによる直接的なテストスクリプトの呼び出しから、Makefileに定義されたtestターゲットを介したgotestの実行へと移行したことを明確に示しています。これにより、テストの実行がより標準化され、makeシステムに統合されました。
関連リンク
- Go言語の
strconvパッケージの現在のドキュメント: https://pkg.go.dev/strconv - Go言語のテストに関する現在のドキュメント: https://go.dev/doc/tutorial/add-a-test (現在の
go testの概念)
参考にした情報源リンク
- Go言語の初期のテストフレームワークに関する議論やコミット履歴 (GitHubのGoリポジトリのコミット履歴を遡って確認)
- Go言語の
strconvパッケージのソースコード (現在の実装と比較して、当時の実装の推測) - Go言語の数値型とビット演算に関する一般的な知識