logharness.go
1 package testutils 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "log" 8 "os" 9 "strings" 10 "testing" 11 ) 12 13 // A LogHarness runs sets of assertions against the log output of a function. Assertions are grouped 14 // into sequences of messages that are expected to be found in the log output. Calling one of the Expect 15 // methods on the harness adds an expectation to the default sequence of messages. Additional sequences 16 // can be created by calling NewSequence. 17 type LogHarness struct { 18 buf bytes.Buffer 19 sequences []*Sequence 20 } 21 22 type Expectation interface { 23 IsMatch(line string) bool 24 String() string 25 } 26 27 // Run executes the function f and captures any output written using Go's standard log. Each sequence 28 // of expected messages is then 29 func (h *LogHarness) Run(t *testing.T, f func()) { 30 // Capture raw log output 31 fl := log.Flags() 32 log.SetFlags(0) 33 log.SetOutput(&h.buf) 34 f() 35 log.SetFlags(fl) 36 log.SetOutput(os.Stderr) 37 38 for _, seq := range h.sequences { 39 seq.Assert(t, bufio.NewScanner(bytes.NewReader(h.buf.Bytes()))) 40 } 41 } 42 43 // Expect adds an expectation to the default sequence that the log contains a line equal to s 44 func (h *LogHarness) Expect(s string) { 45 if len(h.sequences) == 0 { 46 h.sequences = append(h.sequences, &Sequence{name: ""}) 47 } 48 h.sequences[0].Expect(s) 49 } 50 51 // ExpectPrefix adds an to the default sequence expectation that the log contains a line starting with s 52 func (h *LogHarness) ExpectPrefix(s string) { 53 if len(h.sequences) == 0 { 54 h.sequences = append(h.sequences, &Sequence{name: ""}) 55 } 56 h.sequences[0].ExpectPrefix(s) 57 } 58 59 // NewSequence creates a new sequence of expected log messages 60 func (h *LogHarness) NewSequence(name string) *Sequence { 61 seq := &Sequence{name: name} 62 h.sequences = append(h.sequences, seq) 63 return seq 64 } 65 66 type prefix string 67 68 func (p prefix) IsMatch(line string) bool { 69 return strings.HasPrefix(line, string(p)) 70 } 71 72 func (p prefix) String() string { 73 return fmt.Sprintf("prefix %q", string(p)) 74 } 75 76 type text string 77 78 func (t text) IsMatch(line string) bool { 79 return line == string(t) 80 } 81 82 func (t text) String() string { 83 return fmt.Sprintf("text %q", string(t)) 84 } 85 86 type Sequence struct { 87 name string 88 exp []Expectation 89 } 90 91 func (seq *Sequence) Assert(t *testing.T, s *bufio.Scanner) { 92 var tag string 93 if seq.name != "" { 94 tag = fmt.Sprintf("[%s] ", seq.name) 95 } 96 // Match raw log lines against expectations 97 exploop: 98 for _, e := range seq.exp { 99 for s.Scan() { 100 if e.IsMatch(s.Text()) { 101 t.Logf("%ssaw: %s", tag, s.Text()) 102 continue exploop 103 } 104 } 105 if s.Err() == nil { 106 t.Errorf("%sdid not see expected %s", tag, e.String()) 107 return 108 } 109 } 110 } 111 112 // Expect adds an expectation that the log contains a line equal to s 113 func (seq *Sequence) Expect(s string) { 114 seq.exp = append(seq.exp, text(s)) 115 } 116 117 // ExpectPrefix adds an expectation that the log contains a line starting with s 118 func (seq *Sequence) ExpectPrefix(s string) { 119 seq.exp = append(seq.exp, prefix(s)) 120 }