podman/test/utils/matchers.go

178 lines
4.8 KiB
Go

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 matches when assertion is > argument. Default 0
// Modeled after the gomega Exit() matcher and also operates on sessions.
func ExitWithError(expectations ...interface{}) *ExitMatcher {
exitCode := 0
expectStderr := ""
// FIXME: once all ExitWithError()s have been migrated to new form,
// change interface to (int, ...string)
if len(expectations) > 0 {
var ok bool
exitCode, ok = expectations[0].(int)
if !ok {
panic("ExitWithError(): first arg, if present, must be an int")
}
if len(expectations) > 1 {
expectStderr, ok = expectations[1].(string)
if !ok {
panic("ExitWithError(): second arg, if present, must be a string")
}
}
}
return &ExitMatcher{ExpectedExitCode: exitCode, 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
}
// FIXME: temporary until all ExitWithError()s are migrated
// to new mandatory-int form.
if matcher.ExpectedExitCode == 0 {
if matcher.ExitCode == 0 {
matcher.msg = "Expected process to exit nonzero. It did not."
return false, nil
}
return true, 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
}
}
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")
}