11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Go2Advent Calendar 2019

Day 12

rakyll/gotestでテスト結果に色をつける。そしてソースも読んでみる

Posted at

TL;DR

  • go testすると標準出力にはテストケースの成功も失敗もターミナルで白文字はわかりづらいよね
  • 他にもツールあるけどrakyll/gotest使うとターミナル・CircleCIとかの出力に色付けされてわかりやすいよ
  • ツール入れて便利だけど、せっかくだからソースコードも読んでみるよ

そんな記事です。兎にも角にもまずはインストールからはじめます。

インストール

公式リポジトリに従いインストールして実行してみます。

# install
$ go get -u github.com/rakyll/gotest

# run test
$ gotest -v

rakyll/gotestの結果はこのようになります。はい。
gotest_result.png

詳細

リポジトリを覗いてみるとrakyll/gotestmain.goのみで実装されているシンプルなツールです。
os.Args[:1]exec.Command()に渡しているのでgo testと同じオプション引数が使用できます。

main.go
// 一部抜粋
func main() {
	setPalette()
	enableOnCI()
	os.Exit(gotest(os.Args[1:]))
}

func gotest(args []string) int {
	var wg sync.WaitGroup
	wg.Add(1)
	defer wg.Wait()

	r, w := io.Pipe()
	defer w.Close()

	args = append([]string{"test"}, args...)
	cmd := exec.Command("go", args...)
	cmd.Stderr = w
	cmd.Stdout = w
	cmd.Env = os.Environ()

	go consume(&wg, r)

	if err := cmd.Run(); err != nil {
		if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
			return ws.ExitStatus()
		}
		return 1
	}
	return 0
}

func consume(wg *sync.WaitGroup, r io.Reader) {
	defer wg.Done()
	reader := bufio.NewReader(r)
	for {
		l, _, err := reader.ReadLine()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Print(err)
			return
		}
		parse(string(l))
	}
}

標準出力への色付けはfatih/colorに依存しています。
デフォルトではテストケースが「successは緑色」「failは赤色」となっています。

main.go
// 一部抜粋
var (
	success = color.New(color.FgGreen)
	fail    = color.New(color.FgHiRed)
)

READMEに書いている通り、環境変数GOTEST_PALETTEを指定することでsuccessfailの色を変更することができます。

$ GOTEST_PALETTE=yellow,blue gotest -v

上記コマンドで色変更するとこのように出力されます。はい。
gotest_palette.png

指定できる色はmapで管理しています。

main.go
// 一部抜粋
var colors = map[string]color.Attribute{
	"black":     color.FgBlack,
	"hiblack":   color.FgHiBlack,
	"red":       color.FgRed,
	"hired":     color.FgHiRed,
	"green":     color.FgGreen,
	"higreen":   color.FgHiGreen,
	"yellow":    color.FgYellow,
	"hiyellow":  color.FgHiYellow,
	"blue":      color.FgBlue,
	"hiblue":    color.FgHiBlue,
	"magenta":   color.FgMagenta,
	"himagenta": color.FgHiMagenta,
	"cyan":      color.FgCyan,
	"hicyan":    color.FgHiCyan,
	"white":     color.FgWhite,
	"hiwhite":   color.FgHiWhite,
}

rakyll/gotestではRun、Pass、Failの3種類の出力を判定して色分けしている。
出力の判定はstrings.HasPrefix()を使って文字列を判別するシンプルな実装。

main.go
// 一部抜粋
var c *color.Color

func parse(line string) {
	trimmed := strings.TrimSpace(line)

	switch {
	case strings.HasPrefix(trimmed, "=== RUN"):
		fallthrough
	case strings.HasPrefix(trimmed, "?"):
		c = nil

	// success
	case strings.HasPrefix(trimmed, "--- PASS"):
		fallthrough
	case strings.HasPrefix(trimmed, "ok"):
		fallthrough
	case strings.HasPrefix(trimmed, "PASS"):
		c = success

	// failure
	case strings.HasPrefix(trimmed, "--- FAIL"):
		fallthrough
	case strings.HasPrefix(trimmed, "FAIL"):
		c = fail
	}

	if c == nil {
		fmt.Printf("%s\n", line)
		return
	}
	c.Printf("%s\n", line)
}

cmd.Run()go testを実行していますがcmd.Run()はコマンドが終わるまで処理をブロックしてしまうため、main関数とは別のgoroutineを生成し、そこでtest結果をio.Pipe()を経由して最終的に色付けを行っている。
ここの部分は以前にgo testの結果をすべて読み取れないケースのバグがあったようで、mattnさんがPull Requestを送り修正されていました。

main.go
    // 一部抜粋
    // ここのgoroutineでgo testの結果をEOFになるまで読み取り、色をつけて出力してる
    go consume(&wg, r)

    if err := cmd.Run(); err != nil {
        if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
            return ws.ExitStatus()
        }
        return 1
    }

詳しい解説はこちらに。goroutine でドハマリした。

簡単ではありますが、以上がrakyll/gotestが行っている処理でした。
余談ですがソースコードを読んでみてio.Pipe()に関して勉強になりました。

終わりに

issueにもありますがt.Skip()した時にswitchの条件に一致せず直前のループで処理した色を出力してしまう実装になっているので、Skip用の色付けを行う修正PR送って見るのもありかもしれません。
と思ったらPRあった...

参考

go testの出力をリッチにしてくれるツールは色々あるよ

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?