mirror of https://github.com/docker/cli.git
Merge pull request #1 from dnephin/add-dockerfile
Add Dockerfile and fix vendor.conf
This commit is contained in:
commit
bbee80a62e
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
_ "github.com/docker/docker/autogen/winresources/docker"
|
_ "github.com/docker/cli/autogen/winresources"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:cgo_import_dynamic main.dummy CommandLineToArgvW%2 "shell32.dll"
|
//go:cgo_import_dynamic main.dummy CommandLineToArgvW%2 "shell32.dll"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
FROM golang:1.8-alpine
|
||||||
|
|
||||||
|
RUN apk add -U git
|
||||||
|
|
||||||
|
RUN go get github.com/LK4D4/vndr && \
|
||||||
|
cp /go/bin/vndr /usr/bin && \
|
||||||
|
rm -rf /go/src/* /go/pkg/* /go/bin/*
|
||||||
|
|
||||||
|
RUN go get github.com/mitchellh/gox && \
|
||||||
|
cp /go/bin/gox /usr/bin && \
|
||||||
|
rm -rf /go/src/* /go/pkg/* /go/bin/*
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
WORKDIR /go/src/github.com/docker/cli
|
||||||
|
CMD sh
|
|
@ -7,8 +7,8 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
|
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
|
||||||
github.com/docker/docker f33f2578881af36dc403a2063a00b19a4844fcbe
|
github.com/docker/docker f33f2578881af36dc403a2063a00b19a4844fcbe
|
||||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
|
||||||
github.com/docker/docker-credential-helpers v0.5.0
|
github.com/docker/docker-credential-helpers v0.5.0
|
||||||
|
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
||||||
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
||||||
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
|
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
|
||||||
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
|
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
|
||||||
|
@ -17,8 +17,9 @@ github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
github.com/docker/notary v0.4.2
|
github.com/docker/notary v0.4.2
|
||||||
github.com/docker/swarmkit b19d028de0a6e9ca281afeb76cea2544b9edd839
|
github.com/docker/swarmkit b19d028de0a6e9ca281afeb76cea2544b9edd839
|
||||||
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
||||||
github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93
|
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
||||||
github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb
|
github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb
|
||||||
|
github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
|
@ -27,7 +28,6 @@ github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
|
||||||
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
||||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
||||||
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
||||||
github.com/opencontainers/runtime-spec 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
|
|
||||||
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
||||||
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
||||||
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
|
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
|
||||||
|
@ -41,6 +41,5 @@ golang.org/x/net c427ad74c6d7a814201695e9ffde0c5d400a7674
|
||||||
golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
||||||
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
|
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
|
||||||
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||||
google.golang.org/genproto b3e7c2fb04031add52c4817f53f43757ccbf9c18
|
|
||||||
google.golang.org/grpc v1.0.4
|
google.golang.org/grpc v1.0.4
|
||||||
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
Gocheck - A rich testing framework for Go
|
||||||
|
|
||||||
|
Copyright (c) 2010-2013 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,10 @@
|
||||||
|
Go-check
|
||||||
|
========
|
||||||
|
|
||||||
|
This is a fork of https://github.com/go-check/check
|
||||||
|
|
||||||
|
The intention of this fork is not to change any of the original behavior, but add
|
||||||
|
some specific behaviors needed for some of my projects already using this test suite.
|
||||||
|
For documentation on the main behavior of go-check see the aforementioned repo.
|
||||||
|
|
||||||
|
The original branch is intact at `orig_v1`
|
|
@ -0,0 +1,187 @@
|
||||||
|
// Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
|
||||||
|
// testingB is a type passed to Benchmark functions to manage benchmark
|
||||||
|
// timing and to specify the number of iterations to run.
|
||||||
|
type timer struct {
|
||||||
|
start time.Time // Time test or benchmark started
|
||||||
|
duration time.Duration
|
||||||
|
N int
|
||||||
|
bytes int64
|
||||||
|
timerOn bool
|
||||||
|
benchTime time.Duration
|
||||||
|
// The initial states of memStats.Mallocs and memStats.TotalAlloc.
|
||||||
|
startAllocs uint64
|
||||||
|
startBytes uint64
|
||||||
|
// The net total of this test after being run.
|
||||||
|
netAllocs uint64
|
||||||
|
netBytes uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTimer starts timing a test. This function is called automatically
|
||||||
|
// before a benchmark starts, but it can also used to resume timing after
|
||||||
|
// a call to StopTimer.
|
||||||
|
func (c *C) StartTimer() {
|
||||||
|
if !c.timerOn {
|
||||||
|
c.start = time.Now()
|
||||||
|
c.timerOn = true
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
c.startAllocs = memStats.Mallocs
|
||||||
|
c.startBytes = memStats.TotalAlloc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTimer stops timing a test. This can be used to pause the timer
|
||||||
|
// while performing complex initialization that you don't
|
||||||
|
// want to measure.
|
||||||
|
func (c *C) StopTimer() {
|
||||||
|
if c.timerOn {
|
||||||
|
c.duration += time.Now().Sub(c.start)
|
||||||
|
c.timerOn = false
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
c.netAllocs += memStats.Mallocs - c.startAllocs
|
||||||
|
c.netBytes += memStats.TotalAlloc - c.startBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetTimer sets the elapsed benchmark time to zero.
|
||||||
|
// It does not affect whether the timer is running.
|
||||||
|
func (c *C) ResetTimer() {
|
||||||
|
if c.timerOn {
|
||||||
|
c.start = time.Now()
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
c.startAllocs = memStats.Mallocs
|
||||||
|
c.startBytes = memStats.TotalAlloc
|
||||||
|
}
|
||||||
|
c.duration = 0
|
||||||
|
c.netAllocs = 0
|
||||||
|
c.netBytes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes informs the number of bytes that the benchmark processes
|
||||||
|
// on each iteration. If this is called in a benchmark it will also
|
||||||
|
// report MB/s.
|
||||||
|
func (c *C) SetBytes(n int64) {
|
||||||
|
c.bytes = n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) nsPerOp() int64 {
|
||||||
|
if c.N <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return c.duration.Nanoseconds() / int64(c.N)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) mbPerSec() float64 {
|
||||||
|
if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) timerString() string {
|
||||||
|
if c.N <= 0 {
|
||||||
|
return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9)
|
||||||
|
}
|
||||||
|
mbs := c.mbPerSec()
|
||||||
|
mb := ""
|
||||||
|
if mbs != 0 {
|
||||||
|
mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
|
||||||
|
}
|
||||||
|
nsop := c.nsPerOp()
|
||||||
|
ns := fmt.Sprintf("%10d ns/op", nsop)
|
||||||
|
if c.N > 0 && nsop < 100 {
|
||||||
|
// The format specifiers here make sure that
|
||||||
|
// the ones digits line up for all three possible formats.
|
||||||
|
if nsop < 10 {
|
||||||
|
ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
|
||||||
|
} else {
|
||||||
|
ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memStats := ""
|
||||||
|
if c.benchMem {
|
||||||
|
allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N))
|
||||||
|
allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N))
|
||||||
|
memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(x, y int) int {
|
||||||
|
if x > y {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundDown10 rounds a number down to the nearest power of 10.
|
||||||
|
func roundDown10(n int) int {
|
||||||
|
var tens = 0
|
||||||
|
// tens = floor(log_10(n))
|
||||||
|
for n > 10 {
|
||||||
|
n = n / 10
|
||||||
|
tens++
|
||||||
|
}
|
||||||
|
// result = 10^tens
|
||||||
|
result := 1
|
||||||
|
for i := 0; i < tens; i++ {
|
||||||
|
result *= 10
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
|
||||||
|
func roundUp(n int) int {
|
||||||
|
base := roundDown10(n)
|
||||||
|
if n < (2 * base) {
|
||||||
|
return 2 * base
|
||||||
|
}
|
||||||
|
if n < (5 * base) {
|
||||||
|
return 5 * base
|
||||||
|
}
|
||||||
|
return 10 * base
|
||||||
|
}
|
|
@ -0,0 +1,939 @@
|
||||||
|
// Package check is a rich testing extension for Go's testing package.
|
||||||
|
//
|
||||||
|
// For details about the project, see:
|
||||||
|
//
|
||||||
|
// http://labix.org/gocheck
|
||||||
|
//
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Internal type which deals with suite method calling.
|
||||||
|
|
||||||
|
const (
|
||||||
|
fixtureKd = iota
|
||||||
|
testKd
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
succeededSt = iota
|
||||||
|
failedSt
|
||||||
|
skippedSt
|
||||||
|
panickedSt
|
||||||
|
fixturePanickedSt
|
||||||
|
missedSt
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcStatus uint32
|
||||||
|
|
||||||
|
// A method value can't reach its own Method structure.
|
||||||
|
type methodType struct {
|
||||||
|
reflect.Value
|
||||||
|
Info reflect.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMethod(receiver reflect.Value, i int) *methodType {
|
||||||
|
return &methodType{receiver.Method(i), receiver.Type().Method(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method *methodType) PC() uintptr {
|
||||||
|
return method.Info.Func.Pointer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method *methodType) suiteName() string {
|
||||||
|
t := method.Info.Type.In(0)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method *methodType) String() string {
|
||||||
|
return method.suiteName() + "." + method.Info.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method *methodType) matches(re *regexp.Regexp) bool {
|
||||||
|
return (re.MatchString(method.Info.Name) ||
|
||||||
|
re.MatchString(method.suiteName()) ||
|
||||||
|
re.MatchString(method.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type C struct {
|
||||||
|
method *methodType
|
||||||
|
kind funcKind
|
||||||
|
testName string
|
||||||
|
_status funcStatus
|
||||||
|
logb *logger
|
||||||
|
logw io.Writer
|
||||||
|
done chan *C
|
||||||
|
reason string
|
||||||
|
mustFail bool
|
||||||
|
tempDir *tempDir
|
||||||
|
benchMem bool
|
||||||
|
startTime time.Time
|
||||||
|
timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) status() funcStatus {
|
||||||
|
return funcStatus(atomic.LoadUint32((*uint32)(&c._status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) setStatus(s funcStatus) {
|
||||||
|
atomic.StoreUint32((*uint32)(&c._status), uint32(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) stopNow() {
|
||||||
|
runtime.Goexit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger is a concurrency safe byte.Buffer
|
||||||
|
type logger struct {
|
||||||
|
sync.Mutex
|
||||||
|
writer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Write(buf []byte) (int, error) {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
return l.writer.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
return l.writer.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) String() string {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
return l.writer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Handling of temporary files and directories.
|
||||||
|
|
||||||
|
type tempDir struct {
|
||||||
|
sync.Mutex
|
||||||
|
path string
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *tempDir) newPath() string {
|
||||||
|
td.Lock()
|
||||||
|
defer td.Unlock()
|
||||||
|
if td.path == "" {
|
||||||
|
var err error
|
||||||
|
for i := 0; i != 100; i++ {
|
||||||
|
path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int())
|
||||||
|
if err = os.Mkdir(path, 0700); err == nil {
|
||||||
|
td.path = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if td.path == "" {
|
||||||
|
panic("Couldn't create temporary directory: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := filepath.Join(td.path, strconv.Itoa(td.counter))
|
||||||
|
td.counter += 1
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *tempDir) removeAll() {
|
||||||
|
td.Lock()
|
||||||
|
defer td.Unlock()
|
||||||
|
if td.path != "" {
|
||||||
|
err := os.RemoveAll(td.path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new temporary directory which is automatically removed after
|
||||||
|
// the suite finishes running.
|
||||||
|
func (c *C) MkDir() string {
|
||||||
|
path := c.tempDir.newPath()
|
||||||
|
if err := os.Mkdir(path, 0700); err != nil {
|
||||||
|
panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error()))
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Low-level logging functions.
|
||||||
|
|
||||||
|
func (c *C) log(args ...interface{}) {
|
||||||
|
c.writeLog([]byte(fmt.Sprint(args...) + "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logf(format string, args ...interface{}) {
|
||||||
|
c.writeLog([]byte(fmt.Sprintf(format+"\n", args...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logNewLine() {
|
||||||
|
c.writeLog([]byte{'\n'})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) writeLog(buf []byte) {
|
||||||
|
c.logb.Write(buf)
|
||||||
|
if c.logw != nil {
|
||||||
|
c.logw.Write(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasStringOrError(x interface{}) (ok bool) {
|
||||||
|
_, ok = x.(fmt.Stringer)
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok = x.(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logValue(label string, value interface{}) {
|
||||||
|
if label == "" {
|
||||||
|
if hasStringOrError(value) {
|
||||||
|
c.logf("... %#v (%q)", value, value)
|
||||||
|
} else {
|
||||||
|
c.logf("... %#v", value)
|
||||||
|
}
|
||||||
|
} else if value == nil {
|
||||||
|
c.logf("... %s = nil", label)
|
||||||
|
} else {
|
||||||
|
if hasStringOrError(value) {
|
||||||
|
fv := fmt.Sprintf("%#v", value)
|
||||||
|
qv := fmt.Sprintf("%q", value)
|
||||||
|
if fv != qv {
|
||||||
|
c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s, ok := value.(string); ok && isMultiLine(s) {
|
||||||
|
c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value))
|
||||||
|
c.logMultiLine(s)
|
||||||
|
} else {
|
||||||
|
c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logMultiLine(s string) {
|
||||||
|
b := make([]byte, 0, len(s)*2)
|
||||||
|
i := 0
|
||||||
|
n := len(s)
|
||||||
|
for i < n {
|
||||||
|
j := i + 1
|
||||||
|
for j < n && s[j-1] != '\n' {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
b = append(b, "... "...)
|
||||||
|
b = strconv.AppendQuote(b, s[i:j])
|
||||||
|
if j < n {
|
||||||
|
b = append(b, " +"...)
|
||||||
|
}
|
||||||
|
b = append(b, '\n')
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
c.writeLog(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMultiLine(s string) bool {
|
||||||
|
for i := 0; i+1 < len(s); i++ {
|
||||||
|
if s[i] == '\n' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logString(issue string) {
|
||||||
|
c.log("... ", issue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logCaller(skip int) {
|
||||||
|
// This is a bit heavier than it ought to be.
|
||||||
|
skip += 1 // Our own frame.
|
||||||
|
pc, callerFile, callerLine, ok := runtime.Caller(skip)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var testFile string
|
||||||
|
var testLine int
|
||||||
|
testFunc := runtime.FuncForPC(c.method.PC())
|
||||||
|
if runtime.FuncForPC(pc) != testFunc {
|
||||||
|
for {
|
||||||
|
skip += 1
|
||||||
|
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||||
|
// Note that the test line may be different on
|
||||||
|
// distinct calls for the same test. Showing
|
||||||
|
// the "internal" line is helpful when debugging.
|
||||||
|
if runtime.FuncForPC(pc) == testFunc {
|
||||||
|
testFile, testLine = file, line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if testFile != "" && (testFile != callerFile || testLine != callerLine) {
|
||||||
|
c.logCode(testFile, testLine)
|
||||||
|
}
|
||||||
|
c.logCode(callerFile, callerLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logCode(path string, line int) {
|
||||||
|
c.logf("%s:%d:", nicePath(path), line)
|
||||||
|
code, err := printLine(path, line)
|
||||||
|
if code == "" {
|
||||||
|
code = "..." // XXX Open the file and take the raw line.
|
||||||
|
if err != nil {
|
||||||
|
code += err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.log(indent(code, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueGo = filepath.Join("reflect", "value.go")
|
||||||
|
var asmGo = filepath.Join("runtime", "asm_")
|
||||||
|
|
||||||
|
func (c *C) logPanic(skip int, value interface{}) {
|
||||||
|
skip++ // Our own frame.
|
||||||
|
initialSkip := skip
|
||||||
|
for ; ; skip++ {
|
||||||
|
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||||
|
if skip == initialSkip {
|
||||||
|
c.logf("... Panic: %s (PC=0x%X)\n", value, pc)
|
||||||
|
}
|
||||||
|
name := niceFuncName(pc)
|
||||||
|
path := nicePath(file)
|
||||||
|
if strings.Contains(path, "/gopkg.in/check.v") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "Value.call" && strings.HasSuffix(path, valueGo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (name == "call16" || name == "call32") && strings.Contains(path, asmGo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.logf("%s:%d\n in %s", nicePath(file), line, name)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logSoftPanic(issue string) {
|
||||||
|
c.log("... Panic: ", issue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) logArgPanic(method *methodType, expectedType string) {
|
||||||
|
c.logf("... Panic: %s argument should be %s",
|
||||||
|
niceFuncName(method.PC()), expectedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Some simple formatting helpers.
|
||||||
|
|
||||||
|
var initWD, initWDErr = os.Getwd()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if initWDErr == nil {
|
||||||
|
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nicePath(path string) string {
|
||||||
|
if initWDErr == nil {
|
||||||
|
if strings.HasPrefix(path, initWD) {
|
||||||
|
return path[len(initWD):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func niceFuncPath(pc uintptr) string {
|
||||||
|
function := runtime.FuncForPC(pc)
|
||||||
|
if function != nil {
|
||||||
|
filename, line := function.FileLine(pc)
|
||||||
|
return fmt.Sprintf("%s:%d", nicePath(filename), line)
|
||||||
|
}
|
||||||
|
return "<unknown path>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func niceFuncName(pc uintptr) string {
|
||||||
|
function := runtime.FuncForPC(pc)
|
||||||
|
if function != nil {
|
||||||
|
name := path.Base(function.Name())
|
||||||
|
if i := strings.Index(name, "."); i > 0 {
|
||||||
|
name = name[i+1:]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(name, "(*") {
|
||||||
|
if i := strings.Index(name, ")"); i > 0 {
|
||||||
|
name = name[2:i] + name[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := strings.LastIndex(name, ".*"); i != -1 {
|
||||||
|
name = name[:i] + "." + name[i+2:]
|
||||||
|
}
|
||||||
|
if i := strings.LastIndex(name, "·"); i != -1 {
|
||||||
|
name = name[:i] + "." + name[i+2:]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return "<unknown function>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Result tracker to aggregate call results.
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Succeeded int
|
||||||
|
Failed int
|
||||||
|
Skipped int
|
||||||
|
Panicked int
|
||||||
|
FixturePanicked int
|
||||||
|
ExpectedFailures int
|
||||||
|
Missed int // Not even tried to run, related to a panic in the fixture.
|
||||||
|
RunError error // Houston, we've got a problem.
|
||||||
|
WorkDir string // If KeepWorkDir is true
|
||||||
|
}
|
||||||
|
|
||||||
|
type resultTracker struct {
|
||||||
|
result Result
|
||||||
|
_lastWasProblem bool
|
||||||
|
_waiting int
|
||||||
|
_missed int
|
||||||
|
_expectChan chan *C
|
||||||
|
_doneChan chan *C
|
||||||
|
_stopChan chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResultTracker() *resultTracker {
|
||||||
|
return &resultTracker{_expectChan: make(chan *C), // Synchronous
|
||||||
|
_doneChan: make(chan *C, 32), // Asynchronous
|
||||||
|
_stopChan: make(chan bool)} // Synchronous
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tracker *resultTracker) start() {
|
||||||
|
go tracker._loopRoutine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tracker *resultTracker) waitAndStop() {
|
||||||
|
<-tracker._stopChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tracker *resultTracker) expectCall(c *C) {
|
||||||
|
tracker._expectChan <- c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tracker *resultTracker) callDone(c *C) {
|
||||||
|
tracker._doneChan <- c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tracker *resultTracker) _loopRoutine() {
|
||||||
|
for {
|
||||||
|
var c *C
|
||||||
|
if tracker._waiting > 0 {
|
||||||
|
// Calls still running. Can't stop.
|
||||||
|
select {
|
||||||
|
// XXX Reindent this (not now to make diff clear)
|
||||||
|
case c = <-tracker._expectChan:
|
||||||
|
tracker._waiting += 1
|
||||||
|
case c = <-tracker._doneChan:
|
||||||
|
tracker._waiting -= 1
|
||||||
|
switch c.status() {
|
||||||
|
case succeededSt:
|
||||||
|
if c.kind == testKd {
|
||||||
|
if c.mustFail {
|
||||||
|
tracker.result.ExpectedFailures++
|
||||||
|
} else {
|
||||||
|
tracker.result.Succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case failedSt:
|
||||||
|
tracker.result.Failed++
|
||||||
|
case panickedSt:
|
||||||
|
if c.kind == fixtureKd {
|
||||||
|
tracker.result.FixturePanicked++
|
||||||
|
} else {
|
||||||
|
tracker.result.Panicked++
|
||||||
|
}
|
||||||
|
case fixturePanickedSt:
|
||||||
|
// Track it as missed, since the panic
|
||||||
|
// was on the fixture, not on the test.
|
||||||
|
tracker.result.Missed++
|
||||||
|
case missedSt:
|
||||||
|
tracker.result.Missed++
|
||||||
|
case skippedSt:
|
||||||
|
if c.kind == testKd {
|
||||||
|
tracker.result.Skipped++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No calls. Can stop, but no done calls here.
|
||||||
|
select {
|
||||||
|
case tracker._stopChan <- true:
|
||||||
|
return
|
||||||
|
case c = <-tracker._expectChan:
|
||||||
|
tracker._waiting += 1
|
||||||
|
case c = <-tracker._doneChan:
|
||||||
|
panic("Tracker got an unexpected done call.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// The underlying suite runner.
|
||||||
|
|
||||||
|
type suiteRunner struct {
|
||||||
|
suite interface{}
|
||||||
|
setUpSuite, tearDownSuite *methodType
|
||||||
|
setUpTest, tearDownTest *methodType
|
||||||
|
onTimeout *methodType
|
||||||
|
tests []*methodType
|
||||||
|
tracker *resultTracker
|
||||||
|
tempDir *tempDir
|
||||||
|
keepDir bool
|
||||||
|
output *outputWriter
|
||||||
|
reportedProblemLast bool
|
||||||
|
benchTime time.Duration
|
||||||
|
benchMem bool
|
||||||
|
checkTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type RunConf struct {
|
||||||
|
Output io.Writer
|
||||||
|
Stream bool
|
||||||
|
Verbose bool
|
||||||
|
Filter string
|
||||||
|
Benchmark bool
|
||||||
|
BenchmarkTime time.Duration // Defaults to 1 second
|
||||||
|
BenchmarkMem bool
|
||||||
|
KeepWorkDir bool
|
||||||
|
CheckTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new suiteRunner able to run all methods in the given suite.
|
||||||
|
func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
|
||||||
|
var conf RunConf
|
||||||
|
if runConf != nil {
|
||||||
|
conf = *runConf
|
||||||
|
}
|
||||||
|
if conf.Output == nil {
|
||||||
|
conf.Output = os.Stdout
|
||||||
|
}
|
||||||
|
if conf.Benchmark {
|
||||||
|
conf.Verbose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteType := reflect.TypeOf(suite)
|
||||||
|
suiteNumMethods := suiteType.NumMethod()
|
||||||
|
suiteValue := reflect.ValueOf(suite)
|
||||||
|
|
||||||
|
runner := &suiteRunner{
|
||||||
|
suite: suite,
|
||||||
|
output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose),
|
||||||
|
tracker: newResultTracker(),
|
||||||
|
benchTime: conf.BenchmarkTime,
|
||||||
|
benchMem: conf.BenchmarkMem,
|
||||||
|
tempDir: &tempDir{},
|
||||||
|
keepDir: conf.KeepWorkDir,
|
||||||
|
tests: make([]*methodType, 0, suiteNumMethods),
|
||||||
|
checkTimeout: conf.CheckTimeout,
|
||||||
|
}
|
||||||
|
if runner.benchTime == 0 {
|
||||||
|
runner.benchTime = 1 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterRegexp *regexp.Regexp
|
||||||
|
if conf.Filter != "" {
|
||||||
|
if regexp, err := regexp.Compile(conf.Filter); err != nil {
|
||||||
|
msg := "Bad filter expression: " + err.Error()
|
||||||
|
runner.tracker.result.RunError = errors.New(msg)
|
||||||
|
return runner
|
||||||
|
} else {
|
||||||
|
filterRegexp = regexp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i != suiteNumMethods; i++ {
|
||||||
|
method := newMethod(suiteValue, i)
|
||||||
|
switch method.Info.Name {
|
||||||
|
case "SetUpSuite":
|
||||||
|
runner.setUpSuite = method
|
||||||
|
case "TearDownSuite":
|
||||||
|
runner.tearDownSuite = method
|
||||||
|
case "SetUpTest":
|
||||||
|
runner.setUpTest = method
|
||||||
|
case "TearDownTest":
|
||||||
|
runner.tearDownTest = method
|
||||||
|
case "OnTimeout":
|
||||||
|
runner.onTimeout = method
|
||||||
|
default:
|
||||||
|
prefix := "Test"
|
||||||
|
if conf.Benchmark {
|
||||||
|
prefix = "Benchmark"
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(method.Info.Name, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filterRegexp == nil || method.matches(filterRegexp) {
|
||||||
|
runner.tests = append(runner.tests, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all methods in the given suite.
|
||||||
|
func (runner *suiteRunner) run() *Result {
|
||||||
|
if runner.tracker.result.RunError == nil && len(runner.tests) > 0 {
|
||||||
|
runner.tracker.start()
|
||||||
|
if runner.checkFixtureArgs() {
|
||||||
|
c := runner.runFixture(runner.setUpSuite, "", nil)
|
||||||
|
if c == nil || c.status() == succeededSt {
|
||||||
|
for i := 0; i != len(runner.tests); i++ {
|
||||||
|
c := runner.runTest(runner.tests[i])
|
||||||
|
if c.status() == fixturePanickedSt {
|
||||||
|
runner.skipTests(missedSt, runner.tests[i+1:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if c != nil && c.status() == skippedSt {
|
||||||
|
runner.skipTests(skippedSt, runner.tests)
|
||||||
|
} else {
|
||||||
|
runner.skipTests(missedSt, runner.tests)
|
||||||
|
}
|
||||||
|
runner.runFixture(runner.tearDownSuite, "", nil)
|
||||||
|
} else {
|
||||||
|
runner.skipTests(missedSt, runner.tests)
|
||||||
|
}
|
||||||
|
runner.tracker.waitAndStop()
|
||||||
|
if runner.keepDir {
|
||||||
|
runner.tracker.result.WorkDir = runner.tempDir.path
|
||||||
|
} else {
|
||||||
|
runner.tempDir.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &runner.tracker.result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a call object with the given suite method, and fork a
|
||||||
|
// goroutine with the provided dispatcher for running it.
|
||||||
|
func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
|
||||||
|
var logw io.Writer
|
||||||
|
if runner.output.Stream {
|
||||||
|
logw = runner.output
|
||||||
|
}
|
||||||
|
if logb == nil {
|
||||||
|
logb = new(logger)
|
||||||
|
}
|
||||||
|
c := &C{
|
||||||
|
method: method,
|
||||||
|
kind: kind,
|
||||||
|
testName: testName,
|
||||||
|
logb: logb,
|
||||||
|
logw: logw,
|
||||||
|
tempDir: runner.tempDir,
|
||||||
|
done: make(chan *C, 1),
|
||||||
|
timer: timer{benchTime: runner.benchTime},
|
||||||
|
startTime: time.Now(),
|
||||||
|
benchMem: runner.benchMem,
|
||||||
|
}
|
||||||
|
runner.tracker.expectCall(c)
|
||||||
|
go (func() {
|
||||||
|
runner.reportCallStarted(c)
|
||||||
|
defer runner.callDone(c)
|
||||||
|
dispatcher(c)
|
||||||
|
})()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeoutErr struct {
|
||||||
|
method *methodType
|
||||||
|
t time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e timeoutErr) Error() string {
|
||||||
|
return fmt.Sprintf("%s test timed out after %v", e.method.String(), e.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTimeout(e error) bool {
|
||||||
|
if e == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := e.(timeoutErr)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as forkCall(), but wait for call to finish before returning.
|
||||||
|
func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
|
||||||
|
var timeout <-chan time.Time
|
||||||
|
if runner.checkTimeout != 0 {
|
||||||
|
timeout = time.After(runner.checkTimeout)
|
||||||
|
}
|
||||||
|
c := runner.forkCall(method, kind, testName, logb, dispatcher)
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
case <-timeout:
|
||||||
|
if runner.onTimeout != nil {
|
||||||
|
// run the OnTimeout callback, allowing the suite to collect any sort of debug information it can
|
||||||
|
// `runFixture` is syncronous, so run this in a separate goroutine with a timeout
|
||||||
|
cChan := make(chan *C)
|
||||||
|
go func() {
|
||||||
|
cChan <- runner.runFixture(runner.onTimeout, c.testName, c.logb)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-cChan:
|
||||||
|
case <-time.After(runner.checkTimeout):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(timeoutErr{method, runner.checkTimeout})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a finished call. If there were any panics, update the call status
|
||||||
|
// accordingly. Then, mark the call as done and report to the tracker.
|
||||||
|
func (runner *suiteRunner) callDone(c *C) {
|
||||||
|
value := recover()
|
||||||
|
if value != nil {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case *fixturePanic:
|
||||||
|
if v.status == skippedSt {
|
||||||
|
c.setStatus(skippedSt)
|
||||||
|
} else {
|
||||||
|
c.logSoftPanic("Fixture has panicked (see related PANIC)")
|
||||||
|
c.setStatus(fixturePanickedSt)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.logPanic(1, value)
|
||||||
|
c.setStatus(panickedSt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.mustFail {
|
||||||
|
switch c.status() {
|
||||||
|
case failedSt:
|
||||||
|
c.setStatus(succeededSt)
|
||||||
|
case succeededSt:
|
||||||
|
c.setStatus(failedSt)
|
||||||
|
c.logString("Error: Test succeeded, but was expected to fail")
|
||||||
|
c.logString("Reason: " + c.reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.reportCallDone(c)
|
||||||
|
c.done <- c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a fixture call synchronously. The fixture will still be run in a
|
||||||
|
// goroutine like all suite methods, but this method will not return
|
||||||
|
// while the fixture goroutine is not done, because the fixture must be
|
||||||
|
// run in a desired order.
|
||||||
|
func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C {
|
||||||
|
if method != nil {
|
||||||
|
c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) {
|
||||||
|
c.ResetTimer()
|
||||||
|
c.StartTimer()
|
||||||
|
defer c.StopTimer()
|
||||||
|
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
|
||||||
|
// in case the fixture method panics. This makes it easier to track the
|
||||||
|
// fixture panic together with other call panics within forkTest().
|
||||||
|
func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C {
|
||||||
|
if skipped != nil && *skipped {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c := runner.runFixture(method, testName, logb)
|
||||||
|
if c != nil && c.status() != succeededSt {
|
||||||
|
if skipped != nil {
|
||||||
|
*skipped = c.status() == skippedSt
|
||||||
|
}
|
||||||
|
panic(&fixturePanic{c.status(), method})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type fixturePanic struct {
|
||||||
|
status funcStatus
|
||||||
|
method *methodType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the suite test method, together with the test-specific fixture,
|
||||||
|
// asynchronously.
|
||||||
|
func (runner *suiteRunner) forkTest(method *methodType) *C {
|
||||||
|
testName := method.String()
|
||||||
|
return runner.forkCall(method, testKd, testName, nil, func(c *C) {
|
||||||
|
var skipped bool
|
||||||
|
defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped)
|
||||||
|
defer c.StopTimer()
|
||||||
|
benchN := 1
|
||||||
|
for {
|
||||||
|
runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped)
|
||||||
|
mt := c.method.Type()
|
||||||
|
if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) {
|
||||||
|
// Rather than a plain panic, provide a more helpful message when
|
||||||
|
// the argument type is incorrect.
|
||||||
|
c.setStatus(panickedSt)
|
||||||
|
c.logArgPanic(c.method, "*check.C")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.method.Info.Name, "Test") {
|
||||||
|
c.ResetTimer()
|
||||||
|
c.StartTimer()
|
||||||
|
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(c.method.Info.Name, "Benchmark") {
|
||||||
|
panic("unexpected method prefix: " + c.method.Info.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
c.N = benchN
|
||||||
|
c.ResetTimer()
|
||||||
|
c.StartTimer()
|
||||||
|
|
||||||
|
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
|
||||||
|
c.StopTimer()
|
||||||
|
if c.status() != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perOpN := int(1e9)
|
||||||
|
if c.nsPerOp() != 0 {
|
||||||
|
perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic taken from the stock testing package:
|
||||||
|
// - Run more iterations than we think we'll need for a second (1.5x).
|
||||||
|
// - Don't grow too fast in case we had timing errors previously.
|
||||||
|
// - Be sure to run at least one more than last time.
|
||||||
|
benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1)
|
||||||
|
benchN = roundUp(benchN)
|
||||||
|
|
||||||
|
skipped = true // Don't run the deferred one if this panics.
|
||||||
|
runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil)
|
||||||
|
skipped = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as forkTest(), but wait for the test to finish before returning.
|
||||||
|
func (runner *suiteRunner) runTest(method *methodType) *C {
|
||||||
|
var timeout <-chan time.Time
|
||||||
|
if runner.checkTimeout != 0 {
|
||||||
|
timeout = time.After(runner.checkTimeout)
|
||||||
|
}
|
||||||
|
c := runner.forkTest(method)
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
case <-timeout:
|
||||||
|
if runner.onTimeout != nil {
|
||||||
|
// run the OnTimeout callback, allowing the suite to collect any sort of debug information it can
|
||||||
|
// `runFixture` is syncronous, so run this in a separate goroutine with a timeout
|
||||||
|
cChan := make(chan *C)
|
||||||
|
go func() {
|
||||||
|
cChan <- runner.runFixture(runner.onTimeout, c.testName, c.logb)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-cChan:
|
||||||
|
case <-time.After(runner.checkTimeout):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(timeoutErr{method, runner.checkTimeout})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to mark tests as skipped or missed. A bit heavy for what
|
||||||
|
// it does, but it enables homogeneous handling of tracking, including
|
||||||
|
// nice verbose output.
|
||||||
|
func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) {
|
||||||
|
for _, method := range methods {
|
||||||
|
runner.runFunc(method, testKd, "", nil, func(c *C) {
|
||||||
|
c.setStatus(status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if the fixture arguments are *check.C. In case of errors,
|
||||||
|
// log the error as a panic in the fixture method call, and return false.
|
||||||
|
func (runner *suiteRunner) checkFixtureArgs() bool {
|
||||||
|
succeeded := true
|
||||||
|
argType := reflect.TypeOf(&C{})
|
||||||
|
for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest, runner.onTimeout} {
|
||||||
|
if method != nil {
|
||||||
|
mt := method.Type()
|
||||||
|
if mt.NumIn() != 1 || mt.In(0) != argType {
|
||||||
|
succeeded = false
|
||||||
|
runner.runFunc(method, fixtureKd, "", nil, func(c *C) {
|
||||||
|
c.logArgPanic(method, "*check.C")
|
||||||
|
c.setStatus(panickedSt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return succeeded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (runner *suiteRunner) reportCallStarted(c *C) {
|
||||||
|
runner.output.WriteCallStarted("START", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (runner *suiteRunner) reportCallDone(c *C) {
|
||||||
|
runner.tracker.callDone(c)
|
||||||
|
switch c.status() {
|
||||||
|
case succeededSt:
|
||||||
|
if c.mustFail {
|
||||||
|
runner.output.WriteCallSuccess("FAIL EXPECTED", c)
|
||||||
|
} else {
|
||||||
|
runner.output.WriteCallSuccess("PASS", c)
|
||||||
|
}
|
||||||
|
case skippedSt:
|
||||||
|
runner.output.WriteCallSuccess("SKIP", c)
|
||||||
|
case failedSt:
|
||||||
|
runner.output.WriteCallProblem("FAIL", c)
|
||||||
|
case panickedSt:
|
||||||
|
runner.output.WriteCallProblem("PANIC", c)
|
||||||
|
case fixturePanickedSt:
|
||||||
|
// That's a testKd call reporting that its fixture
|
||||||
|
// has panicked. The fixture call which caused the
|
||||||
|
// panic itself was tracked above. We'll report to
|
||||||
|
// aid debugging.
|
||||||
|
runner.output.WriteCallProblem("PANIC", c)
|
||||||
|
case missedSt:
|
||||||
|
runner.output.WriteCallSuccess("MISS", c)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,458 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// CommentInterface and Commentf helper, to attach extra information to checks.
|
||||||
|
|
||||||
|
type comment struct {
|
||||||
|
format string
|
||||||
|
args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commentf returns an infomational value to use with Assert or Check calls.
|
||||||
|
// If the checker test fails, the provided arguments will be passed to
|
||||||
|
// fmt.Sprintf, and will be presented next to the logged failure.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i))
|
||||||
|
//
|
||||||
|
// Note that if the comment is constant, a better option is to
|
||||||
|
// simply use a normal comment right above or next to the line, as
|
||||||
|
// it will also get printed with any errors:
|
||||||
|
//
|
||||||
|
// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123)
|
||||||
|
//
|
||||||
|
func Commentf(format string, args ...interface{}) CommentInterface {
|
||||||
|
return &comment{format, args}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommentInterface must be implemented by types that attach extra
|
||||||
|
// information to failed checks. See the Commentf function for details.
|
||||||
|
type CommentInterface interface {
|
||||||
|
CheckCommentString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *comment) CheckCommentString() string {
|
||||||
|
return fmt.Sprintf(c.format, c.args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// The Checker interface.
|
||||||
|
|
||||||
|
// The Checker interface must be provided by checkers used with
|
||||||
|
// the Assert and Check verification methods.
|
||||||
|
type Checker interface {
|
||||||
|
Info() *CheckerInfo
|
||||||
|
Check(params []interface{}, names []string) (result bool, error string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See the Checker interface.
|
||||||
|
type CheckerInfo struct {
|
||||||
|
Name string
|
||||||
|
Params []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *CheckerInfo) Info() *CheckerInfo {
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Not checker logic inverter.
|
||||||
|
|
||||||
|
// The Not checker inverts the logic of the provided checker. The
|
||||||
|
// resulting checker will succeed where the original one failed, and
|
||||||
|
// vice-versa.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(a, Not(Equals), b)
|
||||||
|
//
|
||||||
|
func Not(checker Checker) Checker {
|
||||||
|
return ¬Checker{checker}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notChecker struct {
|
||||||
|
sub Checker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *notChecker) Info() *CheckerInfo {
|
||||||
|
info := *checker.sub.Info()
|
||||||
|
info.Name = "Not(" + info.Name + ")"
|
||||||
|
return &info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
result, error = checker.sub.Check(params, names)
|
||||||
|
result = !result
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// IsNil checker.
|
||||||
|
|
||||||
|
type isNilChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The IsNil checker tests whether the obtained value is nil.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(err, IsNil)
|
||||||
|
//
|
||||||
|
var IsNil Checker = &isNilChecker{
|
||||||
|
&CheckerInfo{Name: "IsNil", Params: []string{"value"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
return isNil(params[0]), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNil(obtained interface{}) (result bool) {
|
||||||
|
if obtained == nil {
|
||||||
|
result = true
|
||||||
|
} else {
|
||||||
|
switch v := reflect.ValueOf(obtained); v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return v.IsNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// NotNil checker. Alias for Not(IsNil), since it's so common.
|
||||||
|
|
||||||
|
type notNilChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The NotNil checker verifies that the obtained value is not nil.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(iface, NotNil)
|
||||||
|
//
|
||||||
|
// This is an alias for Not(IsNil), made available since it's a
|
||||||
|
// fairly common check.
|
||||||
|
//
|
||||||
|
var NotNil Checker = ¬NilChecker{
|
||||||
|
&CheckerInfo{Name: "NotNil", Params: []string{"value"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
return !isNil(params[0]), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Equals checker.
|
||||||
|
|
||||||
|
type equalsChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Equals checker verifies that the obtained value is equal to
|
||||||
|
// the expected value, according to usual Go semantics for ==.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(value, Equals, 42)
|
||||||
|
//
|
||||||
|
var Equals Checker = &equalsChecker{
|
||||||
|
&CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
result = false
|
||||||
|
error = fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return params[0] == params[1], ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// DeepEquals checker.
|
||||||
|
|
||||||
|
type deepEqualsChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DeepEquals checker verifies that the obtained value is deep-equal to
|
||||||
|
// the expected value. The check will work correctly even when facing
|
||||||
|
// slices, interfaces, and values of different types (which always fail
|
||||||
|
// the test).
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(value, DeepEquals, 42)
|
||||||
|
// c.Assert(array, DeepEquals, []string{"hi", "there"})
|
||||||
|
//
|
||||||
|
var DeepEquals Checker = &deepEqualsChecker{
|
||||||
|
&CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
return reflect.DeepEqual(params[0], params[1]), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// HasLen checker.
|
||||||
|
|
||||||
|
type hasLenChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The HasLen checker verifies that the obtained value has the
|
||||||
|
// provided length. In many cases this is superior to using Equals
|
||||||
|
// in conjuction with the len function because in case the check
|
||||||
|
// fails the value itself will be printed, instead of its length,
|
||||||
|
// providing more details for figuring the problem.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(list, HasLen, 5)
|
||||||
|
//
|
||||||
|
var HasLen Checker = &hasLenChecker{
|
||||||
|
&CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
n, ok := params[1].(int)
|
||||||
|
if !ok {
|
||||||
|
return false, "n must be an int"
|
||||||
|
}
|
||||||
|
value := reflect.ValueOf(params[0])
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String:
|
||||||
|
default:
|
||||||
|
return false, "obtained value type has no length"
|
||||||
|
}
|
||||||
|
return value.Len() == n, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// ErrorMatches checker.
|
||||||
|
|
||||||
|
type errorMatchesChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ErrorMatches checker verifies that the error value
|
||||||
|
// is non nil and matches the regular expression provided.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(err, ErrorMatches, "perm.*denied")
|
||||||
|
//
|
||||||
|
var ErrorMatches Checker = errorMatchesChecker{
|
||||||
|
&CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) {
|
||||||
|
if params[0] == nil {
|
||||||
|
return false, "Error value is nil"
|
||||||
|
}
|
||||||
|
err, ok := params[0].(error)
|
||||||
|
if !ok {
|
||||||
|
return false, "Value is not an error"
|
||||||
|
}
|
||||||
|
params[0] = err.Error()
|
||||||
|
names[0] = "error"
|
||||||
|
return matches(params[0], params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Matches checker.
|
||||||
|
|
||||||
|
type matchesChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Matches checker verifies that the string provided as the obtained
|
||||||
|
// value (or the string resulting from obtained.String()) matches the
|
||||||
|
// regular expression provided.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(err, Matches, "perm.*denied")
|
||||||
|
//
|
||||||
|
var Matches Checker = &matchesChecker{
|
||||||
|
&CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
return matches(params[0], params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func matches(value, regex interface{}) (result bool, error string) {
|
||||||
|
reStr, ok := regex.(string)
|
||||||
|
if !ok {
|
||||||
|
return false, "Regex must be a string"
|
||||||
|
}
|
||||||
|
valueStr, valueIsStr := value.(string)
|
||||||
|
if !valueIsStr {
|
||||||
|
if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr {
|
||||||
|
valueStr, valueIsStr = valueWithStr.String(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if valueIsStr {
|
||||||
|
matches, err := regexp.MatchString("^"+reStr+"$", valueStr)
|
||||||
|
if err != nil {
|
||||||
|
return false, "Can't compile regex: " + err.Error()
|
||||||
|
}
|
||||||
|
return matches, ""
|
||||||
|
}
|
||||||
|
return false, "Obtained value is not a string and has no .String()"
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Panics checker.
|
||||||
|
|
||||||
|
type panicsChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Panics checker verifies that calling the provided zero-argument
|
||||||
|
// function will cause a panic which is deep-equal to the provided value.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}).
|
||||||
|
//
|
||||||
|
//
|
||||||
|
var Panics Checker = &panicsChecker{
|
||||||
|
&CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
f := reflect.ValueOf(params[0])
|
||||||
|
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
|
||||||
|
return false, "Function must take zero arguments"
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// If the function has not panicked, then don't do the check.
|
||||||
|
if error != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params[0] = recover()
|
||||||
|
names[0] = "panic"
|
||||||
|
result = reflect.DeepEqual(params[0], params[1])
|
||||||
|
}()
|
||||||
|
f.Call(nil)
|
||||||
|
return false, "Function has not panicked"
|
||||||
|
}
|
||||||
|
|
||||||
|
type panicMatchesChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The PanicMatches checker verifies that calling the provided zero-argument
|
||||||
|
// function will cause a panic with an error value matching
|
||||||
|
// the regular expression provided.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`).
|
||||||
|
//
|
||||||
|
//
|
||||||
|
var PanicMatches Checker = &panicMatchesChecker{
|
||||||
|
&CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) {
|
||||||
|
f := reflect.ValueOf(params[0])
|
||||||
|
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
|
||||||
|
return false, "Function must take zero arguments"
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// If the function has not panicked, then don't do the check.
|
||||||
|
if errmsg != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obtained := recover()
|
||||||
|
names[0] = "panic"
|
||||||
|
if e, ok := obtained.(error); ok {
|
||||||
|
params[0] = e.Error()
|
||||||
|
} else if _, ok := obtained.(string); ok {
|
||||||
|
params[0] = obtained
|
||||||
|
} else {
|
||||||
|
errmsg = "Panic value is not a string or an error"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, errmsg = matches(params[0], params[1])
|
||||||
|
}()
|
||||||
|
f.Call(nil)
|
||||||
|
return false, "Function has not panicked"
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// FitsTypeOf checker.
|
||||||
|
|
||||||
|
type fitsTypeChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FitsTypeOf checker verifies that the obtained value is
|
||||||
|
// assignable to a variable with the same type as the provided
|
||||||
|
// sample value.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c.Assert(value, FitsTypeOf, int64(0))
|
||||||
|
// c.Assert(value, FitsTypeOf, os.Error(nil))
|
||||||
|
//
|
||||||
|
var FitsTypeOf Checker = &fitsTypeChecker{
|
||||||
|
&CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
obtained := reflect.ValueOf(params[0])
|
||||||
|
sample := reflect.ValueOf(params[1])
|
||||||
|
if !obtained.IsValid() {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
if !sample.IsValid() {
|
||||||
|
return false, "Invalid sample value"
|
||||||
|
}
|
||||||
|
return obtained.Type().AssignableTo(sample.Type()), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Implements checker.
|
||||||
|
|
||||||
|
type implementsChecker struct {
|
||||||
|
*CheckerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Implements checker verifies that the obtained value
|
||||||
|
// implements the interface specified via a pointer to an interface
|
||||||
|
// variable.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// var e os.Error
|
||||||
|
// c.Assert(err, Implements, &e)
|
||||||
|
//
|
||||||
|
var Implements Checker = &implementsChecker{
|
||||||
|
&CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||||
|
obtained := reflect.ValueOf(params[0])
|
||||||
|
ifaceptr := reflect.ValueOf(params[1])
|
||||||
|
if !obtained.IsValid() {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface {
|
||||||
|
return false, "ifaceptr should be a pointer to an interface variable"
|
||||||
|
}
|
||||||
|
return obtained.Type().Implements(ifaceptr.Elem().Type()), ""
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestName returns the current test name in the form "SuiteName.TestName"
|
||||||
|
func (c *C) TestName() string {
|
||||||
|
return c.testName
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Basic succeeding/failing logic.
|
||||||
|
|
||||||
|
// Failed returns whether the currently running test has already failed.
|
||||||
|
func (c *C) Failed() bool {
|
||||||
|
return c.status() == failedSt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail marks the currently running test as failed.
|
||||||
|
//
|
||||||
|
// Something ought to have been previously logged so the developer can tell
|
||||||
|
// what went wrong. The higher level helper functions will fail the test
|
||||||
|
// and do the logging properly.
|
||||||
|
func (c *C) Fail() {
|
||||||
|
c.setStatus(failedSt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailNow marks the currently running test as failed and stops running it.
|
||||||
|
// Something ought to have been previously logged so the developer can tell
|
||||||
|
// what went wrong. The higher level helper functions will fail the test
|
||||||
|
// and do the logging properly.
|
||||||
|
func (c *C) FailNow() {
|
||||||
|
c.Fail()
|
||||||
|
c.stopNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Succeed marks the currently running test as succeeded, undoing any
|
||||||
|
// previous failures.
|
||||||
|
func (c *C) Succeed() {
|
||||||
|
c.setStatus(succeededSt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SucceedNow marks the currently running test as succeeded, undoing any
|
||||||
|
// previous failures, and stops running the test.
|
||||||
|
func (c *C) SucceedNow() {
|
||||||
|
c.Succeed()
|
||||||
|
c.stopNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpectFailure informs that the running test is knowingly broken for
|
||||||
|
// the provided reason. If the test does not fail, an error will be reported
|
||||||
|
// to raise attention to this fact. This method is useful to temporarily
|
||||||
|
// disable tests which cover well known problems until a better time to
|
||||||
|
// fix the problem is found, without forgetting about the fact that a
|
||||||
|
// failure still exists.
|
||||||
|
func (c *C) ExpectFailure(reason string) {
|
||||||
|
if reason == "" {
|
||||||
|
panic("Missing reason why the test is expected to fail")
|
||||||
|
}
|
||||||
|
c.mustFail = true
|
||||||
|
c.reason = reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip skips the running test for the provided reason. If run from within
|
||||||
|
// SetUpTest, the individual test being set up will be skipped, and if run
|
||||||
|
// from within SetUpSuite, the whole suite is skipped.
|
||||||
|
func (c *C) Skip(reason string) {
|
||||||
|
if reason == "" {
|
||||||
|
panic("Missing reason why the test is being skipped")
|
||||||
|
}
|
||||||
|
c.reason = reason
|
||||||
|
c.setStatus(skippedSt)
|
||||||
|
c.stopNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Basic logging.
|
||||||
|
|
||||||
|
// GetTestLog returns the current test error output.
|
||||||
|
func (c *C) GetTestLog() string {
|
||||||
|
return c.logb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log logs some information into the test error output.
|
||||||
|
// The provided arguments are assembled together into a string with fmt.Sprint.
|
||||||
|
func (c *C) Log(args ...interface{}) {
|
||||||
|
c.log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log logs some information into the test error output.
|
||||||
|
// The provided arguments are assembled together into a string with fmt.Sprintf.
|
||||||
|
func (c *C) Logf(format string, args ...interface{}) {
|
||||||
|
c.logf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output enables *C to be used as a logger in functions that require only
|
||||||
|
// the minimum interface of *log.Logger.
|
||||||
|
func (c *C) Output(calldepth int, s string) error {
|
||||||
|
d := time.Now().Sub(c.startTime)
|
||||||
|
msec := d / time.Millisecond
|
||||||
|
sec := d / time.Second
|
||||||
|
min := d / time.Minute
|
||||||
|
|
||||||
|
c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error into the test error output and marks the test as failed.
|
||||||
|
// The provided arguments are assembled together into a string with fmt.Sprint.
|
||||||
|
func (c *C) Error(args ...interface{}) {
|
||||||
|
c.logCaller(1)
|
||||||
|
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
|
||||||
|
c.logNewLine()
|
||||||
|
c.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs an error into the test error output and marks the test as failed.
|
||||||
|
// The provided arguments are assembled together into a string with fmt.Sprintf.
|
||||||
|
func (c *C) Errorf(format string, args ...interface{}) {
|
||||||
|
c.logCaller(1)
|
||||||
|
c.logString(fmt.Sprintf("Error: "+format, args...))
|
||||||
|
c.logNewLine()
|
||||||
|
c.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs an error into the test error output, marks the test as failed, and
|
||||||
|
// stops the test execution. The provided arguments are assembled together into
|
||||||
|
// a string with fmt.Sprint.
|
||||||
|
func (c *C) Fatal(args ...interface{}) {
|
||||||
|
c.logCaller(1)
|
||||||
|
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
|
||||||
|
c.logNewLine()
|
||||||
|
c.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatlaf logs an error into the test error output, marks the test as failed, and
|
||||||
|
// stops the test execution. The provided arguments are assembled together into
|
||||||
|
// a string with fmt.Sprintf.
|
||||||
|
func (c *C) Fatalf(format string, args ...interface{}) {
|
||||||
|
c.logCaller(1)
|
||||||
|
c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...)))
|
||||||
|
c.logNewLine()
|
||||||
|
c.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Generic checks and assertions based on checkers.
|
||||||
|
|
||||||
|
// Check verifies if the first value matches the expected value according
|
||||||
|
// to the provided checker. If they do not match, an error is logged, the
|
||||||
|
// test is marked as failed, and the test execution continues.
|
||||||
|
//
|
||||||
|
// Some checkers may not need the expected argument (e.g. IsNil).
|
||||||
|
//
|
||||||
|
// Extra arguments provided to the function are logged next to the reported
|
||||||
|
// problem when the matching fails.
|
||||||
|
func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool {
|
||||||
|
return c.internalCheck("Check", obtained, checker, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert ensures that the first value matches the expected value according
|
||||||
|
// to the provided checker. If they do not match, an error is logged, the
|
||||||
|
// test is marked as failed, and the test execution stops.
|
||||||
|
//
|
||||||
|
// Some checkers may not need the expected argument (e.g. IsNil).
|
||||||
|
//
|
||||||
|
// Extra arguments provided to the function are logged next to the reported
|
||||||
|
// problem when the matching fails.
|
||||||
|
func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) {
|
||||||
|
if !c.internalCheck("Assert", obtained, checker, args...) {
|
||||||
|
c.stopNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool {
|
||||||
|
if checker == nil {
|
||||||
|
c.logCaller(2)
|
||||||
|
c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName))
|
||||||
|
c.logString("Oops.. you've provided a nil checker!")
|
||||||
|
c.logNewLine()
|
||||||
|
c.Fail()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last argument is a bug info, extract it out.
|
||||||
|
var comment CommentInterface
|
||||||
|
if len(args) > 0 {
|
||||||
|
if c, ok := args[len(args)-1].(CommentInterface); ok {
|
||||||
|
comment = c
|
||||||
|
args = args[:len(args)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := append([]interface{}{obtained}, args...)
|
||||||
|
info := checker.Info()
|
||||||
|
|
||||||
|
if len(params) != len(info.Params) {
|
||||||
|
names := append([]string{info.Params[0], info.Name}, info.Params[1:]...)
|
||||||
|
c.logCaller(2)
|
||||||
|
c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", ")))
|
||||||
|
c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1))
|
||||||
|
c.logNewLine()
|
||||||
|
c.Fail()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy since it may be mutated by Check.
|
||||||
|
names := append([]string{}, info.Params...)
|
||||||
|
|
||||||
|
// Do the actual check.
|
||||||
|
result, error := checker.Check(params, names)
|
||||||
|
if !result || error != "" {
|
||||||
|
c.logCaller(2)
|
||||||
|
for i := 0; i != len(params); i++ {
|
||||||
|
c.logValue(names[i], params[i])
|
||||||
|
}
|
||||||
|
if comment != nil {
|
||||||
|
c.logString(comment.CheckCommentString())
|
||||||
|
}
|
||||||
|
if error != "" {
|
||||||
|
c.logString(error)
|
||||||
|
}
|
||||||
|
c.logNewLine()
|
||||||
|
c.Fail()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func indent(s, with string) (r string) {
|
||||||
|
eol := true
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch {
|
||||||
|
case eol && c == '\n' || c == '\r':
|
||||||
|
case c == '\n' || c == '\r':
|
||||||
|
eol = true
|
||||||
|
case eol:
|
||||||
|
eol = false
|
||||||
|
s = s[:i] + with + s[i:]
|
||||||
|
i += len(with)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLine(filename string, line int) (string, error) {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}
|
||||||
|
lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config}
|
||||||
|
ast.Walk(lp, fnode)
|
||||||
|
result := lp.output.Bytes()
|
||||||
|
// Comments leave \n at the end.
|
||||||
|
n := len(result)
|
||||||
|
for n > 0 && result[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
return string(result[:n]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type linePrinter struct {
|
||||||
|
config *printer.Config
|
||||||
|
fset *token.FileSet
|
||||||
|
fnode *ast.File
|
||||||
|
line int
|
||||||
|
output bytes.Buffer
|
||||||
|
stmt ast.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) emit() bool {
|
||||||
|
if lp.stmt != nil {
|
||||||
|
lp.trim(lp.stmt)
|
||||||
|
lp.printWithComments(lp.stmt)
|
||||||
|
lp.stmt = nil
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) printWithComments(n ast.Node) {
|
||||||
|
nfirst := lp.fset.Position(n.Pos()).Line
|
||||||
|
nlast := lp.fset.Position(n.End()).Line
|
||||||
|
for _, g := range lp.fnode.Comments {
|
||||||
|
cfirst := lp.fset.Position(g.Pos()).Line
|
||||||
|
clast := lp.fset.Position(g.End()).Line
|
||||||
|
if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column {
|
||||||
|
for _, c := range g.List {
|
||||||
|
lp.output.WriteString(c.Text)
|
||||||
|
lp.output.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash {
|
||||||
|
// The printer will not include the comment if it starts past
|
||||||
|
// the node itself. Trick it into printing by overlapping the
|
||||||
|
// slash with the end of the statement.
|
||||||
|
g.List[0].Slash = n.End() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node := &printer.CommentedNode{n, lp.fnode.Comments}
|
||||||
|
lp.config.Fprint(&lp.output, lp.fset, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) {
|
||||||
|
if n == nil {
|
||||||
|
if lp.output.Len() == 0 {
|
||||||
|
lp.emit()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
first := lp.fset.Position(n.Pos()).Line
|
||||||
|
last := lp.fset.Position(n.End()).Line
|
||||||
|
if first <= lp.line && last >= lp.line {
|
||||||
|
// Print the innermost statement containing the line.
|
||||||
|
if stmt, ok := n.(ast.Stmt); ok {
|
||||||
|
if _, ok := n.(*ast.BlockStmt); !ok {
|
||||||
|
lp.stmt = stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if first == lp.line && lp.emit() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return lp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) trim(n ast.Node) bool {
|
||||||
|
stmt, ok := n.(ast.Stmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
line := lp.fset.Position(n.Pos()).Line
|
||||||
|
if line != lp.line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch stmt := stmt.(type) {
|
||||||
|
case *ast.IfStmt:
|
||||||
|
stmt.Body = lp.trimBlock(stmt.Body)
|
||||||
|
case *ast.SwitchStmt:
|
||||||
|
stmt.Body = lp.trimBlock(stmt.Body)
|
||||||
|
case *ast.TypeSwitchStmt:
|
||||||
|
stmt.Body = lp.trimBlock(stmt.Body)
|
||||||
|
case *ast.CaseClause:
|
||||||
|
stmt.Body = lp.trimList(stmt.Body)
|
||||||
|
case *ast.CommClause:
|
||||||
|
stmt.Body = lp.trimList(stmt.Body)
|
||||||
|
case *ast.BlockStmt:
|
||||||
|
stmt.List = lp.trimList(stmt.List)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt {
|
||||||
|
if !lp.trim(stmt) {
|
||||||
|
return lp.emptyBlock(stmt)
|
||||||
|
}
|
||||||
|
stmt.Rbrace = stmt.Lbrace
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt {
|
||||||
|
for i := 0; i != len(stmts); i++ {
|
||||||
|
if !lp.trim(stmts[i]) {
|
||||||
|
stmts[i] = lp.emptyStmt(stmts[i])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stmts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt {
|
||||||
|
return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt {
|
||||||
|
p := n.Pos()
|
||||||
|
return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Output writer manages atomic output writing according to settings.
|
||||||
|
|
||||||
|
type outputWriter struct {
|
||||||
|
m sync.Mutex
|
||||||
|
writer io.Writer
|
||||||
|
wroteCallProblemLast bool
|
||||||
|
Stream bool
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter {
|
||||||
|
return &outputWriter{writer: writer, Stream: stream, Verbose: verbose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ow *outputWriter) Write(content []byte) (n int, err error) {
|
||||||
|
ow.m.Lock()
|
||||||
|
n, err = ow.writer.Write(content)
|
||||||
|
ow.m.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ow *outputWriter) WriteCallStarted(label string, c *C) {
|
||||||
|
if ow.Stream {
|
||||||
|
header := renderCallHeader(label, c, "", "\n")
|
||||||
|
ow.m.Lock()
|
||||||
|
ow.writer.Write([]byte(header))
|
||||||
|
ow.m.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ow *outputWriter) WriteCallProblem(label string, c *C) {
|
||||||
|
var prefix string
|
||||||
|
if !ow.Stream {
|
||||||
|
prefix = "\n-----------------------------------" +
|
||||||
|
"-----------------------------------\n"
|
||||||
|
}
|
||||||
|
header := renderCallHeader(label, c, prefix, "\n\n")
|
||||||
|
ow.m.Lock()
|
||||||
|
ow.wroteCallProblemLast = true
|
||||||
|
ow.writer.Write([]byte(header))
|
||||||
|
if !ow.Stream {
|
||||||
|
c.logb.WriteTo(ow.writer)
|
||||||
|
}
|
||||||
|
ow.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ow *outputWriter) WriteCallSuccess(label string, c *C) {
|
||||||
|
if ow.Stream || (ow.Verbose && c.kind == testKd) {
|
||||||
|
// TODO Use a buffer here.
|
||||||
|
var suffix string
|
||||||
|
if c.reason != "" {
|
||||||
|
suffix = " (" + c.reason + ")"
|
||||||
|
}
|
||||||
|
if c.status() == succeededSt {
|
||||||
|
suffix += "\t" + c.timerString()
|
||||||
|
}
|
||||||
|
suffix += "\n"
|
||||||
|
if ow.Stream {
|
||||||
|
suffix += "\n"
|
||||||
|
}
|
||||||
|
header := renderCallHeader(label, c, "", suffix)
|
||||||
|
ow.m.Lock()
|
||||||
|
// Resist temptation of using line as prefix above due to race.
|
||||||
|
if !ow.Stream && ow.wroteCallProblemLast {
|
||||||
|
header = "\n-----------------------------------" +
|
||||||
|
"-----------------------------------\n" +
|
||||||
|
header
|
||||||
|
}
|
||||||
|
ow.wroteCallProblemLast = false
|
||||||
|
ow.writer.Write([]byte(header))
|
||||||
|
ow.m.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCallHeader(label string, c *C, prefix, suffix string) string {
|
||||||
|
pc := c.method.PC()
|
||||||
|
return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc),
|
||||||
|
niceFuncName(pc), suffix)
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Test suite registry.
|
||||||
|
|
||||||
|
var allSuites []interface{}
|
||||||
|
|
||||||
|
// Suite registers the given value as a test suite to be run. Any methods
|
||||||
|
// starting with the Test prefix in the given value will be considered as
|
||||||
|
// a test method.
|
||||||
|
func Suite(suite interface{}) interface{} {
|
||||||
|
allSuites = append(allSuites, suite)
|
||||||
|
return suite
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Public running interface.
|
||||||
|
|
||||||
|
var (
|
||||||
|
oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run")
|
||||||
|
oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode")
|
||||||
|
oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)")
|
||||||
|
oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks")
|
||||||
|
oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark")
|
||||||
|
oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run")
|
||||||
|
oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory")
|
||||||
|
|
||||||
|
newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run")
|
||||||
|
newVerboseFlag = flag.Bool("check.v", false, "Verbose mode")
|
||||||
|
newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)")
|
||||||
|
newBenchFlag = flag.Bool("check.b", false, "Run benchmarks")
|
||||||
|
newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark")
|
||||||
|
newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks")
|
||||||
|
newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run")
|
||||||
|
newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory")
|
||||||
|
checkTimeout = flag.String("check.timeout", "", "Panic if test runs longer than specified duration")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestingT runs all test suites registered with the Suite function,
|
||||||
|
// printing results to stdout, and reporting any failures back to
|
||||||
|
// the "testing" package.
|
||||||
|
func TestingT(testingT *testing.T) {
|
||||||
|
benchTime := *newBenchTime
|
||||||
|
if benchTime == 1*time.Second {
|
||||||
|
benchTime = *oldBenchTime
|
||||||
|
}
|
||||||
|
conf := &RunConf{
|
||||||
|
Filter: *oldFilterFlag + *newFilterFlag,
|
||||||
|
Verbose: *oldVerboseFlag || *newVerboseFlag,
|
||||||
|
Stream: *oldStreamFlag || *newStreamFlag,
|
||||||
|
Benchmark: *oldBenchFlag || *newBenchFlag,
|
||||||
|
BenchmarkTime: benchTime,
|
||||||
|
BenchmarkMem: *newBenchMem,
|
||||||
|
KeepWorkDir: *oldWorkFlag || *newWorkFlag,
|
||||||
|
}
|
||||||
|
if *checkTimeout != "" {
|
||||||
|
timeout, err := time.ParseDuration(*checkTimeout)
|
||||||
|
if err != nil {
|
||||||
|
testingT.Fatalf("error parsing specified timeout flag: %v", err)
|
||||||
|
}
|
||||||
|
conf.CheckTimeout = timeout
|
||||||
|
}
|
||||||
|
if *oldListFlag || *newListFlag {
|
||||||
|
w := bufio.NewWriter(os.Stdout)
|
||||||
|
for _, name := range ListAll(conf) {
|
||||||
|
fmt.Fprintln(w, name)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := RunAll(conf)
|
||||||
|
println(result.String())
|
||||||
|
if !result.Passed() {
|
||||||
|
testingT.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAll runs all test suites registered with the Suite function, using the
|
||||||
|
// provided run configuration.
|
||||||
|
func RunAll(runConf *RunConf) *Result {
|
||||||
|
result := Result{}
|
||||||
|
for _, suite := range allSuites {
|
||||||
|
result.Add(Run(suite, runConf))
|
||||||
|
}
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the provided test suite using the provided run configuration.
|
||||||
|
func Run(suite interface{}, runConf *RunConf) *Result {
|
||||||
|
runner := newSuiteRunner(suite, runConf)
|
||||||
|
return runner.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAll returns the names of all the test functions registered with the
|
||||||
|
// Suite function that will be run with the provided run configuration.
|
||||||
|
func ListAll(runConf *RunConf) []string {
|
||||||
|
var names []string
|
||||||
|
for _, suite := range allSuites {
|
||||||
|
names = append(names, List(suite, runConf)...)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the names of the test functions in the given
|
||||||
|
// suite that will be run with the provided run configuration.
|
||||||
|
func List(suite interface{}, runConf *RunConf) []string {
|
||||||
|
var names []string
|
||||||
|
runner := newSuiteRunner(suite, runConf)
|
||||||
|
for _, t := range runner.tests {
|
||||||
|
names = append(names, t.String())
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Result methods.
|
||||||
|
|
||||||
|
func (r *Result) Add(other *Result) {
|
||||||
|
r.Succeeded += other.Succeeded
|
||||||
|
r.Skipped += other.Skipped
|
||||||
|
r.Failed += other.Failed
|
||||||
|
r.Panicked += other.Panicked
|
||||||
|
r.FixturePanicked += other.FixturePanicked
|
||||||
|
r.ExpectedFailures += other.ExpectedFailures
|
||||||
|
r.Missed += other.Missed
|
||||||
|
if r.WorkDir != "" && other.WorkDir != "" {
|
||||||
|
r.WorkDir += ":" + other.WorkDir
|
||||||
|
} else if other.WorkDir != "" {
|
||||||
|
r.WorkDir = other.WorkDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) Passed() bool {
|
||||||
|
return (r.Failed == 0 && r.Panicked == 0 &&
|
||||||
|
r.FixturePanicked == 0 && r.Missed == 0 &&
|
||||||
|
r.RunError == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) String() string {
|
||||||
|
if r.RunError != nil {
|
||||||
|
return "ERROR: " + r.RunError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var value string
|
||||||
|
if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 &&
|
||||||
|
r.Missed == 0 {
|
||||||
|
value = "OK: "
|
||||||
|
} else {
|
||||||
|
value = "OOPS: "
|
||||||
|
}
|
||||||
|
value += fmt.Sprintf("%d passed", r.Succeeded)
|
||||||
|
if r.Skipped != 0 {
|
||||||
|
value += fmt.Sprintf(", %d skipped", r.Skipped)
|
||||||
|
}
|
||||||
|
if r.ExpectedFailures != 0 {
|
||||||
|
value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures)
|
||||||
|
}
|
||||||
|
if r.Failed != 0 {
|
||||||
|
value += fmt.Sprintf(", %d FAILED", r.Failed)
|
||||||
|
}
|
||||||
|
if r.Panicked != 0 {
|
||||||
|
value += fmt.Sprintf(", %d PANICKED", r.Panicked)
|
||||||
|
}
|
||||||
|
if r.FixturePanicked != 0 {
|
||||||
|
value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked)
|
||||||
|
}
|
||||||
|
if r.Missed != 0 {
|
||||||
|
value += fmt.Sprintf(", %d MISSED", r.Missed)
|
||||||
|
}
|
||||||
|
if r.WorkDir != "" {
|
||||||
|
value += "\nWORK=" + r.WorkDir
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
Loading…
Reference in New Issue