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/gorilla/context 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/mattn/go-shellwords v1.0.3
|
||||
github.com/Microsoft/go-winio v0.4.4
|
||||
|
|
|
@ -18,10 +18,11 @@ patterns.
|
|||
a program to summarize `go test` output and test failures
|
||||
* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) -
|
||||
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 tests based on conditions
|
||||
|
||||
|
||||
## Related
|
||||
|
||||
* [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.
|
||||
// If `--update-golden` is set then the actual content is written to the golden
|
||||
// file.
|
||||
// If the `-test.update-golden` flag is set then the actual content is written
|
||||
// to the golden file.
|
||||
// Returns whether the assertion was successful (true) or not (false)
|
||||
func Assert(t require.TestingT, actual string, filename string, msgAndArgs ...interface{}) bool {
|
||||
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
|
||||
// file. If `--update-golden` is set then the actual content is written to the
|
||||
// golden file.
|
||||
// file. If the `-test.update-golden` flag is set then the actual content is
|
||||
// written to the golden file.
|
||||
// Returns whether the assertion was successful (true) or not (false)
|
||||
// nolint: lll
|
||||
func AssertBytes(t require.TestingT, actual []byte, filename string, msgAndArgs ...interface{}) bool {
|
||||
|
|
|
@ -18,10 +18,8 @@ type testingT interface {
|
|||
Fatalf(string, ...interface{})
|
||||
}
|
||||
|
||||
const (
|
||||
// None is a token to inform Result.Assert that the output should be empty
|
||||
None string = "<NOTHING>"
|
||||
)
|
||||
// None is a token to inform Result.Assert that the output should be empty
|
||||
const None string = "[NOTHING]"
|
||||
|
||||
type lockedBuffer struct {
|
||||
m sync.RWMutex
|
||||
|
@ -170,8 +168,7 @@ func (r *Result) Combined() 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 {
|
||||
return
|
||||
}
|
||||
|
@ -196,7 +193,7 @@ func Command(command string, args ...string) Cmd {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
op(&cmd)
|
||||
}
|
||||
|
@ -207,7 +204,7 @@ func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *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 {
|
||||
return RunCmd(Command(command, args...))
|
||||
}
|
||||
|
@ -218,7 +215,7 @@ func StartCmd(cmd Cmd) *Result {
|
|||
if result.Error != nil {
|
||||
return result
|
||||
}
|
||||
result.SetExitError(result.Cmd.Start())
|
||||
result.setExitError(result.Cmd.Start())
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -253,7 +250,7 @@ func buildCmd(cmd Cmd) *Result {
|
|||
// only wait until the timeout.
|
||||
func WaitOnCmd(timeout time.Duration, result *Result) *Result {
|
||||
if timeout == time.Duration(0) {
|
||||
result.SetExitError(result.Cmd.Wait())
|
||||
result.setExitError(result.Cmd.Wait())
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -271,7 +268,7 @@ func WaitOnCmd(timeout time.Duration, result *Result) *Result {
|
|||
}
|
||||
result.Timeout = true
|
||||
case err := <-done:
|
||||
result.SetExitError(err)
|
||||
result.setExitError(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"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.
|
||||
func GetExitCode(err error) (int, error) {
|
||||
func getExitCode(err error) (int, error) {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
|
@ -22,7 +22,7 @@ func processExitCode(err error) (exitCode int) {
|
|||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
exitCode, exiterr := GetExitCode(err)
|
||||
exitCode, exiterr := getExitCode(err)
|
||||
if exiterr != nil {
|
||||
// TODO: Fix this so we check the error's text.
|
||||
// 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