github.com/paketo-buildpacks/packit@v1.3.2-0.20211206231111-86b75c657449/matchers/contain_lines.go (about) 1 package matchers 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "strings" 8 9 "github.com/onsi/gomega/format" 10 "github.com/onsi/gomega/types" 11 ) 12 13 func ContainLines(expected ...interface{}) types.GomegaMatcher { 14 return &containLinesMatcher{ 15 expected: expected, 16 } 17 } 18 19 type containLinesMatcher struct { 20 expected []interface{} 21 } 22 23 func (matcher *containLinesMatcher) Match(actual interface{}) (success bool, err error) { 24 _, ok := actual.(string) 25 if !ok { 26 _, ok := actual.(fmt.Stringer) 27 if !ok { 28 return false, fmt.Errorf("ContainLinesMatcher requires a string or fmt.Stringer. Got actual: %s", format.Object(actual, 1)) 29 } 30 } 31 32 actualLines := matcher.lines(actual) 33 34 for currentActualLineIndex := 0; currentActualLineIndex < len(actualLines); currentActualLineIndex++ { 35 currentActualLine := actualLines[currentActualLineIndex] 36 currentExpectedLine := matcher.expected[currentActualLineIndex] 37 38 match, err := matcher.compare(currentActualLine, currentExpectedLine) 39 if err != nil { 40 return false, err 41 } 42 43 if match { 44 if currentActualLineIndex+1 == len(matcher.expected) { 45 return true, nil 46 } 47 } else { 48 if len(actualLines) > 1 { 49 actualLines = actualLines[1:] 50 currentActualLineIndex = -1 51 } 52 } 53 } 54 55 return false, nil 56 } 57 58 func (matcher *containLinesMatcher) compare(actual string, expected interface{}) (bool, error) { 59 if m, ok := expected.(types.GomegaMatcher); ok { 60 match, err := m.Match(actual) 61 if err != nil { 62 return false, err 63 } 64 65 return match, nil 66 } 67 68 return reflect.DeepEqual(actual, expected), nil 69 } 70 71 func (matcher *containLinesMatcher) lines(actual interface{}) []string { 72 raw, ok := actual.(string) 73 if !ok { 74 raw = actual.(fmt.Stringer).String() 75 } 76 77 re := regexp.MustCompile(`^\[[a-z]+\]\s`) 78 79 var lines []string 80 for _, line := range strings.Split(raw, "\n") { 81 lines = append(lines, re.ReplaceAllString(line, "")) 82 } 83 84 return lines 85 } 86 87 func (matcher *containLinesMatcher) FailureMessage(actual interface{}) (message string) { 88 actualLines := "\n" + strings.Join(matcher.lines(actual), "\n") 89 missing := matcher.linesMatching(actual, false) 90 if len(missing) > 0 { 91 return fmt.Sprintf("Expected\n%s\nto contain lines\n%s\nbut missing\n%s", format.Object(actualLines, 1), format.Object(matcher.expected, 1), format.Object(missing, 1)) 92 } 93 94 return fmt.Sprintf("Expected\n%s\nto contain lines\n%s\nall lines appear, but may be misordered", format.Object(actualLines, 1), format.Object(matcher.expected, 1)) 95 } 96 97 func (matcher *containLinesMatcher) NegatedFailureMessage(actual interface{}) (message string) { 98 actualLines := "\n" + strings.Join(matcher.lines(actual), "\n") 99 missing := matcher.linesMatching(actual, true) 100 101 return fmt.Sprintf("Expected\n%s\nnot to contain lines\n%s\nbut includes\n%s", format.Object(actualLines, 1), format.Object(matcher.expected, 1), format.Object(missing, 1)) 102 } 103 104 func (matcher *containLinesMatcher) linesMatching(actual interface{}, matching bool) []interface{} { 105 var set []interface{} 106 for _, expected := range matcher.expected { 107 var match bool 108 for _, line := range matcher.lines(actual) { 109 if ok, _ := matcher.compare(line, expected); ok { 110 match = true 111 } 112 } 113 114 if match == matching { 115 set = append(set, expected) 116 } 117 } 118 119 return set 120 }