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>"
 | 
			
		||||
)
 | 
			
		||||
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