github.com/containers/podman/v5@v5.1.0-rc1/test/utils/matchers.go (about) 1 package utils 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 "github.com/onsi/gomega/format" 9 "github.com/onsi/gomega/gexec" 10 "github.com/onsi/gomega/types" 11 ) 12 13 type podmanSession interface { 14 ExitCode() int 15 ErrorToString() string 16 } 17 18 type ExitMatcher struct { 19 types.GomegaMatcher 20 ExpectedExitCode int 21 ExitCode int 22 ExpectedStderr string 23 msg string 24 } 25 26 // ExitWithError checks both exit code and stderr, fails if either does not match 27 // Modeled after the gomega Exit() matcher and also operates on sessions. 28 func ExitWithError(expectExitCode int, expectStderr string) *ExitMatcher { 29 return &ExitMatcher{ExpectedExitCode: expectExitCode, ExpectedStderr: expectStderr} 30 } 31 32 // Match follows gexec.Matcher interface. 33 func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error) { 34 session, ok := actual.(podmanSession) 35 if !ok { 36 return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") 37 } 38 39 matcher.ExitCode = session.ExitCode() 40 if matcher.ExitCode == -1 { 41 matcher.msg = "Expected process to exit. It did not." 42 return false, nil 43 } 44 45 // Check exit code first. If it's not what we want, there's no point 46 // in checking error substrings 47 if matcher.ExitCode != matcher.ExpectedExitCode { 48 matcher.msg = fmt.Sprintf("Command exited with status %d (expected %d)", matcher.ExitCode, matcher.ExpectedExitCode) 49 return false, nil 50 } 51 52 if matcher.ExpectedStderr != "" { 53 if !strings.Contains(session.ErrorToString(), matcher.ExpectedStderr) { 54 matcher.msg = fmt.Sprintf("Command exited %d as expected, but did not emit '%s'", matcher.ExitCode, matcher.ExpectedStderr) 55 return false, nil 56 } 57 } else { 58 if session.ErrorToString() != "" { 59 matcher.msg = "Command exited with expected exit status, but emitted unwanted stderr" 60 return false, nil 61 } 62 } 63 64 return true, nil 65 } 66 67 func (matcher *ExitMatcher) FailureMessage(_ interface{}) (message string) { 68 return matcher.msg 69 } 70 71 func (matcher *ExitMatcher) NegatedFailureMessage(_ interface{}) (message string) { 72 panic("There is no conceivable reason to call Not(ExitWithError) !") 73 } 74 75 func (matcher *ExitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { 76 session, ok := actual.(*gexec.Session) 77 if ok { 78 return session.ExitCode() == -1 79 } 80 return true 81 } 82 83 // ExitCleanly asserts that a PodmanSession exits 0 and with no stderr 84 func ExitCleanly() types.GomegaMatcher { 85 return &exitCleanlyMatcher{} 86 } 87 88 type exitCleanlyMatcher struct { 89 msg string 90 } 91 92 func (matcher *exitCleanlyMatcher) Match(actual interface{}) (success bool, err error) { 93 session, ok := actual.(podmanSession) 94 if !ok { 95 return false, fmt.Errorf("ExitCleanly must be passed a PodmanSession; Got:\n %+v\n%q", actual, format.Object(actual, 1)) 96 } 97 98 exitcode := session.ExitCode() 99 stderr := session.ErrorToString() 100 if exitcode != 0 { 101 matcher.msg = fmt.Sprintf("Command failed with exit status %d", exitcode) 102 if stderr != "" { 103 matcher.msg += ". See above for error message." 104 } 105 return false, nil 106 } 107 108 // Exit status is 0. Now check for anything on stderr 109 if stderr != "" { 110 matcher.msg = fmt.Sprintf("Unexpected warnings seen on stderr: %q", stderr) 111 return false, nil 112 } 113 114 return true, nil 115 } 116 117 func (matcher *exitCleanlyMatcher) FailureMessage(_ interface{}) (message string) { 118 return matcher.msg 119 } 120 121 func (matcher *exitCleanlyMatcher) NegatedFailureMessage(_ interface{}) (message string) { 122 // FIXME - I see no situation in which we could ever want this? 123 return matcher.msg + " (NOT!)" 124 } 125 126 type ValidJSONMatcher struct { 127 types.GomegaMatcher 128 } 129 130 func BeValidJSON() *ValidJSONMatcher { 131 return &ValidJSONMatcher{} 132 } 133 134 func (matcher *ValidJSONMatcher) Match(actual interface{}) (success bool, err error) { 135 s, ok := actual.(string) 136 if !ok { 137 return false, fmt.Errorf("ValidJSONMatcher expects a string, not %q", actual) 138 } 139 140 var i interface{} 141 if err := json.Unmarshal([]byte(s), &i); err != nil { 142 return false, err 143 } 144 return true, nil 145 } 146 147 func (matcher *ValidJSONMatcher) FailureMessage(actual interface{}) (message string) { 148 return format.Message(actual, "to be valid JSON") 149 } 150 151 func (matcher *ValidJSONMatcher) NegatedFailureMessage(actual interface{}) (message string) { 152 return format.Message(actual, "to _not_ be valid JSON") 153 }