Merge pull request #160 from mlaventure/integration-test
Integration test
This commit is contained in:
commit
53cfb9dfbe
35 changed files with 4068 additions and 10 deletions
29
Dockerfile
29
Dockerfile
|
@ -6,6 +6,9 @@ RUN apt-get update && apt-get install -y \
|
|||
curl \
|
||||
git \
|
||||
make \
|
||||
jq \
|
||||
apparmor \
|
||||
libapparmor-dev \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
@ -15,10 +18,36 @@ RUN curl -sSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd6
|
|||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/containerd/vendor
|
||||
|
||||
WORKDIR /go/src/github.com/docker/containerd
|
||||
|
||||
# install golint/vet
|
||||
RUN go get github.com/golang/lint/golint \
|
||||
&& go get golang.org/x/tools/cmd/vet
|
||||
|
||||
# install seccomp: the version shipped in trusty is too old
|
||||
ENV SECCOMP_VERSION 2.3.0
|
||||
RUN set -x \
|
||||
&& export SECCOMP_PATH="$(mktemp -d)" \
|
||||
&& curl -fsSL "https://github.com/seccomp/libseccomp/releases/download/v${SECCOMP_VERSION}/libseccomp-${SECCOMP_VERSION}.tar.gz" \
|
||||
| tar -xzC "$SECCOMP_PATH" --strip-components=1 \
|
||||
&& ( \
|
||||
cd "$SECCOMP_PATH" \
|
||||
&& ./configure --prefix=/usr/local \
|
||||
&& make \
|
||||
&& make install \
|
||||
&& ldconfig \
|
||||
) \
|
||||
&& rm -rf "$SECCOMP_PATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make BUILDTAGS="seccomp apparmor selinux" && make install
|
||||
|
||||
COPY . /go/src/github.com/docker/containerd
|
||||
|
||||
WORKDIR /go/src/github.com/docker/containerd
|
||||
|
|
23
Makefile
23
Makefile
|
@ -1,5 +1,7 @@
|
|||
BUILDTAGS=
|
||||
|
||||
PROJECT=github.com/docker/containerd
|
||||
|
||||
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null)
|
||||
|
||||
|
@ -13,8 +15,12 @@ ifeq ($(INTERACTIVE), 1)
|
|||
DOCKER_FLAGS += -t
|
||||
endif
|
||||
|
||||
TEST_ARTIFACTS_DIR := integration-test/test-artifacts
|
||||
BUNDLE_ARCHIVES_DIR := $(TEST_ARTIFACTS_DIR)/archives
|
||||
|
||||
DOCKER_IMAGE := containerd-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_RUN := docker run --rm -i $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
DOCKER_RUN := docker run --privileged --rm -i $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
|
||||
|
||||
export GOPATH:=$(CURDIR)/vendor:$(GOPATH)
|
||||
|
||||
|
@ -46,7 +52,13 @@ shim: bin
|
|||
shim-static:
|
||||
cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim
|
||||
|
||||
dbuild:
|
||||
$(BUNDLE_ARCHIVES_DIR)/busybox.tar:
|
||||
@mkdir -p $(BUNDLE_ARCHIVES_DIR)
|
||||
curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' -o $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||
|
||||
bundles-rootfs: $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||
|
||||
dbuild: $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||
@docker build --rm --force-rm -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
dtest: dbuild
|
||||
|
@ -68,7 +80,12 @@ shell: dbuild
|
|||
$(DOCKER_RUN) bash
|
||||
|
||||
test: all validate
|
||||
go test -v $(shell go list ./... | grep -v /vendor)
|
||||
go test -v $(shell go list ./... | grep -v /vendor | grep -v /integration-test)
|
||||
ifneq ($(wildcard /.dockerenv), )
|
||||
$(MAKE) install bundles-rootfs
|
||||
cd integration-test ; \
|
||||
go test -check.v $(TESTFLAGS) github.com/docker/containerd/integration-test
|
||||
endif
|
||||
|
||||
validate: fmt
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ func (s *apiServer) State(ctx context.Context, r *types.StateRequest) (*types.St
|
|||
func createAPIContainer(c runtime.Container, getPids bool) (*types.Container, error) {
|
||||
processes, err := c.Processes()
|
||||
if err != nil {
|
||||
return nil, grpc.Errorf(codes.Internal, "get processes for container")
|
||||
return nil, grpc.Errorf(codes.Internal, "get processes for container: "+err.Error())
|
||||
}
|
||||
var procs []*types.Process
|
||||
for _, p := range processes {
|
||||
|
@ -148,7 +148,7 @@ func createAPIContainer(c runtime.Container, getPids bool) (*types.Container, er
|
|||
state := c.State()
|
||||
if getPids && (state == runtime.Running || state == runtime.Paused) {
|
||||
if pids, err = c.Pids(); err != nil {
|
||||
return nil, grpc.Errorf(codes.Internal, "get all pids for container")
|
||||
return nil, grpc.Errorf(codes.Internal, "get all pids for container: "+err.Error())
|
||||
}
|
||||
}
|
||||
return &types.Container{
|
||||
|
|
|
@ -34,10 +34,12 @@ func setAppBefore(app *cli.App) {
|
|||
app.Before = func(context *cli.Context) error {
|
||||
if context.GlobalBool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
if context.GlobalDuration("metrics-interval") > 0 {
|
||||
if err := debugMetrics(context.GlobalDuration("metrics-interval"), context.GlobalString("graphite-address")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := checkLimits(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ clean() {
|
|||
local packages=(
|
||||
"${PROJECT}/containerd" # package main
|
||||
"${PROJECT}/ctr" # package main
|
||||
"${PROJECT}/integration-test" # package main
|
||||
)
|
||||
local platforms=( linux/amd64 linux/386 windows/amd64 windows/386 darwin/amd64 )
|
||||
local buildTagCombos=(
|
||||
|
@ -77,8 +78,8 @@ clean() {
|
|||
|
||||
echo -n 'pruning unused packages, '
|
||||
findArgs=(
|
||||
# This directory contains only .c and .h files which are necessary
|
||||
-path vendor/src/github.com/mattn/go-sqlite3/code
|
||||
# for some reason go list doesn't detect this as a dependency
|
||||
-path vendor/src/github.com/vdemeester/shakers
|
||||
)
|
||||
for import in "${imports[@]}"; do
|
||||
[ "${#findArgs[@]}" -eq 0 ] || findArgs+=( -or )
|
||||
|
|
|
@ -25,4 +25,7 @@ clone git golang.org/x/net 991d3e32f76f19ee6d9caadb3a22eae8d23315f7 https://gith
|
|||
clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git
|
||||
clone git github.com/seccomp/libseccomp-golang 1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1
|
||||
|
||||
clone git github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
clone git github.com/go-check/check a625211d932a2a643d0d17352095f03fb7774663 https://github.com/cpuguy83/check.git
|
||||
|
||||
clean
|
||||
|
|
107
integration-test/bundle_utils_test.go
Normal file
107
integration-test/bundle_utils_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
ocs "github.com/opencontainers/specs/specs-go"
|
||||
)
|
||||
|
||||
var (
|
||||
bundlesDir = filepath.Join("test-artifacts", "oci-bundles")
|
||||
refOciSpecsPath = filepath.Join(bundlesDir, "config.json")
|
||||
)
|
||||
|
||||
type OciProcessArgs struct {
|
||||
Cmd string
|
||||
Args []string
|
||||
}
|
||||
|
||||
type Bundle struct {
|
||||
Source string
|
||||
Name string
|
||||
Spec ocs.Spec
|
||||
Path string
|
||||
}
|
||||
|
||||
var bundleMap map[string]Bundle
|
||||
|
||||
// untarRootfs untars the given `source` tarPath into `destination/rootfs`
|
||||
func untarRootfs(source string, destination string) error {
|
||||
destination = filepath.Join(destination, "rootfs")
|
||||
if err := os.MkdirAll(destination, 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
tar := exec.Command("tar", "-C", destination, "-xf", source)
|
||||
return tar.Run()
|
||||
}
|
||||
|
||||
// CreateBundleWithFilter generate a new oci-bundle named `name` from
|
||||
// the provide `source` rootfs. It starts from the default spec
|
||||
// generated by `runc spec`, overrides the `spec.Process.Args` value
|
||||
// with `args` and set `spec.Process.Terminal` to false. It then apply
|
||||
// `filter()` to the resulting spec if it is provided.
|
||||
func CreateBundleWithFilter(source, name string, args []string, filter func(spec *ocs.Spec)) error {
|
||||
// Generate the spec
|
||||
var spec ocs.Spec
|
||||
if f, err := os.Open(refOciSpecsPath); err != nil {
|
||||
return fmt.Errorf("Failed to open default spec: %v", err)
|
||||
} else {
|
||||
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||
return fmt.Errorf("Failed to load default spec: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
spec.Process.Args = args
|
||||
spec.Process.Terminal = false
|
||||
if filter != nil {
|
||||
filter(&spec)
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(bundlesDir, name)
|
||||
nb := Bundle{source, name, spec, bundlePath}
|
||||
|
||||
// Check that we don't already have such a bundle
|
||||
if b, ok := bundleMap[name]; ok {
|
||||
if reflect.DeepEqual(b, nb) == false {
|
||||
return fmt.Errorf("A bundle name named '%s' already exist but with different properties! %#v != %#v",
|
||||
name, b, nb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Nothing should be there, but just in case
|
||||
os.RemoveAll(bundlePath)
|
||||
|
||||
if err := untarRootfs(filepath.Join(archivesDir, source+".tar"), bundlePath); err != nil {
|
||||
return fmt.Errorf("Failed to untar %s.tar: %v", source, err)
|
||||
}
|
||||
|
||||
// create a place for the io fifo
|
||||
if err := os.Mkdir(filepath.Join(bundlePath, "io"), 0755); err != nil {
|
||||
return fmt.Errorf("Failed to create bundle io directory: %v", err)
|
||||
}
|
||||
|
||||
// Write the updated spec to the right location
|
||||
config, e := os.Create(filepath.Join(bundlePath, "config.json"))
|
||||
if e != nil {
|
||||
return fmt.Errorf("Failed to create oci spec: %v", e)
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
if err := json.NewEncoder(config).Encode(&spec); err != nil {
|
||||
return fmt.Errorf("Failed to encore oci spec: %v", e)
|
||||
}
|
||||
|
||||
bundleMap[name] = nb
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateBusyboxBundle(name string, args []string) error {
|
||||
return CreateBundleWithFilter("busybox", name, args, nil)
|
||||
}
|
228
integration-test/check_test.go
Normal file
228
integration-test/check_test.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
var (
|
||||
outputDirFormat = filepath.Join("test-artifacts", "runs", "%s")
|
||||
archivesDir = filepath.Join("test-artifacts", "archives")
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
func init() {
|
||||
check.Suite(&ContainerdSuite{})
|
||||
}
|
||||
|
||||
type ContainerdSuite struct {
|
||||
cwd string
|
||||
outputDir string
|
||||
logFile *os.File
|
||||
cd *exec.Cmd
|
||||
syncChild chan error
|
||||
grpcClient types.APIClient
|
||||
eventFiltersMutex sync.Mutex
|
||||
eventFilters map[string]func(event *types.Event)
|
||||
}
|
||||
|
||||
// getClient returns a connection to the Suite containerd
|
||||
func (cs *ContainerdSuite) getClient(socket string) error {
|
||||
// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
|
||||
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
|
||||
dialOpts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
dialOpts = append(dialOpts,
|
||||
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout("unix", addr, timeout)
|
||||
},
|
||||
))
|
||||
conn, err := grpc.Dial(socket, dialOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs.grpcClient = types.NewAPIClient(conn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerdEventsHandler will process all events coming from
|
||||
// containerd. If a filter as been register for a given container id
|
||||
// via `SetContainerEventFilter()`, it will be invoked every time an
|
||||
// event for that id is received
|
||||
func (cs *ContainerdSuite) ContainerdEventsHandler(events types.API_EventsClient) {
|
||||
timestamp := uint64(time.Now().Unix())
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
events, _ = cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: timestamp})
|
||||
continue
|
||||
}
|
||||
timestamp = e.Timestamp
|
||||
cs.eventFiltersMutex.Lock()
|
||||
if f, ok := cs.eventFilters[e.Id]; ok {
|
||||
f(e)
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
delete(cs.eventFilters, e.Id)
|
||||
}
|
||||
}
|
||||
cs.eventFiltersMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// generateReferencesSpecs invoke `runc spec` to produce the baseline
|
||||
// specs from which all future bundle will be generated
|
||||
func generateReferenceSpecs(destination string) error {
|
||||
specs := exec.Command("runc", "spec")
|
||||
specs.Dir = destination
|
||||
return specs.Run()
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SetUpSuite(c *check.C) {
|
||||
bundleMap = make(map[string]Bundle)
|
||||
cs.eventFilters = make(map[string]func(event *types.Event))
|
||||
|
||||
// Get our CWD
|
||||
if cwd, err := os.Getwd(); err != nil {
|
||||
c.Fatalf("Could not determine current working directory: %v", err)
|
||||
} else {
|
||||
cs.cwd = cwd
|
||||
}
|
||||
|
||||
// Clean old bundles
|
||||
os.RemoveAll(bundlesDir)
|
||||
|
||||
// Ensure the oci bundles directory exists
|
||||
if err := os.MkdirAll(bundlesDir, 0755); err != nil {
|
||||
c.Fatalf("Failed to create bundles directory: %v", err)
|
||||
}
|
||||
|
||||
// Generate the reference spec
|
||||
if err := generateReferenceSpecs(bundlesDir); err != nil {
|
||||
c.Fatalf("Unable to generate OCI reference spec: %v", err)
|
||||
}
|
||||
|
||||
// Create our output directory
|
||||
od := fmt.Sprintf(outputDirFormat, time.Now().Format("2006-01-02_150405.000000"))
|
||||
cdStateDir := fmt.Sprintf("%s/containerd-master", od)
|
||||
if err := os.MkdirAll(cdStateDir, 0755); err != nil {
|
||||
c.Fatalf("Unable to created output directory '%s': %v", cdStateDir, err)
|
||||
}
|
||||
|
||||
cdGRPCSock := filepath.Join(od, "containerd-master", "containerd.sock")
|
||||
cdLogFile := filepath.Join(od, "containerd-master", "containerd.log")
|
||||
|
||||
f, err := os.OpenFile(cdLogFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR|os.O_SYNC, 0777)
|
||||
if err != nil {
|
||||
c.Fatalf("Failed to create master containerd log file: %v", err)
|
||||
}
|
||||
cs.logFile = f
|
||||
|
||||
cd := exec.Command("containerd", "--debug",
|
||||
"--state-dir", cdStateDir,
|
||||
"--listen", cdGRPCSock,
|
||||
"--metrics-interval", "0m0s",
|
||||
"--runtime-args", fmt.Sprintf("--root=%s", filepath.Join(cs.cwd, cdStateDir, "runc")),
|
||||
)
|
||||
cd.Stderr = f
|
||||
cd.Stdout = f
|
||||
|
||||
if err := cd.Start(); err != nil {
|
||||
c.Fatalf("Unable to start the master containerd: %v", err)
|
||||
}
|
||||
|
||||
cs.outputDir = od
|
||||
cs.cd = cd
|
||||
cs.syncChild = make(chan error)
|
||||
if err := cs.getClient(cdGRPCSock); err != nil {
|
||||
// Kill the daemon
|
||||
cs.cd.Process.Kill()
|
||||
c.Fatalf("Failed to connect to daemon: %v", err)
|
||||
}
|
||||
|
||||
// Monitor events
|
||||
events, err := cs.grpcClient.Events(context.Background(), &types.EventsRequest{})
|
||||
if err != nil {
|
||||
c.Fatalf("Could not register containerd event handler: %v", err)
|
||||
}
|
||||
|
||||
go cs.ContainerdEventsHandler(events)
|
||||
|
||||
go func() {
|
||||
cs.syncChild <- cd.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TearDownSuite(c *check.C) {
|
||||
|
||||
// tell containerd to stop
|
||||
if cs.cd != nil {
|
||||
cs.cd.Process.Signal(os.Interrupt)
|
||||
|
||||
done := false
|
||||
for done == false {
|
||||
select {
|
||||
case err := <-cs.syncChild:
|
||||
if err != nil {
|
||||
c.Errorf("master containerd did not exit cleanly: %v", err)
|
||||
}
|
||||
done = true
|
||||
case <-time.After(3 * time.Second):
|
||||
fmt.Println("Timeout while waiting for containerd to exit, killing it!")
|
||||
cs.cd.Process.Kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cs.logFile != nil {
|
||||
cs.logFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SetContainerEventFilter(id string, filter func(event *types.Event)) {
|
||||
cs.eventFiltersMutex.Lock()
|
||||
cs.eventFilters[id] = filter
|
||||
cs.eventFiltersMutex.Unlock()
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TearDownTest(c *check.C) {
|
||||
ctrs, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to retrieve running containers: %v", err)
|
||||
}
|
||||
|
||||
// Kill all containers that survived
|
||||
for _, ctr := range ctrs {
|
||||
ch := make(chan interface{})
|
||||
cs.SetContainerEventFilter(ctr.Id, func(e *types.Event) {
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
ch <- nil
|
||||
}
|
||||
})
|
||||
|
||||
if err := cs.KillContainer(ctr.Id); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to cleanup leftover test containers: %v", err)
|
||||
}
|
||||
|
||||
<-ch
|
||||
}
|
||||
}
|
249
integration-test/container_utils_test.go
Normal file
249
integration-test/container_utils_test.go
Normal file
|
@ -0,0 +1,249 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) ListRunningContainers() ([]*types.Container, error) {
|
||||
resp, err := cs.grpcClient.State(context.Background(), &types.StateRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Containers, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SignalContainerProcess(id string, procId string, sig uint32) error {
|
||||
_, err := cs.grpcClient.Signal(context.Background(), &types.SignalRequest{
|
||||
Id: id,
|
||||
Pid: procId,
|
||||
Signal: sig,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SignalContainer(id string, sig uint32) error {
|
||||
return cs.SignalContainerProcess(id, "init", sig)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) KillContainer(id string) error {
|
||||
return cs.SignalContainerProcess(id, "init", uint32(syscall.SIGKILL))
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) PauseContainer(id string) error {
|
||||
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "paused",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) ResumeContainer(id string) error {
|
||||
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "running",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) GetContainerStats(id string) (*types.StatsResponse, error) {
|
||||
stats, err := cs.grpcClient.Stats(context.Background(), &types.StatsRequest{
|
||||
Id: id,
|
||||
})
|
||||
return stats, err
|
||||
}
|
||||
|
||||
type stdio struct {
|
||||
stdin string
|
||||
stdout string
|
||||
stderr string
|
||||
stdinf *os.File
|
||||
stdoutf *os.File
|
||||
stderrf *os.File
|
||||
stdoutBuffer bytes.Buffer
|
||||
stderrBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
type containerProcess struct {
|
||||
containerId string
|
||||
pid string
|
||||
bundle *Bundle
|
||||
io stdio
|
||||
eventsCh chan *types.Event
|
||||
cs *ContainerdSuite
|
||||
hasExited bool
|
||||
}
|
||||
|
||||
func (c *containerProcess) openIo() (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
c.io.stdinf, err = os.OpenFile(c.io.stdin, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.io.stdoutf, err = os.OpenFile(c.io.stdout, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(&c.io.stdoutBuffer, c.io.stdoutf)
|
||||
|
||||
c.io.stderrf, err = os.OpenFile(c.io.stderr, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(&c.io.stderrBuffer, c.io.stderrf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *containerProcess) GetNextEvent() *types.Event {
|
||||
e := <-c.eventsCh
|
||||
|
||||
if e.Type == "exit" && e.Pid == c.pid {
|
||||
c.Cleanup()
|
||||
c.hasExited = true
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (c *containerProcess) CloseStdin() error {
|
||||
_, err := c.cs.grpcClient.UpdateProcess(context.Background(), &types.UpdateProcessRequest{
|
||||
Id: c.containerId,
|
||||
Pid: c.pid,
|
||||
CloseStdin: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *containerProcess) Cleanup() {
|
||||
for _, f := range []*os.File{
|
||||
c.io.stdinf,
|
||||
c.io.stdoutf,
|
||||
c.io.stderrf,
|
||||
} {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
f = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewContainerProcess(cs *ContainerdSuite, bundle *Bundle, cid, pid string) (c *containerProcess, err error) {
|
||||
c = &containerProcess{
|
||||
containerId: cid,
|
||||
pid: "init",
|
||||
bundle: bundle,
|
||||
eventsCh: make(chan *types.Event, 8),
|
||||
cs: cs,
|
||||
hasExited: false,
|
||||
}
|
||||
|
||||
for name, path := range map[string]*string{
|
||||
"stdin": &c.io.stdin,
|
||||
"stdout": &c.io.stdout,
|
||||
"stderr": &c.io.stderr,
|
||||
} {
|
||||
*path = filepath.Join(bundle.Path, "io", cid+"-"+pid+"-"+name)
|
||||
if err = syscall.Mkfifo(*path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.openIo(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) StartContainer(id, bundleName string) (c *containerProcess, err error) {
|
||||
bundle, ok := bundleMap[bundleName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No such bundle '%s'", bundleName)
|
||||
}
|
||||
|
||||
c, err = NewContainerProcess(cs, &bundle, id, "init")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &types.CreateContainerRequest{
|
||||
Id: id,
|
||||
BundlePath: filepath.Join(cs.cwd, bundle.Path),
|
||||
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||
}
|
||||
|
||||
cs.SetContainerEventFilter(id, func(event *types.Event) {
|
||||
c.eventsCh <- event
|
||||
})
|
||||
|
||||
if _, err := cs.grpcClient.CreateContainer(context.Background(), r); err != nil {
|
||||
c.Cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) RunContainer(id, bundleName string) (c *containerProcess, err error) {
|
||||
c, err = cs.StartContainer(id, bundleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
e := c.GetNextEvent()
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) AddProcessToContainer(init *containerProcess, pid, cwd string, env, args []string, uid, gid uint32) (c *containerProcess, err error) {
|
||||
c, err = NewContainerProcess(cs, init.bundle, init.containerId, pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr := &types.AddProcessRequest{
|
||||
Id: init.containerId,
|
||||
Pid: pid,
|
||||
Args: args,
|
||||
Cwd: cwd,
|
||||
Env: env,
|
||||
User: &types.User{
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
},
|
||||
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||
}
|
||||
|
||||
_, err = cs.grpcClient.AddProcess(context.Background(), pr)
|
||||
if err != nil {
|
||||
c.Cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
58
integration-test/start_test.go
Normal file
58
integration-test/start_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxLsSlash(t *check.C) {
|
||||
expectedOutput := `bin
|
||||
dev
|
||||
etc
|
||||
home
|
||||
lib
|
||||
lib64
|
||||
linuxrc
|
||||
media
|
||||
mnt
|
||||
opt
|
||||
proc
|
||||
root
|
||||
run
|
||||
sbin
|
||||
sys
|
||||
tmp
|
||||
usr
|
||||
var
|
||||
`
|
||||
if err := CreateBusyboxBundle("busybox-ls-slash", []string{"ls", "/"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c, err := cs.RunContainer("myls", "busybox-ls-slash")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(c.io.stdoutBuffer.String(), checker.Equals, expectedOutput)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxNoSuchFile(t *check.C) {
|
||||
expectedOutput := `oci runtime error: exec: \"NoSuchFile\": executable file not found in $PATH`
|
||||
|
||||
if err := CreateBusyboxBundle("busybox-NoSuchFile", []string{"NoSuchFile"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := cs.RunContainer("NoSuchFile", "busybox-NoSuchFile")
|
||||
t.Assert(err.Error(), checker.Contains, expectedOutput)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTop(t *check.C) {
|
||||
if err := CreateBusyboxBundle("busybox-top", []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := cs.StartContainer("top", "busybox-top")
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -218,7 +219,17 @@ func (c *container) startCmd(pid string, cmd *exec.Cmd, p *process) error {
|
|||
}
|
||||
|
||||
func (c *container) getLibctContainer() (libcontainer.Container, error) {
|
||||
f, err := libcontainer.New("/run/runc", libcontainer.Cgroupfs)
|
||||
runtimeRoot := "/run/runc"
|
||||
|
||||
// Check that the root wasn't changed
|
||||
for _, opt := range c.runtimeArgs {
|
||||
if strings.HasPrefix(opt, "--root=") {
|
||||
runtimeRoot = strings.TrimPrefix(opt, "--root=")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
f, err := libcontainer.New(runtimeRoot, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
46
vendor/src/github.com/docker/docker/pkg/integration/checker/checker.go
vendored
Normal file
46
vendor/src/github.com/docker/docker/pkg/integration/checker/checker.go
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Package checker provide Docker specific implementations of the go-check.Checker interface.
|
||||
package checker
|
||||
|
||||
import (
|
||||
"github.com/go-check/check"
|
||||
"github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
// As a commodity, we bring all check.Checker variables into the current namespace to avoid having
|
||||
// to think about check.X versus checker.X.
|
||||
var (
|
||||
DeepEquals = check.DeepEquals
|
||||
ErrorMatches = check.ErrorMatches
|
||||
FitsTypeOf = check.FitsTypeOf
|
||||
HasLen = check.HasLen
|
||||
Implements = check.Implements
|
||||
IsNil = check.IsNil
|
||||
Matches = check.Matches
|
||||
Not = check.Not
|
||||
NotNil = check.NotNil
|
||||
PanicMatches = check.PanicMatches
|
||||
Panics = check.Panics
|
||||
|
||||
Contains = shakers.Contains
|
||||
ContainsAny = shakers.ContainsAny
|
||||
Count = shakers.Count
|
||||
Equals = shakers.Equals
|
||||
EqualFold = shakers.EqualFold
|
||||
False = shakers.False
|
||||
GreaterOrEqualThan = shakers.GreaterOrEqualThan
|
||||
GreaterThan = shakers.GreaterThan
|
||||
HasPrefix = shakers.HasPrefix
|
||||
HasSuffix = shakers.HasSuffix
|
||||
Index = shakers.Index
|
||||
IndexAny = shakers.IndexAny
|
||||
IsAfter = shakers.IsAfter
|
||||
IsBefore = shakers.IsBefore
|
||||
IsBetween = shakers.IsBetween
|
||||
IsLower = shakers.IsLower
|
||||
IsUpper = shakers.IsUpper
|
||||
LessOrEqualThan = shakers.LessOrEqualThan
|
||||
LessThan = shakers.LessThan
|
||||
TimeEquals = shakers.TimeEquals
|
||||
True = shakers.True
|
||||
TimeIgnore = shakers.TimeIgnore
|
||||
)
|
4
vendor/src/github.com/go-check/check/.gitignore
vendored
Normal file
4
vendor/src/github.com/go-check/check/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
_*
|
||||
*.swp
|
||||
*.[568]
|
||||
[568].out
|
10
vendor/src/github.com/go-check/check/.travis.yml
vendored
Normal file
10
vendor/src/github.com/go-check/check/.travis.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- tip
|
||||
script:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- # go vet ./...
|
||||
- # test -z "$(golint ./... | tee /dev/stderr)"
|
||||
- # test -z "$(gofmt -s -l . | tee /dev/stderr)"
|
||||
- go test -v ./...
|
25
vendor/src/github.com/go-check/check/LICENSE
vendored
Normal file
25
vendor/src/github.com/go-check/check/LICENSE
vendored
Normal file
|
@ -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.
|
10
vendor/src/github.com/go-check/check/README.md
vendored
Normal file
10
vendor/src/github.com/go-check/check/README.md
vendored
Normal file
|
@ -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`
|
2
vendor/src/github.com/go-check/check/TODO
vendored
Normal file
2
vendor/src/github.com/go-check/check/TODO
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
- Assert(slice, Contains, item)
|
||||
- Parallel test support
|
187
vendor/src/github.com/go-check/check/benchmark.go
vendored
Normal file
187
vendor/src/github.com/go-check/check/benchmark.go
vendored
Normal file
|
@ -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
|
||||
}
|
892
vendor/src/github.com/go-check/check/check.go
vendored
Normal file
892
vendor/src/github.com/go-check/check/check.go
vendored
Normal file
|
@ -0,0 +1,892 @@
|
|||
// 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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// 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:
|
||||
panic(fmt.Sprintf("test timed out after %v", 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:
|
||||
panic(fmt.Sprintf("test timed out after %v", 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} {
|
||||
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)
|
||||
}
|
||||
}
|
458
vendor/src/github.com/go-check/check/checkers.go
vendored
Normal file
458
vendor/src/github.com/go-check/check/checkers.go
vendored
Normal file
|
@ -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()), ""
|
||||
}
|
231
vendor/src/github.com/go-check/check/helpers.go
vendored
Normal file
231
vendor/src/github.com/go-check/check/helpers.go
vendored
Normal file
|
@ -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
|
||||
}
|
168
vendor/src/github.com/go-check/check/printer.go
vendored
Normal file
168
vendor/src/github.com/go-check/check/printer.go
vendored
Normal file
|
@ -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}
|
||||
}
|
88
vendor/src/github.com/go-check/check/reporter.go
vendored
Normal file
88
vendor/src/github.com/go-check/check/reporter.go
vendored
Normal file
|
@ -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)
|
||||
}
|
183
vendor/src/github.com/go-check/check/run.go
vendored
Normal file
183
vendor/src/github.com/go-check/check/run.go
vendored
Normal file
|
@ -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
|
||||
}
|
2
vendor/src/github.com/vdemeester/shakers/.gitignore
vendored
Normal file
2
vendor/src/github.com/vdemeester/shakers/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
vendor
|
||||
*.test
|
16
vendor/src/github.com/vdemeester/shakers/Dockerfile
vendored
Normal file
16
vendor/src/github.com/vdemeester/shakers/Dockerfile
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
FROM golang:1.5
|
||||
|
||||
RUN go get golang.org/x/tools/cmd/cover
|
||||
RUN go get github.com/golang/lint/golint
|
||||
RUN go get golang.org/x/tools/cmd/vet
|
||||
RUN go get github.com/Masterminds/glide
|
||||
|
||||
WORKDIR /go/src/github.com/vdemeester/shakers
|
||||
|
||||
# enable GO15VENDOREXPERIMENT
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
|
||||
COPY glide.yaml glide.yaml
|
||||
RUN glide up
|
||||
|
||||
COPY . /go/src/github.com/vdemeester/shakers
|
191
vendor/src/github.com/vdemeester/shakers/LICENSE
vendored
Normal file
191
vendor/src/github.com/vdemeester/shakers/LICENSE
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2015-2016 Vincent Demeester
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
37
vendor/src/github.com/vdemeester/shakers/Makefile
vendored
Normal file
37
vendor/src/github.com/vdemeester/shakers/Makefile
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
.PHONY: all
|
||||
|
||||
SHAKERS_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/vdemeester/shakers/$(BIND_DIR)")
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
SHAKERS_DEV_IMAGE := shakers-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
|
||||
DOCKER_RUN_SHAKERS := docker run $(if $(CIRCLECI),,--rm) -it $(SHAKERS_ENVS) $(SHAKERS_MOUNT) "$(SHAKERS_DEV_IMAGE)"
|
||||
|
||||
print-%: ; @echo $*=$($*)
|
||||
|
||||
default: all
|
||||
|
||||
all: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh
|
||||
|
||||
test-unit: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh test-unit
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh validate-gofmt validate-golint validate-govet
|
||||
|
||||
validate-govet: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh validate-govet
|
||||
|
||||
validate-golint: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh validate-golint
|
||||
|
||||
validate-gofmt: build
|
||||
$(DOCKER_RUN_SHAKERS) ./script/make.sh validate-gofmt
|
||||
|
||||
build:
|
||||
docker build -t "$(SHAKERS_DEV_IMAGE)" .
|
||||
|
||||
shell: build
|
||||
$(DOCKER_RUN_SHAKERS) /bin/bash
|
||||
|
30
vendor/src/github.com/vdemeester/shakers/README.md
vendored
Normal file
30
vendor/src/github.com/vdemeester/shakers/README.md
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Shakers
|
||||
🐹 + 🐙 = 😽 [![Circle CI](https://circleci.com/gh/vdemeester/shakers.svg?style=svg)](https://circleci.com/gh/vdemeester/shakers)
|
||||
|
||||
A collection of `go-check` Checkers to ease the use of it.
|
||||
|
||||
## Building and testing it
|
||||
|
||||
You need either [docker](https://github.com/docker/docker), or `go`
|
||||
and `glide` in order to build and test shakers.
|
||||
|
||||
### Using Docker and Makefile
|
||||
|
||||
You need to run the ``test-unit`` target.
|
||||
```bash
|
||||
$ make test-unit
|
||||
docker build -t "shakers-dev:master" .
|
||||
# […]
|
||||
docker run --rm -it "shakers-dev:master" ./script/make.sh test-unit
|
||||
---> Making bundle: test-unit (in .)
|
||||
+ go test -cover -coverprofile=cover.out .
|
||||
ok github.com/vdemeester/shakers 0.015s coverage: 96.0% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
|
||||
### Using glide and `GO15VENDOREXPERIMENT`
|
||||
|
||||
- Get the dependencies with `glide up` (or use `go get` but you have no garantuees over the version of the dependencies)
|
||||
- If you're using glide (and not standard `go get`) export `GO15VENDOREXPERIMENT` with `export GO15VENDOREXPERIMENT=1`
|
||||
- Run tests with `go test .`
|
46
vendor/src/github.com/vdemeester/shakers/bool.go
vendored
Normal file
46
vendor/src/github.com/vdemeester/shakers/bool.go
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
package shakers
|
||||
|
||||
import (
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
// True checker verifies the obtained value is true
|
||||
//
|
||||
// c.Assert(myBool, True)
|
||||
//
|
||||
var True check.Checker = &boolChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "True",
|
||||
Params: []string{"obtained"},
|
||||
},
|
||||
true,
|
||||
}
|
||||
|
||||
// False checker verifies the obtained value is false
|
||||
//
|
||||
// c.Assert(myBool, False)
|
||||
//
|
||||
var False check.Checker = &boolChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "False",
|
||||
Params: []string{"obtained"},
|
||||
},
|
||||
false,
|
||||
}
|
||||
|
||||
type boolChecker struct {
|
||||
*check.CheckerInfo
|
||||
expected bool
|
||||
}
|
||||
|
||||
func (checker *boolChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return is(checker.expected, params[0])
|
||||
}
|
||||
|
||||
func is(expected bool, obtained interface{}) (bool, string) {
|
||||
obtainedBool, ok := obtained.(bool)
|
||||
if !ok {
|
||||
return false, "obtained value must be a bool."
|
||||
}
|
||||
return obtainedBool == expected, ""
|
||||
}
|
11
vendor/src/github.com/vdemeester/shakers/circle.yml
vendored
Normal file
11
vendor/src/github.com/vdemeester/shakers/circle.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
machine:
|
||||
services:
|
||||
- docker
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- make validate
|
||||
|
||||
test:
|
||||
override:
|
||||
- make test-unit
|
310
vendor/src/github.com/vdemeester/shakers/common.go
vendored
Normal file
310
vendor/src/github.com/vdemeester/shakers/common.go
vendored
Normal file
|
@ -0,0 +1,310 @@
|
|||
package shakers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
// As a commodity, we bring all check.Checker variables into the current namespace to avoid having
|
||||
// to think about check.X versus checker.X.
|
||||
var (
|
||||
DeepEquals = check.DeepEquals
|
||||
ErrorMatches = check.ErrorMatches
|
||||
FitsTypeOf = check.FitsTypeOf
|
||||
HasLen = check.HasLen
|
||||
Implements = check.Implements
|
||||
IsNil = check.IsNil
|
||||
Matches = check.Matches
|
||||
Not = check.Not
|
||||
NotNil = check.NotNil
|
||||
PanicMatches = check.PanicMatches
|
||||
Panics = check.Panics
|
||||
)
|
||||
|
||||
// Equaler is an interface implemented if the type has a Equal method.
|
||||
// This is used to compare struct using shakers.Equals.
|
||||
type Equaler interface {
|
||||
Equal(Equaler) bool
|
||||
}
|
||||
|
||||
// Equals checker verifies the obtained value is equal to the specified one.
|
||||
// It's is smart in a wait that it supports several *types* (built-in, Equaler,
|
||||
// time.Time)
|
||||
//
|
||||
// c.Assert(myStruct, Equals, aStruct, check.Commentf("bouuuhh"))
|
||||
// c.Assert(myTime, Equals, aTime, check.Commentf("bouuuhh"))
|
||||
//
|
||||
var Equals check.Checker = &equalChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "Equals",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type equalChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *equalChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return isEqual(params[0], params[1])
|
||||
}
|
||||
|
||||
func isEqual(obtained, expected interface{}) (bool, string) {
|
||||
switch obtained.(type) {
|
||||
case time.Time:
|
||||
return timeEquals(obtained, expected)
|
||||
case Equaler:
|
||||
return equalerEquals(obtained, expected)
|
||||
default:
|
||||
if reflect.TypeOf(obtained) != reflect.TypeOf(expected) {
|
||||
return false, "obtained value and expected value have not the same type."
|
||||
}
|
||||
return obtained == expected, ""
|
||||
}
|
||||
}
|
||||
|
||||
func equalerEquals(obtained, expected interface{}) (bool, string) {
|
||||
expectedEqualer, ok := expected.(Equaler)
|
||||
if !ok {
|
||||
return false, "expected value must be an Equaler - implementing Equal(Equaler)."
|
||||
}
|
||||
obtainedEqualer, ok := obtained.(Equaler)
|
||||
if !ok {
|
||||
return false, "obtained value must be an Equaler - implementing Equal(Equaler)."
|
||||
}
|
||||
return obtainedEqualer.Equal(expectedEqualer), ""
|
||||
}
|
||||
|
||||
// GreaterThan checker verifies the obtained value is greater than the specified one.
|
||||
// It's is smart in a wait that it supports several *types* (built-in, time.Time)
|
||||
//
|
||||
// c.Assert(myTime, GreaterThan, aTime, check.Commentf("bouuuhh"))
|
||||
// c.Assert(myInt, GreaterThan, 2, check.Commentf("bouuuhh"))
|
||||
//
|
||||
var GreaterThan check.Checker = &greaterThanChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "GreaterThan",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type greaterThanChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *greaterThanChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return greaterThan(params[0], params[1])
|
||||
}
|
||||
|
||||
func greaterThan(obtained, expected interface{}) (bool, string) {
|
||||
if _, ok := obtained.(time.Time); ok {
|
||||
return isAfter(obtained, expected)
|
||||
}
|
||||
if reflect.TypeOf(obtained) != reflect.TypeOf(expected) {
|
||||
return false, "obtained value and expected value have not the same type."
|
||||
}
|
||||
switch v := obtained.(type) {
|
||||
case float32:
|
||||
return v > expected.(float32), ""
|
||||
case float64:
|
||||
return v > expected.(float64), ""
|
||||
case int:
|
||||
return v > expected.(int), ""
|
||||
case int8:
|
||||
return v > expected.(int8), ""
|
||||
case int16:
|
||||
return v > expected.(int16), ""
|
||||
case int32:
|
||||
return v > expected.(int32), ""
|
||||
case int64:
|
||||
return v > expected.(int64), ""
|
||||
case uint:
|
||||
return v > expected.(uint), ""
|
||||
case uint8:
|
||||
return v > expected.(uint8), ""
|
||||
case uint16:
|
||||
return v > expected.(uint16), ""
|
||||
case uint32:
|
||||
return v > expected.(uint32), ""
|
||||
case uint64:
|
||||
return v > expected.(uint64), ""
|
||||
default:
|
||||
return false, "obtained value type not supported."
|
||||
}
|
||||
}
|
||||
|
||||
// GreaterOrEqualThan checker verifies the obtained value is greater or equal than the specified one.
|
||||
// It's is smart in a wait that it supports several *types* (built-in, time.Time)
|
||||
//
|
||||
// c.Assert(myTime, GreaterOrEqualThan, aTime, check.Commentf("bouuuhh"))
|
||||
// c.Assert(myInt, GreaterOrEqualThan, 2, check.Commentf("bouuuhh"))
|
||||
//
|
||||
var GreaterOrEqualThan check.Checker = &greaterOrEqualThanChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "GreaterOrEqualThan",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type greaterOrEqualThanChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *greaterOrEqualThanChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return greaterOrEqualThan(params[0], params[1])
|
||||
}
|
||||
|
||||
func greaterOrEqualThan(obtained, expected interface{}) (bool, string) {
|
||||
if _, ok := obtained.(time.Time); ok {
|
||||
return isAfter(obtained, expected)
|
||||
}
|
||||
if reflect.TypeOf(obtained) != reflect.TypeOf(expected) {
|
||||
return false, "obtained value and expected value have not the same type."
|
||||
}
|
||||
switch v := obtained.(type) {
|
||||
case float32:
|
||||
return v >= expected.(float32), ""
|
||||
case float64:
|
||||
return v >= expected.(float64), ""
|
||||
case int:
|
||||
return v >= expected.(int), ""
|
||||
case int8:
|
||||
return v >= expected.(int8), ""
|
||||
case int16:
|
||||
return v >= expected.(int16), ""
|
||||
case int32:
|
||||
return v >= expected.(int32), ""
|
||||
case int64:
|
||||
return v >= expected.(int64), ""
|
||||
case uint:
|
||||
return v >= expected.(uint), ""
|
||||
case uint8:
|
||||
return v >= expected.(uint8), ""
|
||||
case uint16:
|
||||
return v >= expected.(uint16), ""
|
||||
case uint32:
|
||||
return v >= expected.(uint32), ""
|
||||
case uint64:
|
||||
return v >= expected.(uint64), ""
|
||||
default:
|
||||
return false, "obtained value type not supported."
|
||||
}
|
||||
}
|
||||
|
||||
// LessThan checker verifies the obtained value is less than the specified one.
|
||||
// It's is smart in a wait that it supports several *types* (built-in, time.Time)
|
||||
//
|
||||
// c.Assert(myTime, LessThan, aTime, check.Commentf("bouuuhh"))
|
||||
// c.Assert(myInt, LessThan, 2, check.Commentf("bouuuhh"))
|
||||
//
|
||||
var LessThan check.Checker = &lessThanChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "LessThan",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type lessThanChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *lessThanChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return lessThan(params[0], params[1])
|
||||
}
|
||||
|
||||
func lessThan(obtained, expected interface{}) (bool, string) {
|
||||
if _, ok := obtained.(time.Time); ok {
|
||||
return isBefore(obtained, expected)
|
||||
}
|
||||
if reflect.TypeOf(obtained) != reflect.TypeOf(expected) {
|
||||
return false, "obtained value and expected value have not the same type."
|
||||
}
|
||||
switch v := obtained.(type) {
|
||||
case float32:
|
||||
return v < expected.(float32), ""
|
||||
case float64:
|
||||
return v < expected.(float64), ""
|
||||
case int:
|
||||
return v < expected.(int), ""
|
||||
case int8:
|
||||
return v < expected.(int8), ""
|
||||
case int16:
|
||||
return v < expected.(int16), ""
|
||||
case int32:
|
||||
return v < expected.(int32), ""
|
||||
case int64:
|
||||
return v < expected.(int64), ""
|
||||
case uint:
|
||||
return v < expected.(uint), ""
|
||||
case uint8:
|
||||
return v < expected.(uint8), ""
|
||||
case uint16:
|
||||
return v < expected.(uint16), ""
|
||||
case uint32:
|
||||
return v < expected.(uint32), ""
|
||||
case uint64:
|
||||
return v < expected.(uint64), ""
|
||||
default:
|
||||
return false, "obtained value type not supported."
|
||||
}
|
||||
}
|
||||
|
||||
// LessOrEqualThan checker verifies the obtained value is less or equal than the specified one.
|
||||
// It's is smart in a wait that it supports several *types* (built-in, time.Time)
|
||||
//
|
||||
// c.Assert(myTime, LessThan, aTime, check.Commentf("bouuuhh"))
|
||||
// c.Assert(myInt, LessThan, 2, check.Commentf("bouuuhh"))
|
||||
//
|
||||
var LessOrEqualThan check.Checker = &lessOrEqualThanChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "LessOrEqualThan",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type lessOrEqualThanChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *lessOrEqualThanChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return lessOrEqualThan(params[0], params[1])
|
||||
}
|
||||
|
||||
func lessOrEqualThan(obtained, expected interface{}) (bool, string) {
|
||||
if _, ok := obtained.(time.Time); ok {
|
||||
return isBefore(obtained, expected)
|
||||
}
|
||||
if reflect.TypeOf(obtained) != reflect.TypeOf(expected) {
|
||||
return false, "obtained value and expected value have not the same type."
|
||||
}
|
||||
switch v := obtained.(type) {
|
||||
case float32:
|
||||
return v <= expected.(float32), ""
|
||||
case float64:
|
||||
return v <= expected.(float64), ""
|
||||
case int:
|
||||
return v <= expected.(int), ""
|
||||
case int8:
|
||||
return v <= expected.(int8), ""
|
||||
case int16:
|
||||
return v <= expected.(int16), ""
|
||||
case int32:
|
||||
return v <= expected.(int32), ""
|
||||
case int64:
|
||||
return v <= expected.(int64), ""
|
||||
case uint:
|
||||
return v <= expected.(uint), ""
|
||||
case uint8:
|
||||
return v <= expected.(uint8), ""
|
||||
case uint16:
|
||||
return v <= expected.(uint16), ""
|
||||
case uint32:
|
||||
return v <= expected.(uint32), ""
|
||||
case uint64:
|
||||
return v <= expected.(uint64), ""
|
||||
default:
|
||||
return false, "obtained value type not supported."
|
||||
}
|
||||
}
|
4
vendor/src/github.com/vdemeester/shakers/glide.yaml
vendored
Normal file
4
vendor/src/github.com/vdemeester/shakers/glide.yaml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
package: main
|
||||
import:
|
||||
- package: github.com/go-check/check
|
||||
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
168
vendor/src/github.com/vdemeester/shakers/string.go
vendored
Normal file
168
vendor/src/github.com/vdemeester/shakers/string.go
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Package shakers provide some checker implementation the go-check.Checker interface.
|
||||
package shakers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
// Contains checker verifies that obtained value contains a substring.
|
||||
var Contains check.Checker = &substringChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "Contains",
|
||||
Params: []string{"obtained", "substring"},
|
||||
},
|
||||
strings.Contains,
|
||||
}
|
||||
|
||||
// ContainsAny checker verifies that any Unicode code points in chars
|
||||
// are in the obtained string.
|
||||
var ContainsAny check.Checker = &substringChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "ContainsAny",
|
||||
Params: []string{"obtained", "chars"},
|
||||
},
|
||||
strings.ContainsAny,
|
||||
}
|
||||
|
||||
// HasPrefix checker verifies that obtained value has the specified substring as prefix
|
||||
var HasPrefix check.Checker = &substringChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "HasPrefix",
|
||||
Params: []string{"obtained", "prefix"},
|
||||
},
|
||||
strings.HasPrefix,
|
||||
}
|
||||
|
||||
// HasSuffix checker verifies that obtained value has the specified substring as prefix
|
||||
var HasSuffix check.Checker = &substringChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "HasSuffix",
|
||||
Params: []string{"obtained", "suffix"},
|
||||
},
|
||||
strings.HasSuffix,
|
||||
}
|
||||
|
||||
// EqualFold checker verifies that obtained value is, interpreted as UTF-8 strings, are equal under Unicode case-folding.
|
||||
var EqualFold check.Checker = &substringChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "EqualFold",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
strings.EqualFold,
|
||||
}
|
||||
|
||||
type substringChecker struct {
|
||||
*check.CheckerInfo
|
||||
substringFunction func(string, string) bool
|
||||
}
|
||||
|
||||
func (checker *substringChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
obtained := params[0]
|
||||
substring := params[1]
|
||||
substringStr, ok := substring.(string)
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("%s value must be a string.", names[1])
|
||||
}
|
||||
obtainedString, obtainedIsStr := obtained.(string)
|
||||
if !obtainedIsStr {
|
||||
if obtainedWithStringer, obtainedHasStringer := obtained.(fmt.Stringer); obtainedHasStringer {
|
||||
obtainedString, obtainedIsStr = obtainedWithStringer.String(), true
|
||||
}
|
||||
}
|
||||
if obtainedIsStr {
|
||||
return checker.substringFunction(obtainedString, substringStr), ""
|
||||
}
|
||||
return false, "obtained value is not a string and has no .String()."
|
||||
}
|
||||
|
||||
// IndexAny checker verifies that the index of the first instance of any Unicode code point from chars in the obtained value is equal to expected
|
||||
var IndexAny check.Checker = &substringCountChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IndexAny",
|
||||
Params: []string{"obtained", "chars", "expected"},
|
||||
},
|
||||
strings.IndexAny,
|
||||
}
|
||||
|
||||
// Index checker verifies that the index of the first instance of sep in the obtained value is equal to expected
|
||||
var Index check.Checker = &substringCountChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "Index",
|
||||
Params: []string{"obtained", "sep", "expected"},
|
||||
},
|
||||
strings.Index,
|
||||
}
|
||||
|
||||
// Count checker verifies that obtained value has the specified number of non-overlapping instances of sep
|
||||
var Count check.Checker = &substringCountChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "Count",
|
||||
Params: []string{"obtained", "sep", "expected"},
|
||||
},
|
||||
strings.Count,
|
||||
}
|
||||
|
||||
type substringCountChecker struct {
|
||||
*check.CheckerInfo
|
||||
substringFunction func(string, string) int
|
||||
}
|
||||
|
||||
func (checker *substringCountChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
obtained := params[0]
|
||||
substring := params[1]
|
||||
expected := params[2]
|
||||
substringStr, ok := substring.(string)
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("%s value must be a string.", names[1])
|
||||
}
|
||||
obtainedString, obtainedIsStr := obtained.(string)
|
||||
if !obtainedIsStr {
|
||||
if obtainedWithStringer, obtainedHasStringer := obtained.(fmt.Stringer); obtainedHasStringer {
|
||||
obtainedString, obtainedIsStr = obtainedWithStringer.String(), true
|
||||
}
|
||||
}
|
||||
if obtainedIsStr {
|
||||
return checker.substringFunction(obtainedString, substringStr) == expected, ""
|
||||
}
|
||||
return false, "obtained value is not a string and has no .String()."
|
||||
}
|
||||
|
||||
// IsLower checker verifies that the obtained value is in lower case
|
||||
var IsLower check.Checker = &stringTransformChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IsLower",
|
||||
Params: []string{"obtained"},
|
||||
},
|
||||
strings.ToLower,
|
||||
}
|
||||
|
||||
// IsUpper checker verifies that the obtained value is in lower case
|
||||
var IsUpper check.Checker = &stringTransformChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IsUpper",
|
||||
Params: []string{"obtained"},
|
||||
},
|
||||
strings.ToUpper,
|
||||
}
|
||||
|
||||
type stringTransformChecker struct {
|
||||
*check.CheckerInfo
|
||||
stringFunction func(string) string
|
||||
}
|
||||
|
||||
func (checker *stringTransformChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
obtained := params[0]
|
||||
obtainedString, obtainedIsStr := obtained.(string)
|
||||
if !obtainedIsStr {
|
||||
if obtainedWithStringer, obtainedHasStringer := obtained.(fmt.Stringer); obtainedHasStringer {
|
||||
obtainedString, obtainedIsStr = obtainedWithStringer.String(), true
|
||||
}
|
||||
}
|
||||
if obtainedIsStr {
|
||||
return checker.stringFunction(obtainedString) == obtainedString, ""
|
||||
}
|
||||
return false, "obtained value is not a string and has no .String()."
|
||||
}
|
234
vendor/src/github.com/vdemeester/shakers/time.go
vendored
Normal file
234
vendor/src/github.com/vdemeester/shakers/time.go
vendored
Normal file
|
@ -0,0 +1,234 @@
|
|||
package shakers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
// Default format when parsing (in addition to RFC and default time formats..)
|
||||
const shortForm = "2006-01-02"
|
||||
|
||||
// IsBefore checker verifies the specified value is before the specified time.
|
||||
// It is exclusive.
|
||||
//
|
||||
// c.Assert(myTime, IsBefore, theTime, check.Commentf("bouuuhhh"))
|
||||
//
|
||||
var IsBefore check.Checker = &isBeforeChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IsBefore",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type isBeforeChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *isBeforeChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return isBefore(params[0], params[1])
|
||||
}
|
||||
|
||||
func isBefore(value, t interface{}) (bool, string) {
|
||||
tTime, ok := parseTime(t)
|
||||
if !ok {
|
||||
return false, "expected must be a Time struct, or parseable."
|
||||
}
|
||||
valueTime, valueIsTime := parseTime(value)
|
||||
if valueIsTime {
|
||||
return valueTime.Before(tTime), ""
|
||||
}
|
||||
return false, "obtained value is not a time.Time struct or parseable as a time."
|
||||
}
|
||||
|
||||
// IsAfter checker verifies the specified value is before the specified time.
|
||||
// It is exclusive.
|
||||
//
|
||||
// c.Assert(myTime, IsAfter, theTime, check.Commentf("bouuuhhh"))
|
||||
//
|
||||
var IsAfter check.Checker = &isAfterChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IsAfter",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type isAfterChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *isAfterChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return isAfter(params[0], params[1])
|
||||
}
|
||||
|
||||
func isAfter(value, t interface{}) (bool, string) {
|
||||
tTime, ok := parseTime(t)
|
||||
if !ok {
|
||||
return false, "expected must be a Time struct, or parseable."
|
||||
}
|
||||
valueTime, valueIsTime := parseTime(value)
|
||||
if valueIsTime {
|
||||
return valueTime.After(tTime), ""
|
||||
}
|
||||
return false, "obtained value is not a time.Time struct or parseable as a time."
|
||||
}
|
||||
|
||||
// IsBetween checker verifies the specified time is between the specified start
|
||||
// and end. It's exclusive so if the specified time is at the tip of the interval.
|
||||
//
|
||||
// c.Assert(myTime, IsBetween, startTime, endTime, check.Commentf("bouuuhhh"))
|
||||
//
|
||||
var IsBetween check.Checker = &isBetweenChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "IsBetween",
|
||||
Params: []string{"obtained", "start", "end"},
|
||||
},
|
||||
}
|
||||
|
||||
type isBetweenChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *isBetweenChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return isBetween(params[0], params[1], params[2])
|
||||
}
|
||||
|
||||
func isBetween(value, start, end interface{}) (bool, string) {
|
||||
startTime, ok := parseTime(start)
|
||||
if !ok {
|
||||
return false, "start must be a Time struct, or parseable."
|
||||
}
|
||||
endTime, ok := parseTime(end)
|
||||
if !ok {
|
||||
return false, "end must be a Time struct, or parseable."
|
||||
}
|
||||
valueTime, valueIsTime := parseTime(value)
|
||||
if valueIsTime {
|
||||
return valueTime.After(startTime) && valueTime.Before(endTime), ""
|
||||
}
|
||||
return false, "obtained value is not a time.Time struct or parseable as a time."
|
||||
}
|
||||
|
||||
// TimeEquals checker verifies the specified time is the equal to the expected
|
||||
// time.
|
||||
//
|
||||
// c.Assert(myTime, TimeEquals, expected, check.Commentf("bouhhh"))
|
||||
//
|
||||
// It's possible to ignore some part of the time (like hours, minutes, etc..) using
|
||||
// the TimeIgnore checker with it.
|
||||
//
|
||||
// c.Assert(myTime, TimeIgnore(TimeEquals, time.Hour), expected, check.Commentf("... bouh.."))
|
||||
//
|
||||
var TimeEquals check.Checker = &timeEqualsChecker{
|
||||
&check.CheckerInfo{
|
||||
Name: "TimeEquals",
|
||||
Params: []string{"obtained", "expected"},
|
||||
},
|
||||
}
|
||||
|
||||
type timeEqualsChecker struct {
|
||||
*check.CheckerInfo
|
||||
}
|
||||
|
||||
func (checker *timeEqualsChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
return timeEquals(params[0], params[1])
|
||||
}
|
||||
|
||||
func timeEquals(obtained, expected interface{}) (bool, string) {
|
||||
expectedTime, ok := parseTime(expected)
|
||||
if !ok {
|
||||
return false, "expected must be a Time struct, or parseable."
|
||||
}
|
||||
valueTime, valueIsTime := parseTime(obtained)
|
||||
if valueIsTime {
|
||||
return valueTime.Equal(expectedTime), ""
|
||||
}
|
||||
return false, "obtained value is not a time.Time struct or parseable as a time."
|
||||
}
|
||||
|
||||
// TimeIgnore checker will ignore some part of the time on the encapsulated checker.
|
||||
//
|
||||
// c.Assert(myTime, TimeIgnore(IsBetween, time.Second), start, end)
|
||||
//
|
||||
// FIXME use interface{} for ignore (to enable "Month", ..
|
||||
func TimeIgnore(checker check.Checker, ignore time.Duration) check.Checker {
|
||||
return &timeIgnoreChecker{
|
||||
sub: checker,
|
||||
ignore: ignore,
|
||||
}
|
||||
}
|
||||
|
||||
type timeIgnoreChecker struct {
|
||||
sub check.Checker
|
||||
ignore time.Duration
|
||||
}
|
||||
|
||||
func (checker *timeIgnoreChecker) Info() *check.CheckerInfo {
|
||||
info := *checker.sub.Info()
|
||||
info.Name = fmt.Sprintf("TimeIgnore(%s, %v)", info.Name, checker.ignore)
|
||||
return &info
|
||||
}
|
||||
|
||||
func (checker *timeIgnoreChecker) Check(params []interface{}, names []string) (bool, string) {
|
||||
// Naive implementation : all params are supposed to be date
|
||||
mParams := make([]interface{}, len(params))
|
||||
for index, param := range params {
|
||||
paramTime, ok := parseTime(param)
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("%s must be a Time struct, or parseable.", names[index])
|
||||
}
|
||||
year := paramTime.Year()
|
||||
month := paramTime.Month()
|
||||
day := paramTime.Day()
|
||||
hour := paramTime.Hour()
|
||||
min := paramTime.Minute()
|
||||
sec := paramTime.Second()
|
||||
nsec := paramTime.Nanosecond()
|
||||
location := paramTime.Location()
|
||||
switch checker.ignore {
|
||||
case time.Hour:
|
||||
hour = 0
|
||||
fallthrough
|
||||
case time.Minute:
|
||||
min = 0
|
||||
fallthrough
|
||||
case time.Second:
|
||||
sec = 0
|
||||
fallthrough
|
||||
case time.Millisecond:
|
||||
fallthrough
|
||||
case time.Microsecond:
|
||||
fallthrough
|
||||
case time.Nanosecond:
|
||||
nsec = 0
|
||||
}
|
||||
mParams[index] = time.Date(year, month, day, hour, min, sec, nsec, location)
|
||||
}
|
||||
return checker.sub.Check(mParams, names)
|
||||
}
|
||||
|
||||
func parseTime(datetime interface{}) (time.Time, bool) {
|
||||
switch datetime.(type) {
|
||||
case time.Time:
|
||||
return datetime.(time.Time), true
|
||||
case string:
|
||||
return parseTimeAsString(datetime.(string))
|
||||
default:
|
||||
if datetimeWithStr, ok := datetime.(fmt.Stringer); ok {
|
||||
return parseTimeAsString(datetimeWithStr.String())
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func parseTimeAsString(timeAsStr string) (time.Time, bool) {
|
||||
forms := []string{shortForm, time.RFC3339, time.RFC3339Nano, time.RFC822, time.RFC822Z}
|
||||
for _, form := range forms {
|
||||
datetime, err := time.Parse(form, timeAsStr)
|
||||
if err == nil {
|
||||
return datetime, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
Loading…
Reference in a new issue