mirror of https://github.com/docker/cli.git
Add gotestyourself/poll
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
0426ea1443
commit
683b6226ed
|
@ -21,7 +21,7 @@ github.com/gogo/protobuf v0.4
|
||||||
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4
|
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
github.com/gotestyourself/gotestyourself v1.0.0
|
github.com/gotestyourself/gotestyourself v1.1.0
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/Microsoft/go-winio v0.4.4
|
github.com/Microsoft/go-winio v0.4.4
|
||||||
|
|
|
@ -18,10 +18,11 @@ patterns.
|
||||||
a program to summarize `go test` output and test failures
|
a program to summarize `go test` output and test failures
|
||||||
* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) -
|
* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) -
|
||||||
execute binaries and test the output
|
execute binaries and test the output
|
||||||
|
* [poll](http://godoc.org/github.com/gotestyourself/gotestyourself/poll) -
|
||||||
|
test asynchronous code by polling until a desired state is reached
|
||||||
* [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) -
|
* [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) -
|
||||||
skip tests based on conditions
|
skip tests based on conditions
|
||||||
|
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
* [testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and
|
* [testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and
|
||||||
|
|
|
@ -37,8 +37,8 @@ func update(t require.TestingT, filename string, actual []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert compares the actual content to the expected content in the golden file.
|
// Assert compares the actual content to the expected content in the golden file.
|
||||||
// If `--update-golden` is set then the actual content is written to the golden
|
// If the `-test.update-golden` flag is set then the actual content is written
|
||||||
// file.
|
// to the golden file.
|
||||||
// Returns whether the assertion was successful (true) or not (false)
|
// Returns whether the assertion was successful (true) or not (false)
|
||||||
func Assert(t require.TestingT, actual string, filename string, msgAndArgs ...interface{}) bool {
|
func Assert(t require.TestingT, actual string, filename string, msgAndArgs ...interface{}) bool {
|
||||||
expected := Get(t, filename)
|
expected := Get(t, filename)
|
||||||
|
@ -60,8 +60,8 @@ func Assert(t require.TestingT, actual string, filename string, msgAndArgs ...in
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssertBytes compares the actual result to the expected result in the golden
|
// AssertBytes compares the actual result to the expected result in the golden
|
||||||
// file. If `--update-golden` is set then the actual content is written to the
|
// file. If the `-test.update-golden` flag is set then the actual content is
|
||||||
// golden file.
|
// written to the golden file.
|
||||||
// Returns whether the assertion was successful (true) or not (false)
|
// Returns whether the assertion was successful (true) or not (false)
|
||||||
// nolint: lll
|
// nolint: lll
|
||||||
func AssertBytes(t require.TestingT, actual []byte, filename string, msgAndArgs ...interface{}) bool {
|
func AssertBytes(t require.TestingT, actual []byte, filename string, msgAndArgs ...interface{}) bool {
|
||||||
|
|
|
@ -18,10 +18,8 @@ type testingT interface {
|
||||||
Fatalf(string, ...interface{})
|
Fatalf(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// None is a token to inform Result.Assert that the output should be empty
|
// None is a token to inform Result.Assert that the output should be empty
|
||||||
None string = "<NOTHING>"
|
const None string = "[NOTHING]"
|
||||||
)
|
|
||||||
|
|
||||||
type lockedBuffer struct {
|
type lockedBuffer struct {
|
||||||
m sync.RWMutex
|
m sync.RWMutex
|
||||||
|
@ -170,8 +168,7 @@ func (r *Result) Combined() string {
|
||||||
return r.outBuffer.String() + r.errBuffer.String()
|
return r.outBuffer.String() + r.errBuffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExitError sets Error and ExitCode based on Error
|
func (r *Result) setExitError(err error) {
|
||||||
func (r *Result) SetExitError(err error) {
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -196,7 +193,7 @@ func Command(command string, args ...string) Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCmd runs a command and returns a Result
|
// RunCmd runs a command and returns a Result
|
||||||
func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *Result {
|
func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result {
|
||||||
for _, op := range cmdOperators {
|
for _, op := range cmdOperators {
|
||||||
op(&cmd)
|
op(&cmd)
|
||||||
}
|
}
|
||||||
|
@ -207,7 +204,7 @@ func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *Result {
|
||||||
return WaitOnCmd(cmd.Timeout, result)
|
return WaitOnCmd(cmd.Timeout, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCommand parses a command line and runs it, returning a result
|
// RunCommand runs a command with default options, and returns a result
|
||||||
func RunCommand(command string, args ...string) *Result {
|
func RunCommand(command string, args ...string) *Result {
|
||||||
return RunCmd(Command(command, args...))
|
return RunCmd(Command(command, args...))
|
||||||
}
|
}
|
||||||
|
@ -218,7 +215,7 @@ func StartCmd(cmd Cmd) *Result {
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
result.SetExitError(result.Cmd.Start())
|
result.setExitError(result.Cmd.Start())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +250,7 @@ func buildCmd(cmd Cmd) *Result {
|
||||||
// only wait until the timeout.
|
// only wait until the timeout.
|
||||||
func WaitOnCmd(timeout time.Duration, result *Result) *Result {
|
func WaitOnCmd(timeout time.Duration, result *Result) *Result {
|
||||||
if timeout == time.Duration(0) {
|
if timeout == time.Duration(0) {
|
||||||
result.SetExitError(result.Cmd.Wait())
|
result.setExitError(result.Cmd.Wait())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +268,7 @@ func WaitOnCmd(timeout time.Duration, result *Result) *Result {
|
||||||
}
|
}
|
||||||
result.Timeout = true
|
result.Timeout = true
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
result.SetExitError(err)
|
result.setExitError(err)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetExitCode returns the ExitStatus of a process from the error returned by
|
// getExitCode returns the ExitStatus of a process from the error returned by
|
||||||
// exec.Run(). If the exit status could not be parsed an error is returned.
|
// exec.Run(). If the exit status could not be parsed an error is returned.
|
||||||
func GetExitCode(err error) (int, error) {
|
func getExitCode(err error) (int, error) {
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
return procExit.ExitStatus(), nil
|
return procExit.ExitStatus(), nil
|
||||||
|
@ -22,7 +22,7 @@ func processExitCode(err error) (exitCode int) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
exitCode, exiterr := GetExitCode(err)
|
exitCode, exiterr := getExitCode(err)
|
||||||
if exiterr != nil {
|
if exiterr != nil {
|
||||||
// TODO: Fix this so we check the error's text.
|
// TODO: Fix this so we check the error's text.
|
||||||
// we've failed to retrieve exit code, so we set it to 127
|
// we've failed to retrieve exit code, so we set it to 127
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package icmd
|
||||||
|
|
||||||
|
// CmdOp is an operation which modified a Cmd structure used to execute commands
|
||||||
|
type CmdOp func(*Cmd)
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*Package poll provides tools for testing asynchronous code.
|
||||||
|
*/
|
||||||
|
package poll
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestingT is the subset of testing.T used by WaitOn
|
||||||
|
type TestingT interface {
|
||||||
|
LogT
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogT is a logging interface that is passed to the WaitOn check function
|
||||||
|
type LogT interface {
|
||||||
|
Log(args ...interface{})
|
||||||
|
Logf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings are used to configure the behaviour of WaitOn
|
||||||
|
type Settings struct {
|
||||||
|
// Timeout is the maximum time to wait for the condition. Defaults to 10s
|
||||||
|
Timeout time.Duration
|
||||||
|
// Delay is the time to sleep between checking the condition. Detaults to
|
||||||
|
// 1ms
|
||||||
|
Delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() *Settings {
|
||||||
|
return &Settings{Timeout: 10 * time.Second, Delay: time.Millisecond}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingOp is a function which accepts and modifies Settings
|
||||||
|
type SettingOp func(config *Settings)
|
||||||
|
|
||||||
|
// WithDelay sets the delay to wait between polls
|
||||||
|
func WithDelay(delay time.Duration) SettingOp {
|
||||||
|
return func(config *Settings) {
|
||||||
|
config.Delay = delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout sets the timeout
|
||||||
|
func WithTimeout(timeout time.Duration) SettingOp {
|
||||||
|
return func(config *Settings) {
|
||||||
|
config.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result of a check performed by WaitOn
|
||||||
|
type Result interface {
|
||||||
|
// Error indicates that the check failed and polling should stop, and the
|
||||||
|
// the has failed
|
||||||
|
Error() error
|
||||||
|
// Done indicates that polling should stop, and the test should proceed
|
||||||
|
Done() bool
|
||||||
|
// Message provides the most recent state when polling has not completed
|
||||||
|
Message() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
done bool
|
||||||
|
message string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r result) Done() bool {
|
||||||
|
return r.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r result) Message() string {
|
||||||
|
return r.message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r result) Error() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue returns a Result that indicates to WaitOn that it should continue
|
||||||
|
// polling. The message text will be used as the failure message if the timeout
|
||||||
|
// is reached.
|
||||||
|
func Continue(message string, args ...interface{}) Result {
|
||||||
|
return result{message: fmt.Sprintf(message, args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success returns a Result where Done() returns true, which indicates to WaitOn
|
||||||
|
// that it should stop polling and exit without an error.
|
||||||
|
func Success() Result {
|
||||||
|
return result{done: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a Result that indicates to WaitOn that it should fail the test
|
||||||
|
// and stop polling.
|
||||||
|
func Error(err error) Result {
|
||||||
|
return result{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// return a error result.
|
||||||
|
func WaitOn(t TestingT, check func(t LogT) Result, pollOps ...SettingOp) {
|
||||||
|
config := defaultConfig()
|
||||||
|
for _, pollOp := range pollOps {
|
||||||
|
pollOp(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMessage string
|
||||||
|
after := time.After(config.Timeout)
|
||||||
|
chResult := make(chan Result)
|
||||||
|
for {
|
||||||
|
go func() {
|
||||||
|
chResult <- check(t)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-after:
|
||||||
|
if lastMessage == "" {
|
||||||
|
lastMessage = "first check never completed"
|
||||||
|
}
|
||||||
|
t.Fatalf("timeout hit after %s: %s", config.Timeout, lastMessage)
|
||||||
|
case result := <-chResult:
|
||||||
|
switch {
|
||||||
|
case result.Error() != nil:
|
||||||
|
t.Fatalf("polling check failed: %s", result.Error())
|
||||||
|
case result.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(config.Delay)
|
||||||
|
lastMessage = result.Message()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue