[インデックス 10990] ファイルの概要
このコミットは、Go言語の標準ライブラリtesting
パッケージにおけるgodoc
の出力改善を目的としたものです。具体的には、godoc
がエクスポートされていない埋め込みフィールドのメソッドを適切に表示しないという当時の問題に対する一時的な回避策として、ラッパーメソッドを追加しています。
コミット
commit 416afcb411d7b2fe59d38257bcfe0df3a903919e
Author: Rob Pike <r@golang.org>
Date: Thu Dec 22 17:17:19 2011 -0800
testing: add wrapper methods so the godoc output lists all methods
To be deleted when godoc catches up.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5504079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/416afcb411d7b2fe59d38257bcfe0df3a903919e
元コミット内容
testing: add wrapper methods so the godoc output lists all methods
To be deleted when godoc catches up.
このコミットは、godoc
の出力がすべてのメソッドをリストするように、ラッパーメソッドを追加します。これはgodoc
が改善された際に削除される予定の一時的な変更です。
変更の背景
Go言語のtesting
パッケージには、テスト(*T
)やベンチマーク(*B
)の実行中にエラー報告やログ出力を行うための様々なメソッド(例: Error
, Errorf
, Log
, Logf
, Fail
, FailNow
など)が提供されています。これらのメソッドは、common
という非公開(アンエクスポート)な構造体を*T
や*B
に埋め込むことで実装されていました。
当時のgodoc
ツールには、非公開の埋め込みフィールドを通じて提供されるメソッドを、その埋め込み先の型(この場合は*T
や*B
)の公開メソッドとして適切にドキュメントに表示しないという問題がありました。これにより、開発者がgodoc
を使ってtesting
パッケージのドキュメントを参照した際に、*T
や*B
が提供する重要なメソッド群が欠落して見えてしまうというユーザビリティ上の課題がありました。
このコミットは、godoc
のこの挙動が修正されるまでの間、一時的な回避策として、*T
と*B
のそれぞれに、common
フィールドのメソッドを直接呼び出す公開ラッパーメソッドを追加することで、godoc
がこれらのメソッドを正しく認識し、ドキュメントに表示するようにすることを目的としています。コミットメッセージにある「To be deleted when godoc catches up.」という記述は、この変更が一時的なものであることを明確に示しています。
前提知識の解説
Go言語の埋め込み(Embedding)
Go言語では、構造体の中に別の構造体を「埋め込む」ことができます。これは、他の言語における継承に似た機能を提供しますが、Goでは「コンポジション(合成)」として扱われます。埋め込まれた構造体のフィールドやメソッドは、埋め込み先の構造体のフィールドやメソッドであるかのように直接アクセスできます。
例:
type Common struct {
name string
}
func (c *Common) Log(msg string) {
fmt.Println(c.name + ": " + msg)
}
type MyType struct {
Common // Common構造体を埋め込み
id int
}
func main() {
m := MyType{Common: Common{name: "MyInstance"}, id: 1}
m.Log("Hello") // MyTypeのインスタンスからCommonのLogメソッドを直接呼び出せる
}
この場合、MyType
はCommon
のLog
メソッドを「継承」しているように見えます。しかし、godoc
は、埋め込まれたフィールドが非公開(小文字で始まる)である場合、そのフィールドのメソッドを埋め込み先の型の公開メソッドとして認識しないという問題がありました。testing
パッケージのcommon
フィールドがまさにこのケースに該当していました。
godocツール
godoc
は、Go言語のソースコードからドキュメントを生成するためのツールです。Goのコードは、コメントの書き方によって自動的にドキュメントとして抽出され、Webブラウザで閲覧可能な形式で提供されます。godoc
は、Goの標準的なドキュメンテーションシステムの中核をなすものであり、開発者がライブラリやパッケージのAPIを理解するために不可欠なツールです。
runtime.Caller
runtime.Caller
は、Goのruntime
パッケージが提供する関数で、現在のゴルーチンのコールスタックに関する情報を取得するために使用されます。
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
skip
引数は、スタックフレームをスキップする数を指定します。skip=0
はCaller
自身の呼び出し元、skip=1
はCaller
を呼び出した関数、といった具合です。
testing
パッケージのdecorate
関数では、エラーメッセージにファイル名と行番号を含めるためにruntime.Caller
を使用しています。このコミットでは、ラッパーメソッドが追加されたことでコールスタックの深さが変わり、正しい呼び出し元の情報を取得するためにskip
の値が調整されています。
技術的詳細
このコミットの主要な変更は、src/pkg/testing/wrapper.go
という新しいファイルを追加し、testing.go
内のdecorate
関数のruntime.Caller
の引数を変更し、Makefile
を更新することです。
src/pkg/testing/wrapper.go
の追加
このファイルは、*B
(ベンチマーク)と*T
(テスト)のそれぞれに対して、common
構造体に埋め込まれたメソッド(Fail
, Failed
, FailNow
, Log
, Logf
, Error
, Errorf
, Fatal
, Fatalf
)の公開ラッパーメソッドを定義しています。
例: *B
のFail
メソッドのラッパー
// Fail marks the function as having failed but continues execution.
func (b *B) Fail() {
b.common.Fail()
}
これらのラッパーメソッドは、単にb.common.Fail()
のように、内部のcommon
フィールドの対応するメソッドを呼び出すだけです。これにより、godoc
は*B.Fail()
や*T.Error()
といった公開メソッドとしてこれらを認識し、ドキュメントに表示できるようになります。
src/pkg/testing/testing.go
の変更
decorate
関数は、エラーメッセージにファイル名と行番号を追加するために使用されます。この関数内でruntime.Caller
が呼び出され、コールスタックを遡って呼び出し元の情報を取得します。
変更前:
_, file, line, ok := runtime.Caller(3) // decorate + log + public function.
変更後:
_, file, line, ok := runtime.Caller(4) // decorate + log + public function.
runtime.Caller
のskip
引数が3
から4
に変更されています。これは、wrapper.go
で追加されたラッパーメソッドがコールスタックに1層追加されたため、正しい呼び出し元の情報を取得するためには、さらに1つスタックフレームをスキップする必要があるためです。
src/pkg/testing/Makefile
の変更
新しいファイルwrapper.go
が追加されたため、testing
パッケージをビルドする際にこのファイルがコンパイル対象に含まれるようにMakefile
が更新されています。
変更前:
GOFILES=\
benchmark.go\
example.go\
testing.go\
変更後:
GOFILES=\
benchmark.go\
example.go\
testing.go\
wrapper.go\
wrapper.go
がGOFILES
リストに追加され、ビルドシステムがこの新しいソースファイルを認識するようになります。
コアとなるコードの変更箇所
src/pkg/testing/Makefile
--- a/src/pkg/testing/Makefile
+++ b/src/pkg/testing/Makefile
@@ -6,8 +6,9 @@ include ../../Make.inc
TARG=testing
GOFILES=\
- benchmark.go\\\
- example.go\\\
+\tbenchmark.go\\\
+\texample.go\\\
\ttesting.go\\\
+\twrapper.go\\\
include ../../Make.pkg
src/pkg/testing/testing.go
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -90,7 +90,7 @@ func Short() bool {
// If addFileLine is true, it also prefixes the string with the file and line of the call site.
func decorate(s string, addFileLine bool) string {\
if addFileLine {\
-\t\t_, file, line, ok := runtime.Caller(3) // decorate + log + public function.\
+\t\t_, file, line, ok := runtime.Caller(4) // decorate + log + public function.\
\t\tif ok {\
\t\t\t// Truncate file name at last file name separator.\
\t\t\tif index := strings.LastIndex(file, \"/\"); index >= 0 {\
src/pkg/testing/wrapper.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.
// This file contains wrappers so t.Errorf etc. have documentation.
// TODO: delete when godoc shows exported methods for unexported embedded fields.
// TODO: need to change the argument to runtime.Caller in testing.go from 4 to 3 at that point.
package testing
// Fail marks the function as having failed but continues execution.
func (b *B) Fail() {
b.common.Fail()
}
// Failed returns whether the function has failed.
func (b *B) Failed() bool {
return b.common.Failed()
}
// FailNow marks the function as having failed and stops its execution.
// Execution will continue at the next Test.
func (b *B) FailNow() {
b.common.FailNow()
}
// Log formats its arguments using default formatting, analogous to Println(),
// and records the text in the error log.
func (b *B) Log(args ...interface{}) {
b.common.Log(args...)
}
// Logf formats its arguments according to the format, analogous to Printf(),
// and records the text in the error log.
func (b *B) Logf(format string, args ...interface{}) {
b.common.Logf(format, args...)
}
// Error is equivalent to Log() followed by Fail().
func (b *B) Error(args ...interface{}) {
b.common.Error(args...)
}
// Errorf is equivalent to Logf() followed by Fail().
func (b *B) Errorf(format string, args ...interface{}) {
b.common.Errorf(format, args...)
}
// Fatal is equivalent to Log() followed by FailNow().
func (b *B) Fatal(args ...interface{}) {
b.common.Fatal(args...)
}
// Fatalf is equivalent to Logf() followed by FailNow().
func (b *B) Fatalf(format string, args ...interface{}) {
b.common.Fatalf(format, args...)
}
// Fail marks the function as having failed but continues execution.
func (t *T) Fail() {
t.common.Fail()
}
// Failed returns whether the function has failed.
func (t *T) Failed() bool {
return t.common.Failed()
}
// FailNow marks the function as having failed and stops its execution.
// Execution will continue at the next Test.
func (t *T) FailNow() {
t.common.FailNow()
}
// Log formats its arguments using default formatting, analogous to Println(),
// and records the text in the error log.
func (t *T) Log(args ...interface{}) {
t.common.Log(args...)
}
// Logf formats its arguments according to the format, analogous to Printf(),
// and records the text in the error log.
func (t *T) Logf(format string, args ...interface{}) {
t.common.Logf(format, args...)
}
// Error is equivalent to Log() followed by Fail().
func (t *T) Error(args ...interface{}) {
t.common.Error(args...)
}
// Errorf is equivalent to Logf() followed by Fail().
func (t *T) Errorf(format string, args ...interface{}) {
t.common.Errorf(format, args...)
}
// Fatal is equivalent to Log() followed by FailNow().
func (t *T) Fatal(args ...interface{}) {
t.common.Fatal(args...)
}
// Fatalf is equivalent to Logf() followed by FailNow().
func (t *T) Fatalf(format string, args ...interface{}) {
t.common.Fatalf(format, args...)
}
コアとなるコードの解説
このコミットの核心は、godoc
の制限を回避するために、testing
パッケージの*T
と*B
型に明示的なラッパーメソッドを追加した点にあります。
testing
パッケージでは、*T
と*B
の構造体は、common
という非公開のフィールドを埋め込んでいます。このcommon
フィールドは、テストやベンチマークの実行中に共通のロジック(エラー報告、ログ記録など)を処理するためのメソッド群(例: Fail
, Log
, Error
など)を保持しています。Goの埋め込みの仕組みにより、*T
や*B
のインスタンスからt.Log()
のように直接これらのメソッドを呼び出すことができます。
しかし、当時のgodoc
は、非公開の埋め込みフィールドを通じて提供されるメソッドを、埋め込み先の型の公開APIとして適切にドキュメント化しませんでした。このため、開発者がgodoc
でtesting
パッケージのドキュメントを見た際に、*T
や*B
が提供する重要なメソッド群がリストされず、APIの全貌を把握することが困難でした。
wrapper.go
で追加されたラッパーメソッドは、この問題を解決するための一時的な手段です。これらのラッパーは、*T
や*B
の公開メソッドとして定義され、内部的には単にcommon
フィールドの対応するメソッドを呼び出すだけです。これにより、godoc
はこれらのラッパーメソッドを公開APIとして認識し、ドキュメントに含めることができるようになります。
testing.go
のdecorate
関数におけるruntime.Caller
の引数変更は、このラッパーの追加に伴う副作用への対応です。decorate
関数は、エラーメッセージのソースコード位置を特定するためにコールスタックを遡ります。ラッパーメソッドが導入されたことで、実際の呼び出し元とdecorate
関数の間にラッパーメソッドのスタックフレームが1つ追加されるため、runtime.Caller
のskip
引数を3
から4
に増やすことで、正しい呼び出し元のファイルと行番号を取得できるように調整されました。
この変更は、godoc
の機能が改善され、非公開の埋め込みフィールドのメソッドを適切にドキュメント化できるようになれば、削除される予定の一時的な解決策として位置づけられています。
関連リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語の
runtime
パッケージのドキュメント: https://pkg.go.dev/runtime - Go言語の埋め込みに関する公式ブログ記事 (Go 1.0以前の議論): https://go.dev/blog/go-programming-language-faq#inheritance (直接的な言及はないが、埋め込みの概念を理解するのに役立つ)
参考にした情報源リンク
- Go言語の公式ドキュメント
godoc
ツールの動作に関する一般的な知識runtime.Caller
の挙動に関する一般的な知識- Go言語の埋め込みに関する一般的な知識
- コミットメッセージとコードの変更点
- Go言語のIssueトラッカーやメーリングリストでの関連議論 (具体的なリンクはコミットメッセージの
https://golang.org/cl/5504079
から辿れる可能性あり) - Go言語のソースコードリポジトリI have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is now ready.