[インデックス 13557] ファイルの概要
本コミットは、Go言語の実験的なHTMLパーサーパッケージ exp/html
におけるテストの挙動を修正するものです。具体的には、テストケースの入力処理において、末尾の改行文字の扱いをより厳密にすることで、これまで失敗していた2つのテストケースが正しくパスするように改善しています。
コミット
commit 2f39a33b6a34cbc71ded902053cbd10447b073dc
Author: Andrew Balholm <andybalholm@gmail.com>
Date: Fri Aug 3 09:31:45 2012 +1000
exp/html: in parse tests, discard only one trailing newline
Pass 2 additional tests.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6454090
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2f39a33b6a34cbc71ded902053cbd10447b073dc
元コミット内容
exp/html: in parse tests, discard only one trailing newline
Pass 2 additional tests.
このコミットは、exp/html
パッケージのパーステストにおいて、末尾の改行文字を1つだけ破棄するように変更し、それによって2つの追加テストがパスするようになったことを示しています。
変更の背景
HTMLパーサーのテストは、非常に厳密な入力と期待される出力に基づいて行われます。特に、空白文字や改行文字の扱いは、パース結果に大きな影響を与える可能性があります。
このコミットが行われる前は、exp/html
パッケージのテストユーティリティが、テストケースの入力文字列から末尾の改行文字を「すべて」削除する strings.TrimRight
関数を使用していました。しかし、特定のテストケースでは、入力データの一部として意図的に複数の改行が含まれていたり、あるいはテストファイルのフォーマットとして末尾に単一の改行が存在することが期待されていた可能性があります。
strings.TrimRight(s, "\n")
は、文字列 s
の末尾から連続するすべての改行文字を削除します。例えば、"foo\n\n"
は "foo"
に、"foo\n"
も "foo"
になります。この挙動が、テストケースの意図と合致しない場合、本来パスすべきテストが失敗するという問題が発生していました。
本コミットは、この過剰な改行削除が原因で失敗していた2つのテストケースを修正するために導入されました。テスト入力の処理方法を、末尾に改行が存在する場合に「1つだけ」削除するように変更することで、テストの意図に沿った正確な入力がパーサーに渡されるようになり、結果としてテストがパスするようになりました。
前提知識の解説
- Go言語の
exp/html
パッケージ: これはGo言語の標準ライブラリの一部として提供される前の、実験的なHTMLパーサーパッケージです。HTML5の仕様に準拠したHTMLドキュメントのパース機能を提供します。現在ではgolang.org/x/net/html
が推奨されていますが、このコミット当時はexp/html
が開発中でした。 - HTMLパース: HTMLパースとは、HTMLドキュメントのテキストデータを読み込み、それをブラウザがレンダリングできるような構造化されたデータ(通常はDOMツリー)に変換するプロセスです。このプロセスには、トークン化(文字ストリームを意味のある単位であるトークンに分割する)とツリー構築(トークンからDOMツリーを構築する)が含まれます。空白文字や改行文字の扱いは、HTMLの仕様上、非常に複雑であり、パーサーの正確性に直結します。
- Go言語の
strings
パッケージ: Go言語の標準ライブラリの一部で、文字列操作のためのユーティリティ関数を提供します。strings.TrimRight(s, cutset string)
: 文字列s
の末尾から、cutset
に含まれる文字をすべて削除した新しい文字列を返します。例えば、strings.TrimRight("hello\n\n", "\n")
は"hello"
を返します。strings.HasSuffix(s, suffix string)
: 文字列s
がsuffix
で終わる場合にtrue
を返します。
- Go言語のテスト: Go言語では、
_test.go
というサフィックスを持つファイルにテストコードを記述します。go test
コマンドで実行され、テストの成功/失敗がログに出力されます。本コミットで変更されているtestlogs/*.dat.log
ファイルは、これらのテスト実行結果を記録したログファイルであると考えられます。
技術的詳細
本コミットの核心は、src/pkg/exp/html/parse_test.go
ファイル内の readParseTest
関数における文字列処理の変更です。この関数は、HTMLパーステストの入力データを読み込む役割を担っています。
変更前は、入力されたバイトスライス b
を文字列に変換した後、strings.TrimRight(string(b), "\n")
を用いて末尾の改行文字をすべて削除していました。
// 変更前
text = strings.TrimRight(string(b), "\n")
この TrimRight
の挙動が、特定のテストケースで問題を引き起こしていました。テストデータによっては、入力の末尾に単一の改行が存在することが期待されており、それがテストフレームワークによって削除されるべき「フォーマット上の改行」なのか、それともパーサーが処理すべき「コンテンツの一部としての改行」なのかの区別が曖昧になっていました。TrimRight
は後者の場合でも改行を削除してしまい、パーサーに渡される入力がテストの意図と異なるものになっていたのです。
変更後は、この挙動が以下のように修正されました。
// 変更後
text = string(b)
if strings.HasSuffix(text, "\n") {
text = text[:len(text)-1]
}
この新しいロジックでは、まずバイトスライス b
をそのまま文字列 text
に変換します。次に、strings.HasSuffix(text, "\n")
を使って、文字列が少なくとも1つの改行文字で終わっているかを確認します。もし終わっていれば、text[:len(text)-1]
を使って、文字列の末尾から「1つだけ」改行文字を削除します。
この変更により、例えば入力が "foo\n\n"
であった場合、変更前は "foo"
になっていたのが、変更後は "foo\n"
となります。これにより、テストケースが期待する正確な入力がパーサーに渡されるようになり、これまで失敗していたテストがパスするようになりました。
src/pkg/exp/html/testlogs/tests16.dat.log
と src/pkg/exp/html/testlogs/tests26.dat.log
の変更は、この修正が実際にテスト結果に与えた影響を示しています。これらのログファイルでは、以前 FAIL
となっていた特定のテストケースが、末尾に \n
が追加された状態で PASS
に変更されています。これは、テスト入力の処理方法が変更された結果、パーサーが期待通りの出力を生成できるようになったことを明確に示しています。
コアとなるコードの変更箇所
src/pkg/exp/html/parse_test.go
ファイルの readParseTest
関数内。
--- a/src/pkg/exp/html/parse_test.go
+++ b/src/pkg/exp/html/parse_test.go
@@ -45,7 +45,10 @@ func readParseTest(r *bufio.Reader) (text, want, context string, err error) {
}
b = append(b, line...)
}
- text = strings.TrimRight(string(b), "\n")
+ text = string(b)
+ if strings.HasSuffix(text, "\n") {
+ text = text[:len(text)-1]
+ }
b = b[:0]
// Skip the error list.
また、テストログファイルも変更されています。
src/pkg/exp/html/testlogs/tests16.dat.log
--- a/src/pkg/exp/html/testlogs/tests16.dat.log
+++ b/src/pkg/exp/html/testlogs/tests16.dat.log
@@ -186,6 +186,6 @@ PASS "<iframe><!--<iframe></iframe>--></iframe>"
PASS "<iframe>...<!--X->...<!--/X->...</iframe>"
PASS "<xmp><!--<xmp></xmp>--></xmp>"
PASS "<noembed><!--<noembed></noembed>--></noembed>"
-FAIL "<!doctype html><table>"
+PASS "<!doctype html><table>\n"
PASS "<!doctype html><table><td><span><font></span><span>"
PARSE "<!doctype html><form><table></form><form></table></form>"
src/pkg/exp/html/testlogs/tests26.dat.log
--- a/src/pkg/exp/html/testlogs/tests26.dat.log
+++ b/src/pkg/exp/html/testlogs/tests26.dat.log
@@ -7,7 +7,7 @@ PASS "<!DOCTYPE html><body><b><nobr>1<nobr></b><div><i><nobr>2<nobr></i>3"
PASS "<!DOCTYPE html><body><b><nobr>1<nobr><ins></b><i><nobr>"
PASS "<!DOCTYPE html><body><b><nobr>1<ins><nobr></b><i>2"
-FAIL "<p><code x</code></p>"
+PASS "<p><code x</code></p>\n"
PASS "<!DOCTYPE html><svg><foreignObject><p><i></p>a"
PASS "<!DOCTYPE html><table><tr><td><svg><foreignObject><p><i></p>a"
PASS "<!DOCTYPE html><math><mtext><p><i></p>a"
コアとなるコードの解説
readParseTest
関数は、テストデータファイルからHTMLの入力文字列を読み取る際に使用されます。
変更前のコード text = strings.TrimRight(string(b), "\n")
は、読み込んだバイト列 b
を文字列に変換し、その文字列の末尾から連続するすべての改行文字 (\n
) を削除していました。これは、テストデータファイルの末尾に余分な改行が複数あっても、それらをすべて取り除くことを意図していました。
しかし、この「すべて削除する」という挙動が、特定のテストケースで問題を引き起こしました。テストケースによっては、入力の末尾に単一の改行が存在することが、テストの期待する入力の一部として意図されていた可能性があります。TrimRight
はそのような単一の改行も削除してしまい、パーサーに渡される入力がテストの意図と異なっていました。
変更後のコードは、この問題を解決するために、よりきめ細やかな改行処理を導入しています。
text = string(b)
: まず、読み込んだバイト列b
をそのまま文字列text
に変換します。この時点では、末尾の改行はすべて保持されています。if strings.HasSuffix(text, "\n")
: 次に、text
が少なくとも1つの改行文字 (\n
) で終わっているかどうかを確認します。text = text[:len(text)-1]
: もしtext
が改行で終わっていれば、文字列の最後の1文字(つまり、末尾の改行文字)だけを削除します。
このロジックにより、入力文字列の末尾に複数の改行があったとしても、削除されるのは「最後の1つだけ」になります。例えば、入力が "foo\n\n"
であれば、text
は "foo\n"
になります。入力が "foo\n"
であれば、text
は "foo"
になります。これにより、テストケースが期待する正確な入力がパーサーに渡されるようになり、これまで失敗していたテストが正しくパスするようになりました。
テストログファイルの変更は、このコード変更が実際にテストの成功に繋がったことを示しています。FAIL
から PASS
への変更と、期待される出力に \n
が追加されていることは、パーサーが改行を含む入力を正しく処理できるようになったことを裏付けています。
関連リンク
- Go言語の
exp/html
パッケージに関するGerritの変更リスト: https://golang.org/cl/6454090 - Go言語のHTMLパーサーに関する現在の公式ドキュメント: https://pkg.go.dev/golang.org/x/net/html
参考にした情報源リンク
- Go言語の
strings
パッケージ公式ドキュメント: https://pkg.go.dev/strings - HTML5仕様 (空白文字の処理に関するセクション): https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody (一般的なHTMLパースの背景知識として)
- Go言語のテストに関する公式ドキュメント: https://go.dev/doc/code#testing