Codebase list golang-github-matryer-is / 7348491
New upstream version 1.1.0 Sascha Steinbiss 5 years ago
8 changed file(s) with 816 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 # Compiled Object files, Static and Dynamic libs (Shared Objects)
1 *.o
2 *.a
3 *.so
4
5 # Folders
6 _obj
7 _test
8
9 # Architecture specific extensions/prefixes
10 *.[568vq]
11 [568vq].out
12
13 *.cgo1.go
14 *.cgo2.c
15 _cgo_defun.c
16 _cgo_gotypes.go
17 _cgo_export.*
18
19 _testmain.go
20
21 *.exe
22 *.test
23 *.prof
0 language: go
1
2 sudo: required
3
4 go:
5 - 1.6.x
6 - 1.7.x
7 - tip
8
9 env:
10 - GIMME_OS=linux GIMME_ARCH=amd64
11 - GIMME_OS=darwin GIMME_ARCH=amd64
12 - GIMME_OS=windows GIMME_ARCH=amd64
13
14 install:
15 - go get -d -v ./...
16
17 script:
18 - go build -v ./...
0 MIT License
1
2 Copyright (c) 2017-2018 Mat Ryer
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
0 # is [![GoDoc](https://godoc.org/github.com/matryer/is?status.png)](http://godoc.org/github.com/matryer/is) [![Go Report Card](https://goreportcard.com/badge/github.com/matryer/is)](https://goreportcard.com/report/github.com/matryer/is) [![Build Status](https://travis-ci.org/matryer/is.svg?branch=master)](https://travis-ci.org/matryer/is)
1 Professional lightweight testing mini-framework for Go.
2
3 * Easy to write and read
4 * [Beautifully simple API](https://godoc.org/github.com/matryer/is) with everything you need: `is.Equal`, `is.True`, `is.NoErr`, and `is.Fail`
5 * Use comments to add descriptions (which show up when tests fail)
6
7 Failures are very easy to read:
8
9 ![Examples of failures](https://github.com/matryer/is/raw/master/misc/delicious-failures.png)
10
11 ### Usage
12
13 The following code shows a range of useful ways you can use
14 the helper methods:
15
16 ```go
17 func Test(t *testing.T) {
18
19 is := is.New(t)
20
21 signedin, err := isSignedIn(ctx)
22 is.NoErr(err) // isSignedIn error
23 is.Equal(signedin, true) // must be signed in
24
25 body := readBody(r)
26 is.True(strings.Contains(body, "Hi there"))
27
28 }
29 ```
30
31 ## Color
32
33 To turn off the colors, run `go test` with the `-nocolor` flag.
34
35 ```
36 go test -nocolor
37 ```
+403
-0
is.go less more
0 // Package is provides a lightweight extension to the
1 // standard library's testing capabilities.
2 //
3 // Comments on the assertion lines are used to add
4 // a description.
5 //
6 // The following failing test:
7 //
8 // func Test(t *testing.T) {
9 // is := is.New(t)
10 // a, b := 1, 2
11 // is.Equal(a, b) // expect to be the same
12 // }
13 //
14 // Will output:
15 //
16 // your_test.go:123: 1 != 2 // expect to be the same
17 //
18 // Usage
19 //
20 // The following code shows a range of useful ways you can use
21 // the helper methods:
22 //
23 // func Test(t *testing.T) {
24 //
25 // // always start tests with this
26 // is := is.New(t)
27 //
28 // signedin, err := isSignedIn(ctx)
29 // is.NoErr(err) // isSignedIn error
30 // is.Equal(signedin, true) // must be signed in
31 //
32 // body := readBody(r)
33 // is.True(strings.Contains(body, "Hi there"))
34 //
35 // }
36 package is
37
38 import (
39 "bufio"
40 "bytes"
41 "flag"
42 "fmt"
43 "io"
44 "os"
45 "path/filepath"
46 "reflect"
47 "runtime"
48 "strings"
49 "testing"
50 )
51
52 // T reports when failures occur.
53 // testing.T implements this interface.
54 type T interface {
55 // Fail indicates that the test has failed but
56 // allowed execution to continue.
57 // Fail is called in relaxed mode (via NewRelaxed).
58 Fail()
59 // FailNow indicates that the test has failed and
60 // aborts the test.
61 // FailNow is called in strict mode (via New).
62 FailNow()
63 }
64
65 // I is the test helper harness.
66 type I struct {
67 t T
68 fail func()
69 out io.Writer
70 colorful bool
71 }
72
73 var isColorful bool
74
75 func init() {
76 noColor := flag.Bool("nocolor", false, "turns off colors")
77 flag.Parse()
78 isColorful = !*noColor
79 }
80
81 // New makes a new testing helper using the specified
82 // T through which failures will be reported.
83 // In strict mode, failures call T.FailNow causing the test
84 // to be aborted. See NewRelaxed for alternative behavior.
85 func New(t T) *I {
86 return &I{t, t.FailNow, os.Stdout, isColorful}
87 }
88
89 // NewRelaxed makes a new testing helper using the specified
90 // T through which failures will be reported.
91 // In relaxed mode, failures call T.Fail allowing
92 // multiple failures per test.
93 func NewRelaxed(t T) *I {
94 return &I{t, t.Fail, os.Stdout, isColorful}
95 }
96
97 func (is *I) log(args ...interface{}) {
98 s := is.decorate(fmt.Sprint(args...))
99 fmt.Fprintf(is.out, s)
100 is.fail()
101 }
102
103 func (is *I) logf(format string, args ...interface{}) {
104 is.log(fmt.Sprintf(format, args...))
105 }
106
107 // Fail immediately fails the test.
108 //
109 // func Test(t *testing.T) {
110 // is := is.New(t)
111 // is.Fail() // TODO: write this test
112 // }
113 //
114 // In relaxed mode, execution will continue after a call to
115 // Fail, but that test will still fail.
116 func (is *I) Fail() {
117 is.log("failed")
118 }
119
120 // True asserts that the expression is true. The expression
121 // code itself will be reported if the assertion fails.
122 //
123 // func Test(t *testing.T) {
124 // is := is.New(t)
125 // val := method()
126 // is.True(val != nil) // val should never be nil
127 // }
128 //
129 // Will output:
130 //
131 // your_test.go:123: not true: val != nil
132 func (is *I) True(expression bool) {
133 if !expression {
134 is.log("not true: $ARGS")
135 }
136 }
137
138 // Equal asserts that a and b are equal.
139 //
140 // func Test(t *testing.T) {
141 // is := is.New(t)
142 // a := greet("Mat")
143 // is.Equal(a, "Hi Mat") // greeting
144 // }
145 //
146 // Will output:
147 //
148 // your_test.go:123: Hey Mat != Hi Mat // greeting
149 func (is *I) Equal(a, b interface{}) {
150 if !areEqual(a, b) {
151 if isNil(a) || isNil(b) {
152 aLabel := is.valWithType(a)
153 bLabel := is.valWithType(b)
154 if isNil(a) {
155 aLabel = "<nil>"
156 }
157 if isNil(b) {
158 bLabel = "<nil>"
159 }
160 is.logf("%s != %s", aLabel, bLabel)
161 return
162 }
163 if reflect.ValueOf(a).Type() == reflect.ValueOf(b).Type() {
164 is.logf("%v != %v", a, b)
165 return
166 }
167 is.logf("%s != %s", is.valWithType(a), is.valWithType(b))
168 }
169 }
170
171 // New is a method wrapper around the New function.
172 // It allows you to write subtests using a fimilar
173 // pattern:
174 //
175 // func Test(t *testing.T) {
176 // is := is.New(t)
177 // t.Run("sub", func(t *testing.T) {
178 // is := is.New(t)
179 // // TODO: test
180 // })
181 // }
182 func (is *I) New(t *testing.T) *I {
183 return New(t)
184 }
185
186 // NewRelaxed is a method wrapper aorund the NewRelaxed
187 // method. It allows you to write subtests using a fimilar
188 // pattern:
189 //
190 // func Test(t *testing.T) {
191 // is := is.New(t)
192 // t.Run("sub", func(t *testing.T) {
193 // is := is.New(t)
194 // // TODO: test
195 // })
196 // }
197 func (is *I) NewRelaxed(t *testing.T) *I {
198 return NewRelaxed(t)
199 }
200
201 func (is *I) valWithType(v interface{}) string {
202 if is.colorful {
203 return fmt.Sprintf("%[1]s%[3]T(%[2]s%[3]v%[1]s)%[2]s", colorType, colorNormal, v)
204 }
205 return fmt.Sprintf("%[1]T(%[1]v)", v)
206 }
207
208 // NoErr asserts that err is nil.
209 //
210 // func Test(t *testing.T) {
211 // is := is.New(t)
212 // val, err := getVal()
213 // is.NoErr(err) // getVal error
214 // is.OK(len(val) > 10) // val cannot be short
215 // }
216 //
217 // Will output:
218 //
219 // your_test.go:123: err: not found // getVal error
220 func (is *I) NoErr(err error) {
221 if err != nil {
222 is.logf("err: %s", err.Error())
223 }
224 }
225
226 // isNil gets whether the object is nil or not.
227 func isNil(object interface{}) bool {
228 if object == nil {
229 return true
230 }
231 value := reflect.ValueOf(object)
232 kind := value.Kind()
233 if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
234 return true
235 }
236 return false
237 }
238
239 // areEqual gets whether a equals b or not.
240 func areEqual(a, b interface{}) bool {
241 if isNil(a) || isNil(b) {
242 if isNil(a) && !isNil(b) {
243 return false
244 }
245 if !isNil(a) && isNil(b) {
246 return false
247 }
248 return true
249 }
250 if reflect.DeepEqual(a, b) {
251 return true
252 }
253 aValue := reflect.ValueOf(a)
254 bValue := reflect.ValueOf(b)
255 return aValue == bValue
256 }
257
258 func callerinfo() (path string, line int, ok bool) {
259 for i := 0; ; i++ {
260 _, path, line, ok = runtime.Caller(i)
261 if !ok {
262 return
263 }
264 if strings.HasSuffix(path, "is.go") {
265 continue
266 }
267 return path, line, true
268 }
269 }
270
271 // loadComment gets the Go comment from the specified line
272 // in the specified file.
273 func loadComment(path string, line int) (string, bool) {
274 f, err := os.Open(path)
275 if err != nil {
276 return "", false
277 }
278 defer f.Close()
279 s := bufio.NewScanner(f)
280 i := 1
281 for s.Scan() {
282 if i == line {
283 text := s.Text()
284 commentI := strings.Index(text, "//")
285 if commentI == -1 {
286 return "", false // no comment
287 }
288 text = text[commentI+2:]
289 text = strings.TrimSpace(text)
290 return text, true
291 }
292 i++
293 }
294 return "", false
295 }
296
297 // loadArguments gets the arguments from the function call
298 // on the specified line of the file.
299 func loadArguments(path string, line int) (string, bool) {
300 f, err := os.Open(path)
301 if err != nil {
302 return "", false
303 }
304 defer f.Close()
305 s := bufio.NewScanner(f)
306 i := 1
307 for s.Scan() {
308 if i == line {
309 text := s.Text()
310 braceI := strings.Index(text, "(")
311 if braceI == -1 {
312 return "", false
313 }
314 text = text[braceI+1:]
315 cs := bufio.NewScanner(strings.NewReader(text))
316 cs.Split(bufio.ScanBytes)
317 j := 0
318 c := 1
319 for cs.Scan() {
320 switch cs.Text() {
321 case ")":
322 c--
323 case "(":
324 c++
325 }
326 if c == 0 {
327 break
328 }
329 j++
330 }
331 text = text[:j]
332 return text, true
333 }
334 i++
335 }
336 return "", false
337 }
338
339 // decorate prefixes the string with the file and line of the call site
340 // and inserts the final newline if needed and indentation tabs for formatting.
341 // this function was copied from the testing framework and modified.
342 func (is *I) decorate(s string) string {
343 path, lineNumber, ok := callerinfo() // decorate + log + public function.
344 file := filepath.Base(path)
345 if ok {
346 // Truncate file name at last file name separator.
347 if index := strings.LastIndex(file, "/"); index >= 0 {
348 file = file[index+1:]
349 } else if index = strings.LastIndex(file, "\\"); index >= 0 {
350 file = file[index+1:]
351 }
352 } else {
353 file = "???"
354 lineNumber = 1
355 }
356 buf := new(bytes.Buffer)
357 // Every line is indented at least one tab.
358 buf.WriteByte('\t')
359 if is.colorful {
360 buf.WriteString(colorFile)
361 }
362 fmt.Fprintf(buf, "%s:%d: ", file, lineNumber)
363 if is.colorful {
364 buf.WriteString(colorNormal)
365 }
366 lines := strings.Split(s, "\n")
367 if l := len(lines); l > 1 && lines[l-1] == "" {
368 lines = lines[:l-1]
369 }
370 for i, line := range lines {
371 if i > 0 {
372 // Second and subsequent lines are indented an extra tab.
373 buf.WriteString("\n\t\t")
374 }
375 // expand arguments (if $ARGS is present)
376 if strings.Contains(line, "$ARGS") {
377 args, _ := loadArguments(path, lineNumber)
378 line = strings.Replace(line, "$ARGS", args, -1)
379 }
380 buf.WriteString(line)
381 }
382 comment, ok := loadComment(path, lineNumber)
383 if ok {
384 if is.colorful {
385 buf.WriteString(colorComment)
386 }
387 buf.WriteString(" // ")
388 buf.WriteString(comment)
389 if is.colorful {
390 buf.WriteString(colorNormal)
391 }
392 }
393 buf.WriteString("\n")
394 return buf.String()
395 }
396
397 const (
398 colorNormal = "\u001b[39m"
399 colorComment = "\u001b[32m"
400 colorFile = "\u001b[90m"
401 colorType = "\u001b[90m"
402 )
0 package is
1
2 import (
3 "bytes"
4 "errors"
5 "fmt"
6 "strings"
7 "testing"
8 )
9
10 type mockT struct {
11 failed bool
12 }
13
14 func (m *mockT) FailNow() {
15 m.failed = true
16 }
17 func (m *mockT) Fail() {
18 m.failed = true
19 }
20
21 var tests = []struct {
22 N string
23 F func(is *I)
24 Fail string
25 }{
26 // Equal
27 {
28 N: "Equal(1, 1)",
29 F: func(is *I) {
30 is.Equal(1, 1) // 1 doesn't equal 2
31 },
32 Fail: ``,
33 },
34
35 {
36 N: "Equal(1, 2)",
37 F: func(is *I) {
38 is.Equal(1, 2) // 1 doesn't equal 2
39 },
40 Fail: `1 != 2 // 1 doesn't equal 2`,
41 },
42 {
43 N: "Equal(1, nil)",
44 F: func(is *I) {
45 is.Equal(1, nil) // 1 doesn't equal nil
46 },
47 Fail: `int(1) != <nil> // 1 doesn't equal nil`,
48 },
49 {
50 N: "Equal(nil, 2)",
51 F: func(is *I) {
52 is.Equal(nil, 2) // nil doesn't equal 2
53 },
54 Fail: `<nil> != int(2) // nil doesn't equal 2`,
55 },
56 {
57 N: "Equal(false, false)",
58 F: func(is *I) {
59 is.Equal(false, false) // nil doesn't equal 2
60 },
61 Fail: ``,
62 },
63 {
64 N: "Equal(int32(1), int64(1))",
65 F: func(is *I) {
66 is.Equal(int32(1), int64(1)) // nope
67 },
68 Fail: `int32(1) != int64(1) // nope`,
69 },
70 {
71 N: "Equal(map1, map2)",
72 F: func(is *I) {
73 m1 := map[string]interface{}{"value": 1}
74 m2 := map[string]interface{}{"value": 2}
75 is.Equal(m1, m2) // maps
76 },
77 Fail: `map[value:1] != map[value:2] // maps`,
78 },
79 {
80 N: "Equal(true, map2)",
81 F: func(is *I) {
82 m1 := map[string]interface{}{"value": 1}
83 m2 := map[string]interface{}{"value": 2}
84 is.Equal(m1, m2) // maps
85 },
86 Fail: `map[value:1] != map[value:2] // maps`,
87 },
88 {
89 N: "Equal(slice1, slice2)",
90 F: func(is *I) {
91 s1 := []string{"one", "two", "three"}
92 s2 := []string{"one", "two", "three", "four"}
93 is.Equal(s1, s2) // slices
94 },
95 Fail: `[one two three] != [one two three four] // slices`,
96 },
97 {
98 N: "Equal(nil, chan)",
99 F: func(is *I) {
100 var a chan string
101 b := make(chan string)
102 is.Equal(a, b) // channels
103 },
104 Fail: ` // channels`,
105 },
106 {
107 N: "Equal(nil, slice)",
108 F: func(is *I) {
109 var s1 []string
110 s2 := []string{"one", "two", "three", "four"}
111 is.Equal(s1, s2) // nil slice
112 },
113 Fail: ` // nil slice`,
114 },
115 {
116 N: "Equal(nil, nil)",
117 F: func(is *I) {
118 var s1 []string
119 var s2 []string
120 is.Equal(s1, s2) // nil slices
121 },
122 Fail: ``,
123 },
124 {
125 N: "Equal(nil, map)",
126 F: func(is *I) {
127 var m1 map[string]string
128 m2 := map[string]string{}
129 is.Equal(m1, m2) // nil map
130 },
131 Fail: ` // nil map`,
132 },
133 {
134 N: "Equal(nil, nil)",
135 F: func(is *I) {
136 var m1 map[string]string
137 var m2 map[string]string
138 is.Equal(m1, m2) // nil maps
139 },
140 Fail: ``,
141 },
142
143 // Fail
144 {
145 N: "Fail()",
146 F: func(is *I) {
147 is.Fail() // something went wrong
148 },
149 Fail: "failed // something went wrong",
150 },
151
152 // NoErr
153 {
154 N: "NoErr(nil)",
155 F: func(is *I) {
156 var err error
157 is.NoErr(err) // method shouldn't return error
158 },
159 Fail: "",
160 },
161 {
162 N: "NoErr(error)",
163 F: func(is *I) {
164 err := errors.New("nope")
165 is.NoErr(err) // method shouldn't return error
166 },
167 Fail: "err: nope // method shouldn't return error",
168 },
169
170 // OK
171 {
172 N: "True(1 == 2)",
173 F: func(is *I) {
174 is.True(1 == 2)
175 },
176 Fail: "not true: 1 == 2",
177 },
178 }
179
180 func TestFailures(t *testing.T) {
181 colorful, notColorful := true, false
182 testFailures(t, colorful)
183 testFailures(t, notColorful)
184 }
185
186 func testFailures(t *testing.T, colorful bool) {
187 for _, test := range tests {
188 tt := &mockT{}
189 is := New(tt)
190 var buf bytes.Buffer
191 is.out = &buf
192 is.colorful = colorful
193 test.F(is)
194 if len(test.Fail) == 0 && tt.failed {
195 t.Errorf("shouldn't fail: %s", test.N)
196 continue
197 }
198 if len(test.Fail) > 0 && !tt.failed {
199 t.Errorf("didn't fail: %s", test.N)
200 }
201 if colorful {
202 // if colorful, we won't check the messages
203 // this test is run twice, one without colorful
204 // statements.
205 // see TestFailures
206 fmt.Print(buf.String())
207 continue
208 }
209 output := buf.String()
210 output = strings.TrimSpace(output)
211 if !strings.HasSuffix(output, test.Fail) {
212 t.Errorf("expected `%s` to end with `%s`", output, test.Fail)
213 }
214 }
215 }
216
217 func TestRelaxed(t *testing.T) {
218 tt := &mockT{}
219 is := NewRelaxed(tt)
220 var buf bytes.Buffer
221 is.out = &buf
222 is.colorful = false
223 is.NoErr(errors.New("oops"))
224 is.True(1 == 2)
225 actual := buf.String()
226 if !strings.Contains(actual, `oops`) {
227 t.Errorf("missing: oops")
228 }
229 if !strings.Contains(actual, `1 == 2`) {
230 t.Errorf("missing: 1 == 2")
231 }
232 if !tt.failed {
233 t.Errorf("didn't fail")
234 }
235 }
236
237 func TestLoadComment(t *testing.T) {
238 comment, ok := loadComment("./testdata/example_test.go", 14)
239 if !ok {
240 t.Errorf("loadComment: not ok")
241 }
242 if comment != `this comment will be extracted` {
243 t.Errorf("loadComment: bad comment %s", comment)
244 }
245 }
246
247 func TestLoadArguments(t *testing.T) {
248 arguments, ok := loadArguments("./testdata/example_test.go", 23)
249 if !ok {
250 t.Errorf("loadArguments: not ok")
251 }
252 if arguments != `a == getB()` {
253 t.Errorf("loadArguments: bad arguments %s", arguments)
254 }
255
256 arguments, ok = loadArguments("./testdata/example_test.go", 32)
257 if !ok {
258 t.Errorf("loadArguments: not ok")
259 }
260 if arguments != `a == getB()` {
261 t.Errorf("loadArguments: bad arguments %s", arguments)
262 }
263
264 arguments, _ = loadArguments("./testdata/example_test.go", 28)
265 if len(arguments) > 0 {
266 t.Errorf("should be no arguments: %s", arguments)
267 }
268 }
269
270 // TestSubtests ensures subtests work as expected.
271 // https://github.com/matryer/is/issues/1
272 func TestSubtests(t *testing.T) {
273 t.Run("sub1", func(t *testing.T) {
274 is := New(t)
275 is.Equal(1+1, 2)
276 })
277 }
0 package example
1
2 // CAUTION: DO NOT EDIT
3 // Tests in this project rely on specific lines numbers
4 // throughout this file.
5
6 import (
7 "testing"
8
9 "github.com/matryer/is"
10 )
11
12 func TestSomething(t *testing.T) {
13 // this comment will be extracted
14 }
15
16 func TestSomethingElse(t *testing.T) {
17 is := is.New(t)
18 a, b := 1, 2
19 getB := func() int {
20 return b
21 }
22 is.True(a == getB()) // should be the same
23 }
24
25 func TestSomethingElseTpp(t *testing.T) {
26 is := is.New(t)
27 a, b := 1, 2
28 getB := func() int {
29 return b
30 }
31 is.True(a == getB())
32 }