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

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

このコミットはGo言語の標準ライブラリsrc/pkg/testing/testing.goに対する変更です。testing.goファイルは、Goのテストフレームワークのコア部分を定義しており、テストの実行、ベンチマーク、テスト結果のレポートなど、Goプログラムのテストに関連する主要な機能を提供します。特に、go testコマンドが内部的に利用する機能がここに実装されています。

コミット

commit 337407d8473d96b11b8c4bd053bce463c347eb06
Author: Russ Cox <rsc@golang.org>
Date:   Fri Aug 2 13:51:45 2013 -0400

    testing: make parsing of -cpu more lenient
    
    Also add \n to error message.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12261044

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

https://github.com/golang/go/commit/337407d8473d96b11b8c4bd053bce463c347eb06

元コミット内容

testing: make parsing of -cpu more lenient

Also add \n to error message.

変更の背景

このコミットの主な目的は、go testコマンドで使用される-test.cpuフラグのパース処理をより寛容にすることです。以前の実装では、-test.cpuに指定された値が厳密に数値である必要があり、例えばカンマ区切りのリストに空白が含まれている場合などにパースエラーが発生する可能性がありました。

具体的には、以下の点が改善の背景にあります。

  1. 空白文字の許容: ユーザーが-test.cpuフラグに1, 2, 4のようにカンマと空白を含む値を指定した場合でも、正しくパースできるようにするため。以前はstrings.Splitで分割された文字列に空白が含まれたままだとstrconv.Atoiがエラーを返す可能性がありました。
  2. 空文字列のスキップ: カンマ区切りのリストで1,,4のように空の要素が含まれる場合、それを無視して処理を続行できるようにするため。
  3. エラーメッセージの改善: パースエラーが発生した際のエラーメッセージに改行を追加し、ターミナルでの表示をより分かりやすくするため。

これらの変更により、ユーザーがより柔軟な形式で-test.cpuフラグを指定できるようになり、使い勝手が向上しました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。

  • go testコマンド: Go言語のテストを実行するためのコマンドです。テストファイル(_test.goで終わるファイル)内のテスト関数やベンチマーク関数を実行します。
  • -test.cpuフラグ: go testコマンドのオプションの一つで、ベンチマークテストを実行する際に使用するCPUコアの数を指定します。例えば、-test.cpu=1,2,4と指定すると、ベンチマークが1コア、2コア、4コアのそれぞれで実行されます。
  • runtime.GOMAXPROCS: Goランタイムが同時に実行できるOSスレッドの最大数を制御する環境変数、または同名の関数です。-test.cpuフラグが指定されない場合、デフォルトでruntime.GOMAXPROCS(-1)(現在の設定値)が使用されます。
  • strings.Split(s, sep string) []string: 文字列sを区切り文字sepで分割し、部分文字列のスライスを返します。
  • strings.TrimSpace(s string) string: 文字列sの先頭と末尾にあるすべての空白文字(スペース、タブ、改行など)を削除した新しい文字列を返します。
  • strconv.Atoi(s string) (int, error): 文字列sを整数に変換します。変換できない場合はエラーを返します。
  • fmt.Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error): 指定されたio.Writer(この場合はos.Stderr、標準エラー出力)にフォーマットされた文字列を出力します。
  • os.Stderr: 標準エラー出力にアクセスするための*os.File型の変数です。
  • os.Exit(code int): 現在のプログラムを指定された終了コードで終了させます。0は成功、非ゼロはエラーを示します。

技術的詳細

このコミットは、src/pkg/testing/testing.goファイル内のparseCpuList()関数に焦点を当てています。この関数は、-test.cpuフラグに指定された文字列をパースし、cpuListという整数スライスに変換する役割を担っています。

変更前は、parseCpuList()関数は以下のロジックで動作していました。

  1. *cpuListStr-test.cpuフラグの値)が空の場合、runtime.GOMAXPROCS(-1)cpuListに追加します。
  2. 空でない場合、strings.Split(*cpuListStr, ",")で文字列をカンマで分割します。
  3. 分割された各valについて、strconv.Atoi(val)で整数に変換を試みます。
  4. 変換に失敗するか、変換された値が0以下の場合、エラーメッセージをos.Stderrに出力し、os.Exit(1)でプログラムを終了します。
  5. 成功した場合、cpuListにその値を追加します。

変更後のparseCpuList()関数は、以下の点が異なります。

  1. ループの構造変更: 以前は*cpuListStrが空かどうかで条件分岐していましたが、変更後はまず無条件でstrings.Split(*cpuListStr, ",")で分割し、ループ処理を開始します。これにより、空の-test.cpuフラグが指定された場合(cpuListStrが空文字列の場合)、strings.Splitは空のスライスを返すため、ループは実行されません。
  2. strings.TrimSpace(val)の追加: 各valを整数に変換する前にstrings.TrimSpace(val)が呼び出されます。これにより、"1, 2, 4"のように空白が含まれていても、" 2""2"にトリムされ、strconv.Atoiが正しく処理できるようになります。
  3. 空文字列のスキップ: val = strings.TrimSpace(val)の後にif val == "" { continue }が追加されました。これにより、"1,,4"のようにカンマが連続して空の要素が生成された場合や、" , 2"のように空白のみの要素が生成された場合に、それらの空文字列をスキップして次の要素の処理に進むことができます。
  4. デフォルト値の処理: ループが終了した後、if cpuList == nilという条件が追加されました。これは、-test.cpuフラグが指定されなかった場合(*cpuListStrが空文字列で、ループが一度も実行されなかった場合)や、指定された値がすべて空文字列や空白のみでスキップされた場合にcpuListが空のままになることを意味します。この場合、cpuList = append(cpuList, runtime.GOMAXPROCS(-1))が実行され、デフォルト値が設定されます。
  5. エラーメッセージの改行: エラーメッセージのフォーマット文字列"testing: invalid value %q for -test.cpu"の末尾に\nが追加され、エラー出力後に改行されるようになりました。

これらの変更により、-test.cpuフラグのパースがより堅牢になり、ユーザーの入力ミスに対して寛容になりました。

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

--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -575,16 +575,19 @@ func stopAlarm() {
 }
 
 func parseCpuList() {
-	if len(*cpuListStr) == 0 {
-		cpuList = append(cpuList, runtime.GOMAXPROCS(-1))
-	} else {
-		for _, val := range strings.Split(*cpuListStr, ",") {
-			cpu, err := strconv.Atoi(val)
-			if err != nil || cpu <= 0 {
-				fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val)
-				os.Exit(1)
-			}
-			cpuList = append(cpuList, cpu)
+	for _, val := range strings.Split(*cpuListStr, ",") {
+		val = strings.TrimSpace(val)
+		if val == "" {
+			continue
 		}
-	}
+		cpu, err := strconv.Atoi(val)
+		if err != nil || cpu <= 0 {
+			fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu\n", val)
+			os.Exit(1)
+		}
+		cpuList = append(cpuList, cpu)
+	}
+	if cpuList == nil {
+		cpuList = append(cpuList, runtime.GOMAXPROCS(-1))
+	}
 }

コアとなるコードの解説

変更されたparseCpuList()関数について、変更前と変更後を比較しながら解説します。

変更前:

func parseCpuList() {
	if len(*cpuListStr) == 0 { // (A) -test.cpuが指定されていない場合
		cpuList = append(cpuList, runtime.GOMAXPROCS(-1))
	} else { // (B) -test.cpuが指定されている場合
		for _, val := range strings.Split(*cpuListStr, ",") { // (C) カンマで分割
			cpu, err := strconv.Atoi(val) // (D) 整数に変換
			if err != nil || cpu <= 0 { // (E) エラーチェック
				fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val)
				os.Exit(1)
			}
			cpuList = append(cpuList, cpu)
		}
	}
}

変更前は、まず(A)*cpuListStrが空かどうかをチェックし、空であればデフォルト値を設定していました。空でなければ(B)のブロックに入り、(C)でカンマで分割し、(D)で整数に変換、(E)でエラーチェックを行っていました。この構造では、"1, 2"のような入力があった場合、strings.Split"1"" 2"を返し、" 2"strconv.Atoiが処理しようとするとエラーになる可能性がありました。また、"1,,2"のような入力で空文字列が生成された場合も、strconv.Atoi("")はエラーになります。

変更後:

func parseCpuList() {
	for _, val := range strings.Split(*cpuListStr, ",") { // (1) まずカンマで分割し、ループを開始
		val = strings.TrimSpace(val) // (2) 各要素の前後にある空白を削除
		if val == "" { // (3) 空文字列になった場合はスキップ
			continue
		}
		cpu, err := strconv.Atoi(val) // (4) 整数に変換
		if err != nil || cpu <= 0 { // (5) エラーチェック
			fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu\n", val) // (6) エラーメッセージに改行を追加
			os.Exit(1)
		}
		cpuList = append(cpuList, cpu) // (7) cpuListに追加
	}
	if cpuList == nil { // (8) cpuListが空の場合(-test.cpuが指定されなかったか、すべてスキップされた場合)
		cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) // (9) デフォルト値を設定
	}
}
  1. for _, val := range strings.Split(*cpuListStr, ","): 変更前のようなif len(*cpuListStr) == 0による初期分岐がなくなり、まず無条件で*cpuListStrをカンマで分割し、その結果をループで処理するようになりました。*cpuListStrが空文字列の場合、strings.Splitは空のスライスを返すため、このループは実行されません。
  2. val = strings.TrimSpace(val): 分割された各valに対して、まずstrings.TrimSpaceが適用されます。これにより、" 2"のような文字列は"2"に変換され、strconv.Atoiが正しく処理できるようになります。
  3. if val == "" { continue }: TrimSpaceの後に、valが空文字列になった場合はcontinueで次のループイテレーションに進むようになりました。これにより、"1,,2"のようにカンマが連続して空の要素が生成された場合や、" , 2"のように空白のみの要素が生成された場合に、それらの無効な要素がスキップされます。
  4. cpu, err := strconv.Atoi(val): TrimSpaceと空文字列のチェックを通過したvalのみがstrconv.Atoiで整数に変換されます。
  5. if err != nil || cpu <= 0: 変換エラーまたは値が0以下の場合のチェックは変更ありません。
  6. fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu\n", val): エラーメッセージの末尾に\nが追加され、エラー出力後に改行されるようになりました。
  7. cpuList = append(cpuList, cpu): 有効な値はcpuListに追加されます。
  8. if cpuList == nil: ループが終了した後、cpuListがまだnil(または空のスライス)であるかどうかをチェックします。これは、-test.cpuフラグが全く指定されなかった場合(*cpuListStrが空文字列でループが実行されなかった場合)や、指定された値がすべて空文字列や空白のみでスキップされた場合に発生します。
  9. cpuList = append(cpuList, runtime.GOMAXPROCS(-1)): cpuListが空の場合、runtime.GOMAXPROCS(-1)(現在のGOMAXPROCS値)がデフォルト値としてcpuListに追加されます。

この変更により、-test.cpuフラグのパース処理がより堅牢でユーザーフレンドリーになりました。

関連リンク

参考にした情報源リンク