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  }