Update dependencies

This, among others, updates the `go-flags` library, which includes a
feature that closes #198.
This commit is contained in:
Alexander Neumann
2015-06-28 16:36:50 +02:00
parent d9a8dcfd67
commit d9a90f7b89
60 changed files with 1627 additions and 5187 deletions
+23 -2
View File
@@ -144,6 +144,25 @@ func (c *Command) makeLookup() lookup {
commands: make(map[string]*Command),
}
parent := c.parent
for parent != nil {
if cmd, ok := parent.(*Command); ok {
cmd.fillLookup(&ret, true)
}
if grp, ok := parent.(*Group); ok {
parent = grp
} else {
parent = nil
}
}
c.fillLookup(&ret, false)
return ret
}
func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
c.eachGroup(func(g *Group) {
for _, option := range g.options {
if option.ShortName != 0 {
@@ -156,6 +175,10 @@ func (c *Command) makeLookup() lookup {
}
})
if onlyOptions {
return
}
for _, subcommand := range c.commands {
ret.commands[subcommand.Name] = subcommand
@@ -163,8 +186,6 @@ func (c *Command) makeLookup() lookup {
ret.commands[a] = subcommand
}
}
return ret
}
func (c *Command) groupByName(name string) *Group {
+49 -1
View File
@@ -95,7 +95,55 @@ func TestCommandFlagOrder2(t *testing.T) {
} `command:"cmd"`
}{}
assertParseFail(t, ErrUnknownFlag, "unknown flag `v'", &opts, "cmd", "-v", "-g")
assertParseSuccess(t, &opts, "cmd", "-v", "-g")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if !opts.Command.G {
t.Errorf("Expected Command.G to be true")
}
}
func TestCommandFlagOverride1(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "-v", "cmd")
if !opts.Value {
t.Errorf("Expected Value to be true")
}
if opts.Command.Value {
t.Errorf("Expected Command.Value to be false")
}
}
func TestCommandFlagOverride2(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
Command struct {
Value bool `short:"v"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd", "-v")
if opts.Value {
t.Errorf("Expected Value to be false")
}
if !opts.Command.Value {
t.Errorf("Expected Command.Value to be true")
}
}
func TestCommandEstimate(t *testing.T) {
+20
View File
@@ -312,6 +312,26 @@ func quoteIfNeeded(s string) string {
return s
}
func quoteIfNeededV(s []string) []string {
ret := make([]string, len(s))
for i, v := range s {
ret[i] = quoteIfNeeded(v)
}
return ret
}
func quoteV(s []string) []string {
ret := make([]string, len(s))
for i, v := range s {
ret[i] = strconv.Quote(v)
}
return ret
}
func unquoteIfPossible(s string) (string, error) {
if len(s) == 0 || s[0] != '"' {
return s, nil
+9 -5
View File
@@ -183,12 +183,16 @@ the Commander interface, then its Execute method will be run with the
remaining command line arguments.
Command structs can have options which become valid to parse after the
command has been specified on the command line. It is currently not valid
to specify options from the parent level of the command after the command
name has occurred. Thus, given a top-level option "-v" and a command "add":
command has been specified on the command line, in addition to the options
of all the parent commands. I.e. considering a -v flag on the parser and an
add command, the following are equivalent:
Valid: ./app -v add
Invalid: ./app add -v
./app -v add
./app add -v
However, if the -v flag is defined on the add command, then the first of
the two examples above would fail since the -v flag is not defined before
the add command.
Completion
+24 -16
View File
@@ -173,6 +173,14 @@ func TestMan(t *testing.T) {
tt := time.Now()
var envDefaultName string
if runtime.GOOS == "windows" {
envDefaultName = "%ENV_DEFAULT%"
} else {
envDefaultName = "$ENV_DEFAULT"
}
expected := fmt.Sprintf(`.TH TestMan 1 "%s"
.SH NAME
TestMan \- Test manpage generation
@@ -182,45 +190,45 @@ TestMan \- Test manpage generation
This is a somewhat \fBlonger\fP description of what this does
.SH OPTIONS
.TP
\fB-v, --verbose\fP
\fB\fB\-v\fR, \fB\-\-verbose\fR\fP
Show verbose debug information
.TP
\fB-c\fP
\fB\fB\-c\fR\fP
Call phone number
.TP
\fB--ptrslice\fP
\fB\fB\-\-ptrslice\fR\fP
A slice of pointers to string
.TP
\fB--empty-description\fP
\fB\fB\-\-empty-description\fR\fP
.TP
\fB--default\fP
\fB\fB\-\-default\fR <default: \fI"Some\\nvalue"\fR>\fP
Test default value
.TP
\fB--default-array\fP
\fB\fB\-\-default-array\fR <default: \fI"Some value", "Other\\tvalue"\fR>\fP
Test default array value
.TP
\fB--default-map\fP
\fB\fB\-\-default-map\fR <default: \fI"some:value", "another:value"\fR>\fP
Testdefault map value
.TP
\fB--env-default1\fP
\fB\fB\-\-env-default1\fR <default: \fI"Some value"\fR>\fP
Test env-default1 value
.TP
\fB--env-default2\fP
\fB\fB\-\-env-default2\fR <default: \fI%s\fR>\fP
Test env-default2 value
.TP
\fB--opt-with-arg-name\fP
\fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP
Option with named argument
.TP
\fB-s\fP
\fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
A slice of strings
.TP
\fB--intmap\fP
\fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
A map from string to int
.TP
\fB--sip.opt\fP
\fB\fB\-\-sip.opt\fR\fP
This is a subgroup option
.TP
\fB--sip.sap.opt\fP
\fB\fB\-\-sip.sap.opt\fR\fP
This is a subsubgroup option
.SH COMMANDS
.SS command
@@ -234,9 +242,9 @@ Longer \fBcommand\fP description
\fBAliases\fP: cm, cmd
.TP
\fB--extra-verbose\fP
\fB\fB\-\-extra-verbose\fR\fP
Use for extra verbosity
`, tt.Format("2 January 2006"))
`, tt.Format("2 January 2006"), envDefaultName)
assertDiff(t, got, expected, "man page")
}
+41 -13
View File
@@ -3,30 +3,35 @@ package flags
import (
"fmt"
"io"
"runtime"
"strings"
"time"
)
func manQuote(s string) string {
return strings.Replace(s, "\\", "\\\\", -1)
}
func formatForMan(wr io.Writer, s string) {
for {
idx := strings.IndexRune(s, '`')
if idx < 0 {
fmt.Fprintf(wr, "%s", s)
fmt.Fprintf(wr, "%s", manQuote(s))
break
}
fmt.Fprintf(wr, "%s", s[:idx])
fmt.Fprintf(wr, "%s", manQuote(s[:idx]))
s = s[idx+1:]
idx = strings.IndexRune(s, '\'')
if idx < 0 {
fmt.Fprintf(wr, "%s", s)
fmt.Fprintf(wr, "%s", manQuote(s))
break
}
fmt.Fprintf(wr, "\\fB%s\\fP", s[:idx])
fmt.Fprintf(wr, "\\fB%s\\fP", manQuote(s[:idx]))
s = s[idx+1:]
}
}
@@ -42,7 +47,7 @@ func writeManPageOptions(wr io.Writer, grp *Group) {
fmt.Fprintf(wr, "\\fB")
if opt.ShortName != 0 {
fmt.Fprintf(wr, "-%c", opt.ShortName)
fmt.Fprintf(wr, "\\fB\\-%c\\fR", opt.ShortName)
}
if len(opt.LongName) != 0 {
@@ -50,10 +55,33 @@ func writeManPageOptions(wr io.Writer, grp *Group) {
fmt.Fprintf(wr, ", ")
}
fmt.Fprintf(wr, "--%s", opt.LongNameWithNamespace())
fmt.Fprintf(wr, "\\fB\\-\\-%s\\fR", manQuote(opt.LongNameWithNamespace()))
}
if len(opt.ValueName) != 0 || opt.OptionalArgument {
if opt.OptionalArgument {
fmt.Fprintf(wr, " [\\fI%s=%s\\fR]", manQuote(opt.ValueName), manQuote(strings.Join(quoteV(opt.OptionalValue), ", ")))
} else {
fmt.Fprintf(wr, " \\fI%s\\fR", manQuote(opt.ValueName))
}
}
if len(opt.Default) != 0 {
fmt.Fprintf(wr, " <default: \\fI%s\\fR>", manQuote(strings.Join(quoteV(opt.Default), ", ")))
} else if len(opt.EnvDefaultKey) != 0 {
if runtime.GOOS == "windows" {
fmt.Fprintf(wr, " <default: \\fI%%%s%%\\fR>", manQuote(opt.EnvDefaultKey))
} else {
fmt.Fprintf(wr, " <default: \\fI$%s\\fR>", manQuote(opt.EnvDefaultKey))
}
}
if opt.Required {
fmt.Fprintf(wr, " (\\fIrequired\\fR)")
}
fmt.Fprintln(wr, "\\fP")
if len(opt.Description) != 0 {
formatForMan(wr, opt.Description)
fmt.Fprintln(wr, "")
@@ -85,10 +113,10 @@ func writeManPageCommand(wr io.Writer, name string, root *Command, command *Comm
if len(command.LongDescription) > 0 {
fmt.Fprintln(wr, "")
cmdstart := fmt.Sprintf("The %s command", command.Name)
cmdstart := fmt.Sprintf("The %s command", manQuote(command.Name))
if strings.HasPrefix(command.LongDescription, cmdstart) {
fmt.Fprintf(wr, "The \\fI%s\\fP command", command.Name)
fmt.Fprintf(wr, "The \\fI%s\\fP command", manQuote(command.Name))
formatForMan(wr, command.LongDescription[len(cmdstart):])
fmt.Fprintln(wr, "")
@@ -113,11 +141,11 @@ func writeManPageCommand(wr io.Writer, name string, root *Command, command *Comm
}
if len(usage) > 0 {
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", pre, usage)
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", manQuote(pre), manQuote(usage))
}
if len(command.Aliases) > 0 {
fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", strings.Join(command.Aliases, ", "))
fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", manQuote(strings.Join(command.Aliases, ", ")))
}
writeManPageOptions(wr, command.Group)
@@ -129,9 +157,9 @@ func writeManPageCommand(wr io.Writer, name string, root *Command, command *Comm
func (p *Parser) WriteManPage(wr io.Writer) {
t := time.Now()
fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", p.Name, t.Format("2 January 2006"))
fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", manQuote(p.Name), t.Format("2 January 2006"))
fmt.Fprintln(wr, ".SH NAME")
fmt.Fprintf(wr, "%s \\- %s\n", p.Name, p.ShortDescription)
fmt.Fprintf(wr, "%s \\- %s\n", manQuote(p.Name), manQuote(p.ShortDescription))
fmt.Fprintln(wr, ".SH SYNOPSIS")
usage := p.Usage
@@ -140,7 +168,7 @@ func (p *Parser) WriteManPage(wr io.Writer) {
usage = "[OPTIONS]"
}
fmt.Fprintf(wr, "\\fB%s\\fP %s\n", p.Name, usage)
fmt.Fprintf(wr, "\\fB%s\\fP %s\n", manQuote(p.Name), manQuote(usage))
fmt.Fprintln(wr, ".SH DESCRIPTION")
formatForMan(wr, p.LongDescription)
+6 -1
View File
@@ -35,7 +35,7 @@ type Option struct {
// If true, specifies that the argument to an option flag is optional.
// When no argument to the flag is specified on the command line, the
// value of Default will be set in the field this option represents.
// value of OptionalValue will be set in the field this option represents.
// This is only valid for non-boolean options.
OptionalArgument bool
@@ -155,3 +155,8 @@ func (option *Option) String() string {
func (option *Option) Value() interface{} {
return option.value.Interface()
}
// IsSet returns true if option has been set
func (option *Option) IsSet() bool {
return option.isSet
}
-180
View File
@@ -1,180 +0,0 @@
package testutil
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"time"
)
type HTTPServer struct {
URL string
Timeout time.Duration
started bool
request chan *http.Request
response chan ResponseFunc
}
type Response struct {
Status int
Headers map[string]string
Body string
}
var DefaultClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
func NewHTTPServer() *HTTPServer {
return &HTTPServer{URL: "http://localhost:4444", Timeout: 5 * time.Second}
}
type ResponseFunc func(path string) Response
func (s *HTTPServer) Start() {
if s.started {
return
}
s.started = true
s.request = make(chan *http.Request, 1024)
s.response = make(chan ResponseFunc, 1024)
u, err := url.Parse(s.URL)
if err != nil {
panic(err)
}
l, err := net.Listen("tcp", u.Host)
if err != nil {
panic(err)
}
go http.Serve(l, s)
s.Response(203, nil, "")
for {
// Wait for it to be up.
resp, err := http.Get(s.URL)
if err == nil && resp.StatusCode == 203 {
break
}
time.Sleep(1e8)
}
s.WaitRequest() // Consume dummy request.
}
// Flush discards all pending requests and responses.
func (s *HTTPServer) Flush() {
for {
select {
case <-s.request:
case <-s.response:
default:
return
}
}
}
func body(req *http.Request) string {
data, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
return string(data)
}
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.ParseMultipartForm(1e6)
data, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(data))
s.request <- req
var resp Response
select {
case respFunc := <-s.response:
resp = respFunc(req.URL.Path)
case <-time.After(s.Timeout):
const msg = "ERROR: Timeout waiting for test to prepare a response\n"
fmt.Fprintf(os.Stderr, msg)
resp = Response{500, nil, msg}
}
if resp.Headers != nil {
h := w.Header()
for k, v := range resp.Headers {
h.Set(k, v)
}
}
if resp.Status != 0 {
w.WriteHeader(resp.Status)
}
w.Write([]byte(resp.Body))
}
// WaitRequests returns the next n requests made to the http server from
// the queue. If not enough requests were previously made, it waits until
// the timeout value for them to be made.
func (s *HTTPServer) WaitRequests(n int) []*http.Request {
reqs := make([]*http.Request, 0, n)
for i := 0; i < n; i++ {
select {
case req := <-s.request:
reqs = append(reqs, req)
case <-time.After(s.Timeout):
panic("Timeout waiting for request")
}
}
return reqs
}
// WaitRequest returns the next request made to the http server from
// the queue. If no requests were previously made, it waits until the
// timeout value for one to be made.
func (s *HTTPServer) WaitRequest() *http.Request {
return s.WaitRequests(1)[0]
}
// ResponseFunc prepares the test server to respond the following n
// requests using f to build each response.
func (s *HTTPServer) ResponseFunc(n int, f ResponseFunc) {
for i := 0; i < n; i++ {
s.response <- f
}
}
// ResponseMap maps request paths to responses.
type ResponseMap map[string]Response
// ResponseMap prepares the test server to respond the following n
// requests using the m to obtain the responses.
func (s *HTTPServer) ResponseMap(n int, m ResponseMap) {
f := func(path string) Response {
for rpath, resp := range m {
if rpath == path {
return resp
}
}
body := "Path not found in response map: " + path
return Response{Status: 500, Body: body}
}
s.ResponseFunc(n, f)
}
// Responses prepares the test server to respond the following n requests
// using the provided response parameters.
func (s *HTTPServer) Responses(n int, status int, headers map[string]string, body string) {
f := func(path string) Response {
return Response{status, headers, body}
}
s.ResponseFunc(n, f)
}
// Response prepares the test server to respond the following request
// using the provided response parameters.
func (s *HTTPServer) Response(status int, headers map[string]string, body string) {
s.Responses(1, status, headers, body)
}
-30
View File
@@ -1,30 +0,0 @@
package testutil
import (
"flag"
"github.com/mitchellh/goamz/aws"
. "github.com/motain/gocheck"
)
// Amazon must be used by all tested packages to determine whether to
// run functional tests against the real AWS servers.
var Amazon bool
func init() {
flag.BoolVar(&Amazon, "amazon", false, "Enable tests against amazon server")
}
type LiveSuite struct {
auth aws.Auth
}
func (s *LiveSuite) SetUpSuite(c *C) {
if !Amazon {
c.Skip("amazon tests not enabled (-amazon flag)")
}
auth, err := aws.EnvAuth()
if err != nil {
c.Fatal(err.Error())
}
s.auth = auth
}
-3
View File
@@ -1,3 +0,0 @@
_*
[856].out
[856].out.exe
@@ -1,4 +0,0 @@
_*
*.swp
*.[568]
[568].out
-29
View File
@@ -1,29 +0,0 @@
Gocheck - A rich testing framework for Go
Copyright (c) 2010, Gustavo Niemeyer <gustavo@niemeyer.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* 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 the copyright holder 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.
-30
View File
@@ -1,30 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=launchpad.net/gocheck
GOFILES=\
gocheck.go\
helpers.go\
run.go\
checkers.go\
printer.go\
#TARGDIR=$(GOPATH)/pkg/$(GOOS)_$(GOARCH)
#GCIMPORTS=$(patsubst %,-I %/pkg/$(GOOS)_$(GOARCH),$(subst :, ,$(GOPATH)))
#LDIMPORTS=$(patsubst %,-L %/pkg/$(GOOS)_$(GOARCH),$(subst :, ,$(GOPATH)))
include $(GOROOT)/src/Make.pkg
GOFMT=gofmt
BADFMT=$(shell $(GOFMT) -l $(GOFILES) $(filter-out printer_test.go,$(wildcard *_test.go)))
gofmt: $(BADFMT)
@for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done
ifneq ($(BADFMT),)
ifneq ($(MAKECMDGOALS),gofmt)
#$(warning WARNING: make gofmt: $(BADFMT))
endif
endif
-2
View File
@@ -1,2 +0,0 @@
- Assert(slice, Contains, item)
- Parallel test support
-136
View File
@@ -1,136 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocheck
import (
"fmt"
"time"
)
// 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
}
// 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
}
}
// 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
}
}
// 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()
}
c.duration = 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))
}
}
return fmt.Sprintf("%8d\t%s%s", c.N, ns, mb)
}
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
}
-75
View File
@@ -1,75 +0,0 @@
// These tests verify the test running logic.
package gocheck_test
import (
. "launchpad.net/gocheck"
"time"
)
var benchmarkS = Suite(&BenchmarkS{})
type BenchmarkS struct{}
func (s *BenchmarkS) TestCountSuite(c *C) {
suitesRun += 1
}
func (s *BenchmarkS) TestBasicTestTiming(c *C) {
helper := FixtureHelper{sleepOn: "Test1", sleep: 1000000 * time.Nanosecond}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Test1\t0\\.001s\n" +
"PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Test2\t0\\.000s\n"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestStreamTestTiming(c *C) {
helper := FixtureHelper{sleepOn: "SetUpSuite", sleep: 1000000 * time.Nanosecond}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(&helper, &runConf)
expected := "(?s).*\nPASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.SetUpSuite\t *0\\.001s\n.*"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestBenchmark(c *C) {
helper := FixtureHelper{sleep: 100000}
output := String{}
runConf := RunConf{
Output: &output,
Benchmark: true,
BenchmarkTime: 10000000,
Filter: "Benchmark1",
}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Benchmark1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Benchmark1")
c.Check(helper.calls[6], Equals, "TearDownTest")
// ... and more.
expected := "PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Benchmark1\t *100\t *[12][0-9]{5} ns/op\n"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestBenchmarkBytes(c *C) {
helper := FixtureHelper{sleep: 100000}
output := String{}
runConf := RunConf{
Output: &output,
Benchmark: true,
BenchmarkTime: 10000000,
Filter: "Benchmark2",
}
Run(&helper, &runConf)
expected := "PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Benchmark2\t *100\t *[12][0-9]{5} ns/op\t *[4-9]\\.[0-9]{2} MB/s\n"
c.Assert(output.value, Matches, expected)
}
-82
View File
@@ -1,82 +0,0 @@
// These initial tests are for bootstrapping. They verify that we can
// basically use the testing infrastructure itself to check if the test
// system is working.
//
// These tests use will break down the test runner badly in case of
// errors because if they simply fail, we can't be sure the developer
// will ever see anything (because failing means the failing system
// somehow isn't working! :-)
//
// Do not assume *any* internal functionality works as expected besides
// what's actually tested here.
package gocheck_test
import (
"fmt"
"launchpad.net/gocheck"
"strings"
)
type BootstrapS struct{}
var boostrapS = gocheck.Suite(&BootstrapS{})
func (s *BootstrapS) TestCountSuite(c *gocheck.C) {
suitesRun += 1
}
func (s *BootstrapS) TestFailedAndFail(c *gocheck.C) {
if c.Failed() {
critical("c.Failed() must be false first!")
}
c.Fail()
if !c.Failed() {
critical("c.Fail() didn't put the test in a failed state!")
}
c.Succeed()
}
func (s *BootstrapS) TestFailedAndSucceed(c *gocheck.C) {
c.Fail()
c.Succeed()
if c.Failed() {
critical("c.Succeed() didn't put the test back in a non-failed state")
}
}
func (s *BootstrapS) TestLogAndGetTestLog(c *gocheck.C) {
c.Log("Hello there!")
log := c.GetTestLog()
if log != "Hello there!\n" {
critical(fmt.Sprintf("Log() or GetTestLog() is not working! Got: %#v", log))
}
}
func (s *BootstrapS) TestLogfAndGetTestLog(c *gocheck.C) {
c.Logf("Hello %v", "there!")
log := c.GetTestLog()
if log != "Hello there!\n" {
critical(fmt.Sprintf("Logf() or GetTestLog() is not working! Got: %#v", log))
}
}
func (s *BootstrapS) TestRunShowsErrors(c *gocheck.C) {
output := String{}
gocheck.Run(&FailHelper{}, &gocheck.RunConf{Output: &output})
if strings.Index(output.value, "Expected failure!") == -1 {
critical(fmt.Sprintf("RunWithWriter() output did not contain the "+
"expected failure! Got: %#v",
output.value))
}
}
func (s *BootstrapS) TestRunDoesntShowSuccesses(c *gocheck.C) {
output := String{}
gocheck.Run(&SuccessHelper{}, &gocheck.RunConf{Output: &output})
if strings.Index(output.value, "Expected success!") != -1 {
critical(fmt.Sprintf("RunWithWriter() output contained a successful "+
"test! Got: %#v",
output.value))
}
}
-458
View File
@@ -1,458 +0,0 @@
package gocheck
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 &notChecker{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 = &notNilChecker{
&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()), ""
}
-272
View File
@@ -1,272 +0,0 @@
package gocheck_test
import (
"errors"
"launchpad.net/gocheck"
"reflect"
"runtime"
)
type CheckersS struct{}
var _ = gocheck.Suite(&CheckersS{})
func testInfo(c *gocheck.C, checker gocheck.Checker, name string, paramNames []string) {
info := checker.Info()
if info.Name != name {
c.Fatalf("Got name %s, expected %s", info.Name, name)
}
if !reflect.DeepEqual(info.Params, paramNames) {
c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames)
}
}
func testCheck(c *gocheck.C, checker gocheck.Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) {
info := checker.Info()
if len(params) != len(info.Params) {
c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params))
}
names := append([]string{}, info.Params...)
result_, error_ := checker.Check(params, names)
if result_ != result || error_ != error {
c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)",
info.Name, params, result_, error_, result, error)
}
return params, names
}
func (s *CheckersS) TestComment(c *gocheck.C) {
bug := gocheck.Commentf("a %d bc", 42)
comment := bug.CheckCommentString()
if comment != "a 42 bc" {
c.Fatalf("Commentf returned %#v", comment)
}
}
func (s *CheckersS) TestIsNil(c *gocheck.C) {
testInfo(c, gocheck.IsNil, "IsNil", []string{"value"})
testCheck(c, gocheck.IsNil, true, "", nil)
testCheck(c, gocheck.IsNil, false, "", "a")
testCheck(c, gocheck.IsNil, true, "", (chan int)(nil))
testCheck(c, gocheck.IsNil, false, "", make(chan int))
testCheck(c, gocheck.IsNil, true, "", (error)(nil))
testCheck(c, gocheck.IsNil, false, "", errors.New(""))
testCheck(c, gocheck.IsNil, true, "", ([]int)(nil))
testCheck(c, gocheck.IsNil, false, "", make([]int, 1))
testCheck(c, gocheck.IsNil, false, "", int(0))
}
func (s *CheckersS) TestNotNil(c *gocheck.C) {
testInfo(c, gocheck.NotNil, "NotNil", []string{"value"})
testCheck(c, gocheck.NotNil, false, "", nil)
testCheck(c, gocheck.NotNil, true, "", "a")
testCheck(c, gocheck.NotNil, false, "", (chan int)(nil))
testCheck(c, gocheck.NotNil, true, "", make(chan int))
testCheck(c, gocheck.NotNil, false, "", (error)(nil))
testCheck(c, gocheck.NotNil, true, "", errors.New(""))
testCheck(c, gocheck.NotNil, false, "", ([]int)(nil))
testCheck(c, gocheck.NotNil, true, "", make([]int, 1))
}
func (s *CheckersS) TestNot(c *gocheck.C) {
testInfo(c, gocheck.Not(gocheck.IsNil), "Not(IsNil)", []string{"value"})
testCheck(c, gocheck.Not(gocheck.IsNil), false, "", nil)
testCheck(c, gocheck.Not(gocheck.IsNil), true, "", "a")
}
type simpleStruct struct {
i int
}
func (s *CheckersS) TestEquals(c *gocheck.C) {
testInfo(c, gocheck.Equals, "Equals", []string{"obtained", "expected"})
// The simplest.
testCheck(c, gocheck.Equals, true, "", 42, 42)
testCheck(c, gocheck.Equals, false, "", 42, 43)
// Different native types.
testCheck(c, gocheck.Equals, false, "", int32(42), int64(42))
// With nil.
testCheck(c, gocheck.Equals, false, "", 42, nil)
// Slices
testCheck(c, gocheck.Equals, false, "runtime error: comparing uncomparable type []uint8", []byte{1, 2}, []byte{1, 2})
// Struct values
testCheck(c, gocheck.Equals, true, "", simpleStruct{1}, simpleStruct{1})
testCheck(c, gocheck.Equals, false, "", simpleStruct{1}, simpleStruct{2})
// Struct pointers
testCheck(c, gocheck.Equals, false, "", &simpleStruct{1}, &simpleStruct{1})
testCheck(c, gocheck.Equals, false, "", &simpleStruct{1}, &simpleStruct{2})
}
func (s *CheckersS) TestDeepEquals(c *gocheck.C) {
testInfo(c, gocheck.DeepEquals, "DeepEquals", []string{"obtained", "expected"})
// The simplest.
testCheck(c, gocheck.DeepEquals, true, "", 42, 42)
testCheck(c, gocheck.DeepEquals, false, "", 42, 43)
// Different native types.
testCheck(c, gocheck.DeepEquals, false, "", int32(42), int64(42))
// With nil.
testCheck(c, gocheck.DeepEquals, false, "", 42, nil)
// Slices
testCheck(c, gocheck.DeepEquals, true, "", []byte{1, 2}, []byte{1, 2})
testCheck(c, gocheck.DeepEquals, false, "", []byte{1, 2}, []byte{1, 3})
// Struct values
testCheck(c, gocheck.DeepEquals, true, "", simpleStruct{1}, simpleStruct{1})
testCheck(c, gocheck.DeepEquals, false, "", simpleStruct{1}, simpleStruct{2})
// Struct pointers
testCheck(c, gocheck.DeepEquals, true, "", &simpleStruct{1}, &simpleStruct{1})
testCheck(c, gocheck.DeepEquals, false, "", &simpleStruct{1}, &simpleStruct{2})
}
func (s *CheckersS) TestHasLen(c *gocheck.C) {
testInfo(c, gocheck.HasLen, "HasLen", []string{"obtained", "n"})
testCheck(c, gocheck.HasLen, true, "", "abcd", 4)
testCheck(c, gocheck.HasLen, true, "", []int{1, 2}, 2)
testCheck(c, gocheck.HasLen, false, "", []int{1, 2}, 3)
testCheck(c, gocheck.HasLen, false, "n must be an int", []int{1, 2}, "2")
testCheck(c, gocheck.HasLen, false, "obtained value type has no length", nil, 2)
}
func (s *CheckersS) TestErrorMatches(c *gocheck.C) {
testInfo(c, gocheck.ErrorMatches, "ErrorMatches", []string{"value", "regex"})
testCheck(c, gocheck.ErrorMatches, false, "Error value is nil", nil, "some error")
testCheck(c, gocheck.ErrorMatches, false, "Value is not an error", 1, "some error")
testCheck(c, gocheck.ErrorMatches, true, "", errors.New("some error"), "some error")
testCheck(c, gocheck.ErrorMatches, true, "", errors.New("some error"), "so.*or")
// Verify params mutation
params, names := testCheck(c, gocheck.ErrorMatches, false, "", errors.New("some error"), "other error")
c.Assert(params[0], gocheck.Equals, "some error")
c.Assert(names[0], gocheck.Equals, "error")
}
func (s *CheckersS) TestMatches(c *gocheck.C) {
testInfo(c, gocheck.Matches, "Matches", []string{"value", "regex"})
// Simple matching
testCheck(c, gocheck.Matches, true, "", "abc", "abc")
testCheck(c, gocheck.Matches, true, "", "abc", "a.c")
// Must match fully
testCheck(c, gocheck.Matches, false, "", "abc", "ab")
testCheck(c, gocheck.Matches, false, "", "abc", "bc")
// String()-enabled values accepted
testCheck(c, gocheck.Matches, true, "", reflect.ValueOf("abc"), "a.c")
testCheck(c, gocheck.Matches, false, "", reflect.ValueOf("abc"), "a.d")
// Some error conditions.
testCheck(c, gocheck.Matches, false, "Obtained value is not a string and has no .String()", 1, "a.c")
testCheck(c, gocheck.Matches, false, "Can't compile regex: error parsing regexp: missing closing ]: `[c$`", "abc", "a[c")
}
func (s *CheckersS) TestPanics(c *gocheck.C) {
testInfo(c, gocheck.Panics, "Panics", []string{"function", "expected"})
// Some errors.
testCheck(c, gocheck.Panics, false, "Function has not panicked", func() bool { return false }, "BOOM")
testCheck(c, gocheck.Panics, false, "Function must take zero arguments", 1, "BOOM")
// Plain strings.
testCheck(c, gocheck.Panics, true, "", func() { panic("BOOM") }, "BOOM")
testCheck(c, gocheck.Panics, false, "", func() { panic("KABOOM") }, "BOOM")
testCheck(c, gocheck.Panics, true, "", func() bool { panic("BOOM") }, "BOOM")
// Error values.
testCheck(c, gocheck.Panics, true, "", func() { panic(errors.New("BOOM")) }, errors.New("BOOM"))
testCheck(c, gocheck.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
type deep struct{ i int }
// Deep value
testCheck(c, gocheck.Panics, true, "", func() { panic(&deep{99}) }, &deep{99})
// Verify params/names mutation
params, names := testCheck(c, gocheck.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
c.Assert(params[0], gocheck.ErrorMatches, "KABOOM")
c.Assert(names[0], gocheck.Equals, "panic")
// Verify a nil panic
testCheck(c, gocheck.Panics, true, "", func() { panic(nil) }, nil)
testCheck(c, gocheck.Panics, false, "", func() { panic(nil) }, "NOPE")
}
func (s *CheckersS) TestPanicMatches(c *gocheck.C) {
testInfo(c, gocheck.PanicMatches, "PanicMatches", []string{"function", "expected"})
// Error matching.
testCheck(c, gocheck.PanicMatches, true, "", func() { panic(errors.New("BOOM")) }, "BO.M")
testCheck(c, gocheck.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BO.M")
// Some errors.
testCheck(c, gocheck.PanicMatches, false, "Function has not panicked", func() bool { return false }, "BOOM")
testCheck(c, gocheck.PanicMatches, false, "Function must take zero arguments", 1, "BOOM")
// Plain strings.
testCheck(c, gocheck.PanicMatches, true, "", func() { panic("BOOM") }, "BO.M")
testCheck(c, gocheck.PanicMatches, false, "", func() { panic("KABOOM") }, "BOOM")
testCheck(c, gocheck.PanicMatches, true, "", func() bool { panic("BOOM") }, "BO.M")
// Verify params/names mutation
params, names := testCheck(c, gocheck.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BOOM")
c.Assert(params[0], gocheck.Equals, "KABOOM")
c.Assert(names[0], gocheck.Equals, "panic")
// Verify a nil panic
testCheck(c, gocheck.PanicMatches, false, "Panic value is not a string or an error", func() { panic(nil) }, "")
}
func (s *CheckersS) TestFitsTypeOf(c *gocheck.C) {
testInfo(c, gocheck.FitsTypeOf, "FitsTypeOf", []string{"obtained", "sample"})
// Basic types
testCheck(c, gocheck.FitsTypeOf, true, "", 1, 0)
testCheck(c, gocheck.FitsTypeOf, false, "", 1, int64(0))
// Aliases
testCheck(c, gocheck.FitsTypeOf, false, "", 1, errors.New(""))
testCheck(c, gocheck.FitsTypeOf, false, "", "error", errors.New(""))
testCheck(c, gocheck.FitsTypeOf, true, "", errors.New("error"), errors.New(""))
// Structures
testCheck(c, gocheck.FitsTypeOf, false, "", 1, simpleStruct{})
testCheck(c, gocheck.FitsTypeOf, false, "", simpleStruct{42}, &simpleStruct{})
testCheck(c, gocheck.FitsTypeOf, true, "", simpleStruct{42}, simpleStruct{})
testCheck(c, gocheck.FitsTypeOf, true, "", &simpleStruct{42}, &simpleStruct{})
// Some bad values
testCheck(c, gocheck.FitsTypeOf, false, "Invalid sample value", 1, interface{}(nil))
testCheck(c, gocheck.FitsTypeOf, false, "", interface{}(nil), 0)
}
func (s *CheckersS) TestImplements(c *gocheck.C) {
testInfo(c, gocheck.Implements, "Implements", []string{"obtained", "ifaceptr"})
var e error
var re runtime.Error
testCheck(c, gocheck.Implements, true, "", errors.New(""), &e)
testCheck(c, gocheck.Implements, false, "", errors.New(""), &re)
// Some bad values
testCheck(c, gocheck.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, errors.New(""))
testCheck(c, gocheck.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, interface{}(nil))
testCheck(c, gocheck.Implements, false, "", interface{}(nil), &e)
}
-9
View File
@@ -1,9 +0,0 @@
package gocheck
func PrintLine(filename string, line int) (string, error) {
return printLine(filename, line)
}
func Indent(s, with string) string {
return indent(s, with)
}
-479
View File
@@ -1,479 +0,0 @@
// Tests for the behavior of the test fixture system.
package gocheck_test
import (
. "launchpad.net/gocheck"
)
// -----------------------------------------------------------------------
// Fixture test suite.
type FixtureS struct{}
var fixtureS = Suite(&FixtureS{})
func (s *FixtureS) TestCountSuite(c *C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Basic fixture ordering verification.
func (s *FixtureS) TestOrder(c *C) {
helper := FixtureHelper{}
Run(&helper, nil)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
// -----------------------------------------------------------------------
// Check the behavior when panics occur within tests and fixtures.
func (s *FixtureS) TestPanicOnTest(c *C) {
helper := FixtureHelper{panicOn: "Test1"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
expected := "^\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: FixtureHelper.Test1\n\n" +
"\\.\\.\\. Panic: Test1 \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in panic\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.Test1\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnSetUpTest(c *C) {
helper := FixtureHelper{panicOn: "SetUpTest"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 4)
expected := "^\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: SetUpTest \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in panic\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.SetUpTest\n" +
"\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: Fixture has panicked " +
"\\(see related PANIC\\)\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnTearDownTest(c *C) {
helper := FixtureHelper{panicOn: "TearDownTest"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
expected := "^\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper.TearDownTest\n\n" +
"\\.\\.\\. Panic: TearDownTest \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in panic\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.TearDownTest\n" +
"\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: Fixture has panicked " +
"\\(see related PANIC\\)\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnSetUpSuite(c *C) {
helper := FixtureHelper{panicOn: "SetUpSuite"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 2)
expected := "^\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper.SetUpSuite\n\n" +
"\\.\\.\\. Panic: SetUpSuite \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in panic\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.SetUpSuite\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnTearDownSuite(c *C) {
helper := FixtureHelper{panicOn: "TearDownSuite"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
expected := "^\n-+\n" +
"PANIC: gocheck_test\\.go:[0-9]+: " +
"FixtureHelper.TearDownSuite\n\n" +
"\\.\\.\\. Panic: TearDownSuite \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in panic\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*gocheck_test.go:[0-9]+\n" +
" in FixtureHelper.TearDownSuite\n$"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// A wrong argument on a test or fixture will produce a nice error.
func (s *FixtureS) TestPanicOnWrongTestArg(c *C) {
helper := WrongTestArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "SetUpTest")
c.Check(helper.calls[4], Equals, "Test2")
c.Check(helper.calls[5], Equals, "TearDownTest")
c.Check(helper.calls[6], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 7)
expected := "^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongTestArgHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: WrongTestArgHelper\\.Test1 argument " +
"should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpTestArg(c *C) {
helper := WrongSetUpTestArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpTestArgHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: WrongSetUpTestArgHelper\\.SetUpTest argument " +
"should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArg(c *C) {
helper := WrongSetUpSuiteArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpSuiteArgHelper\\.SetUpSuite\n\n" +
"\\.\\.\\. Panic: WrongSetUpSuiteArgHelper\\.SetUpSuite argument " +
"should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Nice errors also when tests or fixture have wrong arg count.
func (s *FixtureS) TestPanicOnWrongTestArgCount(c *C) {
helper := WrongTestArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "SetUpTest")
c.Check(helper.calls[4], Equals, "Test2")
c.Check(helper.calls[5], Equals, "TearDownTest")
c.Check(helper.calls[6], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 7)
expected := "^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongTestArgCountHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: WrongTestArgCountHelper\\.Test1 argument " +
"should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpTestArgCount(c *C) {
helper := WrongSetUpTestArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpTestArgCountHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: WrongSetUpTestArgCountHelper\\.SetUpTest argument " +
"should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArgCount(c *C) {
helper := WrongSetUpSuiteArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpSuiteArgCountHelper\\.SetUpSuite\n\n" +
"\\.\\.\\. Panic: WrongSetUpSuiteArgCountHelper" +
"\\.SetUpSuite argument should be \\*gocheck\\.C\n"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Helper test suites with wrong function arguments.
type WrongTestArgHelper struct {
FixtureHelper
}
func (s *WrongTestArgHelper) Test1(t int) {
}
type WrongSetUpTestArgHelper struct {
FixtureHelper
}
func (s *WrongSetUpTestArgHelper) SetUpTest(t int) {
}
type WrongSetUpSuiteArgHelper struct {
FixtureHelper
}
func (s *WrongSetUpSuiteArgHelper) SetUpSuite(t int) {
}
type WrongTestArgCountHelper struct {
FixtureHelper
}
func (s *WrongTestArgCountHelper) Test1(c *C, i int) {
}
type WrongSetUpTestArgCountHelper struct {
FixtureHelper
}
func (s *WrongSetUpTestArgCountHelper) SetUpTest(c *C, i int) {
}
type WrongSetUpSuiteArgCountHelper struct {
FixtureHelper
}
func (s *WrongSetUpSuiteArgCountHelper) SetUpSuite(c *C, i int) {
}
// -----------------------------------------------------------------------
// Ensure fixture doesn't run without tests.
type NoTestsHelper struct {
hasRun bool
}
func (s *NoTestsHelper) SetUpSuite(c *C) {
s.hasRun = true
}
func (s *NoTestsHelper) TearDownSuite(c *C) {
s.hasRun = true
}
func (s *FixtureS) TestFixtureDoesntRunWithoutTests(c *C) {
helper := NoTestsHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.hasRun, Equals, false)
}
// -----------------------------------------------------------------------
// Verify that checks and assertions work correctly inside the fixture.
type FixtureCheckHelper struct {
fail string
completed bool
}
func (s *FixtureCheckHelper) SetUpSuite(c *C) {
switch s.fail {
case "SetUpSuiteAssert":
c.Assert(false, Equals, true)
case "SetUpSuiteCheck":
c.Check(false, Equals, true)
}
s.completed = true
}
func (s *FixtureCheckHelper) SetUpTest(c *C) {
switch s.fail {
case "SetUpTestAssert":
c.Assert(false, Equals, true)
case "SetUpTestCheck":
c.Check(false, Equals, true)
}
s.completed = true
}
func (s *FixtureCheckHelper) Test(c *C) {
// Do nothing.
}
func (s *FixtureS) TestSetUpSuiteCheck(c *C) {
helper := FixtureCheckHelper{fail: "SetUpSuiteCheck"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureCheckHelper\\.SetUpSuite\n\n"+
"fixture_test\\.go:[0-9]+:\n"+
" c\\.Check\\(false, Equals, true\\)\n"+
"\\.+ obtained bool = false\n"+
"\\.+ expected bool = true\n\n")
c.Assert(helper.completed, Equals, true)
}
func (s *FixtureS) TestSetUpSuiteAssert(c *C) {
helper := FixtureCheckHelper{fail: "SetUpSuiteAssert"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureCheckHelper\\.SetUpSuite\n\n"+
"fixture_test\\.go:[0-9]+:\n"+
" c\\.Assert\\(false, Equals, true\\)\n"+
"\\.+ obtained bool = false\n"+
"\\.+ expected bool = true\n\n")
c.Assert(helper.completed, Equals, false)
}
// -----------------------------------------------------------------------
// Verify that logging within SetUpTest() persists within the test log itself.
type FixtureLogHelper struct {
c *C
}
func (s *FixtureLogHelper) SetUpTest(c *C) {
s.c = c
c.Log("1")
}
func (s *FixtureLogHelper) Test(c *C) {
c.Log("2")
s.c.Log("3")
c.Log("4")
c.Fail()
}
func (s *FixtureLogHelper) TearDownTest(c *C) {
s.c.Log("5")
}
func (s *FixtureS) TestFixtureLogging(c *C) {
helper := FixtureLogHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureLogHelper\\.Test\n\n"+
"1\n2\n3\n4\n5\n")
}
// -----------------------------------------------------------------------
// Skip() within fixture methods.
func (s *FixtureS) TestSkipSuite(c *C) {
helper := FixtureHelper{skip: true, skipOnN: 0}
output := String{}
result := Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Equals, "")
c.Assert(helper.calls[0], Equals, "SetUpSuite")
c.Assert(helper.calls[1], Equals, "TearDownSuite")
c.Assert(len(helper.calls), Equals, 2)
c.Assert(result.Skipped, Equals, 2)
}
func (s *FixtureS) TestSkipTest(c *C) {
helper := FixtureHelper{skip: true, skipOnN: 1}
output := String{}
result := Run(&helper, &RunConf{Output: &output})
c.Assert(helper.calls[0], Equals, "SetUpSuite")
c.Assert(helper.calls[1], Equals, "SetUpTest")
c.Assert(helper.calls[2], Equals, "SetUpTest")
c.Assert(helper.calls[3], Equals, "Test2")
c.Assert(helper.calls[4], Equals, "TearDownTest")
c.Assert(helper.calls[5], Equals, "TearDownSuite")
c.Assert(len(helper.calls), Equals, 6)
c.Assert(result.Skipped, Equals, 1)
}
-340
View File
@@ -1,340 +0,0 @@
// These tests check that the foundations of gocheck are working properly.
// They already assume that fundamental failing is working already, though,
// since this was tested in bootstrap_test.go. Even then, some care may
// still have to be taken when using external functions, since they should
// of course not rely on functionality tested here.
package gocheck_test
import (
"fmt"
"launchpad.net/gocheck"
"log"
"os"
"regexp"
"strings"
)
// -----------------------------------------------------------------------
// Foundation test suite.
type FoundationS struct{}
var foundationS = gocheck.Suite(&FoundationS{})
func (s *FoundationS) TestCountSuite(c *gocheck.C) {
suitesRun += 1
}
func (s *FoundationS) TestErrorf(c *gocheck.C) {
// Do not use checkState() here. It depends on Errorf() working.
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Errorf(\"Error %%v!\", \"message\")\n"+
"... Error: Error message!\n\n",
getMyLine()+1)
c.Errorf("Error %v!", "message")
failed := c.Failed()
c.Succeed()
if log := c.GetTestLog(); log != expectedLog {
c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog)
c.Fail()
}
if !failed {
c.Logf("Errorf() didn't put the test in a failed state")
c.Fail()
}
}
func (s *FoundationS) TestError(c *gocheck.C) {
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
" c\\.Error\\(\"Error \", \"message!\"\\)\n"+
"\\.\\.\\. Error: Error message!\n\n",
getMyLine()+1)
c.Error("Error ", "message!")
checkState(c, nil,
&expectedState{
name: "Error(`Error `, `message!`)",
failed: true,
log: expectedLog,
})
}
func (s *FoundationS) TestFailNow(c *gocheck.C) {
defer (func() {
if !c.Failed() {
c.Error("FailNow() didn't fail the test")
} else {
c.Succeed()
if c.GetTestLog() != "" {
c.Error("Something got logged:\n" + c.GetTestLog())
}
}
})()
c.FailNow()
c.Log("FailNow() didn't stop the test")
}
func (s *FoundationS) TestSucceedNow(c *gocheck.C) {
defer (func() {
if c.Failed() {
c.Error("SucceedNow() didn't succeed the test")
}
if c.GetTestLog() != "" {
c.Error("Something got logged:\n" + c.GetTestLog())
}
})()
c.Fail()
c.SucceedNow()
c.Log("SucceedNow() didn't stop the test")
}
func (s *FoundationS) TestFailureHeader(c *gocheck.C) {
output := String{}
failHelper := FailHelper{}
gocheck.Run(&failHelper, &gocheck.RunConf{Output: &output})
header := fmt.Sprintf(""+
"\n-----------------------------------"+
"-----------------------------------\n"+
"FAIL: gocheck_test.go:%d: FailHelper.TestLogAndFail\n",
failHelper.testLine)
if strings.Index(output.value, header) == -1 {
c.Errorf(""+
"Failure didn't print a proper header.\n"+
"... Got:\n%s... Expected something with:\n%s",
output.value, header)
}
}
func (s *FoundationS) TestFatal(c *gocheck.C) {
var line int
defer (func() {
if !c.Failed() {
c.Error("Fatal() didn't fail the test")
} else {
c.Succeed()
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Fatal(\"Die \", \"now!\")\n"+
"... Error: Die now!\n\n",
line)
if c.GetTestLog() != expected {
c.Error("Incorrect log:", c.GetTestLog())
}
}
})()
line = getMyLine() + 1
c.Fatal("Die ", "now!")
c.Log("Fatal() didn't stop the test")
}
func (s *FoundationS) TestFatalf(c *gocheck.C) {
var line int
defer (func() {
if !c.Failed() {
c.Error("Fatalf() didn't fail the test")
} else {
c.Succeed()
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Fatalf(\"Die %%s!\", \"now\")\n"+
"... Error: Die now!\n\n",
line)
if c.GetTestLog() != expected {
c.Error("Incorrect log:", c.GetTestLog())
}
}
})()
line = getMyLine() + 1
c.Fatalf("Die %s!", "now")
c.Log("Fatalf() didn't stop the test")
}
func (s *FoundationS) TestCallerLoggingInsideTest(c *gocheck.C) {
log := fmt.Sprintf(""+
"foundation_test.go:%d:\n"+
" result := c.Check\\(10, gocheck.Equals, 20\\)\n"+
"\\.\\.\\. obtained int = 10\n"+
"\\.\\.\\. expected int = 20\n\n",
getMyLine()+1)
result := c.Check(10, gocheck.Equals, 20)
checkState(c, result,
&expectedState{
name: "Check(10, Equals, 20)",
result: false,
failed: true,
log: log,
})
}
func (s *FoundationS) TestCallerLoggingInDifferentFile(c *gocheck.C) {
result, line := checkEqualWrapper(c, 10, 20)
testLine := getMyLine() - 1
log := fmt.Sprintf(""+
"foundation_test.go:%d:\n"+
" result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+
"gocheck_test.go:%d:\n"+
" return c.Check\\(obtained, gocheck.Equals, expected\\), getMyLine\\(\\)\n"+
"\\.\\.\\. obtained int = 10\n"+
"\\.\\.\\. expected int = 20\n\n",
testLine, line)
checkState(c, result,
&expectedState{
name: "Check(10, Equals, 20)",
result: false,
failed: true,
log: log,
})
}
// -----------------------------------------------------------------------
// ExpectFailure() inverts the logic of failure.
type ExpectFailureSucceedHelper struct{}
func (s *ExpectFailureSucceedHelper) TestSucceed(c *gocheck.C) {
c.ExpectFailure("It booms!")
c.Error("Boom!")
}
type ExpectFailureFailHelper struct{}
func (s *ExpectFailureFailHelper) TestFail(c *gocheck.C) {
c.ExpectFailure("Bug #XYZ")
}
func (s *FoundationS) TestExpectFailureFail(c *gocheck.C) {
helper := ExpectFailureFailHelper{}
output := String{}
result := gocheck.Run(&helper, &gocheck.RunConf{Output: &output})
expected := "" +
"^\n-+\n" +
"FAIL: foundation_test\\.go:[0-9]+:" +
" ExpectFailureFailHelper\\.TestFail\n\n" +
"\\.\\.\\. Error: Test succeeded, but was expected to fail\n" +
"\\.\\.\\. Reason: Bug #XYZ\n$"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("ExpectFailure() didn't log properly:\n", output.value)
}
c.Assert(result.ExpectedFailures, gocheck.Equals, 0)
}
func (s *FoundationS) TestExpectFailureSucceed(c *gocheck.C) {
helper := ExpectFailureSucceedHelper{}
output := String{}
result := gocheck.Run(&helper, &gocheck.RunConf{Output: &output})
c.Assert(output.value, gocheck.Equals, "")
c.Assert(result.ExpectedFailures, gocheck.Equals, 1)
}
func (s *FoundationS) TestExpectFailureSucceedVerbose(c *gocheck.C) {
helper := ExpectFailureSucceedHelper{}
output := String{}
result := gocheck.Run(&helper, &gocheck.RunConf{Output: &output, Verbose: true})
expected := "" +
"FAIL EXPECTED: foundation_test\\.go:[0-9]+:" +
" ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("ExpectFailure() didn't log properly:\n", output.value)
}
c.Assert(result.ExpectedFailures, gocheck.Equals, 1)
}
// -----------------------------------------------------------------------
// Skip() allows stopping a test without positive/negative results.
type SkipTestHelper struct{}
func (s *SkipTestHelper) TestFail(c *gocheck.C) {
c.Skip("Wrong platform or whatever")
c.Error("Boom!")
}
func (s *FoundationS) TestSkip(c *gocheck.C) {
helper := SkipTestHelper{}
output := String{}
gocheck.Run(&helper, &gocheck.RunConf{Output: &output})
if output.value != "" {
c.Error("Skip() logged something:\n", output.value)
}
}
func (s *FoundationS) TestSkipVerbose(c *gocheck.C) {
helper := SkipTestHelper{}
output := String{}
gocheck.Run(&helper, &gocheck.RunConf{Output: &output, Verbose: true})
expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" +
" \\(Wrong platform or whatever\\)"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("Skip() didn't log properly:\n", output.value)
}
}
// -----------------------------------------------------------------------
// Check minimum *log.Logger interface provided by *gocheck.C.
type minLogger interface {
Output(calldepth int, s string) error
}
func (s *BootstrapS) TestMinLogger(c *gocheck.C) {
var logger minLogger
logger = log.New(os.Stderr, "", 0)
logger = c
logger.Output(0, "Hello there")
expected := "\\[LOG\\] [.0-9]+ Hello there\n"
output := c.GetTestLog()
matched, err := regexp.MatchString(expected, output)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("Output() didn't log properly:\n", output)
}
}
// -----------------------------------------------------------------------
// Ensure that suites with embedded types are working fine, including the
// the workaround for issue 906.
type EmbeddedInternalS struct {
called bool
}
type EmbeddedS struct {
EmbeddedInternalS
}
var embeddedS = gocheck.Suite(&EmbeddedS{})
func (s *EmbeddedS) TestCountSuite(c *gocheck.C) {
suitesRun += 1
}
func (s *EmbeddedInternalS) TestMethod(c *gocheck.C) {
c.Error("TestMethod() of the embedded type was called!?")
}
func (s *EmbeddedS) TestMethod(c *gocheck.C) {
// http://code.google.com/p/go/issues/detail?id=906
c.Check(s.called, gocheck.Equals, false) // Go issue 906 is affecting the runner?
s.called = true
}
-915
View File
@@ -1,915 +0,0 @@
package gocheck
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"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 int
// 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
status funcStatus
logb *logger
logw io.Writer
done chan *C
reason string
mustFail bool
tempDir *tempDir
timer
}
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/gocheck-%d", os.TempDir(), 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 := path.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")
func (c *C) logPanic(skip int, value interface{}) {
skip += 1 // Our own frame.
initialSkip := skip
for {
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 name == "Value.call" && strings.HasSuffix(path, valueGo) {
break
}
c.logf("%s:%d\n in %s", nicePath(file), line, name)
} else {
break
}
skip += 1
}
}
func (c *C) logSoftPanic(issue string) {
c.log("... Panic: ", issue)
}
func (c *C) logArgPanic(method *methodType, expectedType string) {
c.logf("... Panic: %s argument should be %s",
niceFuncName(method.PC()), expectedType)
}
// -----------------------------------------------------------------------
// Some simple formatting helpers.
var initWD, initWDErr = os.Getwd()
func init() {
if initWDErr == nil {
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
}
}
func nicePath(path string) string {
if initWDErr == nil {
if strings.HasPrefix(path, initWD) {
return path[len(initWD):]
}
}
return path
}
func niceFuncPath(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
filename, line := function.FileLine(pc)
return fmt.Sprintf("%s:%d", nicePath(filename), line)
}
return "<unknown path>"
}
func niceFuncName(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
name := path.Base(function.Name())
if i := strings.Index(name, "."); i > 0 {
name = name[i+1:]
}
if strings.HasPrefix(name, "(*") {
if i := strings.Index(name, ")"); i > 0 {
name = name[2:i] + name[i+1:]
}
}
if i := strings.LastIndex(name, ".*"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
if i := strings.LastIndex(name, "·"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
return name
}
return "<unknown function>"
}
// -----------------------------------------------------------------------
// Result tracker to aggregate call results.
type Result struct {
Succeeded int
Failed int
Skipped int
Panicked int
FixturePanicked int
ExpectedFailures int
Missed int // Not even tried to run, related to a panic in the fixture.
RunError error // Houston, we've got a problem.
}
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
output *outputWriter
reportedProblemLast bool
benchTime time.Duration
}
type RunConf struct {
Output io.Writer
Stream bool
Verbose bool
Filter string
Benchmark bool
BenchmarkTime time.Duration // Defaults to 1 second
}
// 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,
}
runner.tests = make([]*methodType, 0, suiteNumMethods)
runner.tempDir = new(tempDir)
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()
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, 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,
logb: logb,
logw: logw,
tempDir: runner.tempDir,
done: make(chan *C, 1),
timer: timer{benchTime: runner.benchTime},
}
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, logb *logger, dispatcher func(c *C)) *C {
c := runner.forkCall(method, kind, logb, dispatcher)
<-c.done
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.status = skippedSt
} else {
c.logSoftPanic("Fixture has panicked (see related PANIC)")
c.status = fixturePanickedSt
}
default:
c.logPanic(1, value)
c.status = panickedSt
}
}
if c.mustFail {
switch c.status {
case failedSt:
c.status = succeededSt
case succeededSt:
c.status = 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, logb *logger) *C {
if method != nil {
c := runner.runFunc(method, fixtureKd, 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, logb *logger, skipped *bool) *C {
if skipped != nil && *skipped {
return nil
}
c := runner.runFixture(method, 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 {
return runner.forkCall(method, testKd, nil, func(c *C) {
var skipped bool
defer runner.runFixtureWithPanic(runner.tearDownTest, nil, &skipped)
defer c.StopTimer()
benchN := 1
for {
runner.runFixtureWithPanic(runner.setUpTest, 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.status = panickedSt
c.logArgPanic(c.method, "*gocheck.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, nil, nil)
skipped = false
}
})
}
// Same as forkTest(), but wait for the test to finish before returning.
func (runner *suiteRunner) runTest(method *methodType) *C {
c := runner.forkTest(method)
<-c.done
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.status = status
})
}
}
// Verify if the fixture arguments are *gocheck.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, "*gocheck.C")
c.status = 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)
}
}
// -----------------------------------------------------------------------
// 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)
}
-196
View File
@@ -1,196 +0,0 @@
// This file contains just a few generic helpers which are used by the
// other test files.
package gocheck_test
import (
"flag"
"fmt"
"launchpad.net/gocheck"
"os"
"regexp"
"runtime"
"testing"
"time"
)
// We count the number of suites run at least to get a vague hint that the
// test suite is behaving as it should. Otherwise a bug introduced at the
// very core of the system could go unperceived.
const suitesRunExpected = 8
var suitesRun int = 0
func Test(t *testing.T) {
gocheck.TestingT(t)
if suitesRun != suitesRunExpected && flag.Lookup("gocheck.f").Value.String() == "" {
critical(fmt.Sprintf("Expected %d suites to run rather than %d",
suitesRunExpected, suitesRun))
}
}
// -----------------------------------------------------------------------
// Helper functions.
// Break down badly. This is used in test cases which can't yet assume
// that the fundamental bits are working.
func critical(error string) {
fmt.Fprintln(os.Stderr, "CRITICAL: "+error)
os.Exit(1)
}
// Return the file line where it's called.
func getMyLine() int {
if _, _, line, ok := runtime.Caller(1); ok {
return line
}
return -1
}
// -----------------------------------------------------------------------
// Helper type implementing a basic io.Writer for testing output.
// Type implementing the io.Writer interface for analyzing output.
type String struct {
value string
}
// The only function required by the io.Writer interface. Will append
// written data to the String.value string.
func (s *String) Write(p []byte) (n int, err error) {
s.value += string(p)
return len(p), nil
}
// Trivial wrapper to test errors happening on a different file
// than the test itself.
func checkEqualWrapper(c *gocheck.C, obtained, expected interface{}) (result bool, line int) {
return c.Check(obtained, gocheck.Equals, expected), getMyLine()
}
// -----------------------------------------------------------------------
// Helper suite for testing basic fail behavior.
type FailHelper struct {
testLine int
}
func (s *FailHelper) TestLogAndFail(c *gocheck.C) {
s.testLine = getMyLine() - 1
c.Log("Expected failure!")
c.Fail()
}
// -----------------------------------------------------------------------
// Helper suite for testing basic success behavior.
type SuccessHelper struct{}
func (s *SuccessHelper) TestLogAndSucceed(c *gocheck.C) {
c.Log("Expected success!")
}
// -----------------------------------------------------------------------
// Helper suite for testing ordering and behavior of fixture.
type FixtureHelper struct {
calls []string
panicOn string
skip bool
skipOnN int
sleepOn string
sleep time.Duration
bytes int64
}
func (s *FixtureHelper) trace(name string, c *gocheck.C) {
s.calls = append(s.calls, name)
if name == s.panicOn {
panic(name)
}
if s.sleep > 0 && s.sleepOn == name {
time.Sleep(s.sleep)
}
if s.skip && s.skipOnN == len(s.calls)-1 {
c.Skip("skipOnN == n")
}
}
func (s *FixtureHelper) SetUpSuite(c *gocheck.C) {
s.trace("SetUpSuite", c)
}
func (s *FixtureHelper) TearDownSuite(c *gocheck.C) {
s.trace("TearDownSuite", c)
}
func (s *FixtureHelper) SetUpTest(c *gocheck.C) {
s.trace("SetUpTest", c)
}
func (s *FixtureHelper) TearDownTest(c *gocheck.C) {
s.trace("TearDownTest", c)
}
func (s *FixtureHelper) Test1(c *gocheck.C) {
s.trace("Test1", c)
}
func (s *FixtureHelper) Test2(c *gocheck.C) {
s.trace("Test2", c)
}
func (s *FixtureHelper) Benchmark1(c *gocheck.C) {
s.trace("Benchmark1", c)
for i := 0; i < c.N; i++ {
time.Sleep(s.sleep)
}
}
func (s *FixtureHelper) Benchmark2(c *gocheck.C) {
s.trace("Benchmark2", c)
c.SetBytes(1024)
for i := 0; i < c.N; i++ {
time.Sleep(s.sleep)
}
}
// -----------------------------------------------------------------------
// Helper which checks the state of the test and ensures that it matches
// the given expectations. Depends on c.Errorf() working, so shouldn't
// be used to test this one function.
type expectedState struct {
name string
result interface{}
failed bool
log string
}
// Verify the state of the test. Note that since this also verifies if
// the test is supposed to be in a failed state, no other checks should
// be done in addition to what is being tested.
func checkState(c *gocheck.C, result interface{}, expected *expectedState) {
failed := c.Failed()
c.Succeed()
log := c.GetTestLog()
matched, matchError := regexp.MatchString("^"+expected.log+"$", log)
if matchError != nil {
c.Errorf("Error in matching expression used in testing %s",
expected.name)
} else if !matched {
c.Errorf("%s logged:\n----------\n%s----------\n\nExpected:\n----------\n%s\n----------",
expected.name, log, expected.log)
}
if result != expected.result {
c.Errorf("%s returned %#v rather than %#v",
expected.name, result, expected.result)
}
if failed != expected.failed {
if failed {
c.Errorf("%s has failed when it shouldn't", expected.name)
} else {
c.Errorf("%s has not failed when it should", expected.name)
}
}
}
-221
View File
@@ -1,221 +0,0 @@
package gocheck
import (
"fmt"
"strings"
"time"
)
// -----------------------------------------------------------------------
// Basic succeeding/failing logic.
// Return true if the currently running test has already failed.
func (c *C) Failed() bool {
return c.status == failedSt
}
// Mark the currently running test as failed. Something ought to have been
// previously logged so that the developer knows what went wrong. The higher
// level helper functions will fail the test and do the logging properly.
func (c *C) Fail() {
c.status = failedSt
}
// Mark the currently running test as failed, and stop running the test.
// Something ought to have been previously logged so that the developer
// knows 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()
}
// Mark the currently running test as succeeded, undoing any previous
// failures.
func (c *C) Succeed() {
c.status = succeededSt
}
// Mark the currently running test as succeeded, undoing any previous
// failures, and stop running the test.
func (c *C) SucceedNow() {
c.Succeed()
c.stopNow()
}
// Expect the currently running test to fail, for the given reason. If the
// test does not fail, an error will be reported to raise the attention to
// this fact. The reason string is just a summary of why the given test is
// supposed to fail. 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 the running test, for the given reason. If used within SetUpTest,
// the individual test being set up will be skipped, and in SetUpSuite it
// will cause the whole suite to be skipped.
func (c *C) Skip(reason string) {
if reason == "" {
panic("Missing reason why the test is being skipped")
}
c.reason = reason
c.status = skippedSt
c.stopNow()
}
// -----------------------------------------------------------------------
// Basic logging.
// Return the current test error output.
func (c *C) GetTestLog() string {
return c.logb.String()
}
// Log some information into the test error output. The provided arguments
// will be assembled together into a string using fmt.Sprint().
func (c *C) Log(args ...interface{}) {
c.log(args...)
}
// Log some information into the test error output. The provided arguments
// will be assembled together into a string using 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 {
ns := time.Now().Sub(time.Time{}).Nanoseconds()
t := float64(ns%100e9) / 1e9
c.Logf("[LOG] %.05f %s", t, s)
return nil
}
// Log an error into the test error output, and mark the test as failed.
// The provided arguments will be assembled together into a string using
// fmt.Sprint().
func (c *C) Error(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.Fail()
}
// Log an error into the test error output, and mark the test as failed.
// The provided arguments will be assembled together into a string using
// fmt.Sprintf().
func (c *C) Errorf(format string, args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprintf("Error: "+format, args...))
c.logNewLine()
c.Fail()
}
// Log an error into the test error output, mark the test as failed, and
// stop the test execution. The provided arguments will be assembled
// together into a string using fmt.Sprint().
func (c *C) Fatal(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.FailNow()
}
// Log an error into the test error output, mark the test as failed, and
// stop the test execution. The provided arguments will be assembled
// together into a string using 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.
// Verify if the first value matches with the expected value. What
// matching means is defined by the provided checker. In case they do not
// match, an error will be logged, the test will be marked as failed, and
// the test execution will continue. Some checkers may not need the expected
// argument (e.g. IsNil). In either case, any extra arguments provided to
// the function will be logged next to the reported problem when the
// matching fails. This is a handy way to provide problem-specific hints.
func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool {
return c.internalCheck("Check", obtained, checker, args...)
}
// Ensure that the first value matches with the expected value. What
// matching means is defined by the provided checker. In case they do not
// match, an error will be logged, the test will be marked as failed, and
// the test execution will stop. Some checkers may not need the expected
// argument (e.g. IsNil). In either case, any extra arguments provided to
// the function will be logged next to the reported problem when the
// matching fails. This is a handy way to provide problem-specific hints.
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
}
-491
View File
@@ -1,491 +0,0 @@
// These tests verify the inner workings of the helper methods associated
// with gocheck.T.
package gocheck_test
import (
"launchpad.net/gocheck"
"os"
"reflect"
"runtime"
"sync"
)
var helpersS = gocheck.Suite(&HelpersS{})
type HelpersS struct{}
func (s *HelpersS) TestCountSuite(c *gocheck.C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Fake checker and bug info to verify the behavior of Assert() and Check().
type MyChecker struct {
info *gocheck.CheckerInfo
params []interface{}
names []string
result bool
error string
}
func (checker *MyChecker) Info() *gocheck.CheckerInfo {
if checker.info == nil {
return &gocheck.CheckerInfo{Name: "MyChecker", Params: []string{"myobtained", "myexpected"}}
}
return checker.info
}
func (checker *MyChecker) Check(params []interface{}, names []string) (bool, string) {
rparams := checker.params
rnames := checker.names
checker.params = append([]interface{}{}, params...)
checker.names = append([]string{}, names...)
if rparams != nil {
copy(params, rparams)
}
if rnames != nil {
copy(names, rnames)
}
return checker.result, checker.error
}
type myCommentType string
func (c myCommentType) CheckCommentString() string {
return string(c)
}
func myComment(s string) myCommentType {
return myCommentType(s)
}
// -----------------------------------------------------------------------
// Ensure a real checker actually works fine.
func (s *HelpersS) TestCheckerInterface(c *gocheck.C) {
testHelperSuccess(c, "Check(1, Equals, 1)", true, func() interface{} {
return c.Check(1, gocheck.Equals, 1)
})
}
// -----------------------------------------------------------------------
// Tests for Check(), mostly the same as for Assert() following these.
func (s *HelpersS) TestCheckSucceedWithExpected(c *gocheck.C) {
checker := &MyChecker{result: true}
testHelperSuccess(c, "Check(1, checker, 2)", true, func() interface{} {
return c.Check(1, checker, 2)
})
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestCheckSucceedWithoutExpected(c *gocheck.C) {
checker := &MyChecker{result: true, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
testHelperSuccess(c, "Check(1, checker)", true, func() interface{} {
return c.Check(1, checker)
})
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestCheckFailWithExpected(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
func (s *HelpersS) TestCheckFailWithExpectedAndComment(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2, myComment("Hello world!"))
})
}
func (s *HelpersS) TestCheckFailWithExpectedAndStaticComment(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" // Nice leading comment\\.\n" +
" return c\\.Check\\(1, checker, 2\\) // Hello there\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
func() interface{} {
// Nice leading comment.
return c.Check(1, checker, 2) // Hello there
})
}
func (s *HelpersS) TestCheckFailWithoutExpected(c *gocheck.C) {
checker := &MyChecker{result: false, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker\\)\n" +
"\\.+ myvalue int = 1\n\n"
testHelperFailure(c, "Check(1, checker)", false, false, log,
func() interface{} {
return c.Check(1, checker)
})
}
func (s *HelpersS) TestCheckFailWithoutExpectedAndMessage(c *gocheck.C) {
checker := &MyChecker{result: false, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myvalue int = 1\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Check(1, checker, msg)", false, false, log,
func() interface{} {
return c.Check(1, checker, myComment("Hello world!"))
})
}
func (s *HelpersS) TestCheckWithMissingExpected(c *gocheck.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker\\)\n" +
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 2\n\n"
testHelperFailure(c, "Check(1, checker, !?)", false, false, log,
func() interface{} {
return c.Check(1, checker)
})
}
func (s *HelpersS) TestCheckWithTooManyExpected(c *gocheck.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2, 3\\)\n" +
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 4\n\n"
testHelperFailure(c, "Check(1, checker, 2, 3)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2, 3)
})
}
func (s *HelpersS) TestCheckWithError(c *gocheck.C) {
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Some not so cool data provided!\n\n"
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
func (s *HelpersS) TestCheckWithNilChecker(c *gocheck.C) {
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, nil\\)\n" +
"\\.+ Check\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
testHelperFailure(c, "Check(obtained, nil)", false, false, log,
func() interface{} {
return c.Check(1, nil)
})
}
func (s *HelpersS) TestCheckWithParamsAndNamesMutation(c *gocheck.C) {
checker := &MyChecker{result: false, params: []interface{}{3, 4}, names: []string{"newobtained", "newexpected"}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ newobtained int = 3\n" +
"\\.+ newexpected int = 4\n\n"
testHelperFailure(c, "Check(1, checker, 2) with mutation", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
// -----------------------------------------------------------------------
// Tests for Assert(), mostly the same as for Check() above.
func (s *HelpersS) TestAssertSucceedWithExpected(c *gocheck.C) {
checker := &MyChecker{result: true}
testHelperSuccess(c, "Assert(1, checker, 2)", nil, func() interface{} {
c.Assert(1, checker, 2)
return nil
})
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestAssertSucceedWithoutExpected(c *gocheck.C) {
checker := &MyChecker{result: true, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
testHelperSuccess(c, "Assert(1, checker)", nil, func() interface{} {
c.Assert(1, checker)
return nil
})
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestAssertFailWithExpected(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2)
return nil
})
}
func (s *HelpersS) TestAssertFailWithExpectedAndMessage(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Assert(1, checker, 2, msg)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2, myComment("Hello world!"))
return nil
})
}
func (s *HelpersS) TestAssertFailWithoutExpected(c *gocheck.C) {
checker := &MyChecker{result: false, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker\\)\n" +
"\\.+ myvalue int = 1\n\n"
testHelperFailure(c, "Assert(1, checker)", nil, true, log,
func() interface{} {
c.Assert(1, checker)
return nil
})
}
func (s *HelpersS) TestAssertFailWithoutExpectedAndMessage(c *gocheck.C) {
checker := &MyChecker{result: false, info: &gocheck.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myvalue int = 1\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Assert(1, checker, msg)", nil, true, log,
func() interface{} {
c.Assert(1, checker, myComment("Hello world!"))
return nil
})
}
func (s *HelpersS) TestAssertWithMissingExpected(c *gocheck.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker\\)\n" +
"\\.+ Assert\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 2\n\n"
testHelperFailure(c, "Assert(1, checker, !?)", nil, true, log,
func() interface{} {
c.Assert(1, checker)
return nil
})
}
func (s *HelpersS) TestAssertWithError(c *gocheck.C) {
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Some not so cool data provided!\n\n"
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2)
return nil
})
}
func (s *HelpersS) TestAssertWithNilChecker(c *gocheck.C) {
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, nil\\)\n" +
"\\.+ Assert\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
testHelperFailure(c, "Assert(obtained, nil)", nil, true, log,
func() interface{} {
c.Assert(1, nil)
return nil
})
}
// -----------------------------------------------------------------------
// Ensure that values logged work properly in some interesting cases.
func (s *HelpersS) TestValueLoggingWithArrays(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\\[\\]byte{1, 2}, checker, \\[\\]byte{1, 3}\\)\n" +
"\\.+ myobtained \\[\\]uint8 = \\[\\]byte{0x1, 0x2}\n" +
"\\.+ myexpected \\[\\]uint8 = \\[\\]byte{0x1, 0x3}\n\n"
testHelperFailure(c, "Check([]byte{1}, chk, []byte{3})", false, false, log,
func() interface{} {
return c.Check([]byte{1, 2}, checker, []byte{1, 3})
})
}
func (s *HelpersS) TestValueLoggingWithMultiLine(c *gocheck.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\"a\\\\nb\\\\n\", checker, \"a\\\\nb\\\\nc\"\\)\n" +
"\\.+ myobtained string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\\\\n\"\n" +
"\\.+ myexpected string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\\\\n\" \\+\n" +
"\\.+ \"c\"\n\n"
testHelperFailure(c, `Check("a\nb\n", chk, "a\nb\nc")`, false, false, log,
func() interface{} {
return c.Check("a\nb\n", checker, "a\nb\nc")
})
}
func (s *HelpersS) TestValueLoggingWithMultiLineException(c *gocheck.C) {
// If the newline is at the end of the string, don't log as multi-line.
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\"a b\\\\n\", checker, \"a\\\\nb\"\\)\n" +
"\\.+ myobtained string = \"a b\\\\n\"\n" +
"\\.+ myexpected string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\"\n\n"
testHelperFailure(c, `Check("a b\n", chk, "a\nb")`, false, false, log,
func() interface{} {
return c.Check("a b\n", checker, "a\nb")
})
}
// -----------------------------------------------------------------------
// MakeDir() tests.
type MkDirHelper struct {
path1 string
path2 string
isDir1 bool
isDir2 bool
isDir3 bool
isDir4 bool
}
func (s *MkDirHelper) SetUpSuite(c *gocheck.C) {
s.path1 = c.MkDir()
s.isDir1 = isDir(s.path1)
}
func (s *MkDirHelper) Test(c *gocheck.C) {
s.path2 = c.MkDir()
s.isDir2 = isDir(s.path2)
}
func (s *MkDirHelper) TearDownSuite(c *gocheck.C) {
s.isDir3 = isDir(s.path1)
s.isDir4 = isDir(s.path2)
}
func (s *HelpersS) TestMkDir(c *gocheck.C) {
helper := MkDirHelper{}
output := String{}
gocheck.Run(&helper, &gocheck.RunConf{Output: &output})
c.Assert(output.value, gocheck.Equals, "")
c.Check(helper.isDir1, gocheck.Equals, true)
c.Check(helper.isDir2, gocheck.Equals, true)
c.Check(helper.isDir3, gocheck.Equals, true)
c.Check(helper.isDir4, gocheck.Equals, true)
c.Check(helper.path1, gocheck.Not(gocheck.Equals),
helper.path2)
c.Check(isDir(helper.path1), gocheck.Equals, false)
c.Check(isDir(helper.path2), gocheck.Equals, false)
}
func isDir(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.IsDir()
}
return false
}
// Concurrent logging should not corrupt the underling buffer.
// Use go test -race to detect the race in this test.
func (s *HelpersS) TestConcurrentLogging(c *gocheck.C) {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
var start, stop sync.WaitGroup
start.Add(1)
for i, n := 0, runtime.NumCPU()*2; i < n; i++ {
stop.Add(1)
go func(i int) {
start.Wait()
for j := 0; j < 30; j++ {
c.Logf("Worker %d: line %d", i, j)
}
stop.Done()
}(i)
}
start.Done()
stop.Wait()
}
// -----------------------------------------------------------------------
// A couple of helper functions to test helper functions. :-)
func testHelperSuccess(c *gocheck.C, name string, expectedResult interface{}, closure func() interface{}) {
var result interface{}
defer (func() {
if err := recover(); err != nil {
panic(err)
}
checkState(c, result,
&expectedState{
name: name,
result: expectedResult,
failed: false,
log: "",
})
})()
result = closure()
}
func testHelperFailure(c *gocheck.C, name string, expectedResult interface{}, shouldStop bool, log string, closure func() interface{}) {
var result interface{}
defer (func() {
if err := recover(); err != nil {
panic(err)
}
checkState(c, result,
&expectedState{
name: name,
result: expectedResult,
failed: true,
log: log,
})
})()
result = closure()
if shouldStop {
c.Logf("%s didn't stop when it should", name)
}
}
-168
View File
@@ -1,168 +0,0 @@
package gocheck
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}
}
-109
View File
@@ -1,109 +0,0 @@
package gocheck_test
import (
. "launchpad.net/gocheck"
)
var _ = Suite(&PrinterS{})
type PrinterS struct{}
func (s *PrinterS) TestCountSuite(c *C) {
suitesRun += 1
}
var printTestFuncLine int
func init() {
printTestFuncLine = getMyLine() + 3
}
func printTestFunc() {
println(1) // Comment1
if 2 == 2 { // Comment2
println(3) // Comment3
}
switch 5 {
case 6:
println(6) // Comment6
println(7)
}
switch interface{}(9).(type) { // Comment9
case int:
println(10)
println(11)
}
select {
case <-(chan bool)(nil):
println(14)
println(15)
default:
println(16)
println(17)
}
println(19,
20)
_ = func() {
println(21)
println(22)
}
println(24, func() {
println(25)
})
// Leading comment
// with multiple lines.
println(29) // Comment29
}
var printLineTests = []struct {
line int
output string
}{
{1, "println(1) // Comment1"},
{2, "if 2 == 2 { // Comment2\n ...\n}"},
{3, "println(3) // Comment3"},
{5, "switch 5 {\n...\n}"},
{6, "case 6:\n println(6) // Comment6\n ..."},
{7, "println(7)"},
{9, "switch interface{}(9).(type) { // Comment9\n...\n}"},
{10, "case int:\n println(10)\n ..."},
{14, "case <-(chan bool)(nil):\n println(14)\n ..."},
{15, "println(15)"},
{16, "default:\n println(16)\n ..."},
{17, "println(17)"},
{19, "println(19,\n 20)"},
{20, "println(19,\n 20)"},
{21, "_ = func() {\n println(21)\n println(22)\n}"},
{22, "println(22)"},
{24, "println(24, func() {\n println(25)\n})"},
{25, "println(25)"},
{26, "println(24, func() {\n println(25)\n})"},
{29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"},
}
func (s *PrinterS) TestPrintLine(c *C) {
for _, test := range printLineTests {
output, err := PrintLine("printer_test.go", printTestFuncLine+test.line)
c.Assert(err, IsNil)
c.Assert(output, Equals, test.output)
}
}
var indentTests = []struct {
in, out string
}{
{"", ""},
{"\n", "\n"},
{"a", ">>>a"},
{"a\n", ">>>a\n"},
{"a\nb", ">>>a\n>>>b"},
{" ", ">>> "},
}
func (s *PrinterS) TestIndent(c *C) {
for _, test := range indentTests {
out := Indent(test.in, ">>>")
c.Assert(out, Equals, test.out)
}
}
-152
View File
@@ -1,152 +0,0 @@
package gocheck
import (
"bufio"
"flag"
"fmt"
"os"
"testing"
"time"
)
// -----------------------------------------------------------------------
// Test suite registry.
var allSuites []interface{}
// Register 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 to
// be run.
func Suite(suite interface{}) interface{} {
allSuites = append(allSuites, suite)
return suite
}
// -----------------------------------------------------------------------
// Public running interface.
var (
filterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run")
verboseFlag = flag.Bool("gocheck.v", false, "Verbose mode")
streamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)")
benchFlag = flag.Bool("gocheck.b", false, "Run benchmarks")
benchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark")
listFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run")
)
// Run all test suites registered with the Suite() function, printing
// results to stdout, and reporting any failures back to the 'testing'
// module.
func TestingT(testingT *testing.T) {
conf := &RunConf{
Filter: *filterFlag,
Verbose: *verboseFlag,
Stream: *streamFlag,
Benchmark: *benchFlag,
BenchmarkTime: *benchTime,
}
if *listFlag {
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
// given run configuration.
func RunAll(runConf *RunConf) *Result {
result := Result{}
for _, suite := range allSuites {
result.Add(Run(suite, runConf))
}
return &result
}
// Run runs the given 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 prints the names of the test functions in the given
// suite that will be run with the provided run configuration
// to the given Writer.
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
}
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)
}
return value
}
-397
View File
@@ -1,397 +0,0 @@
// These tests verify the test running logic.
package gocheck_test
import (
"errors"
. "launchpad.net/gocheck"
"sync"
)
var runnerS = Suite(&RunS{})
type RunS struct{}
func (s *RunS) TestCountSuite(c *C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Tests ensuring result counting works properly.
func (s *RunS) TestSuccess(c *C) {
output := String{}
result := Run(&SuccessHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 1)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestFailure(c *C) {
output := String{}
result := Run(&FailHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 1)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestFixture(c *C) {
output := String{}
result := Run(&FixtureHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 2)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnTest(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "Test1"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 1)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 1)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnSetUpTest(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "SetUpTest"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 1)
c.Check(result.Missed, Equals, 2)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnSetUpSuite(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "SetUpSuite"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 1)
c.Check(result.Missed, Equals, 2)
c.Check(result.RunError, IsNil)
}
// -----------------------------------------------------------------------
// Check result aggregation.
func (s *RunS) TestAdd(c *C) {
result := &Result{
Succeeded: 1,
Skipped: 2,
Failed: 3,
Panicked: 4,
FixturePanicked: 5,
Missed: 6,
ExpectedFailures: 7,
}
result.Add(&Result{
Succeeded: 10,
Skipped: 20,
Failed: 30,
Panicked: 40,
FixturePanicked: 50,
Missed: 60,
ExpectedFailures: 70,
})
c.Check(result.Succeeded, Equals, 11)
c.Check(result.Skipped, Equals, 22)
c.Check(result.Failed, Equals, 33)
c.Check(result.Panicked, Equals, 44)
c.Check(result.FixturePanicked, Equals, 55)
c.Check(result.Missed, Equals, 66)
c.Check(result.ExpectedFailures, Equals, 77)
c.Check(result.RunError, IsNil)
}
// -----------------------------------------------------------------------
// Check the Passed() method.
func (s *RunS) TestPassed(c *C) {
c.Assert((&Result{}).Passed(), Equals, true)
c.Assert((&Result{Succeeded: 1}).Passed(), Equals, true)
c.Assert((&Result{Skipped: 1}).Passed(), Equals, true)
c.Assert((&Result{Failed: 1}).Passed(), Equals, false)
c.Assert((&Result{Panicked: 1}).Passed(), Equals, false)
c.Assert((&Result{FixturePanicked: 1}).Passed(), Equals, false)
c.Assert((&Result{Missed: 1}).Passed(), Equals, false)
c.Assert((&Result{RunError: errors.New("!")}).Passed(), Equals, false)
}
// -----------------------------------------------------------------------
// Check that result printing is working correctly.
func (s *RunS) TestPrintSuccess(c *C) {
result := &Result{Succeeded: 5}
c.Check(result.String(), Equals, "OK: 5 passed")
}
func (s *RunS) TestPrintFailure(c *C) {
result := &Result{Failed: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FAILED")
}
func (s *RunS) TestPrintSkipped(c *C) {
result := &Result{Skipped: 5}
c.Check(result.String(), Equals, "OK: 0 passed, 5 skipped")
}
func (s *RunS) TestPrintExpectedFailures(c *C) {
result := &Result{ExpectedFailures: 5}
c.Check(result.String(), Equals, "OK: 0 passed, 5 expected failures")
}
func (s *RunS) TestPrintPanicked(c *C) {
result := &Result{Panicked: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 PANICKED")
}
func (s *RunS) TestPrintFixturePanicked(c *C) {
result := &Result{FixturePanicked: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FIXTURE-PANICKED")
}
func (s *RunS) TestPrintMissed(c *C) {
result := &Result{Missed: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 MISSED")
}
func (s *RunS) TestPrintAll(c *C) {
result := &Result{Succeeded: 1, Skipped: 2, ExpectedFailures: 3,
Panicked: 4, FixturePanicked: 5, Missed: 6}
c.Check(result.String(), Equals,
"OOPS: 1 passed, 2 skipped, 3 expected failures, 4 PANICKED, "+
"5 FIXTURE-PANICKED, 6 MISSED")
}
func (s *RunS) TestPrintRunError(c *C) {
result := &Result{Succeeded: 1, Failed: 1,
RunError: errors.New("Kaboom!")}
c.Check(result.String(), Equals, "ERROR: Kaboom!")
}
// -----------------------------------------------------------------------
// Verify that the method pattern flag works correctly.
func (s *RunS) TestFilterTestName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "Test[91]"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
}
func (s *RunS) TestFilterTestNameWithAll(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: ".*"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterSuiteName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "FixtureHelper"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterSuiteNameAndTestName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "FixtureHelper\\.Test2"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test2")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
}
func (s *RunS) TestFilterAllOut(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "NotFound"}
Run(&helper, &runConf)
c.Check(len(helper.calls), Equals, 0)
}
func (s *RunS) TestRequirePartialMatch(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "est"}
Run(&helper, &runConf)
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterError(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "]["}
result := Run(&helper, &runConf)
c.Check(result.String(), Equals,
"ERROR: Bad filter expression: error parsing regexp: missing closing ]: `[`")
c.Check(len(helper.calls), Equals, 0)
}
// -----------------------------------------------------------------------
// Verify that List works correctly.
func (s *RunS) TestListFiltered(c *C) {
names := List(&FixtureHelper{}, &RunConf{Filter: "1"})
c.Assert(names, DeepEquals, []string{
"FixtureHelper.Test1",
})
}
func (s *RunS) TestList(c *C) {
names := List(&FixtureHelper{}, &RunConf{})
c.Assert(names, DeepEquals, []string{
"FixtureHelper.Test1",
"FixtureHelper.Test2",
})
}
// -----------------------------------------------------------------------
// Verify that verbose mode prints tests which pass as well.
func (s *RunS) TestVerboseMode(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Test1\t *[.0-9]+s\n" +
"PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
c.Assert(output.value, Matches, expected)
}
func (s *RunS) TestVerboseModeWithFailBeforePass(c *C) {
helper := FixtureHelper{panicOn: "Test1"}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "(?s).*PANIC.*\n-+\n" + // Should have an extra line.
"PASS: gocheck_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
c.Assert(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Verify the stream output mode. In this mode there's no output caching.
type StreamHelper struct {
l2 sync.Mutex
l3 sync.Mutex
}
func (s *StreamHelper) SetUpSuite(c *C) {
c.Log("0")
}
func (s *StreamHelper) Test1(c *C) {
c.Log("1")
s.l2.Lock()
s.l3.Lock()
go func() {
s.l2.Lock() // Wait for "2".
c.Log("3")
s.l3.Unlock()
}()
}
func (s *StreamHelper) Test2(c *C) {
c.Log("2")
s.l2.Unlock()
s.l3.Lock() // Wait for "3".
c.Fail()
c.Log("4")
}
func (s *RunS) TestStreamMode(c *C) {
helper := &StreamHelper{}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(helper, &runConf)
expected := "START: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\n0\n" +
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\t *[.0-9]+s\n\n" +
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test1\n1\n" +
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.Test1\t *[.0-9]+s\n\n" +
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n2\n3\n4\n" +
"FAIL: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n\n"
c.Assert(output.value, Matches, expected)
}
type StreamMissHelper struct{}
func (s *StreamMissHelper) SetUpSuite(c *C) {
c.Log("0")
c.Fail()
}
func (s *StreamMissHelper) Test1(c *C) {
c.Log("1")
}
func (s *RunS) TestStreamModeWithMiss(c *C) {
helper := &StreamMissHelper{}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(helper, &runConf)
expected := "START: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n0\n" +
"FAIL: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n\n" +
"START: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n" +
"MISS: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n\n"
c.Assert(output.value, Matches, expected)
}
+505 -122
View File
@@ -1,11 +1,16 @@
package sftp
import (
"bytes"
"encoding"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"path"
"sync"
"sync/atomic"
"time"
"github.com/kr/fs"
@@ -13,8 +18,19 @@ import (
"golang.org/x/crypto/ssh"
)
// MaxPacket sets the maximum size of the payload.
func MaxPacket(size int) func(*Client) error {
return func(c *Client) error {
if size < 1<<15 {
return fmt.Errorf("size must be greater or equal to 32k")
}
c.maxPacket = size
return nil
}
}
// New creates a new SFTP client on conn.
func NewClient(conn *ssh.Client) (*Client, error) {
func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
s, err := conn.NewSession()
if err != nil {
return nil, err
@@ -31,21 +47,34 @@ func NewClient(conn *ssh.Client) (*Client, error) {
return nil, err
}
return NewClientPipe(pr, pw)
return NewClientPipe(pr, pw, opts...)
}
// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.
// This can be used for connecting to an SFTP server over TCP/TLS or by using
// the system's ssh client program (e.g. via exec.Command).
func NewClientPipe(rd io.Reader, wr io.WriteCloser) (*Client, error) {
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error) (*Client, error) {
sftp := &Client{
w: wr,
r: rd,
w: wr,
r: rd,
maxPacket: 1 << 15,
inflight: make(map[uint32]chan<- result),
recvClosed: make(chan struct{}),
}
if err := sftp.sendInit(); err != nil {
if err := sftp.applyOptions(opts...); err != nil {
wr.Close()
return nil, err
}
return sftp, sftp.recvVersion()
if err := sftp.sendInit(); err != nil {
wr.Close()
return nil, err
}
if err := sftp.recvVersion(); err != nil {
wr.Close()
return nil, err
}
go sftp.recv()
return sftp, nil
}
// Client represents an SFTP session on a *ssh.ClientConn SSH connection.
@@ -54,14 +83,23 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser) (*Client, error) {
//
// Client implements the github.com/kr/fs.FileSystem interface.
type Client struct {
w io.WriteCloser
r io.Reader
mu sync.Mutex // locks mu and seralises commands to the server
nextid uint32
w io.WriteCloser
r io.Reader
maxPacket int // max packet size read or written.
nextid uint32
mu sync.Mutex // ensures only on request is in flight to the server at once
inflight map[uint32]chan<- result // outstanding requests
recvClosed chan struct{} // remote end has closed the connection
}
// Close closes the SFTP session.
func (c *Client) Close() error { return c.w.Close() }
func (c *Client) Close() error {
err := c.w.Close()
<-c.recvClosed
return err
}
// Create creates the named file mode 0666 (before umask), truncating it if
// it already exists. If successful, methods on the returned File can be
@@ -78,12 +116,9 @@ func (c *Client) sendInit() error {
})
}
// returns the current value of c.nextid and increments it
// callers is expected to hold c.mu
// returns the next value of c.nextid
func (c *Client) nextId() uint32 {
v := c.nextid
c.nextid++
return v
return atomic.AddUint32(&c.nextid, 1)
}
func (c *Client) recvVersion() error {
@@ -103,6 +138,46 @@ func (c *Client) recvVersion() error {
return nil
}
// broadcastErr sends an error to all goroutines waiting for a response.
func (c *Client) broadcastErr(err error) {
c.mu.Lock()
listeners := make([]chan<- result, 0, len(c.inflight))
for _, ch := range c.inflight {
listeners = append(listeners, ch)
}
c.mu.Unlock()
for _, ch := range listeners {
ch <- result{err: err}
}
}
// recv continuously reads from the server and forwards responses to the
// appropriate channel.
func (c *Client) recv() {
defer close(c.recvClosed)
for {
typ, data, err := recvPacket(c.r)
if err != nil {
// Return the error to all listeners.
c.broadcastErr(err)
return
}
sid, _ := unmarshalUint32(data)
c.mu.Lock()
ch, ok := c.inflight[sid]
delete(c.inflight, sid)
c.mu.Unlock()
if !ok {
// This is an unexpected occurrence. Send the error
// back to all listeners so that they terminate
// gracefully.
c.broadcastErr(fmt.Errorf("sid: %v not fond", sid))
return
}
ch <- result{typ: typ, data: data}
}
}
// Walk returns a new Walker rooted at root.
func (c *Client) Walk(root string) *fs.Walker {
return fs.WalkFS(root, c)
@@ -117,8 +192,6 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
}
defer c.close(handle) // this has to defer earlier than the lock below
var attrs []os.FileInfo
c.mu.Lock()
defer c.mu.Unlock()
var done = false
for !done {
id := c.nextId()
@@ -163,8 +236,6 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
return attrs, err
}
func (c *Client) opendir(path string) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpOpendirPacket{
Id: id,
@@ -189,8 +260,6 @@ func (c *Client) opendir(path string) (string, error) {
}
func (c *Client) Lstat(p string) (os.FileInfo, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpLstatPacket{
Id: id,
@@ -216,8 +285,6 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
// ReadLink reads the target of a symbolic link.
func (c *Client) ReadLink(p string) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpReadlinkPacket{
Id: id,
@@ -247,8 +314,6 @@ func (c *Client) ReadLink(p string) (string, error) {
// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpSetstatPacket{
Id: id,
@@ -315,8 +380,6 @@ func (c *Client) OpenFile(path string, f int) (*File, error) {
}
func (c *Client) open(path string, pflags uint32) (*File, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpOpenPacket{
Id: id,
@@ -341,43 +404,10 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
}
}
// readAt reads len(buf) bytes from the remote file indicated by handle starting
// from offset.
func (c *Client) readAt(handle string, offset uint64, buf []byte) (uint32, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpReadPacket{
Id: id,
Handle: handle,
Offset: offset,
Len: uint32(len(buf)),
})
if err != nil {
return 0, err
}
switch typ {
case ssh_FXP_DATA:
sid, data := unmarshalUint32(data)
if sid != id {
return 0, &unexpectedIdErr{id, sid}
}
l, data := unmarshalUint32(data)
n := copy(buf, data[:l])
return uint32(n), nil
case ssh_FXP_STATUS:
return 0, eofOrErr(unmarshalStatus(id, data))
default:
return 0, unimplementedPacketErr(typ)
}
}
// close closes a handle handle previously returned in the response
// to SSH_FXP_OPEN or SSH_FXP_OPENDIR. The handle becomes invalid
// immediately after this request has been sent.
func (c *Client) close(handle string) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpClosePacket{
Id: id,
@@ -395,8 +425,6 @@ func (c *Client) close(handle string) error {
}
func (c *Client) fstat(handle string) (*FileStat, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpFstatPacket{
Id: id,
@@ -420,6 +448,40 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
}
}
// Get vfs stats from remote host.
// Implementing statvfs@openssh.com SSH_FXP_EXTENDED feature
// from http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/PROTOCOL?txt
func (c *Client) StatVFS(path string) (*StatVFS, error) {
// send the StatVFS packet to the server
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpStatvfsPacket{
Id: id,
Path: path,
})
if err != nil {
return nil, err
}
switch typ {
// server responded with valid data
case ssh_FXP_EXTENDED_REPLY:
var response StatVFS
err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
if err != nil {
return nil, errors.New("can not parse reply")
}
return &response, nil
// the resquest failed
case ssh_FXP_STATUS:
return nil, errors.New(fxp(ssh_FXP_STATUS).String())
default:
return nil, unimplementedPacketErr(typ)
}
}
// Join joins any number of path elements into a single path, adding a
// separating slash if necessary. The result is Cleaned; in particular, all
// empty strings are ignored.
@@ -437,8 +499,6 @@ func (c *Client) Remove(path string) error {
}
func (c *Client) removeFile(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpRemovePacket{
Id: id,
@@ -456,8 +516,6 @@ func (c *Client) removeFile(path string) error {
}
func (c *Client) removeDirectory(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpRmdirPacket{
Id: id,
@@ -476,8 +534,6 @@ func (c *Client) removeDirectory(path string) error {
// Rename renames a file.
func (c *Client) Rename(oldname, newname string) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpRenamePacket{
Id: id,
@@ -495,46 +551,41 @@ func (c *Client) Rename(oldname, newname string) error {
}
}
func (c *Client) sendRequest(p encoding.BinaryMarshaler) (byte, []byte, error) {
if err := sendPacket(c.w, p); err != nil {
return 0, nil, err
}
return recvPacket(c.r)
// result captures the result of receiving the a packet from the server
type result struct {
typ byte
data []byte
err error
}
// writeAt writes len(buf) bytes from the remote file indicated by handle starting
// from offset.
func (c *Client) writeAt(handle string, offset uint64, buf []byte) (uint32, error) {
type idmarshaler interface {
id() uint32
encoding.BinaryMarshaler
}
func (c *Client) sendRequest(p idmarshaler) (byte, []byte, error) {
ch := make(chan result, 1)
c.dispatchRequest(ch, p)
s := <-ch
return s.typ, s.data, s.err
}
func (c *Client) dispatchRequest(ch chan<- result, p idmarshaler) {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpWritePacket{
Id: id,
Handle: handle,
Offset: offset,
Length: uint32(len(buf)),
Data: buf,
})
if err != nil {
return 0, err
}
switch typ {
case ssh_FXP_STATUS:
if err := okOrErr(unmarshalStatus(id, data)); err != nil {
return 0, err
}
return uint32(len(buf)), nil
default:
return 0, unimplementedPacketErr(typ)
c.inflight[p.id()] = ch
if err := sendPacket(c.w, p); err != nil {
delete(c.inflight, p.id())
c.mu.Unlock()
ch <- result{err: err}
return
}
c.mu.Unlock()
}
// Creates the specified directory. An error will be returned if a file or
// directory with the specified path already exists, or if the directory's
// parent folder does not exist (the method cannot create complete paths).
func (c *Client) Mkdir(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpMkdirPacket{
Id: id,
@@ -551,6 +602,17 @@ func (c *Client) Mkdir(path string) error {
}
}
// applyOptions applies options functions to the Client.
// If an error is encountered, option processing ceases.
func (c *Client) applyOptions(opts ...func(*Client) error) error {
for _, f := range opts {
if err := f(c); err != nil {
return err
}
}
return nil
}
// File represents a remote file.
type File struct {
c *Client
@@ -565,21 +627,226 @@ func (f *File) Close() error {
return f.c.close(f.handle)
}
const maxConcurrentRequests = 64
// Read reads up to len(b) bytes from the File. It returns the number of
// bytes read and an error, if any. EOF is signaled by a zero count with
// err set to io.EOF.
func (f *File) Read(b []byte) (int, error) {
var read int
for len(b) > 0 {
n, err := f.c.readAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
f.offset += uint64(n)
read += int(n)
if err != nil {
return read, err
}
b = b[n:]
// Split the read into multiple maxPacket sized concurrent reads
// bounded by maxConcurrentRequests. This allows reads with a suitably
// large buffer to transfer data at a much faster rate due to
// overlapping round trip times.
inFlight := 0
desiredInFlight := 1
offset := f.offset
ch := make(chan result)
type inflightRead struct {
b []byte
offset uint64
}
return read, nil
reqs := map[uint32]inflightRead{}
type offsetErr struct {
offset uint64
err error
}
var firstErr offsetErr
sendReq := func(b []byte, offset uint64) {
reqId := f.c.nextId()
f.c.dispatchRequest(ch, sshFxpReadPacket{
Id: reqId,
Handle: f.handle,
Offset: offset,
Len: uint32(len(b)),
})
inFlight++
reqs[reqId] = inflightRead{b: b, offset: offset}
}
var read int
for len(b) > 0 || inFlight > 0 {
for inFlight < desiredInFlight && len(b) > 0 && firstErr.err == nil {
l := min(len(b), f.c.maxPacket)
rb := b[:l]
sendReq(rb, offset)
offset += uint64(l)
b = b[l:]
}
if inFlight == 0 {
break
}
select {
case res := <-ch:
inFlight--
if res.err != nil {
firstErr = offsetErr{offset: 0, err: res.err}
break
}
reqId, data := unmarshalUint32(res.data)
req, ok := reqs[reqId]
if !ok {
firstErr = offsetErr{offset: 0, err: fmt.Errorf("sid: %v not found", reqId)}
break
}
delete(reqs, reqId)
switch res.typ {
case ssh_FXP_STATUS:
if firstErr.err == nil || req.offset < firstErr.offset {
firstErr = offsetErr{offset: req.offset, err: eofOrErr(unmarshalStatus(reqId, res.data))}
break
}
case ssh_FXP_DATA:
l, data := unmarshalUint32(data)
n := copy(req.b, data[:l])
read += n
if n < len(req.b) {
sendReq(req.b[l:], req.offset+uint64(l))
}
if desiredInFlight < maxConcurrentRequests {
desiredInFlight++
}
default:
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
break
}
}
}
// If the error is anything other than EOF, then there
// may be gaps in the data copied to the buffer so it's
// best to return 0 so the caller can't make any
// incorrect assumptions about the state of the buffer.
if firstErr.err != nil && firstErr.err != io.EOF {
read = 0
}
f.offset += uint64(read)
return read, firstErr.err
}
// WriteTo writes the file to w. The return value is the number of bytes
// written. Any error encountered during the write is also returned.
func (f *File) WriteTo(w io.Writer) (int64, error) {
fi, err := f.Stat()
if err != nil {
return 0, err
}
inFlight := 0
desiredInFlight := 1
offset := f.offset
writeOffset := offset
fileSize := uint64(fi.Size())
ch := make(chan result)
type inflightRead struct {
b []byte
offset uint64
}
reqs := map[uint32]inflightRead{}
pendingWrites := map[uint64][]byte{}
type offsetErr struct {
offset uint64
err error
}
var firstErr offsetErr
sendReq := func(b []byte, offset uint64) {
reqId := f.c.nextId()
f.c.dispatchRequest(ch, sshFxpReadPacket{
Id: reqId,
Handle: f.handle,
Offset: offset,
Len: uint32(len(b)),
})
inFlight++
reqs[reqId] = inflightRead{b: b, offset: offset}
}
var copied int64
for firstErr.err == nil || inFlight > 0 {
for inFlight < desiredInFlight && firstErr.err == nil {
b := make([]byte, f.c.maxPacket)
sendReq(b, offset)
offset += uint64(f.c.maxPacket)
if offset > fileSize {
desiredInFlight = 1
}
}
if inFlight == 0 {
break
}
select {
case res := <-ch:
inFlight--
if res.err != nil {
firstErr = offsetErr{offset: 0, err: res.err}
break
}
reqId, data := unmarshalUint32(res.data)
req, ok := reqs[reqId]
if !ok {
firstErr = offsetErr{offset: 0, err: fmt.Errorf("sid: %v not found", reqId)}
break
}
delete(reqs, reqId)
switch res.typ {
case ssh_FXP_STATUS:
if firstErr.err == nil || req.offset < firstErr.offset {
firstErr = offsetErr{offset: req.offset, err: eofOrErr(unmarshalStatus(reqId, res.data))}
break
}
case ssh_FXP_DATA:
l, data := unmarshalUint32(data)
if req.offset == writeOffset {
nbytes, err := w.Write(data)
copied += int64(nbytes)
if err != nil {
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err}
break
}
if nbytes < int(l) {
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite}
break
}
switch {
case offset > fileSize:
desiredInFlight = 1
case desiredInFlight < maxConcurrentRequests:
desiredInFlight++
}
writeOffset += uint64(nbytes)
for pendingData, ok := pendingWrites[writeOffset]; ok; pendingData, ok = pendingWrites[writeOffset] {
nbytes, err := w.Write(pendingData)
if err != nil {
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err}
break
}
if nbytes < len(pendingData) {
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite}
break
}
writeOffset += uint64(nbytes)
inFlight--
}
} else {
// Don't write the data yet because
// this response came in out of order
// and we need to wait for responses
// for earlier segments of the file.
inFlight++ // Pending writes should still be considered inFlight.
pendingWrites[req.offset] = data
}
default:
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
break
}
}
}
if firstErr.err != io.EOF {
return copied, firstErr.err
}
return copied, nil
}
// Stat returns the FileInfo structure describing file. If there is an
@@ -592,24 +859,140 @@ func (f *File) Stat() (os.FileInfo, error) {
return fileInfoFromStat(fs, path.Base(f.path)), nil
}
// clamp writes to less than 32k
const maxWritePacket = 1 << 15
// Write writes len(b) bytes to the File. It returns the number of bytes
// written and an error, if any. Write returns a non-nil error when n !=
// len(b).
func (f *File) Write(b []byte) (int, error) {
var written int
for len(b) > 0 {
n, err := f.c.writeAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
f.offset += uint64(n)
written += int(n)
if err != nil {
return written, err
// Split the write into multiple maxPacket sized concurrent writes
// bounded by maxConcurrentRequests. This allows writes with a suitably
// large buffer to transfer data at a much faster rate due to
// overlapping round trip times.
inFlight := 0
desiredInFlight := 1
offset := f.offset
ch := make(chan result)
var firstErr error
written := len(b)
for len(b) > 0 || inFlight > 0 {
for inFlight < desiredInFlight && len(b) > 0 && firstErr == nil {
l := min(len(b), f.c.maxPacket)
rb := b[:l]
f.c.dispatchRequest(ch, sshFxpWritePacket{
Id: f.c.nextId(),
Handle: f.handle,
Offset: offset,
Length: uint32(len(rb)),
Data: rb,
})
inFlight++
offset += uint64(l)
b = b[l:]
}
if inFlight == 0 {
break
}
select {
case res := <-ch:
inFlight--
if res.err != nil {
firstErr = res.err
break
}
switch res.typ {
case ssh_FXP_STATUS:
id, _ := unmarshalUint32(res.data)
err := okOrErr(unmarshalStatus(id, res.data))
if err != nil && firstErr == nil {
firstErr = err
break
}
if desiredInFlight < maxConcurrentRequests {
desiredInFlight++
}
default:
firstErr = unimplementedPacketErr(res.typ)
break
}
}
b = b[n:]
}
return written, nil
// If error is non-nil, then there may be gaps in the data written to
// the file so it's best to return 0 so the caller can't make any
// incorrect assumptions about the state of the file.
if firstErr != nil {
written = 0
}
f.offset += uint64(written)
return written, firstErr
}
// ReadFrom reads data from r until EOF and writes it to the file. The return
// value is the number of bytes read. Any error except io.EOF encountered
// during the read is also returned.
func (f *File) ReadFrom(r io.Reader) (int64, error) {
inFlight := 0
desiredInFlight := 1
offset := f.offset
ch := make(chan result)
var firstErr error
read := int64(0)
b := make([]byte, f.c.maxPacket)
for inFlight > 0 || firstErr == nil {
for inFlight < desiredInFlight && firstErr == nil {
n, err := r.Read(b)
if err != nil {
firstErr = err
}
f.c.dispatchRequest(ch, sshFxpWritePacket{
Id: f.c.nextId(),
Handle: f.handle,
Offset: offset,
Length: uint32(n),
Data: b[:n],
})
inFlight++
offset += uint64(n)
read += int64(n)
}
if inFlight == 0 {
break
}
select {
case res := <-ch:
inFlight--
if res.err != nil {
firstErr = res.err
break
}
switch res.typ {
case ssh_FXP_STATUS:
id, _ := unmarshalUint32(res.data)
err := okOrErr(unmarshalStatus(id, res.data))
if err != nil && firstErr == nil {
firstErr = err
break
}
if desiredInFlight < maxConcurrentRequests {
desiredInFlight++
}
default:
firstErr = unimplementedPacketErr(res.typ)
break
}
}
}
if firstErr == io.EOF {
firstErr = nil
}
// If error is non-nil, then there may be gaps in the data written to
// the file so it's best to return 0 so the caller can't make any
// incorrect assumptions about the state of the file.
if firstErr != nil {
read = 0
}
f.offset += uint64(read)
return read, firstErr
}
// Seek implements io.Seeker by setting the client offset for the next Read or
+324 -47
View File
@@ -14,15 +14,18 @@ import (
"path"
"path/filepath"
"reflect"
"syscall"
"testing"
"testing/quick"
"time"
"github.com/kr/fs"
)
const (
READONLY = true
READWRITE = false
READONLY = true
READWRITE = false
NO_DELAY time.Duration = 0
debuglevel = "ERROR" // set to "DEBUG" for debugging
)
@@ -30,9 +33,57 @@ const (
var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process")
var testSftp = flag.String("sftp", "/usr/lib/openssh/sftp-server", "location of the sftp server binary")
type delayedWrite struct {
t time.Time
b []byte
}
// delayedWriter wraps a writer and artificially delays the write. This is
// meant to mimic connections with various latencies. Error's returned from the
// underlying writer will panic so this should only be used over reliable
// connections.
type delayedWriter struct {
w io.WriteCloser
ch chan delayedWrite
closed chan struct{}
}
func newDelayedWriter(w io.WriteCloser, delay time.Duration) io.WriteCloser {
ch := make(chan delayedWrite, 128)
closed := make(chan struct{})
go func() {
for writeMsg := range ch {
time.Sleep(writeMsg.t.Add(delay).Sub(time.Now()))
n, err := w.Write(writeMsg.b)
if err != nil {
panic("write error")
}
if n < len(writeMsg.b) {
panic("showrt write")
}
}
w.Close()
close(closed)
}()
return delayedWriter{w: w, ch: ch, closed: closed}
}
func (w delayedWriter) Write(b []byte) (int, error) {
bcopy := make([]byte, len(b))
copy(bcopy, b)
w.ch <- delayedWrite{t: time.Now(), b: bcopy}
return len(b), nil
}
func (w delayedWriter) Close() error {
close(w.ch)
<-w.closed
return nil
}
// testClient returns a *Client connected to a localy running sftp-server
// the *exec.Cmd returned must be defer Wait'd.
func testClient(t testing.TB, readonly bool) (*Client, *exec.Cmd) {
func testClient(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) {
if !*testIntegration {
t.Skip("skipping intergration test")
}
@@ -45,6 +96,9 @@ func testClient(t testing.TB, readonly bool) (*Client, *exec.Cmd) {
if err != nil {
t.Fatal(err)
}
if delay > NO_DELAY {
pw = newDelayedWriter(pw, delay)
}
pr, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
@@ -58,19 +112,11 @@ func testClient(t testing.TB, readonly bool) (*Client, *exec.Cmd) {
t.Fatal(err)
}
if err := sftp.sendInit(); err != nil {
defer cmd.Wait()
t.Fatal(err)
}
if err := sftp.recvVersion(); err != nil {
defer cmd.Wait()
t.Fatal(err)
}
return sftp, cmd
}
func TestNewClient(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
if err := sftp.Close(); err != nil {
@@ -79,7 +125,7 @@ func TestNewClient(t *testing.T) {
}
func TestClientLstat(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -105,7 +151,7 @@ func TestClientLstat(t *testing.T) {
}
func TestClientLstatMissing(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -122,7 +168,7 @@ func TestClientLstatMissing(t *testing.T) {
}
func TestClientMkdir(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -140,7 +186,7 @@ func TestClientMkdir(t *testing.T) {
}
func TestClientOpen(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -199,7 +245,7 @@ func (s seek) end(t *testing.T, r io.ReadSeeker) {
}
func TestClientSeek(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -243,7 +289,7 @@ func TestClientSeek(t *testing.T) {
}
func TestClientCreate(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -262,7 +308,7 @@ func TestClientCreate(t *testing.T) {
}
func TestClientAppend(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -281,7 +327,7 @@ func TestClientAppend(t *testing.T) {
}
func TestClientCreateFailed(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -302,7 +348,7 @@ func TestClientCreateFailed(t *testing.T) {
}
func TestClientFileStat(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -333,7 +379,7 @@ func TestClientFileStat(t *testing.T) {
}
func TestClientRemove(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -350,7 +396,7 @@ func TestClientRemove(t *testing.T) {
}
func TestClientRemoveDir(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -367,7 +413,7 @@ func TestClientRemoveDir(t *testing.T) {
}
func TestClientRemoveFailed(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -384,7 +430,7 @@ func TestClientRemoveFailed(t *testing.T) {
}
func TestClientRename(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -405,7 +451,7 @@ func TestClientRename(t *testing.T) {
}
func TestClientReadLine(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -449,7 +495,7 @@ var clientReadTests = []struct {
}
func TestClientRead(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -537,7 +583,7 @@ var clientWriteTests = []struct {
}
func TestClientWrite(t *testing.T) {
sftp, cmd := testClient(t, READWRITE)
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -664,7 +710,7 @@ func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool)
}
func TestClientWalk(t *testing.T) {
sftp, cmd := testClient(t, READONLY)
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
@@ -747,11 +793,29 @@ func TestClientWalk(t *testing.T) {
}
}
func benchmarkRead(b *testing.B, bufsize int) {
// sftp/issue/42, abrupt server hangup would result in client hangs.
func TestServerRoughDisconnect(t *testing.T) {
sftp, cmd := testClient(t, READONLY, NO_DELAY)
f, err := sftp.Open("/dev/zero")
if err != nil {
t.Fatal(err)
}
defer f.Close()
go func() {
time.Sleep(100 * time.Millisecond)
cmd.Process.Kill()
}()
io.Copy(ioutil.Discard, f)
sftp.Close()
}
func benchmarkRead(b *testing.B, bufsize int, delay time.Duration) {
size := 10*1024*1024 + 123 // ~10MiB
// open sftp client
sftp, cmd := testClient(b, READONLY)
sftp, cmd := testClient(b, READONLY, delay)
defer cmd.Wait()
defer sftp.Close()
@@ -786,38 +850,50 @@ func benchmarkRead(b *testing.B, bufsize int) {
}
func BenchmarkRead1k(b *testing.B) {
benchmarkRead(b, 1*1024)
benchmarkRead(b, 1*1024, NO_DELAY)
}
func BenchmarkRead16k(b *testing.B) {
benchmarkRead(b, 16*1024)
benchmarkRead(b, 16*1024, NO_DELAY)
}
func BenchmarkRead32k(b *testing.B) {
benchmarkRead(b, 32*1024)
benchmarkRead(b, 32*1024, NO_DELAY)
}
func BenchmarkRead128k(b *testing.B) {
benchmarkRead(b, 128*1024)
benchmarkRead(b, 128*1024, NO_DELAY)
}
func BenchmarkRead512k(b *testing.B) {
benchmarkRead(b, 512*1024)
benchmarkRead(b, 512*1024, NO_DELAY)
}
func BenchmarkRead1MiB(b *testing.B) {
benchmarkRead(b, 1024*1024)
benchmarkRead(b, 1024*1024, NO_DELAY)
}
func BenchmarkRead4MiB(b *testing.B) {
benchmarkRead(b, 4*1024*1024)
benchmarkRead(b, 4*1024*1024, NO_DELAY)
}
func benchmarkWrite(b *testing.B, bufsize int) {
func BenchmarkRead4MiBDelay10Msec(b *testing.B) {
benchmarkRead(b, 4*1024*1024, 10*time.Millisecond)
}
func BenchmarkRead4MiBDelay50Msec(b *testing.B) {
benchmarkRead(b, 4*1024*1024, 50*time.Millisecond)
}
func BenchmarkRead4MiBDelay150Msec(b *testing.B) {
benchmarkRead(b, 4*1024*1024, 150*time.Millisecond)
}
func benchmarkWrite(b *testing.B, bufsize int, delay time.Duration) {
size := 10*1024*1024 + 123 // ~10MiB
// open sftp client
sftp, cmd := testClient(b, false)
sftp, cmd := testClient(b, false, delay)
defer cmd.Wait()
defer sftp.Close()
@@ -870,29 +946,230 @@ func benchmarkWrite(b *testing.B, bufsize int) {
}
func BenchmarkWrite1k(b *testing.B) {
benchmarkWrite(b, 1*1024)
benchmarkWrite(b, 1*1024, NO_DELAY)
}
func BenchmarkWrite16k(b *testing.B) {
benchmarkWrite(b, 16*1024)
benchmarkWrite(b, 16*1024, NO_DELAY)
}
func BenchmarkWrite32k(b *testing.B) {
benchmarkWrite(b, 32*1024)
benchmarkWrite(b, 32*1024, NO_DELAY)
}
func BenchmarkWrite128k(b *testing.B) {
benchmarkWrite(b, 128*1024)
benchmarkWrite(b, 128*1024, NO_DELAY)
}
func BenchmarkWrite512k(b *testing.B) {
benchmarkWrite(b, 512*1024)
benchmarkWrite(b, 512*1024, NO_DELAY)
}
func BenchmarkWrite1MiB(b *testing.B) {
benchmarkWrite(b, 1024*1024)
benchmarkWrite(b, 1024*1024, NO_DELAY)
}
func BenchmarkWrite4MiB(b *testing.B) {
benchmarkWrite(b, 4*1024*1024)
benchmarkWrite(b, 4*1024*1024, NO_DELAY)
}
func BenchmarkWrite4MiBDelay10Msec(b *testing.B) {
benchmarkWrite(b, 4*1024*1024, 10*time.Millisecond)
}
func BenchmarkWrite4MiBDelay50Msec(b *testing.B) {
benchmarkWrite(b, 4*1024*1024, 50*time.Millisecond)
}
func BenchmarkWrite4MiBDelay150Msec(b *testing.B) {
benchmarkWrite(b, 4*1024*1024, 150*time.Millisecond)
}
func TestClientStatVFS(t *testing.T) {
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
vfs, err := sftp.StatVFS("/")
if err != nil {
t.Fatal(err)
}
// get system stats
s := syscall.Statfs_t{}
err = syscall.Statfs("/", &s)
if err != nil {
t.Fatal(err)
}
// check some stats
if vfs.Frsize != uint64(s.Frsize) {
t.Fatal("fr_size does not match")
}
if vfs.Bsize != uint64(s.Bsize) {
t.Fatal("f_bsize does not match")
}
if vfs.Namemax != uint64(s.Namelen) {
t.Fatal("f_namemax does not match")
}
if vfs.Bavail != s.Bavail {
t.Fatal("f_bavail does not match")
}
}
func benchmarkCopyDown(b *testing.B, fileSize int64, delay time.Duration) {
// Create a temp file and fill it with zero's.
src, err := ioutil.TempFile("", "sftptest")
if err != nil {
b.Fatal(err)
}
defer src.Close()
srcFilename := src.Name()
defer os.Remove(srcFilename)
zero, err := os.Open("/dev/zero")
if err != nil {
b.Fatal(err)
}
n, err := io.Copy(src, io.LimitReader(zero, fileSize))
if err != nil {
b.Fatal(err)
}
if n < fileSize {
b.Fatal("short copy")
}
zero.Close()
src.Close()
sftp, cmd := testClient(b, READONLY, delay)
defer cmd.Wait()
defer sftp.Close()
b.ResetTimer()
b.SetBytes(fileSize)
for i := 0; i < b.N; i++ {
dst, err := ioutil.TempFile("", "sftptest")
if err != nil {
b.Fatal(err)
}
defer os.Remove(dst.Name())
src, err := sftp.Open(srcFilename)
if err != nil {
b.Fatal(err)
}
defer src.Close()
n, err := io.Copy(dst, src)
if err != nil {
b.Fatalf("copy error: %v", err)
}
if n < fileSize {
b.Fatal("unable to copy all bytes")
}
dst.Close()
fi, err := os.Stat(dst.Name())
if err != nil {
b.Fatal(err)
}
if fi.Size() != fileSize {
b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size())
}
os.Remove(dst.Name())
}
}
func BenchmarkCopyDown10MiBDelay10Msec(b *testing.B) {
benchmarkCopyDown(b, 10*1024*1024, 10*time.Millisecond)
}
func BenchmarkCopyDown10MiBDelay50Msec(b *testing.B) {
benchmarkCopyDown(b, 10*1024*1024, 50*time.Millisecond)
}
func BenchmarkCopyDown10MiBDelay150Msec(b *testing.B) {
benchmarkCopyDown(b, 10*1024*1024, 150*time.Millisecond)
}
func benchmarkCopyUp(b *testing.B, fileSize int64, delay time.Duration) {
// Create a temp file and fill it with zero's.
src, err := ioutil.TempFile("", "sftptest")
if err != nil {
b.Fatal(err)
}
defer src.Close()
srcFilename := src.Name()
defer os.Remove(srcFilename)
zero, err := os.Open("/dev/zero")
if err != nil {
b.Fatal(err)
}
n, err := io.Copy(src, io.LimitReader(zero, fileSize))
if err != nil {
b.Fatal(err)
}
if n < fileSize {
b.Fatal("short copy")
}
zero.Close()
src.Close()
sftp, cmd := testClient(b, false, delay)
defer cmd.Wait()
defer sftp.Close()
b.ResetTimer()
b.SetBytes(fileSize)
for i := 0; i < b.N; i++ {
tmp, err := ioutil.TempFile("", "sftptest")
if err != nil {
b.Fatal(err)
}
tmp.Close()
defer os.Remove(tmp.Name())
dst, err := sftp.Create(tmp.Name())
if err != nil {
b.Fatal(err)
}
defer dst.Close()
src, err := os.Open(srcFilename)
if err != nil {
b.Fatal(err)
}
defer src.Close()
n, err := io.Copy(dst, src)
if err != nil {
b.Fatalf("copy error: %v", err)
}
if n < fileSize {
b.Fatal("unable to copy all bytes")
}
fi, err := os.Stat(tmp.Name())
if err != nil {
b.Fatal(err)
}
if fi.Size() != fileSize {
b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size())
}
os.Remove(tmp.Name())
}
}
func BenchmarkCopyUp10MiBDelay10Msec(b *testing.B) {
benchmarkCopyUp(b, 10*1024*1024, 10*time.Millisecond)
}
func BenchmarkCopyUp10MiBDelay50Msec(b *testing.B) {
benchmarkCopyUp(b, 10*1024*1024, 50*time.Millisecond)
}
func BenchmarkCopyUp10MiBDelay150Msec(b *testing.B) {
benchmarkCopyUp(b, 10*1024*1024, 150*time.Millisecond)
}
@@ -22,6 +22,7 @@ var (
HOST = flag.String("host", "localhost", "ssh server hostname")
PORT = flag.Int("port", 22, "ssh server port")
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
SIZE = flag.Int("s", 1<<15, "set max packet size")
)
func init() {
@@ -49,7 +50,7 @@ func main() {
}
defer conn.Close()
c, err := sftp.NewClient(conn)
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
if err != nil {
log.Fatalf("unable to start sftp subsytem: %v", err)
}
@@ -22,6 +22,7 @@ var (
HOST = flag.String("host", "localhost", "ssh server hostname")
PORT = flag.Int("port", 22, "ssh server port")
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
SIZE = flag.Int("s", 1<<15, "set max packet size")
)
func init() {
@@ -49,7 +50,7 @@ func main() {
}
defer conn.Close()
c, err := sftp.NewClient(conn)
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
if err != nil {
log.Fatalf("unable to start sftp subsytem: %v", err)
}
-147
View File
@@ -1,147 +0,0 @@
// gsftp implements a simple sftp client.
//
// gsftp understands the following commands:
//
// List a directory (and its subdirectories)
// gsftp ls DIR
//
// Fetch a remote file
// gsftp fetch FILE
//
// Put the contents of stdin to a remote file
// cat LOCALFILE | gsftp put REMOTEFILE
//
// Print the details of a remote file
// gsftp stat FILE
//
// Remove a remote file
// gsftp rm FILE
//
// Rename a file
// gsftp mv OLD NEW
//
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
)
var (
USER = flag.String("user", os.Getenv("USER"), "ssh username")
HOST = flag.String("host", "localhost", "ssh server hostname")
PORT = flag.Int("port", 22, "ssh server port")
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
)
func init() {
flag.Parse()
if len(flag.Args()) < 1 {
log.Fatal("subcommand required")
}
}
func main() {
var auths []ssh.AuthMethod
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
if *PASS != "" {
auths = append(auths, ssh.Password(*PASS))
}
config := ssh.ClientConfig{
User: *USER,
Auth: auths,
}
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
log.Fatalf("unable to connect to [%s]: %v", addr, err)
}
defer conn.Close()
client, err := sftp.NewClient(conn)
if err != nil {
log.Fatalf("unable to start sftp subsytem: %v", err)
}
defer client.Close()
switch cmd := flag.Args()[0]; cmd {
case "ls":
if len(flag.Args()) < 2 {
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
}
walker := client.Walk(flag.Args()[1])
for walker.Step() {
if err := walker.Err(); err != nil {
log.Println(err)
continue
}
fmt.Println(walker.Path())
}
case "fetch":
if len(flag.Args()) < 2 {
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
}
f, err := client.Open(flag.Args()[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := io.Copy(os.Stdout, f); err != nil {
log.Fatal(err)
}
case "put":
if len(flag.Args()) < 2 {
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
}
f, err := client.Create(flag.Args()[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := io.Copy(f, os.Stdin); err != nil {
log.Fatal(err)
}
case "stat":
if len(flag.Args()) < 2 {
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
}
f, err := client.Open(flag.Args()[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
log.Fatalf("unable to stat file: %v", err)
}
fmt.Printf("%s %d %v\n", fi.Name(), fi.Size(), fi.Mode())
case "rm":
if len(flag.Args()) < 2 {
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
}
if err := client.Remove(flag.Args()[1]); err != nil {
log.Fatalf("unable to remove file: %v", err)
}
case "mv":
if len(flag.Args()) < 3 {
log.Fatalf("%s %s: old and new name required", cmd, os.Args[0])
}
if err := client.Rename(flag.Args()[1], flag.Args()[2]); err != nil {
log.Fatalf("unable to rename file: %v", err)
}
default:
log.Fatalf("unknown subcommand: %v", cmd)
}
}
@@ -23,6 +23,7 @@ var (
HOST = flag.String("host", "localhost", "ssh server hostname")
PORT = flag.Int("port", 22, "ssh server port")
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
SIZE = flag.Int("s", 1<<15, "set max packet size")
)
func init() {
@@ -50,7 +51,7 @@ func main() {
}
defer conn.Close()
c, err := sftp.NewClient(conn)
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
if err != nil {
log.Fatalf("unable to start sftp subsytem: %v", err)
}
@@ -23,6 +23,7 @@ var (
HOST = flag.String("host", "localhost", "ssh server hostname")
PORT = flag.Int("port", 22, "ssh server port")
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
SIZE = flag.Int("s", 1<<15, "set max packet size")
)
func init() {
@@ -50,7 +51,7 @@ func main() {
}
defer conn.Close()
c, err := sftp.NewClient(conn)
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
if err != nil {
log.Fatalf("unable to start sftp subsytem: %v", err)
}
+71 -1
View File
@@ -64,7 +64,6 @@ func unmarshalString(b []byte) (string, []byte) {
}
// sendPacket marshals p according to RFC 4234.
func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
bb, err := m.MarshalBinary()
if err != nil {
@@ -142,6 +141,8 @@ func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_READDIR, p.Id, p.Handle)
}
func (p sshFxpReaddirPacket) id() uint32 { return p.Id }
type sshFxpOpendirPacket struct {
Id uint32
Path string
@@ -151,11 +152,15 @@ func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_OPENDIR, p.Id, p.Path)
}
func (p sshFxpOpendirPacket) id() uint32 { return p.Id }
type sshFxpLstatPacket struct {
Id uint32
Path string
}
func (p sshFxpLstatPacket) id() uint32 { return p.Id }
func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_LSTAT, p.Id, p.Path)
}
@@ -165,6 +170,8 @@ type sshFxpFstatPacket struct {
Handle string
}
func (p sshFxpFstatPacket) id() uint32 { return p.Id }
func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_FSTAT, p.Id, p.Handle)
}
@@ -178,11 +185,15 @@ func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_CLOSE, p.Id, p.Handle)
}
func (p sshFxpClosePacket) id() uint32 { return p.Id }
type sshFxpRemovePacket struct {
Id uint32
Filename string
}
func (p sshFxpRemovePacket) id() uint32 { return p.Id }
func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_REMOVE, p.Id, p.Filename)
}
@@ -192,6 +203,8 @@ type sshFxpRmdirPacket struct {
Path string
}
func (p sshFxpRmdirPacket) id() uint32 { return p.Id }
func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_RMDIR, p.Id, p.Path)
}
@@ -201,6 +214,8 @@ type sshFxpReadlinkPacket struct {
Path string
}
func (p sshFxpReadlinkPacket) id() uint32 { return p.Id }
func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
return marshalIdString(ssh_FXP_READLINK, p.Id, p.Path)
}
@@ -212,6 +227,8 @@ type sshFxpOpenPacket struct {
Flags uint32 // ignored
}
func (p sshFxpOpenPacket) id() uint32 { return p.Id }
func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 +
4 + len(p.Path) +
@@ -233,6 +250,8 @@ type sshFxpReadPacket struct {
Len uint32
}
func (p sshFxpReadPacket) id() uint32 { return p.Id }
func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
4 + len(p.Handle) +
@@ -253,6 +272,8 @@ type sshFxpRenamePacket struct {
Newpath string
}
func (p sshFxpRenamePacket) id() uint32 { return p.Id }
func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
4 + len(p.Oldpath) +
@@ -274,6 +295,8 @@ type sshFxpWritePacket struct {
Data []byte
}
func (s sshFxpWritePacket) id() uint32 { return s.Id }
func (s sshFxpWritePacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
4 + len(s.Handle) +
@@ -296,6 +319,8 @@ type sshFxpMkdirPacket struct {
Flags uint32 // ignored
}
func (p sshFxpMkdirPacket) id() uint32 { return p.Id }
func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
4 + len(p.Path) +
@@ -316,6 +341,8 @@ type sshFxpSetstatPacket struct {
Attrs interface{}
}
func (p sshFxpSetstatPacket) id() uint32 { return p.Id }
func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
4 + len(p.Path) +
@@ -329,3 +356,46 @@ func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
b = marshal(b, p.Attrs)
return b, nil
}
type sshFxpStatvfsPacket struct {
Id uint32
Path string
}
func (p sshFxpStatvfsPacket) id() uint32 { return p.Id }
func (p sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) {
l := 1 + 4 + // type(byte) + uint32
len(p.Path) +
len("statvfs@openssh.com")
b := make([]byte, 0, l)
b = append(b, ssh_FXP_EXTENDED)
b = marshalUint32(b, p.Id)
b = marshalString(b, "statvfs@openssh.com")
b = marshalString(b, p.Path)
return b, nil
}
type StatVFS struct {
Id uint32
Bsize uint64 /* file system block size */
Frsize uint64 /* fundamental fs block size */
Blocks uint64 /* number of blocks (unit f_frsize) */
Bfree uint64 /* free blocks in file system */
Bavail uint64 /* free blocks for non-root */
Files uint64 /* total file inodes */
Ffree uint64 /* free file inodes */
Favail uint64 /* free file inodes for to non-root */
Fsid uint64 /* file system id */
Flag uint64 /* bit mask of f_flag values */
Namemax uint64 /* maximum filename length */
}
func (p *StatVFS) TotalSpace() uint64 {
return p.Frsize * p.Blocks
}
func (p *StatVFS) FreeSpace() uint64 {
return p.Frsize * p.Bfree
}