mirror of https://github.com/grpc/grpc-go.git
274 lines
6.5 KiB
Go
274 lines
6.5 KiB
Go
/*
|
|
*
|
|
* Copyright 2020 gRPC 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 grpctest
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/grpclog"
|
|
)
|
|
|
|
// tLogr serves as the grpclog logger and is the interface through which
|
|
// expected errors are declared in tests.
|
|
var tLogr *tLogger
|
|
|
|
const callingFrame = 4
|
|
|
|
type logType int
|
|
|
|
func (l logType) String() string {
|
|
switch l {
|
|
case infoLog:
|
|
return "INFO"
|
|
case warningLog:
|
|
return "WARNING"
|
|
case errorLog:
|
|
return "ERROR"
|
|
case fatalLog:
|
|
return "FATAL"
|
|
}
|
|
return "UNKNOWN"
|
|
}
|
|
|
|
const (
|
|
infoLog logType = iota
|
|
warningLog
|
|
errorLog
|
|
fatalLog
|
|
)
|
|
|
|
type tLogger struct {
|
|
v int
|
|
initialized bool
|
|
|
|
mu sync.Mutex // guards t, start, and errors
|
|
t *testing.T
|
|
start time.Time
|
|
errors map[*regexp.Regexp]int
|
|
}
|
|
|
|
func init() {
|
|
vLevel := 0 // Default verbosity level
|
|
|
|
if vLevelEnv, found := os.LookupEnv("GRPC_GO_LOG_VERBOSITY_LEVEL"); found {
|
|
// If found, attempt to convert. If conversion is successful, update vLevel.
|
|
// If conversion fails, log a warning, but vLevel remains its default of 0.
|
|
if val, err := strconv.Atoi(vLevelEnv); err == nil {
|
|
vLevel = val
|
|
} else {
|
|
// Log the error if the environment variable is not a valid integer.
|
|
fmt.Printf("Warning: GRPC_GO_LOG_VERBOSITY_LEVEL environment variable '%s' is not a valid integer. "+
|
|
"Using default verbosity level 0. Error: %v\n", vLevelEnv, err)
|
|
}
|
|
}
|
|
// Initialize tLogr with the determined verbosity level.
|
|
tLogr = &tLogger{errors: make(map[*regexp.Regexp]int), v: vLevel}
|
|
}
|
|
|
|
// getCallingPrefix returns the <file:line> at the given depth from the stack.
|
|
func getCallingPrefix(depth int) (string, error) {
|
|
_, file, line, ok := runtime.Caller(depth)
|
|
if !ok {
|
|
return "", errors.New("frame request out-of-bounds")
|
|
}
|
|
return fmt.Sprintf("%s:%d", path.Base(file), line), nil
|
|
}
|
|
|
|
// log logs the message with the specified parameters to the tLogger.
|
|
func (tl *tLogger) log(ltype logType, depth int, format string, args ...any) {
|
|
tl.mu.Lock()
|
|
defer tl.mu.Unlock()
|
|
prefix, err := getCallingPrefix(callingFrame + depth)
|
|
if err != nil {
|
|
tl.t.Error(err)
|
|
return
|
|
}
|
|
args = append([]any{ltype.String() + " " + prefix}, args...)
|
|
args = append(args, fmt.Sprintf(" (t=+%s)", time.Since(tl.start)))
|
|
|
|
if format == "" {
|
|
switch ltype {
|
|
case errorLog:
|
|
// fmt.Sprintln is used rather than fmt.Sprint because tl.Log uses fmt.Sprintln behavior.
|
|
if tl.expected(fmt.Sprintln(args...)) {
|
|
tl.t.Log(args...)
|
|
} else {
|
|
tl.t.Error(args...)
|
|
}
|
|
case fatalLog:
|
|
panic(fmt.Sprint(args...))
|
|
default:
|
|
tl.t.Log(args...)
|
|
}
|
|
} else {
|
|
// Add formatting directives for the callingPrefix and timeSuffix.
|
|
format = "%v " + format + "%s"
|
|
switch ltype {
|
|
case errorLog:
|
|
if tl.expected(fmt.Sprintf(format, args...)) {
|
|
tl.t.Logf(format, args...)
|
|
} else {
|
|
tl.t.Errorf(format, args...)
|
|
}
|
|
case fatalLog:
|
|
panic(fmt.Sprintf(format, args...))
|
|
default:
|
|
tl.t.Logf(format, args...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// update updates the testing.T that the testing logger logs to. Should be done
|
|
// before every test. It also initializes the tLogger if it has not already.
|
|
func (tl *tLogger) update(t *testing.T) {
|
|
tl.mu.Lock()
|
|
defer tl.mu.Unlock()
|
|
if !tl.initialized {
|
|
grpclog.SetLoggerV2(tl)
|
|
tl.initialized = true
|
|
}
|
|
tl.t = t
|
|
tl.start = time.Now()
|
|
tl.errors = map[*regexp.Regexp]int{}
|
|
}
|
|
|
|
// ExpectError declares an error to be expected. For the next test, the first
|
|
// error log matching the expression (using FindString) will not cause the test
|
|
// to fail. "For the next test" includes all the time until the next call to
|
|
// Update(). Note that if an expected error is not encountered, this will cause
|
|
// the test to fail.
|
|
func ExpectError(expr string) {
|
|
ExpectErrorN(expr, 1)
|
|
}
|
|
|
|
// ExpectErrorN declares an error to be expected n times.
|
|
func ExpectErrorN(expr string, n int) {
|
|
tLogr.mu.Lock()
|
|
defer tLogr.mu.Unlock()
|
|
re, err := regexp.Compile(expr)
|
|
if err != nil {
|
|
tLogr.t.Error(err)
|
|
return
|
|
}
|
|
tLogr.errors[re] += n
|
|
}
|
|
|
|
// endTest checks if expected errors were not encountered.
|
|
func (tl *tLogger) endTest(t *testing.T) {
|
|
tl.mu.Lock()
|
|
defer tl.mu.Unlock()
|
|
for re, count := range tl.errors {
|
|
if count > 0 {
|
|
t.Errorf("Expected error '%v' not encountered", re.String())
|
|
}
|
|
}
|
|
tl.errors = map[*regexp.Regexp]int{}
|
|
}
|
|
|
|
// expected determines if the error string is protected or not.
|
|
func (tl *tLogger) expected(s string) bool {
|
|
for re, count := range tl.errors {
|
|
if re.FindStringIndex(s) != nil {
|
|
tl.errors[re]--
|
|
if count <= 1 {
|
|
delete(tl.errors, re)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (tl *tLogger) Info(args ...any) {
|
|
tl.log(infoLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Infoln(args ...any) {
|
|
tl.log(infoLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Infof(format string, args ...any) {
|
|
tl.log(infoLog, 0, format, args...)
|
|
}
|
|
|
|
func (tl *tLogger) InfoDepth(depth int, args ...any) {
|
|
tl.log(infoLog, depth, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Warning(args ...any) {
|
|
tl.log(warningLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Warningln(args ...any) {
|
|
tl.log(warningLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Warningf(format string, args ...any) {
|
|
tl.log(warningLog, 0, format, args...)
|
|
}
|
|
|
|
func (tl *tLogger) WarningDepth(depth int, args ...any) {
|
|
tl.log(warningLog, depth, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Error(args ...any) {
|
|
tl.log(errorLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Errorln(args ...any) {
|
|
tl.log(errorLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Errorf(format string, args ...any) {
|
|
tl.log(errorLog, 0, format, args...)
|
|
}
|
|
|
|
func (tl *tLogger) ErrorDepth(depth int, args ...any) {
|
|
tl.log(errorLog, depth, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Fatal(args ...any) {
|
|
tl.log(fatalLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Fatalln(args ...any) {
|
|
tl.log(fatalLog, 0, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) Fatalf(format string, args ...any) {
|
|
tl.log(fatalLog, 0, format, args...)
|
|
}
|
|
|
|
func (tl *tLogger) FatalDepth(depth int, args ...any) {
|
|
tl.log(fatalLog, depth, "", args...)
|
|
}
|
|
|
|
func (tl *tLogger) V(l int) bool {
|
|
return l <= tl.v
|
|
}
|