[インデックス 14054] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtesting
パッケージにおけるt.Log
関数の出力フォーマットに関するバグ修正です。具体的には、t.Log
に複数行の文字列(改行文字を含む文字列)を渡した場合に、出力されるログに余分なタブや改行が含まれてしまう問題を解決します。これにより、テスト結果の可読性が向上し、より期待される形式でログが出力されるようになります。
コミット
commit 51310d832027f0660098f5f809dc587f0a8b8f9c
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Mon Oct 8 00:21:53 2012 +0800
testing: fix extra tabs when t.Log("string")
t.Log("line 1\nline 2\nline 3")
Old output:
=== RUN TestLine3
--- PASS: TestLine3 (0.00 seconds)
testing_test.go:25: line 1
line 2
line 3
PASS
New output:
=== RUN TestLine3
--- PASS: TestLine3 (0.00 seconds)
testing_test.go:24: line 1
line 2
line 3
PASS
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6613069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/51310d832027f0660098f5f809dc587f0a8b8f9c
元コミット内容
testing: fix extra tabs when t.Log("string")
t.Log("line 1\nline 2\nline 3")
Old output:
=== RUN TestLine3
--- PASS: TestLine3 (0.00 seconds)
testing_test.go:25: line 1
line 2
line 3
PASS
New output:
=== RUN TestLine3
--- PASS: TestLine3 (0.00 seconds)
testing_test.go:24: line 1
line 2
line 3
PASS
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6613069
変更の背景
Go言語のtesting
パッケージは、ユニットテストやベンチマークテストを記述するための標準的なフレームワークを提供します。テスト中にデバッグ情報や進捗状況を出力するために、*testing.T
型のメソッドであるLog
やLogf
が使用されます。これらのメソッドは、テストの実行中に標準出力にメッセージを記録します。
このコミットが修正しようとしている問題は、t.Log
に複数行の文字列(つまり、改行文字\n
を含む文字列)を渡した場合に発生していました。従来のtesting
パッケージの実装では、t.Log
によって出力される各行の先頭に、ファイル名と行番号に続くインデント(タブ)が追加されます。しかし、複数行の文字列の最後の行が改行で終わっていない場合、その行の後に余分な改行とインデントが追加されてしまい、さらにその後に続くテスト結果(例: PASS
)が不自然な位置に表示されるという問題がありました。
コミットメッセージに示されている「Old output」と「New output」の比較を見ると、この問題が明確に理解できます。
Old output:
testing_test.go:25: line 1
line 2
line 3
PASS
line 3
の後に余分な改行とインデントがあり、その後にPASS
が続いています。これは、t.Log
に渡された文字列がline 3
で終わっており、その後に改行がないにもかかわらず、testing
パッケージが自動的に改行を追加し、さらにその改行の後に続くはずのないインデントを追加してしまっていたためです。
New output:
testing_test.go:24: line 1
line 2
line 3
PASS
修正後では、line 3
の直後にPASS
が続き、余分な改行やインデントがなくなっています。これにより、テスト出力がより自然で期待される形式になりました。この修正は、テスト結果の可読性を高め、開発者がテストの出力をより正確に解釈できるようにするために重要でした。
前提知識の解説
Go言語の testing
パッケージ
Go言語には、標準でテストをサポートするためのtesting
パッケージが用意されています。このパッケージを使用することで、ユニットテスト、ベンチマークテスト、例(Example)などを簡単に記述できます。
- テスト関数: テスト関数は
func TestXxx(*testing.T)
というシグネチャを持ちます。Xxx
は任意の英数字の文字列で、大文字で始まる必要があります。 *testing.T
: テスト関数に渡される*testing.T
型の引数は、テストの状態を管理し、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。t.Log
/t.Logf
: これらのメソッドは、テスト中に情報を標準出力に記録するために使用されます。t.Log
は引数をそのまま出力し、t.Logf
はフォーマット文字列と引数を受け取ります。これらのログは、テストが成功した場合でも表示されます(t.Error
やt.Fatal
とは異なります)。- テストの実行: Goのテストは、通常
go test
コマンドで実行されます。このコマンドは、現在のディレクトリまたは指定されたパッケージ内のテストファイルを検索し、テスト関数を実行します。
strings.Split
関数
strings.Split(s, sep string)
は、Go言語の標準ライブラリstrings
パッケージに含まれる関数です。この関数は、指定された文字列s
を、指定された区切り文字sep
で分割し、部分文字列のスライスを返します。
例:
s := "apple,banana,orange"
parts := strings.Split(s, ",") // partsは []string{"apple", "banana", "orange"}
このコミットの文脈では、t.Log
に渡された文字列が改行文字\n
で分割され、各行が個別に処理されるために使用されています。
bufio.Buffer
bytes.Buffer
は、Go言語の標準ライブラリbytes
パッケージに含まれる型です。これは可変長のバイトバッファを実装しており、io.Writer
インターフェースを満たします。文字列の構築や、バイト列の効率的な操作によく使用されます。このコミットのdecorate
関数では、ログ出力の各部分(ファイル名、行番号、各行の文字列、改行など)を効率的に結合するために使用されています。
技術的詳細
このコミットの修正は、src/pkg/testing/testing.go
ファイル内のdecorate
関数に集中しています。decorate
関数は、t.Log
やt.Logf
によって出力されるメッセージを整形する役割を担っています。具体的には、ログメッセージの先頭にファイル名と行番号を追加し、複数行のメッセージを適切にインデントして出力するロジックが含まれています。
修正前のdecorate
関数には、以下の問題がありました。
- 末尾の空行の扱い:
strings.Split(s, "\n")
は、文字列が改行で終わっている場合、最後の要素として空文字列を返します。例えば、"line1\nline2\n"
を\n
で分割すると、["line1", "line2", ""]
というスライスが生成されます。修正前は、この末尾の空文字列も通常の行として処理され、余分なインデントと改行が追加されていました。 - 無条件の末尾改行追加: 修正前は、入力文字列
s
が改行で終わっていない場合、無条件に末尾に改行を追加していました。これは、t.Log
の出力が常に改行で終わるようにするためのものでしたが、前述の末尾の空行の処理と組み合わさることで、意図しない余分な改行とインデントを生み出す原因となっていました。
このコミットでは、これらの問題を解決するために以下の変更が加えられました。
変更点1: 末尾の空行の除去
- lines := strings.Split(s, "\n")
+ lines := strings.Split(s, "\n")
+ if l := len(lines); l > 1 && lines[l-1] == "" {
+ lines = lines[:l-1]
+ }
strings.Split
で分割されたlines
スライスに対して、以下のチェックが追加されました。
l := len(lines)
:lines
スライスの長さを取得します。l > 1
: スライスが複数の要素を持つことを確認します。これは、単一の行や空文字列の場合に誤って処理しないようにするためです。lines[l-1] == ""
: スライスの最後の要素が空文字列であるかどうかを確認します。これは、入力文字列が改行で終わっている場合に発生します。
もしこれらの条件が真であれば、lines = lines[:l-1]
によって最後の空文字列の要素がスライスから除去されます。これにより、t.Log("line 1\nline 2\n")
のような入力に対して、["line 1", "line 2"]
として処理され、余分な出力が抑制されます。
変更点2: 無条件の末尾改行追加の簡素化
- if l := len(s); l > 0 && s[len(s)-1] != '\n' {
- // Add final new line if needed.
- buf.WriteByte('\n')
- }
+ buf.WriteByte('\n')
修正前は、入力文字列s
が空でなく、かつ末尾が改行でない場合にのみ、buf.WriteByte('\n')
で改行を追加していました。
修正後では、この条件分岐が削除され、buf.WriteByte('\n')
が無条件に実行されるようになりました。
一見すると、これは常に改行を追加するように見えるため、問題が再発するように思えるかもしれません。しかし、変更点1で末尾の空行が適切に処理されるようになったため、この無条件の改行追加は、t.Log
の出力が常に改行で終わるという意図をシンプルに実現するようになりました。
例えば、t.Log("hello")
の場合、lines
は["hello"]
となり、変更点1の条件は満たされません。その後、buf.WriteString("hello")
が実行され、最後にbuf.WriteByte('\n')
で改行が追加され、hello\n
という期待される出力になります。
t.Log("line 1\nline 2\nline 3")
の場合、lines
は["line 1", "line 2", "line 3"]
となり、変更点1の条件は満たされません。各行が処理され、最後にbuf.WriteByte('\n')
で改行が追加され、期待される出力になります。
この二つの変更が組み合わさることで、t.Log
の出力がより予測可能でクリーンなものになりました。
コアとなるコードの変更箇所
src/pkg/testing/testing.go
ファイルのdecorate
関数が変更されています。
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -160,6 +160,9 @@ func decorate(s string) string {
fmt.Fprintf(buf, "%s:%d: ", file, line)
lines := strings.Split(s, "\n")
+ if l := len(lines); l > 1 && lines[l-1] == "" {
+ lines = lines[:l-1]
+ }
for i, line := range lines {
if i > 0 {
buf.WriteByte('\n')
@@ -172,10 +175,7 @@ func decorate(s string) string {
}
buf.WriteString(line)
}
- if l := len(s); l > 0 && s[len(s)-1] != '\n' {
- // Add final new line if needed.
- buf.WriteByte('\n')
- }
+ buf.WriteByte('\n')
return buf.String()
}
コアとなるコードの解説
追加されたコード
if l := len(lines); l > 1 && lines[l-1] == "" {
lines = lines[:l-1]
}
このコードブロックは、strings.Split
によって生成されたlines
スライスの末尾に空文字列が存在する場合(これは元の文字列が改行で終わっていることを意味します)、その空文字列の要素をスライスから除去します。これにより、余分な改行とインデントがログ出力に追加されるのを防ぎます。
削除されたコード
if l := len(s); l > 0 && s[len(s)-1] != '\n' {
// Add final new line if needed.
buf.WriteByte('\n')
}
このコードブロックは、入力文字列s
が改行で終わっていない場合にのみ、末尾に改行を追加していました。この条件分岐は削除され、代わりに以下のコードが追加されました。
変更後のコード(末尾の改行処理)
buf.WriteByte('\n')
この行は、decorate
関数が処理するすべてのログメッセージの末尾に、無条件に改行文字を追加します。これは、t.Log
の出力が常に改行で終わるというtesting
パッケージの意図をシンプルに実現するためのものです。前述の末尾の空行除去のロジックと組み合わせることで、期待されるクリーンな出力が得られます。
関連リンク
- Go言語
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語
strings
パッケージのドキュメント: https://pkg.go.dev/strings - Go言語
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - このコミットのGo Gerritレビューページ: https://golang.org/cl/6613069
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/51310d832027f0660098f5f809dc587f0a8b8f9c
- Go言語の公式ドキュメント
- Go言語のソースコード
strings.Split
の挙動に関する一般的な情報