[インデックス 12096] ファイルの概要
このコミットは、Go言語プロジェクトのテストインフラストラクチャにおける重要な変更を示しています。具体的には、既存のtest/run
シェルスクリプトとerrchk
(Perlスクリプト)をGo言語で書き直したtest/run.go
という新しいファイルを追加しています。これにより、テスト実行の効率化とGoエコシステム内での一貫性向上を目指しています。
コミット
commit ce837b308f1f05ff002fb3d2d869d7bf6a778799
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Feb 21 14:28:49 2012 +1100
test: rewrite test/run shell script + errchk (perl) in Go
This doesn't run all ~750 of the tests, but most.
Progress on issue 2833
R=golang-dev, ality, rsc, r, r
CC=golang-dev
https://golang.org/cl/5625044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ce837b308f1f05ff002fb3d2d869d7bf6a778799
元コミット内容
test: rewrite test/run shell script + errchk (perl) in Go
このコミットは、test/run
シェルスクリプトとerrchk
(Perlスクリプト)をGo言語で書き直すものです。
これにより、約750あるテストの大部分が実行されます。
これは、Issue 2833の進捗です。
変更の背景
この変更の背景には、Go言語プロジェクトのテストインフラストラクチャの改善という明確な目的があります。コミットメッセージに記載されている「Issue 2833」は、GoプロジェクトのIssueトラッカーで「cmd/go: make 'go test' run the test suite」というタイトルで登録されています。このIssueは、Goのテストスイートをgo test
コマンドで実行できるようにすることを目指しており、その一環として、既存のシェルスクリプトやPerlスクリプトに依存していたテスト実行ロジックをGo言語自体で実装し直す必要がありました。
従来のテスト実行は、シェルスクリプト(test/run
)とPerlスクリプト(errchk
)に依存していました。これらのスクリプトは、Go言語のテストフレームワークとは異なる外部ツールであり、以下のような課題を抱えていました。
- 依存関係の複雑さ: シェルやPerlといった外部の実行環境に依存するため、テスト環境のセットアップが複雑になる可能性がありました。
- メンテナンス性: Go言語のプロジェクトでありながら、テストロジックが異なる言語で書かれているため、Go開発者にとってのメンテナンスコストが高くなる傾向がありました。
- パフォーマンス: シェルスクリプトやPerlスクリプトは、Go言語で直接実装された場合と比較して、テストの実行速度やリソース効率の面で劣る可能性がありました。
- Goエコシステムとの統合: Go言語の標準的なツールやライブラリとの連携が難しく、テストインフラ全体の統一感が損なわれていました。
これらの課題を解決し、Go言語のテストスイートをより堅牢で、メンテナンスしやすく、効率的なものにするために、テスト実行ロジックをGo言語で再実装することが決定されました。このコミットは、その取り組みの重要な一歩となります。
前提知識の解説
このコミットを理解するためには、以下のGo言語および一般的なプログラミングに関する知識が役立ちます。
- Go言語の基本的な構文と構造:
package main
,import
,func
,var
,struct
,interface
,channel
などの基本的な要素。 - Goの標準ライブラリ:
os
,os/exec
: プロセス実行、環境変数、ファイルシステム操作。io/ioutil
: ファイルの読み書き。path/filepath
: パス操作。bytes
: バイトスライス操作。strings
: 文字列操作。regexp
: 正規表現。fmt
: フォーマット済みI/O。log
: ロギング。flag
: コマンドライン引数のパース。runtime
: Goランタイムとのインタラクション(例:runtime.GOARCH
)。sort
: ソートアルゴリズム。strconv
: 文字列と数値の変換。errors
: エラーハンドリング。go/build
: Goのビルドシステムに関する情報(例:build.ArchChar
)。
- Goのツールチェイン:
go tool gc
: Goコンパイラ(gc
はGo Compilerの略。アーキテクチャによって6g
,8g
などとなる)。go tool ld
: Goリンカ(ld
はLinkerの略。アーキテクチャによって6l
,8l
などとなる)。- Goのビルドプロセス(コンパイル、リンク、実行)。
- シェルスクリプトとPerlスクリプト: 従来のテスト実行に使われていたスクリプト言語の基本的な役割と、それらがどのようにGoプログラムの実行を制御していたか。
- 正規表現:
errchk
の機能や、test/run.go
内のエラーチェックロジックで正規表現がどのように使われているか。 - 並行処理: Goのgoroutineとchannelを用いた並行テスト実行の概念。
ratec
(レートリミッター)とtoRun
(テストキュー)の役割。 - テスト駆動開発 (TDD) / テストの重要性: ソフトウェア開発におけるテストの役割と、テストスイートがプロジェクトの健全性を保つ上でいかに重要か。
技術的詳細
test/run.go
は、Go言語で書かれたテストランナーであり、従来のシェルスクリプトとPerlスクリプトの機能を置き換えるものです。その主要な機能と技術的詳細は以下の通りです。
-
テストの発見と実行:
main
関数は、コマンドライン引数で指定されたGoファイル、またはdirs
変数に定義されたディレクトリ(.
,ken
,chan
,interface
,syntax
,dwarf
,fixedbugs
,bugs
)内のすべての.go
ファイルをテスト対象として検出します。- 各Goファイルは
test
構造体として表現され、startTest
関数によってテストキュー(toRun
チャネル)に追加されます。 runTests
goroutineがtoRun
チャネルからテストを取り出し、並行して実行します。
-
並行テスト実行:
numParallel
フラグ(デフォルト8)によって、同時に実行されるテストの最大数が制御されます。ratec
チャネルは、この並行数を制限するためのセマフォとして機能します。テストが開始される前にratec <- true
でトークンを取得し、テスト完了後に<-ratec
でトークンを解放します。verbose
フラグが設定されている場合、並行数は1に設定され、テストが逐次実行されます。
-
テストの種類とアクション:
- 各テストGoファイルの先頭には、
// compile
,// build
,// run
,// errorcheck
のいずれかのアクションがコメントとして記述されています。 test.run()
メソッドは、このアクションを解析し、それに応じた処理を実行します。compile
: Goコンパイラ(go tool gc
)を使用してファイルをコンパイルします。build
: コンパイル後、Goリンカ(go tool ld
)を使用して実行可能ファイルをビルドします。run
: ビルド後、生成された実行可能ファイルを実際に実行し、その標準出力が期待される出力(.out
ファイルに記述)と一致するかを検証します。errorcheck
: コンパイル時のエラーメッセージを解析し、テストファイル内に記述された期待されるエラーパターン(// ERROR "..."
)と一致するかを検証します。これは従来のerrchk
Perlスクリプトの機能をGoで再実装したものです。
- 各テストGoファイルの先頭には、
-
エラーチェック (
errorcheck
アクション):test.errorCheck(outStr string)
関数がこのロジックを実装しています。- コンパイラの出力(
outStr
)を解析し、各エラーメッセージがどのファイル、どの行で発生したかを特定します。 - テストファイル内の
// ERROR "regexp"
形式のコメントを読み取り、期待されるエラーメッセージの正規表現パターンを抽出します。 wantedError
構造体は、期待されるエラーの正規表現、行番号、ファイル名、そしてエラーメッセージをフィルタリングするための正規表現を保持します。partitionStrings
関数は、コンパイラ出力から特定のエラーメッセージを抽出し、残りのメッセージと分離します。- 抽出されたエラーメッセージが期待される正規表現パターンと一致するかを検証します。一致しない場合や、期待されるエラーが見つからない場合はエラーとして報告されます。
LINE
キーワード(例:LINE+1
,LINE-2
)を使用して、エラーが報告される行番号を相対的に指定できる機能も含まれています。
-
一時ディレクトリの利用:
- 各テストは、
ioutil.TempDir
を使用して作成される一時ディレクトリ内で実行されます。これにより、テスト間の干渉を防ぎ、クリーンな環境でテストを実行できます。テスト完了後、この一時ディレクトリは削除されます。
- 各テストは、
-
Goツールチェインの利用:
toolPath
関数は、GOROOT
環境変数とgo tool
コマンドの慣習に従って、gc
(コンパイラ)やld
(リンカ)などのGoツールへのパスを解決します。os/exec.Command
を使用して、これらのGoツールをサブプロセスとして実行します。
-
結果の集計と表示:
- すべてのテストが完了した後、
main
関数はテスト結果(成功、失敗、スキップ)を集計し、summary
フラグが設定されていればその概要を表示します。 - テストが一つでも失敗した場合、プログラムは終了コード1で終了し、CI/CDシステムなどでのテスト失敗を通知します。
- すべてのテストが完了した後、
このtest/run.go
の実装は、Go言語の標準ライブラリを効果的に活用し、GoプロジェクトのテストインフラをGo言語自体で完結させるという目標を達成しています。特に、正規表現を用いたエラーチェックロジックは、従来のPerlスクリプトの複雑な機能をGoで簡潔に再実装している点が注目されます。
コアとなるコードの変更箇所
このコミットでは、test/run.go
という新しいファイルが追加されています。このファイル全体がコアとなる変更箇所です。
diff --git a/test/run.go b/test/run.go
new file mode 100644
index 0000000000..67ff413717
--- /dev/null
+++ b/test/run.go
@@ -0,0 +1,454 @@
+// #ignore
+
+// Copyright 2012 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.
+
+// Run runs tests in the test directory.
+//
+// TODO(bradfitz): docs of some sort, once we figure out how we're changing
+// headers of files
+package main
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "go/build"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+var (
+ verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.")
+ numParallel = flag.Int("n", 8, "number of parallel tests to run")
+ summary = flag.Bool("summary", false, "show summary of results")
+)
+
+var (
+ // gc and ld are [568][gl].
+ gc, ld string
+
+ // letter is the build.ArchChar
+ letter string
+
+ // dirs are the directories to look for *.go files in.
+ // TODO(bradfitz): just use all directories?
+ dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"}
+
+ // ratec controls the max number of tests running at a time.
+ ratec chan bool
+
+ // toRun is the channel of tests to run.
+ // It is nil until the first test is started.
+ toRun chan *test
+)
+
+// maxTests is an upper bound on the total number of tests.
+// It is used as a channel buffer size to make sure sends don't block.
+const maxTests = 5000
+
+func main() {
+ flag.Parse()
+ if *verbose {
+ *numParallel = 1
+ }
+
+ ratec = make(chan bool, *numParallel)
+ var err error
+ letter, err = build.ArchChar(build.DefaultContext.GOARCH)
+ check(err)
+ gc = letter + "g"
+ ld = letter + "l"
+
+ var tests []*test
+ if flag.NArg() > 0 {
+ for _, arg := range flag.Args() {
+ if arg == "-" || arg == "--" {
+ // Permit running either:
+ // $ go run run.go - env.go
+ // $ go run run.go -- env.go
+ continue
+ }
+ if !strings.HasSuffix(arg, ".go") {
+ log.Fatalf("can't yet deal with non-go file %q", arg)
+ }
+ dir, file := filepath.Split(arg)
+ tests = append(tests, startTest(dir, file))
+ }
+ } else {
+ for _, dir := range dirs {
+ for _, baseGoFile := range goFiles(dir) {
+ tests = append(tests, startTest(dir, baseGoFile))
+ }
+ }
+ }
+
+ failed := false
+ resCount := map[string]int{}
+ for _, test := range tests {
+ <-test.donec
+ _, isSkip := test.err.(skipError)
+ if isSkip {
+ resCount["skip"]++
+ if !*verbose {
+ continue
+ }
+ }
+ errStr := "pass"
+ if test.err != nil {
+ errStr = test.err.Error()
+ if !isSkip {
+ failed = true
+ }
+ }
+ resCount[errStr]++
+ if !*verbose && test.err == nil {
+ continue
+ }
+ fmt.Printf("%-10s %-20s: %s\\n", test.action, test.goFileName(), errStr)
+ }
+
+ if *summary {
+ for k, v := range resCount {
+ fmt.Printf("%5d %s\\n", v, k)
+ }
+ }
+
+ if failed {
+ os.Exit(1)
+ }
+}
+
+func toolPath(name string) string {
+ p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
+ if _, err := os.Stat(p); err != nil {
+ log.Fatalf("didn't find binary at %s", p)
+ }
+ return p
+}
+
+func goFiles(dir string) []string {
+ f, err := os.Open(dir)
+ check(err)
+ dirnames, err := f.Readdirnames(-1)
+ check(err)
+ names := []string{}
+ for _, name := range dirnames {
+ if strings.HasSuffix(name, ".go") {
+ names = append(names, name)
+ }
+ }
+ sort.Strings(names)
+ return names
+}
+
+// skipError describes why a test was skipped.
+type skipError string
+
+func (s skipError) Error() string { return string(s) }
+
+func check(err error) {
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// test holds the state of a test.
+type test struct {
+ dir, gofile string
+ donec chan bool // closed when done
+
+ src string
+ action string // "compile", "build", "run", "errorcheck"
+
+ tempDir string
+ err error
+}
+
+// startTest
+func startTest(dir, gofile string) *test {
+ t := &test{
+ dir: dir,
+ gofile: gofile,
+ donec: make(chan bool, 1),
+ }
+ if toRun == nil {
+ toRun = make(chan *test, maxTests)
+ go runTests()
+ }
+ select {
+ case toRun <- t:
+ default:
+ panic("toRun buffer size (maxTests) is too small")
+ }
+ return t
+}
+
+// runTests runs tests in parallel, but respecting the order they
+// were enqueued on the toRun channel.
+func runTests() {
+ for {
+ ratec <- true
+ t := <-toRun
+ go func() {
+ t.run()
+ <-ratec
+ }()
+ }
+}
+
+func (t *test) goFileName() string {
+ return filepath.Join(t.dir, t.gofile)
+}
+
+// run runs a test.
+func (t *test) run() {
+ defer close(t.donec)
+
+ srcBytes, err := ioutil.ReadFile(t.goFileName())
+ if err != nil {
+ t.err = err
+ return
+ }
+ t.src = string(srcBytes)
+ if t.src[0] == '\\n' {
+ t.err = skipError("starts with newline")
+ return
+ }
+ pos := strings.Index(t.src, "\\n\\n")
+ if pos == -1 {
+ t.err = errors.New("double newline not found")
+ return
+ }
+ action := t.src[:pos]
+ if strings.HasPrefix(action, "//") {
+ action = action[2:]
+ }
+ action = strings.TrimSpace(action)
+
+ switch action {
+ case "compile", "build", "run", "errorcheck":
+ t.action = action
+ default:
+ t.err = skipError("skipped; unknown pattern: " + action)
+ t.action = "??"
+ return
+ }
+
+ t.makeTempDir()
+ defer os.RemoveAll(t.tempDir)
+
+ err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644)
+ check(err)
+
+ cmd := exec.Command("go", "tool", gc, "-e", "-o", "a."+letter, t.gofile)
+ var buf bytes.Buffer
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.Dir = t.tempDir
+ err = cmd.Run()
+ out := buf.String()
+
+ if action == "errorcheck" {
+ t.err = t.errorCheck(out)
+ return
+ }
+
+ if err != nil {
+ t.err = fmt.Errorf("build = %v (%q)", err, out)
+ return
+ }
+
+ if action == "compile" {
+ return
+ }
+
+ if action == "build" || action == "run" {
+ buf.Reset()
+ cmd = exec.Command("go", "tool", ld, "-o", "a.out", "a."+letter)
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.Dir = t.tempDir
+ err = cmd.Run()
+ out = buf.String()
+ if err != nil {
+ t.err = fmt.Errorf("link = %v (%q)", err, out)
+ return
+ }
+ if action == "build" {
+ return
+ }
+ }
+
+ if action == "run" {
+ buf.Reset()
+ cmd = exec.Command(filepath.Join(t.tempDir, "a.out"))
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.Dir = t.tempDir
+ cmd.Env = append(cmd.Env, "GOARCH="+runtime.GOARCH)
+ err = cmd.Run()
+ out = buf.String()
+ if err != nil {
+ t.err = fmt.Errorf("run = %v (%q)", err, out)
+ return
+ }
+
+ if out != t.expectedOutput() {
+ t.err = fmt.Errorf("output differs; got:\\n%s", out)
+ }
+ return
+ }
+
+ t.err = fmt.Errorf("unimplemented action %q", action)
+}
+
+func (t *test) String() string {
+ return filepath.Join(t.dir, t.gofile)
+}
+
+func (t *test) makeTempDir() {
+ var err error
+ t.tempDir, err = ioutil.TempDir("", "")
+ check(err)
+}
+
+func (t *test) expectedOutput() string {
+ filename := filepath.Join(t.dir, t.gofile)
+ filename = filename[:len(filename)-len(".go")]
+ filename += ".out"
+ b, _ := ioutil.ReadFile(filename)
+ return string(b)
+}
+
+func (t *test) errorCheck(outStr string) (err error) {
+ defer func() {
+ if *verbose && err != nil {
+ log.Printf("%s gc output:\\n%s", t, outStr)
+ }
+ }()
+ var errs []error
+
+ var out []string
+ // 6g error messages continue onto additional lines with leading tabs.
+ // Split the output at the beginning of each line that doesn't begin with a tab.
+ for _, line := range strings.Split(outStr, "\\n") {
+ if strings.HasPrefix(line, "\\t") {
+ out[len(out)-1] += "\\n" + line
+ } else {
+ out = append(out, line)
+ }
+ }
+
+ for _, we := range t.wantedErrors() {
+ var errmsgs []string
+ errmsgs, out = partitionStrings(we.filterRe, out)
+ if len(errmsgs) == 0 {
+ errs = append(errs, fmt.Errorf("errchk: %s:%d: missing expected error: %s", we.file, we.lineNum, we.reStr))
+ continue
+ }
+ matched := false
+ for _, errmsg := range errmsgs {
+ if we.re.MatchString(errmsg) {
+ matched = true
+ } else {
+ out = append(out, errmsg)
+ }
+ }
+ if !matched {
+ errs = append(errs, fmt.Errorf("errchk: %s:%d: error(s) on line didn't match pattern: %s", we.file, we.lineNum, we.reStr))
+ continue
+ }
+ }
+
+ if len(errs) == 0 {
+ return nil
+ }
+ if len(errs) == 1 {
+ return errs[0]
+ }
+ var buf bytes.Buffer
+ buf.WriteString("Multiple errors:\\n")
+ for _, err := range errs {
+ fmt.Fprintf(&buf, "%s\\n", err.Error())
+ }
+ return errors.New(buf.String())
+
+}
+
+func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) {
+ for _, s := range strs {
+ if rx.MatchString(s) {
+ matched = append(matched, s)
+ } else {
+ unmatched = append(unmatched, s)
+ }
+ }
+ return
+}
+
+type wantedError struct {
+ reStr string
+ re *regexp.Regexp
+ lineNum int
+ file string
+ filterRe *regexp.Regexp // /^file:linenum\\b/m
+}
+
+var (
+ errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
+ errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
+ lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
+)
+
+func (t *test) wantedErrors() (errs []wantedError) {
+ for i, line := range strings.Split(t.src, "\\n") {
+ lineNum := i + 1
+ if strings.Contains(line, "////") {
+ // double comment disables ERROR
+ continue
+ }
+ m := errRx.FindStringSubmatch(line)
+ if m == nil {
+ continue
+ }
+ all := m[1]
+ mm := errQuotesRx.FindAllStringSubmatch(all, -1)
+ if mm == nil {
+ log.Fatalf("invalid errchk line in %s: %s", t.goFileName(), line)
+ }
+ for _, m := range mm {
+ rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
+ n := lineNum
+ if strings.HasPrefix(m, "LINE+") {
+ delta, _ := strconv.Atoi(m[5:])
+ n += delta
+ } else if strings.HasPrefix(m, "LINE-") {
+ delta, _ := strconv.Atoi(m[5:])
+ n -= delta
+ }
+ return fmt.Sprintf("%s:%d", t.gofile, n)
+ })
+ filterPattern := fmt.Sprintf(`^(\\w+/)?%s:%d[:[]`, t.gofile, lineNum)
+ errs = append(errs, wantedError{
+ reStr: rx,
+ re: regexp.MustCompile(rx),
+ filterRe: regexp.MustCompile(filterPattern),
+ lineNum: lineNum,
+ file: t.gofile,
+ })
+ }
+ }
+
+ return
+}
コアとなるコードの解説
test/run.go
は、Go言語のテストスイートを実行するためのスタンドアロンなプログラムです。以下に主要な部分を解説します。
-
main
関数:- コマンドライン引数をパースし、並行実行数(
-n
)や詳細出力(-v
)、サマリー表示(--summary
)などのオプションを設定します。 go/build
パッケージを使用して現在のアーキテクチャ(GOARCH
)に対応するコンパイラ(gc
)とリンカ(ld
)のプレフィックス(例:6g
,6l
)を取得します。- テスト対象のGoファイルを特定し、それぞれを
test
構造体として初期化し、toRun
チャネルに送ります。 - すべてのテストが完了するのを待ち、結果を集計して表示します。一つでも失敗したテストがあれば、終了コード1で終了します。
- コマンドライン引数をパースし、並行実行数(
-
test
構造体:- 個々のテストに関する状態(テストファイルのディレクトリとファイル名、ソースコード、実行するアクション、一時ディレクトリ、発生したエラーなど)を保持します。
donec
チャネルは、テストの完了を通知するために使用されます。
-
startTest
関数とrunTests
goroutine:startTest
は新しいtest
インスタンスを作成し、toRun
チャネルに送信します。runTests
は無限ループでtoRun
チャネルからテストを受け取り、ratec
チャネル(並行実行数を制御するセマフォ)を介して、制限された数のgoroutineでtest.run()
メソッドを並行実行します。
-
test.run()
メソッド:- テストファイルのソースコードを読み込み、先頭のコメント行から実行するアクション(
compile
,build
,run
,errorcheck
)を決定します。 - テスト実行のための一時ディレクトリを作成し、テストファイルをそこにコピーします。
go tool gc
(コンパイラ)とgo tool ld
(リンカ)をos/exec.Command
で実行し、コンパイルおよびリンクを行います。- アクションが
run
の場合、ビルドされた実行可能ファイルを一時ディレクトリ内で実行し、その出力が期待される出力ファイル(.out
拡張子)の内容と一致するかを検証します。 - アクションが
errorcheck
の場合、test.errorCheck()
メソッドを呼び出して、コンパイラの出力と期待されるエラーパターンを比較します。
- テストファイルのソースコードを読み込み、先頭のコメント行から実行するアクション(
-
test.errorCheck(outStr string)
メソッド:- このメソッドは、Goコンパイラからのエラー出力(
outStr
)を解析し、テストファイル内に記述された期待されるエラー(// ERROR "..."
)と照合します。 - コンパイラのエラーメッセージは複数行にわたることがあるため、タブで始まる行を前の行に結合して一つのエラーメッセージとして扱います。
test.wantedErrors()
から取得した期待されるエラーパターン(正規表現)と、実際のコンパイラ出力を比較します。partitionStrings
関数は、正規表現にマッチする文字列とマッチしない文字列を分割するヘルパー関数です。- 期待されるエラーが見つからない場合や、実際のエラーが期待されるパターンと一致しない場合にエラーを報告します。
- このメソッドは、Goコンパイラからのエラー出力(
-
test.wantedErrors()
メソッド:- テストファイルのソースコードを走査し、
// ERROR "..."
形式のコメントを抽出します。 - 抽出された文字列から正規表現パターンを生成し、
wantedError
構造体として返します。 LINE
キーワード(例:LINE+1
,LINE-2
)を処理し、エラーが報告される行番号を動的に計算します。これにより、テストコードの変更に柔軟に対応できます。
- テストファイルのソースコードを走査し、
このコードは、Go言語の並行処理機能(goroutineとchannel)を効果的に利用してテストを並行実行し、Goの標準ライブラリ(os/exec
, regexp
, io/ioutil
など)を駆使して、従来のシェルスクリプトやPerlスクリプトが担っていた複雑なテストロジックをGo言語で堅牢に再実装しています。
関連リンク
- Go Issue 2833: https://github.com/golang/go/issues/2833
- Go CL 5625044: https://golang.org/cl/5625044 (これはコミットメッセージに記載されているGoのコードレビューシステムGerritのチェンジリストへのリンクです。GitHubのコミットページと内容は同じですが、Goコミュニティ内での開発プロセスを示すものです。)
参考にした情報源リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語の標準ライブラリドキュメント: https://golang.org/pkg/
- GoのIssueトラッカー: https://github.com/golang/go/issues
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/
- Goのテストに関する一般的な情報 (例:
go test
コマンド): https://golang.org/cmd/go/#hdr-Test_packages - Goのツールチェインに関する情報 (例:
go tool
): https://golang.org/cmd/go/#hdr-Tool_commands - 正規表現に関する一般的な情報 (Goの
regexp
パッケージ): https://golang.org/pkg/regexp/ - シェルスクリプトとPerlスクリプトに関する一般的な知識。
- Go言語の並行処理に関する情報 (goroutine, channel): https://golang.org/doc/effective_go.html#concurrency
- Goのビルドシステムに関する情報 (
go/build
パッケージ): https://golang.org/pkg/go/build/ - Goの
os/exec
パッケージに関する情報: https://golang.org/pkg/os/exec/ - Goの
io/ioutil
パッケージに関する情報: https://golang.org/pkg/io/ioutil/ - Goの
path/filepath
パッケージに関する情報: https://golang.org/pkg/path/filepath/ - Goの
bytes
パッケージに関する情報: https://golang.org/pkg/bytes/ - Goの
strings
パッケージに関する情報: https://golang.org/pkg/strings/ - Goの
fmt
パッケージに関する情報: https://golang.org/pkg/fmt/ - Goの
log
パッケージに関する情報: https://golang.org/pkg/log/ - Goの
flag
パッケージに関する情報: https://golang.org/pkg/flag/ - Goの
runtime
パッケージに関する情報: https://golang.org/pkg/runtime/ - Goの
sort
パッケージに関する情報: https://golang.org/pkg/sort/ - Goの
strconv
パッケージに関する情報: https://golang.org/pkg/strconv/ - Goの
errors
パッケージに関する情報: https://golang.org/pkg/errors/