1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #39012 from thaJeztah/bump_dependencies

Bump various dependencies
This commit is contained in:
Sebastiaan van Stijn 2019-04-10 11:10:32 +02:00 committed by GitHub
commit c2cb72e085
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1166 additions and 454 deletions

View file

@ -8,23 +8,23 @@ github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.0 github.com/gorilla/mux v1.7.0
github.com/Microsoft/opengcs a10967154e143a36014584a6f664344e3bb0aa64 github.com/Microsoft/opengcs a10967154e143a36014584a6f664344e3bb0aa64
github.com/konsorten/go-windows-terminal-sequences v1.0.1 github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e # v1.0.2
github.com/kr/pty 5cf931ef8f github.com/kr/pty 521317be5ebc228a0f0ede099fa2a0b5ece22e49 # v1.1.4
github.com/mattn/go-shellwords v1.0.3 github.com/mattn/go-shellwords a72fbe27a1b0ed0df2f02754945044ce1456608b # v1.0.5
github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1 github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia a7f0089c6f496e8e70402f61733606daa326cac5 # v2.3.0
github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 # v0.1.0 github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 # v0.1.0
golang.org/x/net eb5bcb51f2a31c7d5141d810b70815c05d9c9146 golang.org/x/net eb5bcb51f2a31c7d5141d810b70815c05d9c9146
golang.org/x/sys 4b34438f7a67ee5f45cc6132e2bad873a20324e9 golang.org/x/sys 4b34438f7a67ee5f45cc6132e2bad873a20324e9
github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3 github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0 github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
golang.org/x/text f21a4dfb5e38f5895301dc265a8def02365cc3d0 # v0.3.0 golang.org/x/text f21a4dfb5e38f5895301dc265a8def02365cc3d0 # v0.3.0
gotest.tools v2.1.0 gotest.tools 1083505acf35a0bd8a696b26837e1fb3187a7a83 # v2.3.0
github.com/google/go-cmp v0.2.0 github.com/google/go-cmp v0.2.0
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 github.com/RackSec/srslog a4725f04ec91af1a91b380da679d6e0c2f061e59
github.com/imdario/mergo v0.3.6 github.com/imdario/mergo 7c29201646fa3de8506f701213473dd407f19646 # v0.3.7
golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca golang.org/x/sync e225da77a7e68af35c70ccbf71af2b83e6acac3c
# buildkit # buildkit
github.com/moby/buildkit b3028967ae6259c9a31c1a1deeccd30fe3469cce github.com/moby/buildkit b3028967ae6259c9a31c1a1deeccd30fe3469cce
@ -97,7 +97,7 @@ github.com/Graylog2/go-gelf 4143646226541087117ff2f83334ea48b3201841
github.com/fluent/fluent-logger-golang v1.3.0 github.com/fluent/fluent-logger-golang v1.3.0
# fluent-logger-golang deps # fluent-logger-golang deps
github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972 github.com/philhofer/fwd bb6d471dc95d4fe11e432687f8b70ff496cf3136 # v1.0.0
github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad
# fsnotify # fsnotify

View file

@ -90,6 +90,22 @@ w.Debug("this is debug")
w.Write([]byte("these are some bytes")) w.Write([]byte("these are some bytes"))
``` ```
If you need further control over connection attempts, you can use the DialWithCustomDialer
function. To continue with the DialWithTLSConfig example:
```
netDialer := &net.Dialer{Timeout: time.Second*5} // easy timeouts
realNetwork := "tcp" // real network, other vars your dail func can close over
dial := func(network, addr string) (net.Conn, error) {
// cannot use "network" here as it'll simply be "custom" which will fail
return tls.DialWithDialer(netDialer, realNetwork, addr, &config)
}
w, err := DialWithCustomDialer("custom", "192.168.0.52:514", syslog.LOG_ERR, "testtag", dial)
```
Your custom dial func can set timeouts, proxy connections, and do whatever else it needs before returning a net.Conn.
# Generating TLS Certificates # Generating TLS Certificates
We've provided a script that you can use to generate a self-signed keypair: We've provided a script that you can use to generate a self-signed keypair:

View file

@ -37,6 +37,7 @@ func (w *Writer) getDialer() dialerFunctionWrapper {
dialers := map[string]dialerFunctionWrapper{ dialers := map[string]dialerFunctionWrapper{
"": dialerFunctionWrapper{"unixDialer", w.unixDialer}, "": dialerFunctionWrapper{"unixDialer", w.unixDialer},
"tcp+tls": dialerFunctionWrapper{"tlsDialer", w.tlsDialer}, "tcp+tls": dialerFunctionWrapper{"tlsDialer", w.tlsDialer},
"custom": dialerFunctionWrapper{"customDialer", w.customDialer},
} }
dialer, ok := dialers[w.network] dialer, ok := dialers[w.network]
if !ok { if !ok {
@ -85,3 +86,19 @@ func (w *Writer) basicDialer() (serverConn, string, error) {
} }
return sc, hostname, err return sc, hostname, err
} }
// customDialer uses the custom dialer when the Writer was created
// giving developers total control over how connections are made and returned.
// Note it does not check if cdialer is nil, as it should only be referenced from getDialer.
func (w *Writer) customDialer() (serverConn, string, error) {
c, err := w.customDial(w.network, w.raddr)
var sc serverConn
hostname := w.hostname
if err == nil {
sc = &netConn{conn: c}
if hostname == "" {
hostname = c.LocalAddr().String()
}
}
return sc, hostname, err
}

View file

@ -6,6 +6,8 @@ import (
"time" "time"
) )
const appNameMaxLength = 48 // limit to 48 chars as per RFC5424
// Formatter is a type of function that takes the consituent parts of a // Formatter is a type of function that takes the consituent parts of a
// syslog message and returns a formatted string. A different Formatter is // syslog message and returns a formatted string. A different Formatter is
// defined for each different syslog protocol we support. // defined for each different syslog protocol we support.
@ -37,12 +39,20 @@ func RFC3164Formatter(p Priority, hostname, tag, content string) string {
return msg return msg
} }
// if string's length is greater than max, then use the last part
func truncateStartStr(s string, max int) string {
if (len(s) > max) {
return s[len(s) - max:]
}
return s
}
// RFC5424Formatter provides an RFC 5424 compliant message. // RFC5424Formatter provides an RFC 5424 compliant message.
func RFC5424Formatter(p Priority, hostname, tag, content string) string { func RFC5424Formatter(p Priority, hostname, tag, content string) string {
timestamp := time.Now().Format(time.RFC3339) timestamp := time.Now().Format(time.RFC3339)
pid := os.Getpid() pid := os.Getpid()
appName := os.Args[0] appName := truncateStartStr(os.Args[0], appNameMaxLength)
msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s", msg := fmt.Sprintf("<%d>%d %s %s %s %d %s - %s",
p, 1, timestamp, hostname, appName, pid, tag, content) p, 1, timestamp, hostname, appName, pid, tag, content)
return msg return msg
} }

View file

@ -3,8 +3,10 @@ package srslog
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"os" "os"
) )
@ -15,6 +17,10 @@ type serverConn interface {
close() error close() error
} }
// DialFunc is the function signature to be used for a custom dialer callback
// with DialWithCustomDialer
type DialFunc func(string, string) (net.Conn, error)
// New establishes a new connection to the system log daemon. Each // New establishes a new connection to the system log daemon. Each
// write to the returned Writer sends a log message with the given // write to the returned Writer sends a log message with the given
// priority and prefix. // priority and prefix.
@ -31,6 +37,22 @@ func Dial(network, raddr string, priority Priority, tag string) (*Writer, error)
return DialWithTLSConfig(network, raddr, priority, tag, nil) return DialWithTLSConfig(network, raddr, priority, tag, nil)
} }
// ErrNilDialFunc is returned from DialWithCustomDialer when a nil DialFunc is passed,
// avoiding a nil pointer deference panic.
var ErrNilDialFunc = errors.New("srslog: nil DialFunc passed to DialWithCustomDialer")
// DialWithCustomDialer establishes a connection by calling customDial.
// Each write to the returned Writer sends a log message with the given facility, severity and tag.
// Network must be "custom" in order for this package to use customDial.
// While network and raddr will be passed to customDial, it is allowed for customDial to ignore them.
// If customDial is nil, this function returns ErrNilDialFunc.
func DialWithCustomDialer(network, raddr string, priority Priority, tag string, customDial DialFunc) (*Writer, error) {
if customDial == nil {
return nil, ErrNilDialFunc
}
return dialAllParameters(network, raddr, priority, tag, nil, customDial)
}
// DialWithTLSCertPath establishes a secure connection to a log daemon by connecting to // DialWithTLSCertPath establishes a secure connection to a log daemon by connecting to
// address raddr on the specified network. It uses certPath to load TLS certificates and configure // address raddr on the specified network. It uses certPath to load TLS certificates and configure
// the secure connection. // the secure connection.
@ -59,6 +81,11 @@ func DialWithTLSCert(network, raddr string, priority Priority, tag string, serve
// DialWithTLSConfig establishes a secure connection to a log daemon by connecting to // DialWithTLSConfig establishes a secure connection to a log daemon by connecting to
// address raddr on the specified network. It uses tlsConfig to configure the secure connection. // address raddr on the specified network. It uses tlsConfig to configure the secure connection.
func DialWithTLSConfig(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config) (*Writer, error) { func DialWithTLSConfig(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config) (*Writer, error) {
return dialAllParameters(network, raddr, priority, tag, tlsConfig, nil)
}
// implementation of the various functions above
func dialAllParameters(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config, customDial DialFunc) (*Writer, error) {
if err := validatePriority(priority); err != nil { if err := validatePriority(priority); err != nil {
return nil, err return nil, err
} }
@ -69,12 +96,13 @@ func DialWithTLSConfig(network, raddr string, priority Priority, tag string, tls
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
w := &Writer{ w := &Writer{
priority: priority, priority: priority,
tag: tag, tag: tag,
hostname: hostname, hostname: hostname,
network: network, network: network,
raddr: raddr, raddr: raddr,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
customDial: customDial,
} }
_, err := w.connect() _, err := w.connect()

View file

@ -17,6 +17,9 @@ type Writer struct {
framer Framer framer Framer
formatter Formatter formatter Formatter
//non-nil if custom dialer set, used in getDialer
customDial DialFunc
mu sync.RWMutex // guards conn mu sync.RWMutex // guards conn
conn serverConn conn serverConn
} }
@ -71,15 +74,20 @@ func (w *Writer) SetFramer(f Framer) {
w.framer = f w.framer = f
} }
// SetHostname changes the hostname for syslog messages if needed.
func (w *Writer) SetHostname(hostname string) {
w.hostname = hostname
}
// Write sends a log message to the syslog daemon using the default priority // Write sends a log message to the syslog daemon using the default priority
// passed into `srslog.New` or the `srslog.Dial*` functions. // passed into `srslog.New` or the `srslog.Dial*` functions.
func (w *Writer) Write(b []byte) (int, error) { func (w *Writer) Write(b []byte) (int, error) {
return w.writeAndRetry(w.priority, string(b)) return w.writeAndRetry(w.priority, string(b))
} }
// WriteWithPriority sends a log message with a custom priority // WriteWithPriority sends a log message with a custom priority.
func (w *Writer) WriteWithPriority(p Priority, b []byte) (int, error) { func (w *Writer) WriteWithPriority(p Priority, b []byte) (int, error) {
return w.writeAndRetry(p, string(b)) return w.writeAndRetryWithPriority(p, string(b))
} }
// Close closes a connection to the syslog daemon. // Close closes a connection to the syslog daemon.
@ -149,12 +157,20 @@ func (w *Writer) Debug(m string) (err error) {
return err return err
} }
func (w *Writer) writeAndRetry(p Priority, s string) (int, error) { // writeAndRetry takes a severity and the string to write. Any facility passed to
pr := (w.priority & facilityMask) | (p & severityMask) // it as part of the severity Priority will be ignored.
func (w *Writer) writeAndRetry(severity Priority, s string) (int, error) {
pr := (w.priority & facilityMask) | (severity & severityMask)
return w.writeAndRetryWithPriority(pr, s)
}
// writeAndRetryWithPriority differs from writeAndRetry in that it allows setting
// of both the facility and the severity.
func (w *Writer) writeAndRetryWithPriority(p Priority, s string) (int, error) {
conn := w.getConn() conn := w.getConn()
if conn != nil { if conn != nil {
if n, err := w.write(conn, pr, s); err == nil { if n, err := w.write(conn, p, s); err == nil {
return n, err return n, err
} }
} }
@ -163,7 +179,7 @@ func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
if conn, err = w.connect(); err != nil { if conn, err = w.connect(); err != nil {
return 0, err return 0, err
} }
return w.write(conn, pr, s) return w.write(conn, p, s)
} }
// write generates and writes a syslog formatted string. It formats the // write generates and writes a syslog formatted string. It formats the

View file

@ -13,6 +13,7 @@ It is ready for production use. [It is used in several projects by Docker, Googl
[![Build Status][1]][2] [![Build Status][1]][2]
[![Coverage Status][7]][8] [![Coverage Status][7]][8]
[![Sourcegraph][9]][10] [![Sourcegraph][9]][10]
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield)
[1]: https://travis-ci.org/imdario/mergo.png [1]: https://travis-ci.org/imdario/mergo.png
[2]: https://travis-ci.org/imdario/mergo [2]: https://travis-ci.org/imdario/mergo
@ -27,7 +28,7 @@ It is ready for production use. [It is used in several projects by Docker, Googl
### Latest release ### Latest release
[Release v0.3.6](https://github.com/imdario/mergo/releases/tag/v0.3.6). [Release v0.3.7](https://github.com/imdario/mergo/releases/tag/v0.3.7).
### Important note ### Important note
@ -217,6 +218,21 @@ If I can help you, you have an idea or you are using Mergo in your projects, don
Written by [Dario Castañé](http://dario.im). Written by [Dario Castañé](http://dario.im).
## Top Contributors
[![0](https://sourcerer.io/fame/imdario/imdario/mergo/images/0)](https://sourcerer.io/fame/imdario/imdario/mergo/links/0)
[![1](https://sourcerer.io/fame/imdario/imdario/mergo/images/1)](https://sourcerer.io/fame/imdario/imdario/mergo/links/1)
[![2](https://sourcerer.io/fame/imdario/imdario/mergo/images/2)](https://sourcerer.io/fame/imdario/imdario/mergo/links/2)
[![3](https://sourcerer.io/fame/imdario/imdario/mergo/images/3)](https://sourcerer.io/fame/imdario/imdario/mergo/links/3)
[![4](https://sourcerer.io/fame/imdario/imdario/mergo/images/4)](https://sourcerer.io/fame/imdario/imdario/mergo/links/4)
[![5](https://sourcerer.io/fame/imdario/imdario/mergo/images/5)](https://sourcerer.io/fame/imdario/imdario/mergo/links/5)
[![6](https://sourcerer.io/fame/imdario/imdario/mergo/images/6)](https://sourcerer.io/fame/imdario/imdario/mergo/links/6)
[![7](https://sourcerer.io/fame/imdario/imdario/mergo/images/7)](https://sourcerer.io/fame/imdario/imdario/mergo/links/7)
## License ## License
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)

View file

@ -72,6 +72,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
case reflect.Struct: case reflect.Struct:
srcMap := src.Interface().(map[string]interface{}) srcMap := src.Interface().(map[string]interface{})
for key := range srcMap { for key := range srcMap {
config.overwriteWithEmptyValue = true
srcValue := srcMap[key] srcValue := srcMap[key]
fieldName := changeInitialCase(key, unicode.ToUpper) fieldName := changeInitialCase(key, unicode.ToUpper)
dstElement := dst.FieldByName(fieldName) dstElement := dst.FieldByName(fieldName)

View file

@ -26,9 +26,10 @@ func hasExportedField(dst reflect.Value) (exported bool) {
} }
type Config struct { type Config struct {
Overwrite bool Overwrite bool
AppendSlice bool AppendSlice bool
Transformers Transformers Transformers Transformers
overwriteWithEmptyValue bool
} }
type Transformers interface { type Transformers interface {
@ -40,6 +41,8 @@ type Transformers interface {
// short circuiting on recursive types. // short circuiting on recursive types.
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite overwrite := config.Overwrite
overwriteWithEmptySrc := config.overwriteWithEmptyValue
config.overwriteWithEmptyValue = false
if !src.IsValid() { if !src.IsValid() {
return return
@ -74,7 +77,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
} }
} }
} else { } else {
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
dst.Set(src) dst.Set(src)
} }
} }
@ -125,7 +128,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dstSlice = reflect.ValueOf(dstElement.Interface()) dstSlice = reflect.ValueOf(dstElement.Interface())
} }
if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
dstSlice = srcSlice dstSlice = srcSlice
} else if config.AppendSlice { } else if config.AppendSlice {
if srcSlice.Type() != dstSlice.Type() { if srcSlice.Type() != dstSlice.Type() {
@ -136,7 +139,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dst.SetMapIndex(key, dstSlice) dst.SetMapIndex(key, dstSlice)
} }
} }
if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map { if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
continue continue
} }
@ -151,7 +154,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if !dst.CanSet() { if !dst.CanSet() {
break break
} }
if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
dst.Set(src) dst.Set(src)
} else if config.AppendSlice { } else if config.AppendSlice {
if src.Type() != dst.Type() { if src.Type() != dst.Type() {
@ -191,7 +194,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
return return
} }
default: default:
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
dst.Set(src) dst.Set(src)
} }
} }

View file

@ -26,6 +26,7 @@ The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
We thank all the authors who provided code to this library: We thank all the authors who provided code to this library:
* Felix Kollmann * Felix Kollmann
* Nicolas Perraut
## License ## License

View file

@ -0,0 +1,11 @@
// +build linux darwin
package sequences
import (
"fmt"
)
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
return fmt.Errorf("windows only package")
}

64
vendor/github.com/kr/pty/README.md generated vendored
View file

@ -8,6 +8,8 @@ Pty is a Go package for using unix pseudo-terminals.
## Example ## Example
### Command
```go ```go
package main package main
@ -34,3 +36,65 @@ func main() {
io.Copy(os.Stdout, f) io.Copy(os.Stdout, f)
} }
``` ```
### Shell
```go
package main
import (
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/kr/pty"
"golang.org/x/crypto/ssh/terminal"
)
func test() error {
// Create arbitrary command.
c := exec.Command("bash")
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
// Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, ptmx)
return nil
}
func main() {
if err := test(); err != nil {
log.Fatal(err)
}
}
```

2
vendor/github.com/kr/pty/ioctl.go generated vendored
View file

@ -1,3 +1,5 @@
// +build !windows
package pty package pty
import "syscall" import "syscall"

View file

@ -8,23 +8,28 @@ import (
) )
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p) sname, err := ptsname(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = grantpt(p) if err := grantpt(p); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
err = unlockpt(p) if err := unlockpt(p); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }

80
vendor/github.com/kr/pty/pty_dragonfly.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
package pty
import (
"errors"
"os"
"strings"
"syscall"
"unsafe"
)
// same code as pty_darwin.go
func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
return nil, nil, err
}
t, err := os.OpenFile(sname, os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func grantpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func unlockpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func isptmaster(fd uintptr) (bool, error) {
err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
return err == nil, err
}
var (
emptyFiodgnameArg fiodgnameArg
ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) {
name := make([]byte, _C_SPECNAMELEN)
fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
if err != nil {
return "", err
}
for i, c := range name {
if c == 0 {
s := "/dev/" + string(name[:i])
return strings.Replace(s, "ptm", "pts", -1), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}

View file

@ -7,22 +7,28 @@ import (
"unsafe" "unsafe"
) )
func posix_openpt(oflag int) (fd int, err error) { func posixOpenpt(oflag int) (fd int, err error) {
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
fd = int(r0) fd = int(r0)
if e1 != 0 { if e1 != 0 {
err = e1 err = e1
} }
return return fd, err
} }
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC) fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
p := os.NewFile(uintptr(fd), "/dev/pts") p := os.NewFile(uintptr(fd), "/dev/pts")
// In case of error after this point, make sure we close the pts fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p) sname, err := ptsname(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -42,7 +48,7 @@ func isptmaster(fd uintptr) (bool, error) {
var ( var (
emptyFiodgnameArg fiodgnameArg emptyFiodgnameArg fiodgnameArg
ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
) )
func ptsname(f *os.File) (string, error) { func ptsname(f *os.File) (string, error) {
@ -59,8 +65,7 @@ func ptsname(f *os.File) (string, error) {
buf = make([]byte, n) buf = make([]byte, n)
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
) )
err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg))) if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
if err != nil {
return "", err return "", err
} }

View file

@ -12,14 +12,19 @@ func open() (pty, tty *os.File, err error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p) sname, err := ptsname(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = unlockpt(p) if err := unlockpt(p); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -41,6 +46,6 @@ func ptsname(f *os.File) (string, error) {
func unlockpt(f *os.File) error { func unlockpt(f *os.File) error {
var u _C_int var u _C_int
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock // use TIOCSPTLCK with a pointer to zero to clear the lock
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
} }

33
vendor/github.com/kr/pty/pty_openbsd.go generated vendored Normal file
View file

@ -0,0 +1,33 @@
package pty
import (
"os"
"syscall"
"unsafe"
)
func open() (pty, tty *os.File, err error) {
/*
* from ptm(4):
* The PTMGET command allocates a free pseudo terminal, changes its
* ownership to the caller, revokes the access privileges for all previous
* users, opens the file descriptors for the pty and tty devices and
* returns them to the caller in struct ptmget.
*/
p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, nil, err
}
defer p.Close()
var ptm ptmget
if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil {
return nil, nil, err
}
pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm")
tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm")
return pty, tty, nil
}

View file

@ -1,4 +1,4 @@
// +build !linux,!darwin,!freebsd // +build !linux,!darwin,!freebsd,!dragonfly,!openbsd
package pty package pty

36
vendor/github.com/kr/pty/run.go generated vendored
View file

@ -1,3 +1,5 @@
// +build !windows
package pty package pty
import ( import (
@ -10,15 +12,41 @@ import (
// and c.Stderr, calls c.Start, and returns the File of the tty's // and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty. // corresponding pty.
func Start(c *exec.Cmd) (pty *os.File, err error) { func Start(c *exec.Cmd) (pty *os.File, err error) {
return StartWithSize(c, nil)
}
// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
// and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty.
//
// This will resize the pty to the specified size before starting the command
func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
pty, tty, err := Open() pty, tty, err := Open()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer tty.Close() defer tty.Close()
c.Stdout = tty if sz != nil {
c.Stdin = tty err = Setsize(pty, sz)
c.Stderr = tty if err != nil {
c.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} pty.Close()
return nil, err
}
}
if c.Stdout == nil {
c.Stdout = tty
}
if c.Stderr == nil {
c.Stderr = tty
}
if c.Stdin == nil {
c.Stdin = tty
}
if c.SysProcAttr == nil {
c.SysProcAttr = &syscall.SysProcAttr{}
}
c.SysProcAttr.Setctty = true
c.SysProcAttr.Setsid = true
err = c.Start() err = c.Start()
if err != nil { if err != nil {
pty.Close() pty.Close()

49
vendor/github.com/kr/pty/util.go generated vendored
View file

@ -1,3 +1,5 @@
// +build !windows
package pty package pty
import ( import (
@ -6,26 +8,53 @@ import (
"unsafe" "unsafe"
) )
// InheritSize applies the terminal size of pty to tty. This should be run
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
// the pty receives a window size change notification.
func InheritSize(pty, tty *os.File) error {
size, err := GetsizeFull(pty)
if err != nil {
return err
}
err = Setsize(tty, size)
if err != nil {
return err
}
return nil
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var ws Winsize
err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
return &ws, err
}
// Getsize returns the number of rows (lines) and cols (positions // Getsize returns the number of rows (lines) and cols (positions
// in each line) in terminal t. // in each line) in terminal t.
func Getsize(t *os.File) (rows, cols int, err error) { func Getsize(t *os.File) (rows, cols int, err error) {
var ws winsize ws, err := GetsizeFull(t)
err = windowrect(&ws, t.Fd()) return int(ws.Rows), int(ws.Cols), err
return int(ws.ws_row), int(ws.ws_col), err
} }
type winsize struct { // Winsize describes the terminal size.
ws_row uint16 type Winsize struct {
ws_col uint16 Rows uint16 // ws_row: Number of rows (in cells)
ws_xpixel uint16 Cols uint16 // ws_col: Number of columns (in cells)
ws_ypixel uint16 X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
} }
func windowrect(ws *winsize, fd uintptr) error { func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
_, _, errno := syscall.Syscall( _, _, errno := syscall.Syscall(
syscall.SYS_IOCTL, syscall.SYS_IOCTL,
fd, fd,
syscall.TIOCGWINSZ, a2,
uintptr(unsafe.Pointer(ws)), uintptr(unsafe.Pointer(ws)),
) )
if errno != 0 { if errno != 0 {

14
vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go generated vendored Normal file
View file

@ -0,0 +1,14 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_dragonfly.go
package pty
const (
_C_SPECNAMELEN = 0x3f
)
type fiodgnameArg struct {
Name *byte
Len uint32
Pad_cgo_0 [4]byte
}

12
vendor/github.com/kr/pty/ztypes_mipsx.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go
// +build linux
// +build mips mipsle mips64 mips64le
package pty
type (
_C_int int32
_C_uint uint32
)

13
vendor/github.com/kr/pty/ztypes_openbsd_386.go generated vendored Normal file
View file

@ -0,0 +1,13 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_openbsd.go
package pty
type ptmget struct {
Cfd int32
Sfd int32
Cn [16]int8
Sn [16]int8
}
var ioctl_PTMGET = 0x40287401

13
vendor/github.com/kr/pty/ztypes_openbsd_amd64.go generated vendored Normal file
View file

@ -0,0 +1,13 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_openbsd.go
package pty
type ptmget struct {
Cfd int32
Sfd int32
Cn [16]int8
Sn [16]int8
}
var ioctl_PTMGET = 0x40287401

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"os" "os"
"regexp" "regexp"
"strings"
) )
var ( var (
@ -21,13 +22,17 @@ func isSpace(r rune) bool {
return false return false
} }
func replaceEnv(s string) string { func replaceEnv(getenv func(string) string, s string) string {
if getenv == nil {
getenv = os.Getenv
}
return envRe.ReplaceAllStringFunc(s, func(s string) string { return envRe.ReplaceAllStringFunc(s, func(s string) string {
s = s[1:] s = s[1:]
if s[0] == '{' { if s[0] == '{' {
s = s[1 : len(s)-1] s = s[1 : len(s)-1]
} }
return os.Getenv(s) return getenv(s)
}) })
} }
@ -35,16 +40,24 @@ type Parser struct {
ParseEnv bool ParseEnv bool
ParseBacktick bool ParseBacktick bool
Position int Position int
// If ParseEnv is true, use this for getenv.
// If nil, use os.Getenv.
Getenv func(string) string
} }
func NewParser() *Parser { func NewParser() *Parser {
return &Parser{ParseEnv, ParseBacktick, 0} return &Parser{
ParseEnv: ParseEnv,
ParseBacktick: ParseBacktick,
Position: 0,
}
} }
func (p *Parser) Parse(line string) ([]string, error) { func (p *Parser) Parse(line string) ([]string, error) {
args := []string{} args := []string{}
buf := "" buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote bool var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
backtick := "" backtick := ""
pos := -1 pos := -1
@ -68,12 +81,12 @@ loop:
} }
if isSpace(r) { if isSpace(r) {
if singleQuoted || doubleQuoted || backQuote { if singleQuoted || doubleQuoted || backQuote || dollarQuote {
buf += string(r) buf += string(r)
backtick += string(r) backtick += string(r)
} else if got { } else if got {
if p.ParseEnv { if p.ParseEnv {
buf = replaceEnv(buf) buf = replaceEnv(p.Getenv, buf)
} }
args = append(args, buf) args = append(args, buf)
buf = "" buf = ""
@ -84,7 +97,7 @@ loop:
switch r { switch r {
case '`': case '`':
if !singleQuoted && !doubleQuoted { if !singleQuoted && !doubleQuoted && !dollarQuote {
if p.ParseBacktick { if p.ParseBacktick {
if backQuote { if backQuote {
out, err := shellRun(backtick) out, err := shellRun(backtick)
@ -100,18 +113,55 @@ loop:
backtick = "" backtick = ""
backQuote = !backQuote backQuote = !backQuote
} }
case ')':
if !singleQuoted && !doubleQuoted && !backQuote {
if p.ParseBacktick {
if dollarQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
if r == ')' {
buf = buf[:len(buf)-len(backtick)-2] + out
} else {
buf = buf[:len(buf)-len(backtick)-1] + out
}
}
backtick = ""
dollarQuote = !dollarQuote
continue
}
backtick = ""
dollarQuote = !dollarQuote
}
case '(':
if !singleQuoted && !doubleQuoted && !backQuote {
if !dollarQuote && strings.HasSuffix(buf, "$") {
dollarQuote = true
buf += "("
continue
} else {
return nil, errors.New("invalid command line string")
}
}
case '"': case '"':
if !singleQuoted { if !singleQuoted && !dollarQuote {
doubleQuoted = !doubleQuoted doubleQuoted = !doubleQuoted
continue continue
} }
case '\'': case '\'':
if !doubleQuoted { if !doubleQuoted && !dollarQuote {
singleQuoted = !singleQuoted singleQuoted = !singleQuoted
continue continue
} }
case ';', '&', '|', '<', '>': case ';', '&', '|', '<', '>':
if !(escaped || singleQuoted || doubleQuoted || backQuote) { if !(escaped || singleQuoted || doubleQuoted || backQuote) {
if r == '>' && len(buf) > 0 {
if c := buf[0]; '0' <= c && c <= '9' {
i -= 1
got = false
}
}
pos = i pos = i
break loop break loop
} }
@ -119,19 +169,19 @@ loop:
got = true got = true
buf += string(r) buf += string(r)
if backQuote { if backQuote || dollarQuote {
backtick += string(r) backtick += string(r)
} }
} }
if got { if got {
if p.ParseEnv { if p.ParseEnv {
buf = replaceEnv(buf) buf = replaceEnv(p.Getenv, buf)
} }
args = append(args, buf) args = append(args, buf)
} }
if escaped || singleQuoted || doubleQuoted || backQuote { if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
return nil, errors.New("invalid command line string") return nil, errors.New("invalid command line string")
} }

24
vendor/github.com/mattn/go-shellwords/util_go15.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// +build !go1.6
package shellwords
import (
"os"
"os/exec"
"runtime"
"strings"
)
func shellRun(line string) (string, error) {
var b []byte
var err error
if runtime.GOOS == "windows" {
b, err = exec.Command(os.Getenv("COMSPEC"), "/c", line).Output()
} else {
b, err = exec.Command(os.Getenv("SHELL"), "-c", line).Output()
}
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}

View file

@ -1,4 +1,4 @@
// +build !windows // +build !windows,go1.6
package shellwords package shellwords
@ -13,6 +13,9 @@ func shellRun(line string) (string, error) {
shell := os.Getenv("SHELL") shell := os.Getenv("SHELL")
b, err := exec.Command(shell, "-c", line).Output() b, err := exec.Command(shell, "-c", line).Output()
if err != nil { if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b)) return "", errors.New(err.Error() + ":" + string(b))
} }
return strings.TrimSpace(string(b)), nil return strings.TrimSpace(string(b)), nil

View file

@ -1,3 +1,5 @@
// +build windows,go1.6
package shellwords package shellwords
import ( import (
@ -11,6 +13,9 @@ func shellRun(line string) (string, error) {
shell := os.Getenv("COMSPEC") shell := os.Getenv("COMSPEC")
b, err := exec.Command(shell, "/c", line).Output() b, err := exec.Command(shell, "/c", line).Output()
if err != nil { if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b)) return "", errors.New(err.Error() + ":" + string(b))
} }
return strings.TrimSpace(string(b)), nil return strings.TrimSpace(string(b)), nil

View file

@ -109,6 +109,10 @@ func (r *Reader) more() {
if a == 0 && r.state == nil { if a == 0 && r.state == nil {
r.state = io.ErrNoProgress r.state = io.ErrNoProgress
return return
} else if a > 0 && r.state == io.EOF {
// discard the io.EOF if we read more than 0 bytes.
// the next call to Read should return io.EOF again.
r.state = nil
} }
r.data = r.data[:len(r.data)+a] r.data = r.data[:len(r.data)+a]
} }

View file

@ -1,8 +1,6 @@
# go-patricia # # go-patricia #
**Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)<br /> **Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)<br />
**Build Status**: [![Build
Status](https://drone.io/github.com/tchap/go-patricia/status.png)](https://drone.io/github.com/tchap/go-patricia/latest)<br />
**Test Coverage**: [![Coverage **Test Coverage**: [![Coverage
Status](https://coveralls.io/repos/tchap/go-patricia/badge.png)](https://coveralls.io/r/tchap/go-patricia) Status](https://coveralls.io/repos/tchap/go-patricia/badge.png)](https://coveralls.io/r/tchap/go-patricia)
@ -117,7 +115,3 @@ MIT, check the `LICENSE` file.
[![Gittip [![Gittip
Badge](http://img.shields.io/gittip/alanhamlett.png)](https://www.gittip.com/tchap/ Badge](http://img.shields.io/gittip/alanhamlett.png)](https://www.gittip.com/tchap/
"Gittip Badge") "Gittip Badge")
[![Bitdeli
Badge](https://d2weczhvl823v0.cloudfront.net/tchap/go-patricia/trend.png)](https://bitdeli.com/free
"Bitdeli Badge")

View file

@ -20,6 +20,7 @@ type childList interface {
next(b byte) *Trie next(b byte) *Trie
walk(prefix *Prefix, visitor VisitorFunc) error walk(prefix *Prefix, visitor VisitorFunc) error
print(w io.Writer, indent int) print(w io.Writer, indent int)
clone() childList
total() int total() int
} }
@ -143,6 +144,17 @@ func (list *sparseChildList) total() int {
return tot return tot
} }
func (list *sparseChildList) clone() childList {
clones := make(tries, len(list.children), cap(list.children))
for i, child := range list.children {
clones[i] = child.Clone()
}
return &sparseChildList{
children: clones,
}
}
func (list *sparseChildList) print(w io.Writer, indent int) { func (list *sparseChildList) print(w io.Writer, indent int) {
for _, child := range list.children { for _, child := range list.children {
if child != nil { if child != nil {
@ -314,6 +326,32 @@ func (list *denseChildList) print(w io.Writer, indent int) {
} }
} }
func (list *denseChildList) clone() childList {
clones := make(tries, cap(list.children))
if list.numChildren != 0 {
clonedCount := 0
for i := list.headIndex; i < len(list.children); i++ {
child := list.children[i]
if child != nil {
clones[i] = child.Clone()
clonedCount++
if clonedCount == list.numChildren {
break
}
}
}
}
return &denseChildList{
min: list.min,
max: list.max,
numChildren: list.numChildren,
headIndex: list.headIndex,
children: clones,
}
}
func (list *denseChildList) total() int { func (list *denseChildList) total() int {
tot := 0 tot := 0
for _, child := range list.children { for _, child := range list.children {

View file

@ -77,6 +77,18 @@ func MaxChildrenPerSparseNode(value int) Option {
} }
} }
// Clone makes a copy of an existing trie.
// Items stored in both tries become shared, obviously.
func (trie *Trie) Clone() *Trie {
return &Trie{
prefix: append(Prefix(nil), trie.prefix...),
item: trie.item,
maxPrefixPerNode: trie.maxPrefixPerNode,
maxChildrenPerSparseNode: trie.maxChildrenPerSparseNode,
children: trie.children.clone(),
}
}
// Item returns the item stored in the root of this trie. // Item returns the item stored in the root of this trie.
func (trie *Trie) Item() Item { func (trie *Trie) Item() Item {
return trie.item return trie.item

View file

@ -7,9 +7,8 @@
package errgroup package errgroup
import ( import (
"context"
"sync" "sync"
"golang.org/x/net/context"
) )
// A Group is a collection of goroutines working on subtasks that are part of // A Group is a collection of goroutines working on subtasks that are part of

View file

@ -7,12 +7,8 @@ package semaphore // import "golang.org/x/sync/semaphore"
import ( import (
"container/list" "container/list"
"context"
"sync" "sync"
// Use the old context because packages that depend on this one
// (e.g. cloud.google.com/go/...) must run on Go 1.6.
// TODO(jba): update to "context" when possible.
"golang.org/x/net/context"
) )
type waiter struct { type waiter struct {
@ -36,9 +32,9 @@ type Weighted struct {
waiters list.List waiters list.List
} }
// Acquire acquires the semaphore with a weight of n, blocking only until ctx // Acquire acquires the semaphore with a weight of n, blocking until resources
// is done. On success, returns nil. On failure, returns ctx.Err() and leaves // are available or ctx is done. On success, returns nil. On failure, returns
// the semaphore unchanged. // ctx.Err() and leaves the semaphore unchanged.
// //
// If ctx is already done, Acquire may still succeed without blocking. // If ctx is already done, Acquire may still succeed without blocking.
func (s *Weighted) Acquire(ctx context.Context, n int64) error { func (s *Weighted) Acquire(ctx context.Context, n int64) error {

View file

@ -1,202 +1,13 @@
Copyright 2018 gotest.tools authors
Apache License Licensed under the Apache License, Version 2.0 (the "License");
Version 2.0, January 2004 you may not use this file except in compliance with the License.
http://www.apache.org/licenses/ You may obtain a copy of the License at
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION http://www.apache.org/licenses/LICENSE-2.0
1. Definitions. Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
"License" shall mean the terms and conditions for use, reproduction, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
and distribution as defined by Sections 1 through 9 of this document. See the License for the specific language governing permissions and
limitations under the License.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -3,7 +3,7 @@
A collection of packages to augment `testing` and support common patterns. A collection of packages to augment `testing` and support common patterns.
[![GoDoc](https://godoc.org/gotest.tools?status.svg)](https://godoc.org/gotest.tools) [![GoDoc](https://godoc.org/gotest.tools?status.svg)](https://godoc.org/gotest.tools)
[![CircleCI](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master) [![CircleCI](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master)
[![Go Reportcard](https://goreportcard.com/badge/gotest.tools)](https://goreportcard.com/report/gotest.tools) [![Go Reportcard](https://goreportcard.com/badge/gotest.tools)](https://goreportcard.com/report/gotest.tools)
@ -29,3 +29,7 @@ A collection of packages to augment `testing` and support common patterns.
* [gotest.tools/gotestsum](https://github.com/gotestyourself/gotestsum) - go test runner with custom output * [gotest.tools/gotestsum](https://github.com/gotestyourself/gotestsum) - go test runner with custom output
* [maxbrunsfeld/counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) - generate fakes for interfaces * [maxbrunsfeld/counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) - generate fakes for interfaces
* [jonboulle/clockwork](https://github.com/jonboulle/clockwork) - a fake clock for testing code that uses `time` * [jonboulle/clockwork](https://github.com/jonboulle/clockwork) - a fake clock for testing code that uses `time`
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).

View file

@ -4,6 +4,7 @@ package cmp // import "gotest.tools/assert/cmp"
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -58,6 +59,39 @@ func toResult(success bool, msg string) Result {
return ResultFailure(msg) return ResultFailure(msg)
} }
// RegexOrPattern may be either a *regexp.Regexp or a string that is a valid
// regexp pattern.
type RegexOrPattern interface{}
// Regexp succeeds if value v matches regular expression re.
//
// Example:
// assert.Assert(t, cmp.Regexp("^[0-9a-f]{32}$", str))
// r := regexp.MustCompile("^[0-9a-f]{32}$")
// assert.Assert(t, cmp.Regexp(r, str))
func Regexp(re RegexOrPattern, v string) Comparison {
match := func(re *regexp.Regexp) Result {
return toResult(
re.MatchString(v),
fmt.Sprintf("value %q does not match regexp %q", v, re.String()))
}
return func() Result {
switch regex := re.(type) {
case *regexp.Regexp:
return match(regex)
case string:
re, err := regexp.Compile(regex)
if err != nil {
return ResultFailure(err.Error())
}
return match(re)
default:
return ResultFailure(fmt.Sprintf("invalid type %T for regex pattern", regex))
}
}
}
// Equal succeeds if x == y. See assert.Equal for full documentation. // Equal succeeds if x == y. See assert.Equal for full documentation.
func Equal(x, y interface{}) Comparison { func Equal(x, y interface{}) Comparison {
return func() Result { return func() Result {
@ -186,7 +220,7 @@ func Error(err error, message string) Comparison {
return ResultFailure("expected an error, got nil") return ResultFailure("expected an error, got nil")
case err.Error() != message: case err.Error() != message:
return ResultFailure(fmt.Sprintf( return ResultFailure(fmt.Sprintf(
"expected error %q, got %+v", message, err)) "expected error %q, got %s", message, formatErrorMessage(err)))
} }
return ResultSuccess return ResultSuccess
} }
@ -201,12 +235,22 @@ func ErrorContains(err error, substring string) Comparison {
return ResultFailure("expected an error, got nil") return ResultFailure("expected an error, got nil")
case !strings.Contains(err.Error(), substring): case !strings.Contains(err.Error(), substring):
return ResultFailure(fmt.Sprintf( return ResultFailure(fmt.Sprintf(
"expected error to contain %q, got %+v", substring, err)) "expected error to contain %q, got %s", substring, formatErrorMessage(err)))
} }
return ResultSuccess return ResultSuccess
} }
} }
func formatErrorMessage(err error) string {
if _, ok := err.(interface {
Cause() error
}); ok {
return fmt.Sprintf("%q\n%+v", err, err)
}
// This error was not wrapped with github.com/pkg/errors
return fmt.Sprintf("%q", err)
}
// Nil succeeds if obj is a nil interface, pointer, or function. // Nil succeeds if obj is a nil interface, pointer, or function.
// //
// Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices, // Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices,

View file

@ -9,31 +9,37 @@ import (
"gotest.tools/internal/source" "gotest.tools/internal/source"
) )
// Result of a Comparison. // A Result of a Comparison.
type Result interface { type Result interface {
Success() bool Success() bool
} }
type result struct { // StringResult is an implementation of Result that reports the error message
// string verbatim and does not provide any templating or formatting of the
// message.
type StringResult struct {
success bool success bool
message string message string
} }
func (r result) Success() bool { // Success returns true if the comparison was successful.
func (r StringResult) Success() bool {
return r.success return r.success
} }
func (r result) FailureMessage() string { // FailureMessage returns the message used to provide additional information
// about the failure.
func (r StringResult) FailureMessage() string {
return r.message return r.message
} }
// ResultSuccess is a constant which is returned by a ComparisonWithResult to // ResultSuccess is a constant which is returned by a ComparisonWithResult to
// indicate success. // indicate success.
var ResultSuccess = result{success: true} var ResultSuccess = StringResult{success: true}
// ResultFailure returns a failed Result with a failure message. // ResultFailure returns a failed Result with a failure message.
func ResultFailure(message string) Result { func ResultFailure(message string) StringResult {
return result{message: message} return StringResult{message: message}
} }
// ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure // ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure

View file

@ -70,7 +70,6 @@ func filterPrintableExpr(args []ast.Expr) []ast.Expr {
result[i] = starExpr.X result[i] = starExpr.X
continue continue
} }
result[i] = nil
} }
return result return result
} }

View file

@ -79,6 +79,9 @@ func ToMap(env []string) map[string]string {
} }
func getParts(raw string) (string, string) { func getParts(raw string) (string, string) {
if raw == "" {
return "", ""
}
// Environment variables on windows can begin with = // Environment variables on windows can begin with =
// http://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx // http://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx
parts := strings.SplitN(raw[1:], "=", 2) parts := strings.SplitN(raw[1:], "=", 2)

View file

@ -7,6 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings"
"gotest.tools/assert" "gotest.tools/assert"
"gotest.tools/x/subtest" "gotest.tools/x/subtest"
@ -40,20 +42,25 @@ func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
tempfile, err := ioutil.TempFile("", prefix+"-") tempfile, err := ioutil.TempFile("", cleanPrefix(prefix)+"-")
assert.NilError(t, err) assert.NilError(t, err)
file := &File{path: tempfile.Name()} file := &File{path: tempfile.Name()}
assert.NilError(t, tempfile.Close()) assert.NilError(t, tempfile.Close())
assert.NilError(t, applyPathOps(file, ops))
for _, op := range ops {
assert.NilError(t, op(file))
}
if tc, ok := t.(subtest.TestContext); ok { if tc, ok := t.(subtest.TestContext); ok {
tc.AddCleanup(file.Remove) tc.AddCleanup(file.Remove)
} }
return file return file
} }
func cleanPrefix(prefix string) string {
// windows requires both / and \ are replaced
if runtime.GOOS == "windows" {
prefix = strings.Replace(prefix, string(os.PathSeparator), "-", -1)
}
return strings.Replace(prefix, "/", "-", -1)
}
// Path returns the full path to the file // Path returns the full path to the file
func (f *File) Path() string { func (f *File) Path() string {
return f.path return f.path
@ -76,13 +83,10 @@ func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
path, err := ioutil.TempDir("", prefix+"-") path, err := ioutil.TempDir("", cleanPrefix(prefix)+"-")
assert.NilError(t, err) assert.NilError(t, err)
dir := &Dir{path: path} dir := &Dir{path: path}
assert.NilError(t, applyPathOps(dir, ops))
for _, op := range ops {
assert.NilError(t, op(dir))
}
if tc, ok := t.(subtest.TestContext); ok { if tc, ok := t.(subtest.TestContext); ok {
tc.AddCleanup(dir.Remove) tc.AddCleanup(dir.Remove)
} }

View file

@ -24,7 +24,9 @@ type resource struct {
type file struct { type file struct {
resource resource
content io.ReadCloser content io.ReadCloser
ignoreCariageReturn bool
compareContentFunc func(b []byte) CompareResult
} }
func (f *file) Type() string { func (f *file) Type() string {
@ -42,7 +44,8 @@ func (f *symlink) Type() string {
type directory struct { type directory struct {
resource resource
items map[string]dirEntry items map[string]dirEntry
filepathGlobs map[string]*filePath
} }
func (f *directory) Type() string { func (f *directory) Type() string {
@ -94,8 +97,9 @@ func newDirectory(path string, info os.FileInfo) (*directory, error) {
} }
return &directory{ return &directory{
resource: newResourceFromInfo(info), resource: newResourceFromInfo(info),
items: items, items: items,
filepathGlobs: make(map[string]*filePath),
}, nil }, nil
} }
@ -113,6 +117,9 @@ func getTypedResource(path string, info os.FileInfo) (dirEntry, error) {
func newSymlink(path string, info os.FileInfo) (*symlink, error) { func newSymlink(path string, info os.FileInfo) (*symlink, error) {
target, err := os.Readlink(path) target, err := os.Readlink(path)
if err != nil {
return nil, err
}
return &symlink{ return &symlink{
resource: newResourceFromInfo(info), resource: newResourceFromInfo(info),
target: target, target: target,
@ -122,6 +129,9 @@ func newSymlink(path string, info os.FileInfo) (*symlink, error) {
func newFile(path string, info os.FileInfo) (*file, error) { func newFile(path string, info os.FileInfo) (*file, error) {
// TODO: defer file opening to reduce number of open FDs? // TODO: defer file opening to reduce number of open FDs?
readCloser, err := os.Open(path) readCloser, err := os.Open(path)
if err != nil {
return nil, err
}
return &file{ return &file{
resource: newResourceFromInfo(info), resource: newResourceFromInfo(info),
content: readCloser, content: readCloser,

View file

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"gotest.tools/assert"
) )
const defaultFileMode = 0644 const defaultFileMode = 0644
@ -144,6 +145,14 @@ func WithDir(name string, ops ...PathOp) PathOp {
} }
} }
// Apply the PathOps to the File
func Apply(t assert.TestingT, path Path, ops ...PathOp) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert.NilError(t, applyPathOps(path, ops))
}
func applyPathOps(path Path, ops []PathOp) error { func applyPathOps(path Path, ops []PathOp) error {
for _, op := range ops { for _, op := range ops {
if err := op(path); err != nil { if err := op(path); err != nil {
@ -172,23 +181,35 @@ func copyDirectory(source, dest string) error {
for _, entry := range entries { for _, entry := range entries {
sourcePath := filepath.Join(source, entry.Name()) sourcePath := filepath.Join(source, entry.Name())
destPath := filepath.Join(dest, entry.Name()) destPath := filepath.Join(dest, entry.Name())
if entry.IsDir() { switch {
case entry.IsDir():
if err := os.Mkdir(destPath, 0755); err != nil { if err := os.Mkdir(destPath, 0755); err != nil {
return err return err
} }
if err := copyDirectory(sourcePath, destPath); err != nil { if err := copyDirectory(sourcePath, destPath); err != nil {
return err return err
} }
continue case entry.Mode()&os.ModeSymlink != 0:
} if err := copySymLink(sourcePath, destPath); err != nil {
// TODO: handle symlinks return err
if err := copyFile(sourcePath, destPath); err != nil { }
return err default:
if err := copyFile(sourcePath, destPath); err != nil {
return err
}
} }
} }
return nil return nil
} }
func copySymLink(source, dest string) error {
link, err := os.Readlink(source)
if err != nil {
return err
}
return os.Symlink(link, dest)
}
func copyFile(source, dest string) error { func copyFile(source, dest string) error {
content, err := ioutil.ReadFile(source) content, err := ioutil.ReadFile(source)
if err != nil { if err != nil {
@ -219,7 +240,7 @@ func WithSymlink(path, target string) PathOp {
func WithHardlink(path, target string) PathOp { func WithHardlink(path, target string) PathOp {
return func(root Path) error { return func(root Path) error {
if _, ok := root.(manifestDirectory); ok { if _, ok := root.(manifestDirectory); ok {
return errors.New("WithHardlink yet implemented for manifests") return errors.New("WithHardlink not implemented for manifests")
} }
return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path)) return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path))
} }
@ -230,7 +251,7 @@ func WithHardlink(path, target string) PathOp {
func WithTimestamps(atime, mtime time.Time) PathOp { func WithTimestamps(atime, mtime time.Time) PathOp {
return func(root Path) error { return func(root Path) error {
if _, ok := root.(manifestDirectory); ok { if _, ok := root.(manifestDirectory); ok {
return errors.New("WithTimestamp yet implemented for manifests") return errors.New("WithTimestamp not implemented for manifests")
} }
return os.Chtimes(root.Path(), atime, mtime) return os.Chtimes(root.Path(), atime, mtime)
} }

View file

@ -64,6 +64,13 @@ func (p *directoryPath) AddFile(path string, ops ...PathOp) error {
return applyPathOps(exp, ops) return applyPathOps(exp, ops)
} }
func (p *directoryPath) AddGlobFiles(glob string, ops ...PathOp) error {
newFile := &file{resource: newResource(0)}
newFilePath := &filePath{file: newFile}
p.directory.filepathGlobs[glob] = newFilePath
return applyPathOps(newFilePath, ops)
}
func (p *directoryPath) AddDirectory(path string, ops ...PathOp) error { func (p *directoryPath) AddDirectory(path string, ops ...PathOp) error {
newDir := newDirectoryWithDefaults() newDir := newDirectoryWithDefaults()
p.directory.items[path] = newDir p.directory.items[path] = newDir
@ -87,8 +94,9 @@ func Expected(t assert.TestingT, ops ...PathOp) Manifest {
func newDirectoryWithDefaults() *directory { func newDirectoryWithDefaults() *directory {
return &directory{ return &directory{
resource: newResource(defaultRootDirMode), resource: newResource(defaultRootDirMode),
items: make(map[string]dirEntry), items: make(map[string]dirEntry),
filepathGlobs: make(map[string]*filePath),
} }
} }
@ -127,6 +135,15 @@ func MatchAnyFileContent(path Path) error {
return nil return nil
} }
// MatchContentIgnoreCarriageReturn is a PathOp that ignores cariage return
// discrepancies.
func MatchContentIgnoreCarriageReturn(path Path) error {
if m, ok := path.(*filePath); ok {
m.file.ignoreCariageReturn = true
}
return nil
}
const anyFile = "*" const anyFile = "*"
// MatchExtraFiles is a PathOp that updates a Manifest to allow a directory // MatchExtraFiles is a PathOp that updates a Manifest to allow a directory
@ -138,6 +155,37 @@ func MatchExtraFiles(path Path) error {
return nil return nil
} }
// CompareResult is the result of comparison.
//
// See gotest.tools/assert/cmp.StringResult for a convenient implementation of
// this interface.
type CompareResult interface {
Success() bool
FailureMessage() string
}
// MatchFileContent is a PathOp that updates a Manifest to use the provided
// function to determine if a file's content matches the expectation.
func MatchFileContent(f func([]byte) CompareResult) PathOp {
return func(path Path) error {
if m, ok := path.(*filePath); ok {
m.file.compareContentFunc = f
}
return nil
}
}
// MatchFilesWithGlob is a PathOp that updates a Manifest to match files using
// glob pattern, and check them using the ops.
func MatchFilesWithGlob(glob string, ops ...PathOp) PathOp {
return func(path Path) error {
if m, ok := path.(*directoryPath); ok {
m.AddGlobFiles(glob, ops...)
}
return nil
}
}
// anyFileMode is represented by uint32_max // anyFileMode is represented by uint32_max
const anyFileMode os.FileMode = 4294967295 const anyFileMode os.FileMode = 4294967295

View file

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strings" "strings"
@ -67,6 +68,11 @@ func eqResource(x, y resource) []problem {
return p return p
} }
func removeCarriageReturn(in []byte) []byte {
return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1)
}
// nolint: gocyclo
func eqFile(x, y *file) []problem { func eqFile(x, y *file) []problem {
p := eqResource(x.resource, y.resource) p := eqResource(x.resource, y.resource)
@ -96,6 +102,19 @@ func eqFile(x, y *file) []problem {
return p return p
} }
if x.compareContentFunc != nil {
r := x.compareContentFunc(yContent)
if !r.Success() {
p = append(p, existenceProblem("content", r.FailureMessage()))
}
return p
}
if x.ignoreCariageReturn || y.ignoreCariageReturn {
xContent = removeCarriageReturn(xContent)
yContent = removeCarriageReturn(yContent)
}
if !bytes.Equal(xContent, yContent) { if !bytes.Equal(xContent, yContent) {
p = append(p, diffContent(xContent, yContent)) p = append(p, diffContent(xContent, yContent))
} }
@ -126,7 +145,13 @@ func indent(s, prefix string) string {
func eqSymlink(x, y *symlink) []problem { func eqSymlink(x, y *symlink) []problem {
p := eqResource(x.resource, y.resource) p := eqResource(x.resource, y.resource)
if x.target != y.target { xTarget := x.target
yTarget := y.target
if runtime.GOOS == "windows" {
xTarget = strings.ToLower(xTarget)
yTarget = strings.ToLower(yTarget)
}
if xTarget != yTarget {
p = append(p, notEqual("target", x.target, y.target)) p = append(p, notEqual("target", x.target, y.target))
} }
return p return p
@ -135,11 +160,13 @@ func eqSymlink(x, y *symlink) []problem {
func eqDirectory(path string, x, y *directory) []failure { func eqDirectory(path string, x, y *directory) []failure {
p := eqResource(x.resource, y.resource) p := eqResource(x.resource, y.resource)
var f []failure var f []failure
matchedFiles := make(map[string]bool)
for _, name := range sortedKeys(x.items) { for _, name := range sortedKeys(x.items) {
if name == anyFile { if name == anyFile {
continue continue
} }
matchedFiles[name] = true
xEntry := x.items[name] xEntry := x.items[name]
yEntry, ok := y.items[name] yEntry, ok := y.items[name]
if !ok { if !ok {
@ -155,19 +182,30 @@ func eqDirectory(path string, x, y *directory) []failure {
f = append(f, eqEntry(filepath.Join(path, name), xEntry, yEntry)...) f = append(f, eqEntry(filepath.Join(path, name), xEntry, yEntry)...)
} }
if _, ok := x.items[anyFile]; !ok { if len(x.filepathGlobs) != 0 {
for _, name := range sortedKeys(y.items) { for _, name := range sortedKeys(y.items) {
if _, ok := x.items[name]; !ok { m := matchGlob(name, y.items[name], x.filepathGlobs)
yEntry := y.items[name] matchedFiles[name] = m.match
p = append(p, existenceProblem(name, "unexpected %s", yEntry.Type())) f = append(f, m.failures...)
}
} }
} }
if len(p) > 0 { if _, ok := x.items[anyFile]; ok {
f = append(f, failure{path: path, problems: p}) return maybeAppendFailure(f, path, p)
} }
return f for _, name := range sortedKeys(y.items) {
if !matchedFiles[name] {
p = append(p, existenceProblem(name, "unexpected %s", y.items[name].Type()))
}
}
return maybeAppendFailure(f, path, p)
}
func maybeAppendFailure(failures []failure, path string, problems []problem) []failure {
if len(problems) > 0 {
return append(failures, failure{path: path, problems: problems})
}
return failures
} }
func sortedKeys(items map[string]dirEntry) []string { func sortedKeys(items map[string]dirEntry) []string {
@ -199,6 +237,30 @@ func eqEntry(path string, x, y dirEntry) []failure {
return nil return nil
} }
type globMatch struct {
match bool
failures []failure
}
func matchGlob(name string, yEntry dirEntry, globs map[string]*filePath) globMatch {
m := globMatch{}
for glob, expectedFile := range globs {
ok, err := filepath.Match(glob, name)
if err != nil {
p := errProblem("failed to match glob pattern", err)
f := failure{path: name, problems: []problem{p}}
m.failures = append(m.failures, f)
}
if ok {
m.match = true
m.failures = eqEntry(name, expectedFile.file, yEntry)
return m
}
}
return m
}
func formatFailures(failures []failure) string { func formatFailures(failures []failure) string {
sort.Slice(failures, func(i, j int) bool { sort.Slice(failures, func(i, j int) bool {
return failures[i].path < failures[j].path return failures[i].path < failures[j].path

View file

@ -132,18 +132,21 @@ func (r *Result) String() string {
if r.Timeout { if r.Timeout {
timeout = " (timeout)" timeout = " (timeout)"
} }
var errString string
if r.Error != nil {
errString = "\nError: " + r.Error.Error()
}
return fmt.Sprintf(` return fmt.Sprintf(`
Command: %s Command: %s
ExitCode: %d%s ExitCode: %d%s%s
Error: %v
Stdout: %v Stdout: %v
Stderr: %v Stderr: %v
`, `,
strings.Join(r.Cmd.Args, " "), strings.Join(r.Cmd.Args, " "),
r.ExitCode, r.ExitCode,
timeout, timeout,
r.Error, errString,
r.Stdout(), r.Stdout(),
r.Stderr()) r.Stderr())
} }

View file

@ -1,4 +1,38 @@
package icmd package icmd
import (
"io"
"time"
)
// CmdOp is an operation which modified a Cmd structure used to execute commands // CmdOp is an operation which modified a Cmd structure used to execute commands
type CmdOp func(*Cmd) type CmdOp func(*Cmd)
// WithTimeout sets the timeout duration of the command
func WithTimeout(timeout time.Duration) CmdOp {
return func(c *Cmd) {
c.Timeout = timeout
}
}
// WithEnv sets the environment variable of the command.
// Each arguments are in the form of KEY=VALUE
func WithEnv(env ...string) CmdOp {
return func(c *Cmd) {
c.Env = env
}
}
// Dir sets the working directory of the command
func Dir(path string) CmdOp {
return func(c *Cmd) {
c.Dir = path
}
}
// WithStdin sets the standard input of the command to the specified reader
func WithStdin(r io.Reader) CmdOp {
return func(c *Cmd) {
c.Stdin = r
}
}

View file

@ -1,4 +1,4 @@
/* Package difflib is a partial port of Python difflib module. /*Package difflib is a partial port of Python difflib module.
Original source: https://github.com/pmezard/go-difflib Original source: https://github.com/pmezard/go-difflib
@ -20,12 +20,14 @@ func max(a, b int) int {
return b return b
} }
// Match stores line numbers of size of match
type Match struct { type Match struct {
A int A int
B int B int
Size int Size int
} }
// OpCode identifies the type of diff
type OpCode struct { type OpCode struct {
Tag byte Tag byte
I1 int I1 int
@ -73,19 +75,20 @@ type SequenceMatcher struct {
opCodes []OpCode opCodes []OpCode
} }
// NewMatcher returns a new SequenceMatcher
func NewMatcher(a, b []string) *SequenceMatcher { func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true} m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b) m.SetSeqs(a, b)
return &m return &m
} }
// Set two sequences to be compared. // SetSeqs sets two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) { func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a) m.SetSeq1(a)
m.SetSeq2(b) m.SetSeq2(b)
} }
// Set the first sequence to be compared. The second sequence to be compared is // SetSeq1 sets the first sequence to be compared. The second sequence to be compared is
// not changed. // not changed.
// //
// SequenceMatcher computes and caches detailed information about the second // SequenceMatcher computes and caches detailed information about the second
@ -103,7 +106,7 @@ func (m *SequenceMatcher) SetSeq1(a []string) {
m.opCodes = nil m.opCodes = nil
} }
// Set the second sequence to be compared. The first sequence to be compared is // SetSeq2 sets the second sequence to be compared. The first sequence to be compared is
// not changed. // not changed.
func (m *SequenceMatcher) SetSeq2(b []string) { func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b { if &b == &m.b {
@ -129,12 +132,12 @@ func (m *SequenceMatcher) chainB() {
m.bJunk = map[string]struct{}{} m.bJunk = map[string]struct{}{}
if m.IsJunk != nil { if m.IsJunk != nil {
junk := m.bJunk junk := m.bJunk
for s, _ := range b2j { for s := range b2j {
if m.IsJunk(s) { if m.IsJunk(s) {
junk[s] = struct{}{} junk[s] = struct{}{}
} }
} }
for s, _ := range junk { for s := range junk {
delete(b2j, s) delete(b2j, s)
} }
} }
@ -149,7 +152,7 @@ func (m *SequenceMatcher) chainB() {
popular[s] = struct{}{} popular[s] = struct{}{}
} }
} }
for s, _ := range popular { for s := range popular {
delete(b2j, s) delete(b2j, s)
} }
} }
@ -259,7 +262,7 @@ func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
return Match{A: besti, B: bestj, Size: bestsize} return Match{A: besti, B: bestj, Size: bestsize}
} }
// Return list of triples describing matching subsequences. // GetMatchingBlocks returns a list of triples describing matching subsequences.
// //
// Each triple is of the form (i, j, n), and means that // Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in // a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
@ -323,7 +326,7 @@ func (m *SequenceMatcher) GetMatchingBlocks() []Match {
return m.matchingBlocks return m.matchingBlocks
} }
// Return list of 5-tuples describing how to turn a into b. // GetOpCodes returns a list of 5-tuples describing how to turn a into b.
// //
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple // Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the // has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
@ -374,7 +377,7 @@ func (m *SequenceMatcher) GetOpCodes() []OpCode {
return m.opCodes return m.opCodes
} }
// Isolate change clusters by eliminating ranges with no changes. // GetGroupedOpCodes isolates change clusters by eliminating ranges with no changes.
// //
// Return a generator of groups with up to n lines of context. // Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes(). // Each group is in the same format as returned by GetOpCodes().
@ -384,7 +387,7 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
} }
codes := m.GetOpCodes() codes := m.GetOpCodes()
if len(codes) == 0 { if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}} codes = []OpCode{{'e', 0, 1, 0, 1}}
} }
// Fixup leading and trailing groups if they show no changes. // Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' { if codes[0].Tag == 'e' {

View file

@ -0,0 +1,53 @@
package source
import (
"go/ast"
"go/token"
"github.com/pkg/errors"
)
func scanToDeferLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
var matchedNode ast.Node
ast.Inspect(node, func(node ast.Node) bool {
switch {
case node == nil || matchedNode != nil:
return false
case fileset.Position(node.End()).Line == lineNum:
if funcLit, ok := node.(*ast.FuncLit); ok {
matchedNode = funcLit
return false
}
}
return true
})
debug("defer line node: %s", debugFormatNode{matchedNode})
return matchedNode
}
func guessDefer(node ast.Node) (ast.Node, error) {
defers := collectDefers(node)
switch len(defers) {
case 0:
return nil, errors.New("failed to expression in defer")
case 1:
return defers[0].Call, nil
default:
return nil, errors.Errorf(
"ambiguous call expression: multiple (%d) defers in call block",
len(defers))
}
}
func collectDefers(node ast.Node) []*ast.DeferStmt {
var defers []*ast.DeferStmt
ast.Inspect(node, func(node ast.Node) bool {
if d, ok := node.(*ast.DeferStmt); ok {
defers = append(defers, d)
debug("defer: %s", debugFormatNode{d})
return false
}
return true
})
return defers
}

View file

@ -24,106 +24,12 @@ func FormattedCallExprArg(stackIndex int, argPos int) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if argPos >= len(args) {
return "", errors.New("failed to find expression")
}
return FormatNode(args[argPos]) return FormatNode(args[argPos])
} }
func getNodeAtLine(filename string, lineNum int) (ast.Node, error) {
fileset := token.NewFileSet()
astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse source file: %s", filename)
}
node := scanToLine(fileset, astFile, lineNum)
if node == nil {
return nil, errors.Errorf(
"failed to find an expression on line %d in %s", lineNum, filename)
}
return node, nil
}
func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
v := &scanToLineVisitor{lineNum: lineNum, fileset: fileset}
ast.Walk(v, node)
return v.matchedNode
}
type scanToLineVisitor struct {
lineNum int
matchedNode ast.Node
fileset *token.FileSet
}
func (v *scanToLineVisitor) Visit(node ast.Node) ast.Visitor {
if node == nil || v.matchedNode != nil {
return nil
}
if v.nodePosition(node).Line == v.lineNum {
v.matchedNode = node
return nil
}
return v
}
// In golang 1.9 the line number changed from being the line where the statement
// ended to the line where the statement began.
func (v *scanToLineVisitor) nodePosition(node ast.Node) token.Position {
if goVersionBefore19 {
return v.fileset.Position(node.End())
}
return v.fileset.Position(node.Pos())
}
var goVersionBefore19 = isGOVersionBefore19()
func isGOVersionBefore19() bool {
version := runtime.Version()
// not a release version
if !strings.HasPrefix(version, "go") {
return false
}
version = strings.TrimPrefix(version, "go")
parts := strings.Split(version, ".")
if len(parts) < 2 {
return false
}
minor, err := strconv.ParseInt(parts[1], 10, 32)
return err == nil && parts[0] == "1" && minor < 9
}
func getCallExprArgs(node ast.Node) ([]ast.Expr, error) {
visitor := &callExprVisitor{}
ast.Walk(visitor, node)
if visitor.expr == nil {
return nil, errors.New("failed to find call expression")
}
return visitor.expr.Args, nil
}
type callExprVisitor struct {
expr *ast.CallExpr
}
func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor {
if v.expr != nil || node == nil {
return nil
}
debug("visit (%T): %s", node, debugFormatNode{node})
if callExpr, ok := node.(*ast.CallExpr); ok {
v.expr = callExpr
return nil
}
return v
}
// FormatNode using go/format.Node and return the result as a string
func FormatNode(node ast.Node) (string, error) {
buf := new(bytes.Buffer)
err := format.Node(buf, token.NewFileSet(), node)
return buf.String(), err
}
// CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at // CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at
// the index in the call stack. // the index in the call stack.
func CallExprArgs(stackIndex int) ([]ast.Expr, error) { func CallExprArgs(stackIndex int) ([]ast.Expr, error) {
@ -137,12 +43,109 @@ func CallExprArgs(stackIndex int) ([]ast.Expr, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
debug("found node (%T): %s", node, debugFormatNode{node}) debug("found node: %s", debugFormatNode{node})
return getCallExprArgs(node) return getCallExprArgs(node)
} }
var debugEnabled = os.Getenv("GOTESTYOURSELF_DEBUG") != "" func getNodeAtLine(filename string, lineNum int) (ast.Node, error) {
fileset := token.NewFileSet()
astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse source file: %s", filename)
}
if node := scanToLine(fileset, astFile, lineNum); node != nil {
return node, nil
}
if node := scanToDeferLine(fileset, astFile, lineNum); node != nil {
node, err := guessDefer(node)
if err != nil || node != nil {
return node, err
}
}
return nil, errors.Errorf(
"failed to find an expression on line %d in %s", lineNum, filename)
}
func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
var matchedNode ast.Node
ast.Inspect(node, func(node ast.Node) bool {
switch {
case node == nil || matchedNode != nil:
return false
case nodePosition(fileset, node).Line == lineNum:
matchedNode = node
return false
}
return true
})
return matchedNode
}
// In golang 1.9 the line number changed from being the line where the statement
// ended to the line where the statement began.
func nodePosition(fileset *token.FileSet, node ast.Node) token.Position {
if goVersionBefore19 {
return fileset.Position(node.End())
}
return fileset.Position(node.Pos())
}
var goVersionBefore19 = func() bool {
version := runtime.Version()
// not a release version
if !strings.HasPrefix(version, "go") {
return false
}
version = strings.TrimPrefix(version, "go")
parts := strings.Split(version, ".")
if len(parts) < 2 {
return false
}
minor, err := strconv.ParseInt(parts[1], 10, 32)
return err == nil && parts[0] == "1" && minor < 9
}()
func getCallExprArgs(node ast.Node) ([]ast.Expr, error) {
visitor := &callExprVisitor{}
ast.Walk(visitor, node)
if visitor.expr == nil {
return nil, errors.New("failed to find call expression")
}
debug("callExpr: %s", debugFormatNode{visitor.expr})
return visitor.expr.Args, nil
}
type callExprVisitor struct {
expr *ast.CallExpr
}
func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor {
if v.expr != nil || node == nil {
return nil
}
debug("visit: %s", debugFormatNode{node})
switch typed := node.(type) {
case *ast.CallExpr:
v.expr = typed
return nil
case *ast.DeferStmt:
ast.Walk(v, typed.Call.Fun)
return nil
}
return v
}
// FormatNode using go/format.Node and return the result as a string
func FormatNode(node ast.Node) (string, error) {
buf := new(bytes.Buffer)
err := format.Node(buf, token.NewFileSet(), node)
return buf.String(), err
}
var debugEnabled = os.Getenv("GOTESTTOOLS_DEBUG") != ""
func debug(format string, args ...interface{}) { func debug(format string, args ...interface{}) {
if debugEnabled { if debugEnabled {
@ -159,5 +162,5 @@ func (n debugFormatNode) String() string {
if err != nil { if err != nil {
return fmt.Sprintf("failed to format %s: %s", n.Node, err) return fmt.Sprintf("failed to format %s: %s", n.Node, err)
} }
return out return fmt.Sprintf("(%T) %s", n.Node, out)
} }

39
vendor/gotest.tools/poll/check.go vendored Normal file
View file

@ -0,0 +1,39 @@
package poll
import (
"net"
"os"
)
// Check is a function which will be used as check for the WaitOn method.
type Check func(t LogT) Result
// FileExists looks on filesystem and check that path exists.
func FileExists(path string) Check {
return func(t LogT) Result {
_, err := os.Stat(path)
if os.IsNotExist(err) {
t.Logf("waiting on file %s to exist", path)
return Continue("file %s does not exist", path)
}
if err != nil {
return Error(err)
}
return Success()
}
}
// Connection try to open a connection to the address on the
// named network. See net.Dial for a description of the network and
// address parameters.
func Connection(network, address string) Check {
return func(t LogT) Result {
_, err := net.Dial(network, address)
if err != nil {
t.Logf("waiting on socket %s://%s to be available...", network, address)
return Continue("socket %s://%s not available", network, address)
}
return Success()
}
}

View file

@ -104,7 +104,7 @@ func Error(err error) Result {
// WaitOn a condition or until a timeout. Poll by calling check and exit when // WaitOn a condition or until a timeout. Poll by calling check and exit when
// check returns a done Result. To fail a test and exit polling with an error // check returns a done Result. To fail a test and exit polling with an error
// return a error result. // return a error result.
func WaitOn(t TestingT, check func(t LogT) Result, pollOps ...SettingOp) { func WaitOn(t TestingT, check Check, pollOps ...SettingOp) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }

View file

@ -19,17 +19,29 @@ type skipT interface {
Log(args ...interface{}) Log(args ...interface{})
} }
// Result of skip function
type Result interface {
Skip() bool
Message() string
}
type helperT interface { type helperT interface {
Helper() Helper()
} }
// BoolOrCheckFunc can be a bool or func() bool, other types will panic // BoolOrCheckFunc can be a bool, func() bool, or func() Result. Other types will panic
type BoolOrCheckFunc interface{} type BoolOrCheckFunc interface{}
// If the condition expression evaluates to true, or the condition function returns // If the condition expression evaluates to true, skip the test.
// true, skip the test. //
// The condition argument may be one of three types: bool, func() bool, or
// func() SkipResult.
// When called with a bool, the test will be skip if the condition evaluates to true.
// When called with a func() bool, the test will be skip if the function returns true.
// When called with a func() Result, the test will be skip if the Skip method
// of the result returns true.
// The skip message will contain the source code of the expression. // The skip message will contain the source code of the expression.
// Extra message text can be passed as a format string with args // Extra message text can be passed as a format string with args.
func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) { func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
@ -41,12 +53,18 @@ func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) {
if check() { if check() {
t.Skip(format.WithCustomMessage(getFunctionName(check), msgAndArgs...)) t.Skip(format.WithCustomMessage(getFunctionName(check), msgAndArgs...))
} }
case func() Result:
result := check()
if result.Skip() {
msg := getFunctionName(check) + ": " + result.Message()
t.Skip(format.WithCustomMessage(msg, msgAndArgs...))
}
default: default:
panic(fmt.Sprintf("invalid type for condition arg: %T", check)) panic(fmt.Sprintf("invalid type for condition arg: %T", check))
} }
} }
func getFunctionName(function func() bool) string { func getFunctionName(function interface{}) string {
funcPath := runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name() funcPath := runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name()
return strings.SplitN(path.Base(funcPath), ".", 2)[1] return strings.SplitN(path.Base(funcPath), ".", 2)[1]
} }