forked from smarty/gunit
/
gunit.go
172 lines (150 loc) · 5.55 KB
/
gunit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Package gunit provides "testing" package hooks and convenience
// functions for writing tests in an xUnit style.
// NOTE: Only some of the exported names in this package
// are meant to be referenced by users of this package:
//
// - Fixture // (as an embedded field on your xUnit-style struct)
// - Fixture.So(...) // (as a convenient assertion method: So(expected, should.Equal, actual))
// - Fixture.Ok(...) // (as a convenient boolean assertion method: Ok(condition, optionalMessage))
// - Fixture.Error(...) // (works just like *testing.T.Error(...))
// - Fixture.Errorf(...) // (works just like *testing.T.Errorf(...))
// - Fixture.Print(...) // (works just like fmt.Print)
// - Fixture.Printf(...) // (works just like fmt.Printf)
// - Fixture.Println(...) // (works just like fmt.Println)
//
// The rest are called from code generated by the command at
// github.com/smartystreets/gunit/gunit.
// Please see the README file and the examples folder for examples.
package gunit
import (
"bytes"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/smartystreets/assertions"
"github.com/smartystreets/gunit/gunit/generate"
)
// TT represents the functional subset from *testing.T needed by Fixture.
type TT interface {
Log(args ...interface{})
Fail()
Failed() bool
SkipNow()
}
// Fixture keeps track of test status (failed, passed, skipped) and
// handles custom logging for xUnit style tests as an embedded field.
type Fixture struct {
t TT
log *bytes.Buffer
verbose bool
context string
}
// NewFixture is called by generated code.
func NewFixture(t TT, verbose bool, code string) *Fixture {
context := ""
decoded, err := hex.DecodeString(code)
if err == nil {
context = "\n" + string(decoded)
}
return &Fixture{t: t, verbose: verbose, log: &bytes.Buffer{}, context: context}
}
// So is a convenience method for reporting assertion failure messages,
// say from the assertion functions found in github.com/smartystreets/assertions/should.
// Example: self.So(actual, should.Equal, expected)
func (self *Fixture) So(actual interface{}, assert func(actual interface{}, expected ...interface{}) string, expected ...interface{}) bool {
ok, failure := assertions.So(actual, assert, expected...)
if !ok {
self.t.Fail()
self.reportFailure(failure)
}
return ok
}
func (self *Fixture) Ok(condition bool, messages ...string) {
if !condition {
if len(messages) == 0 {
messages = append(messages, "Expected condition to be true, was false instead.")
}
self.t.Fail()
self.reportFailure(strings.Join(messages, ", "))
}
}
func (self *Fixture) Error(args ...interface{}) {
self.t.Fail()
self.reportFailure(fmt.Sprint(args...))
}
func (self *Fixture) Errorf(format string, args ...interface{}) {
self.t.Fail()
self.reportFailure(fmt.Sprintf(format, args...))
}
func (self *Fixture) reportFailure(failure string) {
_, file, line, _ := runtime.Caller(2) // 0: reportFailure + 1: Error/Errorf/So/Ok + 2: func Test...
self.log.WriteString(fmt.Sprintf("%s:%d\n", file, line))
self.log.WriteString(FormatFailureContext(line, self.context))
self.Print(failure + "\n\n")
}
// Print is analogous to fmt.Print and is ideal for printing in the middle of a test case.
func (self *Fixture) Print(a ...interface{}) (n int, err error) {
return self.print(fmt.Sprint(a...))
}
// Printf is analogous to fmt.Printf and is ideal for printing in the middle of a test case.
func (self *Fixture) Printf(format string, a ...interface{}) (n int, err error) {
return self.print(fmt.Sprintf(format, a...))
}
// Println is analogous to fmt.Println and is ideal for printing in the middle of a test case.
func (self *Fixture) Println(a ...interface{}) (n int, err error) {
return self.print(fmt.Sprintln(a...))
}
// print ensures that the necessary indentation is prepended to every line of each printed message.
func (self *Fixture) print(message string) (n int, err error) {
return fmt.Fprint(self.log, message)
}
// Finalize is called by generated code.
func (self *Fixture) Finalize() {
if r := recover(); r != nil {
self.recover(r)
}
if self.t.Failed() || (self.verbose && self.log.Len() > 0) {
self.t.Log("\n" + self.log.String() + "\n")
}
}
func (self *Fixture) recover(r interface{}) {
self.Println("X PANIC:", r)
buffer := make([]byte, 1024*16)
runtime.Stack(buffer, false)
self.Println(strings.TrimSpace(string(buffer)))
self.Println("* (Additional tests may have been skipped as a result of the panic shown above.)")
self.t.Fail()
}
//////////////////////////////////////////////////////////////////////////////
// Validate is called by generated code.
func Validate(checksum string) {
_, file, _, ok := runtime.Caller(1)
if !ok {
exit("Unable to resolve the test file from runtime.Caller(...).\n")
}
current, err := generate.Checksum(filepath.Dir(file))
if err != nil {
exit("Could not calculate checksum of current go files. Error: %s\n", err.Error())
}
if checksum != current {
exit("The checksum provided [%s] does not match the current file listing [%s]. Please re-run the `gunit` command and try again.\n", checksum, current)
}
}
func exit(message string, args ...interface{}) {
fmt.Fprintf(os.Stderr, message, args...)
os.Exit(1)
}
////////////////////////////////////////////////////////////////////////////////
func init() {
working, err := os.Getwd()
if err != nil {
exit("Could not resolve working directory. Error: %s\n", err)
}
_, err = os.Stat(filepath.Join(working, generate.GeneratedFilename))
if err != nil {
exit("Having written one or more gunit Fixtures in this package, please run `gunit` and try again.\n")
}
}