blob: 6f2eeb3bae2614f79768f5aba2526ea6fbeca914 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package memlogger
import (
"context"
"fmt"
"reflect"
"strings"
"go.chromium.org/luci/common/logging"
)
// ShouldHaveLog is a goconvey custom assertion which asserts that the logger has
// received a log. It takes up to 3 arguments.
//
// `actual` should either be a *MemLogger or a context.Context containing
// a *MemLogger.
//
// argument 1 (expected[0]) is the log level.
// argument 2 (expected[1]) is a substring the message. If omitted or the empty string, this value is not checked.
// argument 3 (expected[2]) is the fields data. If omitted or nil, this value is not checked.
func ShouldHaveLog(actual interface{}, expected ...interface{}) string {
var ok bool
var m *MemLogger
switch x := actual.(type) {
case *MemLogger:
m = x
case context.Context:
if m, ok = logging.Get(x).(*MemLogger); !ok {
return "context does not contain a *MemLogger"
}
default:
return fmt.Sprintf("actual value must be a *MemLogger or context.Context, not %T", actual)
}
level := logging.Level(0)
msg := ""
data := map[string]interface{}(nil)
switch len(expected) {
case 3:
data, ok = expected[2].(map[string]interface{})
if !ok {
fields, ok := expected[2].(logging.Fields)
if !ok {
return fmt.Sprintf(
"Third argument to this assertion must be an map[string]interface{} (was %T)", expected[2])
}
data = fields
}
fallthrough
case 2:
msg, ok = expected[1].(string)
if !ok {
return fmt.Sprintf(
"Second argument to this assertion must be a string (was %T)", expected[1])
}
fallthrough
case 1:
level, ok = expected[0].(logging.Level)
if !ok {
return "First argument to this assertion must be a logging.Level"
}
default:
return fmt.Sprintf(
"This assertion requires at least 1 comparison value (you provided %d)", len(expected))
}
predicate := func(e *LogEntry) bool {
switch {
case e.Level != level:
return false
case msg != "" && !strings.Contains(e.Msg, msg):
return false
case data != nil && !reflect.DeepEqual(data, e.Data):
return false
default:
return true
}
}
if m.HasFunc(predicate) {
return ""
}
logString := fmt.Sprintf("Level %s", level)
if msg != "" {
logString += fmt.Sprintf(" message '%s'", msg)
}
if data != nil {
logString += fmt.Sprintf(" data '%v'", data)
}
return fmt.Sprintf("No log matching %s", logString)
}
// ShouldNotHaveLog is the inverse of ShouldHaveLog. It asserts that the logger
// has not seen such a log.
func ShouldNotHaveLog(actual interface{}, expected ...interface{}) string {
res := ShouldHaveLog(actual, expected...)
if res != "" {
return ""
}
return "Found a log, but wasn't supposed to."
}