[インデックス 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言語の数値型とビット演算に関する一般的な知識