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

[インデックス 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型のメソッドであるLogLogfが使用されます。これらのメソッドは、テストの実行中に標準出力にメッセージを記録します。

このコミットが修正しようとしている問題は、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.Errort.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.Logt.Logfによって出力されるメッセージを整形する役割を担っています。具体的には、ログメッセージの先頭にファイル名と行番号を追加し、複数行のメッセージを適切にインデントして出力するロジックが含まれています。

修正前のdecorate関数には、以下の問題がありました。

  1. 末尾の空行の扱い: strings.Split(s, "\n")は、文字列が改行で終わっている場合、最後の要素として空文字列を返します。例えば、"line1\nline2\n"\nで分割すると、["line1", "line2", ""]というスライスが生成されます。修正前は、この末尾の空文字列も通常の行として処理され、余分なインデントと改行が追加されていました。
  2. 無条件の末尾改行追加: 修正前は、入力文字列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パッケージの意図をシンプルに実現するためのものです。前述の末尾の空行除去のロジックと組み合わせることで、期待されるクリーンな出力が得られます。

関連リンク

参考にした情報源リンク