go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/logging/memlogger/assertions.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package memlogger 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "strings" 22 23 "go.chromium.org/luci/common/logging" 24 ) 25 26 // ShouldHaveLog is a goconvey custom assertion which asserts that the logger has 27 // received a log. It takes up to 3 arguments. 28 // 29 // `actual` should either be a *MemLogger or a context.Context containing 30 // a *MemLogger. 31 // 32 // argument 1 (expected[0]) is the log level. 33 // argument 2 (expected[1]) is a substring the message. If omitted or the empty string, this value is not checked. 34 // argument 3 (expected[2]) is the fields data. If omitted or nil, this value is not checked. 35 func ShouldHaveLog(actual any, expected ...any) string { 36 var ok bool 37 var m *MemLogger 38 39 switch x := actual.(type) { 40 case *MemLogger: 41 m = x 42 case context.Context: 43 if m, ok = logging.Get(x).(*MemLogger); !ok { 44 return "context does not contain a *MemLogger" 45 } 46 default: 47 return fmt.Sprintf("actual value must be a *MemLogger or context.Context, not %T", actual) 48 } 49 50 level := logging.Level(0) 51 msg := "" 52 data := map[string]any(nil) 53 54 switch len(expected) { 55 case 3: 56 57 data, ok = expected[2].(map[string]any) 58 if !ok { 59 fields, ok := expected[2].(logging.Fields) 60 if !ok { 61 return fmt.Sprintf( 62 "Third argument to this assertion must be an map[string]any (was %T)", expected[2]) 63 } 64 data = fields 65 } 66 fallthrough 67 case 2: 68 69 msg, ok = expected[1].(string) 70 if !ok { 71 return fmt.Sprintf( 72 "Second argument to this assertion must be a string (was %T)", expected[1]) 73 } 74 fallthrough 75 case 1: 76 level, ok = expected[0].(logging.Level) 77 if !ok { 78 return "First argument to this assertion must be a logging.Level" 79 } 80 81 default: 82 return fmt.Sprintf( 83 "This assertion requires at least 1 comparison value (you provided %d)", len(expected)) 84 } 85 86 predicate := func(e *LogEntry) bool { 87 switch { 88 case e.Level != level: 89 return false 90 91 case msg != "" && !strings.Contains(e.Msg, msg): 92 return false 93 94 case data != nil && !reflect.DeepEqual(data, e.Data): 95 return false 96 97 default: 98 return true 99 } 100 } 101 if m.HasFunc(predicate) { 102 return "" 103 } 104 105 logString := fmt.Sprintf("Level %s", level) 106 if msg != "" { 107 logString += fmt.Sprintf(" message '%s'", msg) 108 } 109 110 if data != nil { 111 logString += fmt.Sprintf(" data '%v'", data) 112 } 113 114 return fmt.Sprintf("No log matching %s", logString) 115 } 116 117 // ShouldNotHaveLog is the inverse of ShouldHaveLog. It asserts that the logger 118 // has not seen such a log. 119 func ShouldNotHaveLog(actual any, expected ...any) string { 120 res := ShouldHaveLog(actual, expected...) 121 122 if res != "" { 123 return "" 124 } 125 126 return "Found a log, but wasn't supposed to." 127 }