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