Documentation
¶
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type FieldIgnoreFunc ¶
FieldIgnoreFunc is a function that decides which fields' values should be ignored in a log message.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger logs messages to a test runner, and can optionally report differences between log messages received and those expected.
Example ¶
/*
Copyright (2020) Cobalt Speech and Language Inc.
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 main
import (
"bytes"
"fmt"
)
// fakeRunner implements TestRunner, for testing the Logger.
type fakeRunner struct {
b bytes.Buffer
failed bool
}
func (r *fakeRunner) Fail() {
r.failed = true
}
func (r *fakeRunner) Log(args ...interface{}) {
fmt.Fprint(&r.b, args...)
}
func main() {
runner := &fakeRunner{}
logger, _ := NewLogger(runner)
logger.Error("msg", "There was a problem.", "data", 3.14)
logger.Debug("msg", "Here's some pertinent information.", "numCalls", 17)
logger.Done()
fmt.Println(runner.b.String())
}
Output: error {"msg":"There was a problem.","data":"3.14"} debug {"msg":"Here's some pertinent information.","numCalls":"17"}
func NewLogger ¶
func NewLogger(runner TestRunner, opts ...LoggerOption) (*Logger, error)
NewLogger creates a logger that reports all log messages to the provided test runner.
func (*Logger) Debug ¶
func (l *Logger) Debug(keyvals ...interface{})
Debug checks whether the Logger expected a debug log line next. If not, it's reported to the test runner.
func (*Logger) Done ¶
func (l *Logger) Done()
Done signals to the Logger that no more log methods will be called. It checks whether the Logger expected more log messages, and calls runner.Fail if any differences were reported.
func (*Logger) Error ¶
func (l *Logger) Error(keyvals ...interface{})
Error checks whether the Logger expected an error log line next. If not, it's reported to the test runner.
type LoggerOption ¶
func WithActualOutputFile ¶
func WithActualOutputFile(file string) LoggerOption
WithActualOutputFile specifies a file path where all actual log messages are written. If a truth file has been provided with WithTruthFile, the actual output file is only created if actual log messages differ from those in the truth file.
Example ¶
hypFile, remove, err := writeTemporaryFile(strings.Join([]string{
`error {"msg":"There was a problem.","data":"3.14"}`,
`debug {"msg":"Here's the number of calls.","numCalls":"18"}`,
}, "\n"))
if err != nil {
fmt.Println(err)
return
}
defer remove()
// Get a file we can use for the actual log output.
var actualFile string
actualFile, remove, err = writeTemporaryFile("")
if err != nil {
fmt.Println(err)
return
}
defer remove()
runner := fakeRunner{}
logger, err := NewLogger(&runner, WithTruthFile(hypFile), WithActualOutputFile(actualFile))
if err != nil {
fmt.Println(err)
return
}
logger.Error("msg", "There was a problem.", "data", 3.14)
logger.Debug("msg", "Here's some pertinent information.", "numCalls", 18) // doesn't match hyp
logger.Trace("msg", "This trace message shouldn't be here.")
logger.Done()
// Get the actual log output.
var actualBytes []byte
actualBytes, err = ioutil.ReadFile(actualFile)
if err != nil {
fmt.Println(err)
return
}
fmt.Print(runner.b.String())
fmt.Print(string(actualBytes))
fmt.Println(runner.failed)
Output: unexpected log message (-want +got): string( - `debug {"msg":"Here's the number of calls.","numCalls":"18"}`, + `debug {"msg":"Here's some pertinent information.","numCalls":"18"}`, ) unexpected log message (-want +got): string( - "", + `trace {"msg":"This trace message shouldn't be here."}`, ) error {"msg":"There was a problem.","data":"3.14"} debug {"msg":"Here's some pertinent information.","numCalls":"18"} trace {"msg":"This trace message shouldn't be here."} true
func WithFieldIgnoreFunc ¶
func WithFieldIgnoreFunc(ignorer FieldIgnoreFunc) LoggerOption
WithFieldIgnoreFunc provides the Logger with a function that returns a list of fields to ignore in each log message. The keys will still be checked, but not the values. The keys and values in the log message are formatted as strings before being provided to the FieldIgnorer.
If ignorer is nil, this option has no effect.
Example ¶
hypFile, remove, err := writeTemporaryFile(strings.Join([]string{
`trace {"msg":"An ID was generated.","id":"brh634381n1ts5ibr1eg"}`,
`debug {"msg":"This ID is deterministic.","id":"42"}`,
}, "\n"))
if err != nil {
fmt.Println(err)
return
}
defer remove()
runner := fakeRunner{}
ignorer := func(fields map[string]string) []string {
msg, ok := fields["msg"]
// Ignore only the "id" field in the non-deterministic ID log line.
if ok && msg == "An ID was generated." {
return []string{"id"}
}
return nil
}
logger, err := NewLogger(&runner, WithTruthFile(hypFile), WithFieldIgnoreFunc(ignorer))
if err != nil {
fmt.Println(err)
return
}
// This is not deterministic.
id := strconv.Itoa(rand.Intn(10000000))
logger.Trace("msg", "An ID was generated.", "id", id)
logger.Debug("msg", "This ID is deterministic.", "id", 12) // doesn't match hyp
logger.Done()
fmt.Print(strings.Replace(runner.b.String(), id, "<id removed>", 1))
Output: trace {"msg":"An ID was generated.","id":"<id removed>"} debug {"msg":"This ID is deterministic.","id":"12"} unexpected log message (-want +got): string( - `debug {"msg":"This ID is deterministic.","id":"42"}`, + `debug {"msg":"This ID is deterministic.","id":"12"}`, )
func WithTruthFile ¶
func WithTruthFile(file string) LoggerOption
WithTruthFile sets the Logger to compare each log message with the log text in the provided file. Each expected log line should be separated from the next by a newline. If there are any differences between expected and received log lines, they are reported to the test runner and the runner's Fail method will be called when Done is called (unless WithoutFailure is used in conjunction with this option).
This function does not handle cases where the logging output order is non-deterministic (e.g. if the function being tested uses multiple goroutines).
Example ¶
// Write an example file.
hypFile, remove, err := writeTemporaryFile(strings.Join([]string{
`error {"msg":"There was a problem.","data":"3.14"}`,
`debug {"msg":"Here's the number of calls.","numCalls":"17"}`,
}, "\n"))
if err != nil {
fmt.Println(err)
return
}
defer remove()
runner := fakeRunner{}
logger, err := NewLogger(&runner, WithTruthFile(hypFile))
if err != nil {
fmt.Println(err)
return
}
logger.Error("msg", "There was a problem.", "data", 3.14)
logger.Debug("msg", "Here's some pertinent information.", "numCalls", 17) // doesn't match hyp
logger.Trace("msg", "This trace message shouldn't be here.")
logger.Done()
fmt.Print(runner.b.String())
fmt.Println(runner.failed)
Output: error {"msg":"There was a problem.","data":"3.14"} debug {"msg":"Here's some pertinent information.","numCalls":"17"} unexpected log message (-want +got): string( - `debug {"msg":"Here's the number of calls.","numCalls":"17"}`, + `debug {"msg":"Here's some pertinent information.","numCalls":"17"}`, ) trace {"msg":"This trace message shouldn't be here."} unexpected log message (-want +got): string( - "", + `trace {"msg":"This trace message shouldn't be here."}`, ) true
func WithoutFailure ¶
func WithoutFailure() LoggerOption
WithoutFailure sets the Logger to not call Fail() on the test runner even if log messages are different than expected.
Example ¶
hypFile, remove, err := writeTemporaryFile(strings.Join([]string{
`error {"msg":"There was a problem.","data":"3.14"}`,
`debug {"msg":"Here's the number of calls.","numCalls":"19"}`,
}, "\n"))
if err != nil {
fmt.Println(err)
return
}
defer remove()
runner := fakeRunner{}
logger, err := NewLogger(&runner, WithTruthFile(hypFile), WithoutFailure())
if err != nil {
fmt.Println(err)
return
}
logger.Error("msg", "There was a problem.", "data", 3.14)
// This log message doesn't match, but we still want the test to pass.
logger.Debug("msg", "Here's some pertinent information.", "numCalls", 19)
logger.Done()
fmt.Println(runner.failed)
Output: false
type TestRunner ¶
type TestRunner interface {
Log(args ...interface{})
Fail()
}
TestRunner is an interface for an object that can receive reports of test failure and logging. It is the subset of testing.TB that the Logger requires.