package utils import ( "encoding/json" "fmt" "strings" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/types" ) type podmanSession interface { ExitCode() int ErrorToString() string } type ExitMatcher struct { types.GomegaMatcher ExpectedExitCode int ExitCode int ExpectedStderr string msg string } // ExitWithError checks both exit code and stderr, fails if either does not match // Modeled after the gomega Exit() matcher and also operates on sessions. func ExitWithError(expectExitCode int, expectStderr string) *ExitMatcher { return &ExitMatcher{ExpectedExitCode: expectExitCode, ExpectedStderr: expectStderr} } // Match follows gexec.Matcher interface. func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error) { session, ok := actual.(podmanSession) if !ok { return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") } matcher.ExitCode = session.ExitCode() if matcher.ExitCode == -1 { matcher.msg = "Expected process to exit. It did not." return false, nil } // Check exit code first. If it's not what we want, there's no point // in checking error substrings if matcher.ExitCode != matcher.ExpectedExitCode { matcher.msg = fmt.Sprintf("Command exited with status %d (expected %d)", matcher.ExitCode, matcher.ExpectedExitCode) return false, nil } if matcher.ExpectedStderr != "" { if !strings.Contains(session.ErrorToString(), matcher.ExpectedStderr) { matcher.msg = fmt.Sprintf("Command exited %d as expected, but did not emit '%s'", matcher.ExitCode, matcher.ExpectedStderr) return false, nil } } else { if session.ErrorToString() != "" { matcher.msg = "Command exited with expected exit status, but emitted unwanted stderr" return false, nil } } return true, nil } func (matcher *ExitMatcher) FailureMessage(_ interface{}) (message string) { return matcher.msg } func (matcher *ExitMatcher) NegatedFailureMessage(_ interface{}) (message string) { panic("There is no conceivable reason to call Not(ExitWithError) !") } func (matcher *ExitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { session, ok := actual.(*gexec.Session) if ok { return session.ExitCode() == -1 } return true } // ExitCleanly asserts that a PodmanSession exits 0 and with no stderr func ExitCleanly() types.GomegaMatcher { return &exitCleanlyMatcher{} } type exitCleanlyMatcher struct { msg string } func (matcher *exitCleanlyMatcher) Match(actual interface{}) (success bool, err error) { session, ok := actual.(podmanSession) if !ok { return false, fmt.Errorf("ExitCleanly must be passed a PodmanSession; Got:\n %+v\n%q", actual, format.Object(actual, 1)) } exitcode := session.ExitCode() stderr := session.ErrorToString() if exitcode != 0 { matcher.msg = fmt.Sprintf("Command failed with exit status %d", exitcode) if stderr != "" { matcher.msg += ". See above for error message." } return false, nil } // Exit status is 0. Now check for anything on stderr if stderr != "" { matcher.msg = fmt.Sprintf("Unexpected warnings seen on stderr: %q", stderr) return false, nil } return true, nil } func (matcher *exitCleanlyMatcher) FailureMessage(_ interface{}) (message string) { return matcher.msg } func (matcher *exitCleanlyMatcher) NegatedFailureMessage(_ interface{}) (message string) { // FIXME - I see no situation in which we could ever want this? return matcher.msg + " (NOT!)" } type ValidJSONMatcher struct { types.GomegaMatcher } func BeValidJSON() *ValidJSONMatcher { return &ValidJSONMatcher{} } func (matcher *ValidJSONMatcher) Match(actual interface{}) (success bool, err error) { s, ok := actual.(string) if !ok { return false, fmt.Errorf("ValidJSONMatcher expects a string, not %q", actual) } var i interface{} if err := json.Unmarshal([]byte(s), &i); err != nil { return false, err } return true, nil } func (matcher *ValidJSONMatcher) FailureMessage(actual interface{}) (message string) { return format.Message(actual, "to be valid JSON") } func (matcher *ValidJSONMatcher) NegatedFailureMessage(actual interface{}) (message string) { return format.Message(actual, "to _not_ be valid JSON") }