diff --git a/Dockerfile b/Dockerfile index 345f85f..2bb508f 100644 --- a/Dockerfile +++ b/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 diff --git a/Makefile b/Makefile index 39b1928..2248e87 100644 --- a/Makefile +++ b/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 diff --git a/api/grpc/server/server.go b/api/grpc/server/server.go index b30081c..f9ff9be 100644 --- a/api/grpc/server/server.go +++ b/api/grpc/server/server.go @@ -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{ diff --git a/containerd/main_linux.go b/containerd/main_linux.go index 49fc790..4c3aa22 100644 --- a/containerd/main_linux.go +++ b/containerd/main_linux.go @@ -34,8 +34,10 @@ func setAppBefore(app *cli.App) { app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) - if err := debugMetrics(context.GlobalDuration("metrics-interval"), context.GlobalString("graphite-address")); err != nil { - return err + 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 { diff --git a/hack/.vendor-helpers.sh b/hack/.vendor-helpers.sh index 3a403e2..870df48 100755 --- a/hack/.vendor-helpers.sh +++ b/hack/.vendor-helpers.sh @@ -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 ) diff --git a/hack/vendor.sh b/hack/vendor.sh index 53cae44..b9c5e33 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -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 diff --git a/integration-test/bundle_utils_test.go b/integration-test/bundle_utils_test.go new file mode 100644 index 0000000..3ed4232 --- /dev/null +++ b/integration-test/bundle_utils_test.go @@ -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) +} diff --git a/integration-test/check_test.go b/integration-test/check_test.go new file mode 100644 index 0000000..23508e7 --- /dev/null +++ b/integration-test/check_test.go @@ -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 + } +} diff --git a/integration-test/container_utils_test.go b/integration-test/container_utils_test.go new file mode 100644 index 0000000..b3429e3 --- /dev/null +++ b/integration-test/container_utils_test.go @@ -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 +} diff --git a/integration-test/start_test.go b/integration-test/start_test.go new file mode 100644 index 0000000..8d1af08 --- /dev/null +++ b/integration-test/start_test.go @@ -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) +} diff --git a/runtime/container_linux.go b/runtime/container_linux.go index 3e6cd32..61bea83 100644 --- a/runtime/container_linux.go +++ b/runtime/container_linux.go @@ -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 } diff --git a/vendor/src/github.com/docker/docker/pkg/integration/checker/checker.go b/vendor/src/github.com/docker/docker/pkg/integration/checker/checker.go new file mode 100644 index 0000000..a531499 --- /dev/null +++ b/vendor/src/github.com/docker/docker/pkg/integration/checker/checker.go @@ -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 +) diff --git a/vendor/src/github.com/go-check/check/.gitignore b/vendor/src/github.com/go-check/check/.gitignore new file mode 100644 index 0000000..191a536 --- /dev/null +++ b/vendor/src/github.com/go-check/check/.gitignore @@ -0,0 +1,4 @@ +_* +*.swp +*.[568] +[568].out diff --git a/vendor/src/github.com/go-check/check/.travis.yml b/vendor/src/github.com/go-check/check/.travis.yml new file mode 100644 index 0000000..ee77b0d --- /dev/null +++ b/vendor/src/github.com/go-check/check/.travis.yml @@ -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 ./... diff --git a/vendor/src/github.com/go-check/check/LICENSE b/vendor/src/github.com/go-check/check/LICENSE new file mode 100644 index 0000000..545cf2d --- /dev/null +++ b/vendor/src/github.com/go-check/check/LICENSE @@ -0,0 +1,25 @@ +Gocheck - A rich testing framework for Go + +Copyright (c) 2010-2013 Gustavo Niemeyer + +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. diff --git a/vendor/src/github.com/go-check/check/README.md b/vendor/src/github.com/go-check/check/README.md new file mode 100644 index 0000000..0ca78d9 --- /dev/null +++ b/vendor/src/github.com/go-check/check/README.md @@ -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` diff --git a/vendor/src/github.com/go-check/check/TODO b/vendor/src/github.com/go-check/check/TODO new file mode 100644 index 0000000..3349827 --- /dev/null +++ b/vendor/src/github.com/go-check/check/TODO @@ -0,0 +1,2 @@ +- Assert(slice, Contains, item) +- Parallel test support diff --git a/vendor/src/github.com/go-check/check/benchmark.go b/vendor/src/github.com/go-check/check/benchmark.go new file mode 100644 index 0000000..46ea9dc --- /dev/null +++ b/vendor/src/github.com/go-check/check/benchmark.go @@ -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 +} diff --git a/vendor/src/github.com/go-check/check/check.go b/vendor/src/github.com/go-check/check/check.go new file mode 100644 index 0000000..4c17a6f --- /dev/null +++ b/vendor/src/github.com/go-check/check/check.go @@ -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 "" +} + +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 "" +} + +// ----------------------------------------------------------------------- +// 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) + } +} diff --git a/vendor/src/github.com/go-check/check/checkers.go b/vendor/src/github.com/go-check/check/checkers.go new file mode 100644 index 0000000..bac3387 --- /dev/null +++ b/vendor/src/github.com/go-check/check/checkers.go @@ -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()), "" +} diff --git a/vendor/src/github.com/go-check/check/helpers.go b/vendor/src/github.com/go-check/check/helpers.go new file mode 100644 index 0000000..58a733b --- /dev/null +++ b/vendor/src/github.com/go-check/check/helpers.go @@ -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 +} diff --git a/vendor/src/github.com/go-check/check/printer.go b/vendor/src/github.com/go-check/check/printer.go new file mode 100644 index 0000000..e0f7557 --- /dev/null +++ b/vendor/src/github.com/go-check/check/printer.go @@ -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} +} diff --git a/vendor/src/github.com/go-check/check/reporter.go b/vendor/src/github.com/go-check/check/reporter.go new file mode 100644 index 0000000..fb04f76 --- /dev/null +++ b/vendor/src/github.com/go-check/check/reporter.go @@ -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) +} diff --git a/vendor/src/github.com/go-check/check/run.go b/vendor/src/github.com/go-check/check/run.go new file mode 100644 index 0000000..f721196 --- /dev/null +++ b/vendor/src/github.com/go-check/check/run.go @@ -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 +} diff --git a/vendor/src/github.com/vdemeester/shakers/.gitignore b/vendor/src/github.com/vdemeester/shakers/.gitignore new file mode 100644 index 0000000..6a42174 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/.gitignore @@ -0,0 +1,2 @@ +vendor +*.test diff --git a/vendor/src/github.com/vdemeester/shakers/Dockerfile b/vendor/src/github.com/vdemeester/shakers/Dockerfile new file mode 100644 index 0000000..9864a04 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/Dockerfile @@ -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 diff --git a/vendor/src/github.com/vdemeester/shakers/LICENSE b/vendor/src/github.com/vdemeester/shakers/LICENSE new file mode 100644 index 0000000..e9e9e84 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/LICENSE @@ -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. diff --git a/vendor/src/github.com/vdemeester/shakers/Makefile b/vendor/src/github.com/vdemeester/shakers/Makefile new file mode 100644 index 0000000..74e60db --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/Makefile @@ -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 + diff --git a/vendor/src/github.com/vdemeester/shakers/README.md b/vendor/src/github.com/vdemeester/shakers/README.md new file mode 100644 index 0000000..165bc96 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/README.md @@ -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 .` diff --git a/vendor/src/github.com/vdemeester/shakers/bool.go b/vendor/src/github.com/vdemeester/shakers/bool.go new file mode 100644 index 0000000..ab2a993 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/bool.go @@ -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, "" +} diff --git a/vendor/src/github.com/vdemeester/shakers/circle.yml b/vendor/src/github.com/vdemeester/shakers/circle.yml new file mode 100644 index 0000000..0298aad --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/circle.yml @@ -0,0 +1,11 @@ +machine: + services: + - docker + +dependencies: + override: + - make validate + +test: + override: + - make test-unit diff --git a/vendor/src/github.com/vdemeester/shakers/common.go b/vendor/src/github.com/vdemeester/shakers/common.go new file mode 100644 index 0000000..a2245c6 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/common.go @@ -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." + } +} diff --git a/vendor/src/github.com/vdemeester/shakers/glide.yaml b/vendor/src/github.com/vdemeester/shakers/glide.yaml new file mode 100644 index 0000000..3d9c7f4 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/glide.yaml @@ -0,0 +1,4 @@ +package: main +import: + - package: github.com/go-check/check + ref: 11d3bc7aa68e238947792f30573146a3231fc0f1 diff --git a/vendor/src/github.com/vdemeester/shakers/string.go b/vendor/src/github.com/vdemeester/shakers/string.go new file mode 100644 index 0000000..75ecb10 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/string.go @@ -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()." +} diff --git a/vendor/src/github.com/vdemeester/shakers/time.go b/vendor/src/github.com/vdemeester/shakers/time.go new file mode 100644 index 0000000..0da8d00 --- /dev/null +++ b/vendor/src/github.com/vdemeester/shakers/time.go @@ -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 +}