golang 源碼閱讀:test2json
go test 加參數 -json 就能輸出 json 格式,下面我們用一個簡單的例子看下對他進行分析,然後分析下相關源碼。對於單測
package test
import (
"fmt"
"testing"
)
func TestAdd(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
{
args: args{
a: 1,
b: 2,
},
want: 3,
},
{
args: args{
a: 1,
b: 2,
},
want: 4,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
func TestSub(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
{
args: args{
a: 1,
b: 2,
},
want: -1,
},
}
for _, tt := range tests {
fmt.Println("before")
fmt.Print("before not endline")
t.Run(tt.name, func(t *testing.T) {
if got := Sub(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Sub() = %v, want %v", got, tt.want)
}
})
fmt.Println("after")
t.Skip("skipping test; 1 not set")
}
}
執行命令
% go test ./test/gotest/test/... -json
它的輸出如下:
{"Time":"2023-05-13T17:27:15.492681+08:00","Action":"run","Package":"learn/test/gotest/test","Test":"TestAdd"}
{"Time":"2023-05-13T17:27:15.493129+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd","Output":"=== RUN TestAdd\n"}
{"Time":"2023-05-13T17:27:15.49316+08:00","Action":"run","Package":"learn/test/gotest/test","Test":"TestAdd/#00"}
{"Time":"2023-05-13T17:27:15.493168+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd/#00","Output":"=== RUN TestAdd/#00\n"}
{"Time":"2023-05-13T17:27:15.493185+08:00","Action":"run","Package":"learn/test/gotest/test","Test":"TestAdd/#01"}
{"Time":"2023-05-13T17:27:15.493191+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd/#01","Output":"=== RUN TestAdd/#01\n"}
{"Time":"2023-05-13T17:27:15.493195+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd/#01","Output":" add_test.go:38: Add() = 3, want 4\n"}
{"Time":"2023-05-13T17:27:15.493444+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd","Output":"--- FAIL: TestAdd (0.00s)\n"}
{"Time":"2023-05-13T17:27:15.493471+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd/#00","Output":" --- PASS: TestAdd/#00 (0.00s)\n"}
{"Time":"2023-05-13T17:27:15.493477+08:00","Action":"pass","Package":"learn/test/gotest/test","Test":"TestAdd/#00","Elapsed":0}
{"Time":"2023-05-13T17:27:15.49349+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestAdd/#01","Output":" --- FAIL: TestAdd/#01 (0.00s)\n"}
{"Time":"2023-05-13T17:27:15.493495+08:00","Action":"fail","Package":"learn/test/gotest/test","Test":"TestAdd/#01","Elapsed":0}
{"Time":"2023-05-13T17:27:15.493499+08:00","Action":"fail","Package":"learn/test/gotest/test","Test":"TestAdd","Elapsed":0}
{"Time":"2023-05-13T17:27:15.493503+08:00","Action":"run","Package":"learn/test/gotest/test","Test":"TestSub"}
{"Time":"2023-05-13T17:27:15.493506+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"=== RUN TestSub\n"}
{"Time":"2023-05-13T17:27:15.49362+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"before\n"}
{"Time":"2023-05-13T17:27:15.493633+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"before not endline=== RUN TestSub/#00\n"}
{"Time":"2023-05-13T17:27:15.493642+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"after\n"}
{"Time":"2023-05-13T17:27:15.493646+08:00","Action":"cont","Package":"learn/test/gotest/test","Test":"TestSub"}
{"Time":"2023-05-13T17:27:15.49365+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"=== CONT TestSub\n"}
{"Time":"2023-05-13T17:27:15.493654+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":" add_test.go:72: skipping test; 1 not set\n"}
{"Time":"2023-05-13T17:27:15.493659+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub","Output":"--- SKIP: TestSub (0.00s)\n"}
{"Time":"2023-05-13T17:27:15.493802+08:00","Action":"output","Package":"learn/test/gotest/test","Test":"TestSub/#00","Output":" --- PASS: TestSub/#00 (0.00s)\n"}
{"Time":"2023-05-13T17:27:15.493818+08:00","Action":"pass","Package":"learn/test/gotest/test","Test":"TestSub/#00","Elapsed":0}
{"Time":"2023-05-13T17:27:15.493824+08:00","Action":"skip","Package":"learn/test/gotest/test","Test":"TestSub","Elapsed":0}
{"Time":"2023-05-13T17:27:15.493827+08:00","Action":"output","Package":"learn/test/gotest/test","Output":"FAIL\n"}
{"Time":"2023-05-13T17:27:15.493899+08:00","Action":"output","Package":"learn/test/gotest/test","Output":"FAIL\tlearn/test/gotest/test\t0.138s\n"}
{"Time":"2023-05-13T17:27:15.493915+08:00","Action":"fail","Package":"learn/test/gotest/test","Elapsed":0.139}
可以看到,和直直接輸出相比,輸出格式變成了 json,每條輸出都是一個 json 對象,它包含屬性有時間,類型、包、測試方法、以及輸出等。比不帶 - json 參數多了很多內容
--- FAIL: TestAdd (0.00s)
--- FAIL: TestAdd/#01 (0.00s)
add_test.go:38: Add() = 3, want 4
before
before not endlineafter
FAIL
FAIL learn/test/gotest/test 0.176s
FAIL
下面分析下它的源碼實現,源碼位於 src/cmd/internal/test2json/test2json.go 輸出被定義成了 event 類型
type event struct {
Time *time.Time `json:",omitempty"`
Action string
Package string `json:",omitempty"`
Test string `json:",omitempty"`
Elapsed *float64 `json:",omitempty"`
Output *textBytes `json:",omitempty"`
}
其中 Converter 負責將 gotest 的輸出轉化爲 event 事件
type Converter struct {
w io.Writer // JSON output stream
pkg string // package to name in events
mode Mode // mode bits
start time.Time // time converter started
testName string // name of current test, for output attribution
report []*event // pending test result reports (nested for subtests)
result string // overall test result if seen
input lineBuffer // input buffer
output lineBuffer // output buffer
}
構造方法如下
func NewConverter(w io.Writer, pkg string, mode Mode) *Converter {
c := new(Converter)
*c = Converter{
w: w,
pkg: pkg,
mode: mode,
start: time.Now(),
input: lineBuffer{
b: make([]byte, 0, inBuffer),
line: c.handleInputLine,
part: c.output.write,
},
output: lineBuffer{
b: make([]byte, 0, outBuffer),
line: c.writeOutputEvent,
part: c.writeOutputEvent,
},
}
return c
}
退出對應了 pass 和 fail 兩種 action
func (c *Converter) Exited(err error) {
if err == nil {
c.result = "pass"
} else {
c.result = "fail"
}
}
相應的文案定義如下,也就是我們常看到的單測結果
updates = [][]byte{
[]byte("=== RUN "),
[]byte("=== PAUSE "),
[]byte("=== CONT "),
}
reports = [][]byte{
[]byte("--- PASS: "),
[]byte("--- FAIL: "),
[]byte("--- SKIP: "),
[]byte("--- BENCH: "),
}
根據輸入的每一行內容,進行解析,得到 event,如果都沒有匹配成功,就把前一個測試的名字賦值給當前測試。否則根據冒號截取內容獲取 Action 和 Test。最後把原始輸入,寫入到 Output 字段裏面。
func (c *Converter) handleInputLine(line []byte) {
// Final PASS or FAIL.
if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) || bytes.HasPrefix(line, bigFailErrorPrefix) {
c.flushReport(0)
c.output.write(line)
if bytes.Equal(line, bigPass) {
c.result = "pass"
} else {
c.result = "fail"
}
return
}
if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(line, skipLineSuffix) && len(c.report) == 0 {
c.result = "skip"
}
for _, magic := range reports {
if bytes.HasPrefix(line, magic) {
actionColon = true
ok = true
break
}
}
if indent > 0 && indent <= len(c.report) {
c.testName = c.report[indent-1].Test
}
action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
name := strings.TrimSpace(string(line[i:]))
c.output.write(origLine)
大於當前層級的結果輸出
func (c *Converter) flushReport(depth int) {
c.testName = ""
for len(c.report) > depth {
e := c.report[len(c.report)-1]
c.report = c.report[:len(c.report)-1]
c.writeEvent(e)
}
}
輸出 output 是一個單獨的事件
func (c *Converter) writeOutputEvent(out []byte) {
c.writeEvent(&event{
Action: "output",
Output: (*textBytes)(&out),
})
}
最後來到了 writevent 函數,把包名字和測試名字賦值,然後序列化。加入換行然後輸出。
func (c *Converter) writeEvent(e *event) {
e.Package = c.pkg
if c.mode&Timestamp != 0 {
t := time.Now()
e.Time = &t
}
if e.Test == "" {
e.Test = c.testName
}
js, err := json.Marshal(e)
js = append(js, '\n')
c.w.Write(js)
總的來說,就是解析字符串,轉化成 json 的一個過程。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/bovpBmNHQmA5s12as7CH1A