Skip to content

Commit

Permalink
Merge branch 'master' of github.com:matryer/is
Browse files Browse the repository at this point in the history
  • Loading branch information
matryer committed Jun 30, 2020
2 parents f94466d + d065469 commit 94c589d
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 58 deletions.
33 changes: 27 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
os:
- linux
- osx
- windows

language: go

sudo: required

go:
# "1.x" always refers to the latest Go version, inc. the patch release.
# e.g. "1.x" is 1.13 until 1.13.1 is available.
- 1.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
- tip

env:
- GIMME_OS=linux GIMME_ARCH=amd64
- GIMME_OS=darwin GIMME_ARCH=amd64
- GIMME_OS=windows GIMME_ARCH=amd64
matrix:
allow_failures:
- os: windows
go: tip
exclude:
# OSX 1.6.4 is not present in travis.
# https://github.com/travis-ci/travis-ci/issues/10309
- go: 1.6.x
os: osx

install:
- go get -d -v ./...
- go get -d -v ./...

script:
- go build -v ./...
- go build -v ./...
- go test ./...
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ func Test(t *testing.T) {

## Color

To turn off the colors, run `go test` with the `-nocolor` flag.
To turn off the colors, run `go test` with the `-nocolor` flag, or with the env var `IS_NO_COLOR=true`.

```
go test -nocolor
```

```
IS_NO_COLOR=true go test
```
64 changes: 64 additions & 0 deletions is-1.7.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// +build go1.7

package is

import (
"regexp"
"runtime"
)

// Helper marks the calling function as a test helper function.
// When printing file and line information, that function will be skipped.
//
// Available with Go 1.7 and later.
func (is *I) Helper() {
is.helpers[callerName(1)] = struct{}{}
}

// callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function).
func callerName(skip int) string {
// Make room for the skip PC.
var pc [1]uintptr
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
if n == 0 {
panic("is: zero callers found")
}
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return frame.Function
}

// The maximum number of stack frames to go through when skipping helper functions for
// the purpose of decorating log messages.
const maxStackLen = 50

var reIsSourceFile = regexp.MustCompile("is(-1.7)?\\.go$")

func (is *I) callerinfo() (path string, line int, ok bool) {
var pc [maxStackLen]uintptr
// Skip two extra frames to account for this function
// and runtime.Callers itself.
n := runtime.Callers(2, pc[:])
if n == 0 {
panic("is: zero callers found")
}
frames := runtime.CallersFrames(pc[:n])
var firstFrame, frame runtime.Frame
for more := true; more; {
frame, more = frames.Next()
if reIsSourceFile.MatchString(frame.File) {
continue
}
if firstFrame.PC == 0 {
firstFrame = frame
}
if _, ok := is.helpers[frame.Function]; ok {
// Frame is inside a helper function.
continue
}
return frame.File, frame.Line, true
}
// If no "non-helper" frame is found, the first non is frame is returned.
return firstFrame.File, firstFrame.Line, true
}
53 changes: 53 additions & 0 deletions is-1.7_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// +build go1.7

package is

import (
"bytes"
"strings"
"testing"
)

// TestSubtests ensures subtests work as expected.
// https://github.com/matryer/is/issues/1
func TestSubtests(t *testing.T) {
t.Run("sub1", func(t *testing.T) {
is := New(t)
is.Equal(1+1, 2)
})
}

func TestHelper(t *testing.T) {
tests := []struct {
name string
helper bool
expectedFilename string
}{
{
name: "without helper",
helper: false,
expectedFilename: "is_helper_test.go",
},
{
name: "with helper",
helper: true,
expectedFilename: "is-1.7_test.go",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tt := &mockT{}
is := NewRelaxed(tt)

var buf bytes.Buffer
is.out = &buf
helper(is, tc.helper)
actual := buf.String()
t.Log(actual)
if !strings.Contains(actual, tc.expectedFilename) {
t.Errorf("string does not contain correct filename: %s", actual)
}
})
}
}
23 changes: 23 additions & 0 deletions is-before-1.7.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// +build !go1.7

package is

import (
"regexp"
"runtime"
)

var reIsSourceFile = regexp.MustCompile("is(-before-1.7)?\\.go$")

func (is *I) callerinfo() (path string, line int, ok bool) {
for i := 0; ; i++ {
_, path, line, ok = runtime.Caller(i)
if !ok {
return
}
if reIsSourceFile.MatchString(path) {
continue
}
return path, line, true
}
}
77 changes: 33 additions & 44 deletions is.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
)
Expand All @@ -69,28 +68,31 @@ type I struct {
fail func()
out io.Writer
colorful bool

helpers map[string]struct{} // functions to be skipped when writing file/line info
}

var noColorFlag bool

func init() {
flag.BoolVar(&noColorFlag, "nocolor", false, "turns off colors")
envNoColor := os.Getenv("IS_NO_COLOR") == "true"
flag.BoolVar(&noColorFlag, "nocolor", envNoColor, "turns off colors")
}

// New makes a new testing helper using the specified
// T through which failures will be reported.
// In strict mode, failures call T.FailNow causing the test
// to be aborted. See NewRelaxed for alternative behavior.
func New(t T) *I {
return &I{t, t.FailNow, os.Stdout, !noColorFlag}
return &I{t, t.FailNow, os.Stdout, !noColorFlag, map[string]struct{}{}}
}

// NewRelaxed makes a new testing helper using the specified
// T through which failures will be reported.
// In relaxed mode, failures call T.Fail allowing
// multiple failures per test.
func NewRelaxed(t T) *I {
return &I{t, t.Fail, os.Stdout, !noColorFlag}
return &I{t, t.Fail, os.Stdout, !noColorFlag, map[string]struct{}{}}
}

func (is *I) log(args ...interface{}) {
Expand Down Expand Up @@ -146,23 +148,14 @@ func (is *I) True(expression bool) {
//
// your_test.go:123: Hey Mat != Hi Mat // greeting
func (is *I) Equal(a, b interface{}) {
if !areEqual(a, b) {
if isNil(a) || isNil(b) {
aLabel := is.valWithType(a)
bLabel := is.valWithType(b)
if isNil(a) {
aLabel = "<nil>"
}
if isNil(b) {
bLabel = "<nil>"
}
is.logf("%s != %s", aLabel, bLabel)
return
}
if reflect.ValueOf(a).Type() == reflect.ValueOf(b).Type() {
is.logf("%v != %v", a, b)
return
}
if areEqual(a, b) {
return
}
if isNil(a) || isNil(b) {
is.logf("%s != %s", is.valWithType(a), is.valWithType(b))
} else if reflect.ValueOf(a).Type() == reflect.ValueOf(b).Type() {
is.logf("%v != %v", a, b)
} else {
is.logf("%s != %s", is.valWithType(a), is.valWithType(b))
}
}
Expand Down Expand Up @@ -198,6 +191,9 @@ func (is *I) NewRelaxed(t *testing.T) *I {
}

func (is *I) valWithType(v interface{}) string {
if isNil(v) {
return "<nil>"
}
if is.colorful {
return fmt.Sprintf("%[1]s%[3]T(%[2]s%[3]v%[1]s)%[2]s", colorType, colorNormal, v)
}
Expand Down Expand Up @@ -237,15 +233,12 @@ func isNil(object interface{}) bool {

// areEqual gets whether a equals b or not.
func areEqual(a, b interface{}) bool {
if isNil(a) || isNil(b) {
if isNil(a) && !isNil(b) {
return false
}
if !isNil(a) && isNil(b) {
return false
}
if isNil(a) && isNil(b) {
return true
}
if isNil(a) || isNil(b) {
return false
}
if reflect.DeepEqual(a, b) {
return true
}
Expand All @@ -254,19 +247,6 @@ func areEqual(a, b interface{}) bool {
return aValue == bValue
}

func callerinfo() (path string, line int, ok bool) {
for i := 0; ; i++ {
_, path, line, ok = runtime.Caller(i)
if !ok {
return
}
if strings.HasSuffix(path, "is.go") {
continue
}
return path, line, true
}
}

// loadComment gets the Go comment from the specified line
// in the specified file.
func loadComment(path string, line int) (string, bool) {
Expand All @@ -280,7 +260,7 @@ func loadComment(path string, line int) (string, bool) {
for s.Scan() {
if i == line {
text := s.Text()
commentI := strings.Index(text, "//")
commentI := strings.Index(text, "// ")
if commentI == -1 {
return "", false // no comment
}
Expand Down Expand Up @@ -339,7 +319,7 @@ func loadArguments(path string, line int) (string, bool) {
// and inserts the final newline if needed and indentation tabs for formatting.
// this function was copied from the testing framework and modified.
func (is *I) decorate(s string) string {
path, lineNumber, ok := callerinfo() // decorate + log + public function.
path, lineNumber, ok := is.callerinfo() // decorate + log + public function.
file := filepath.Base(path)
if ok {
// Truncate file name at last file name separator.
Expand All @@ -362,6 +342,9 @@ func (is *I) decorate(s string) string {
if is.colorful {
buf.WriteString(colorNormal)
}

s = escapeFormatString(s)

lines := strings.Split(s, "\n")
if l := len(lines); l > 1 && lines[l-1] == "" {
lines = lines[:l-1]
Expand All @@ -384,6 +367,7 @@ func (is *I) decorate(s string) string {
buf.WriteString(colorComment)
}
buf.WriteString(" // ")
comment = escapeFormatString(comment)
buf.WriteString(comment)
if is.colorful {
buf.WriteString(colorNormal)
Expand All @@ -393,9 +377,14 @@ func (is *I) decorate(s string) string {
return buf.String()
}

// escapeFormatString escapes strings for use in formatted functions like Sprintf.
func escapeFormatString(fmt string) string {
return strings.Replace(fmt, "%", "%%", -1)
}

const (
colorNormal = "\u001b[39m"
colorComment = "\u001b[32m"
colorComment = "\u001b[31m"
colorFile = "\u001b[90m"
colorType = "\u001b[90m"
)
10 changes: 10 additions & 0 deletions is_helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build go1.7

package is

func helper(is *I, helper bool) {
if helper {
is.Helper()
}
is.True(false)
}
Loading

0 comments on commit 94c589d

Please sign in to comment.