Auto-update dependencies (#209)

Produced via:
  `dep ensure -update knative.dev/test-infra knative.dev/pkg`
/assign n3wscott
/cc n3wscott
This commit is contained in:
Matt Moore 2020-02-20 06:59:06 -08:00 committed by GitHub
parent c75efc13dc
commit 9d84bba4b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1140 additions and 90 deletions

6
Gopkg.lock generated
View File

@ -966,7 +966,7 @@
[[projects]]
branch = "master"
digest = "1:4d466bcd929b5ae71fddc9153076b396c5d05479f03b2ae6318c3433578205dc"
digest = "1:1e7838d5007ae0a2f64d99f5a2c216749a03247b85b4a7d741c6ae3c4fe14e0a"
name = "knative.dev/pkg"
packages = [
"apis",
@ -986,7 +986,7 @@
"reconciler",
]
pruneopts = "T"
revision = "5d8a01d12cc825688b14117af294139fee2270eb"
revision = "5c9bc970ce963ddad76a59b289ba90d5b118d98f"
[[projects]]
branch = "master"
@ -997,7 +997,7 @@
"tools/dep-collector",
]
pruneopts = "UT"
revision = "fb304f6a7ac965fb3eb5cb16d5e0301ca60e3247"
revision = "359e2ba3ab51221e4a91e78f4031361306bbba0d"
[[projects]]
digest = "1:8730e0150dfb2b7e173890c8b9868e7a273082ef8e39f4940e3506a481cf895c"

27
vendor/knative.dev/pkg/Gopkg.lock generated vendored
View File

@ -148,6 +148,22 @@
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
digest = "1:53becd66889185091b58ea3fc49294996f2179fb05a89702f4de7d15e581b509"
name = "github.com/go-logr/logr"
packages = ["."]
pruneopts = "NUT"
revision = "9fb12b3b21c5415d16ac18dc5cd42c1cfdd40c4e"
version = "v0.1.0"
[[projects]]
digest = "1:340497a512995aa69c0add901d79a2096b3449d35a44a6f1f1115091a9f8c687"
name = "github.com/go-logr/zapr"
packages = ["."]
pruneopts = "NUT"
revision = "03f06a783fbb7dfaf3f629c7825480e43a7105e6"
version = "v0.1.1"
[[projects]]
digest = "1:53151cc4366e3945282d4b783fd41f35222cabbc75601e68d8133648c63498d1"
name = "github.com/gobuffalo/envy"
@ -1279,12 +1295,12 @@
revision = "e17681d19d3ac4837a019ece36c2a0ec31ffe985"
[[projects]]
digest = "1:43099cc4ed575c40f80277c7ba7168df37d0c663bdc4f541325430bd175cce8a"
digest = "1:c0693cb981f43d82a767a3217b7640a4bdb341731d3814b38602f4e5dc4f01b3"
name = "k8s.io/klog"
packages = ["."]
pruneopts = "NUT"
revision = "d98d8acdac006fb39831f1b25640813fef9c314f"
version = "v0.3.3"
revision = "2ca9ad30301bf30a8a6e0fa2110db6b8df699a91"
version = "v1.0.0"
[[projects]]
branch = "master"
@ -1355,8 +1371,9 @@
"github.com/davecgh/go-spew/spew",
"github.com/evanphx/json-patch",
"github.com/ghodss/yaml",
"github.com/go-logr/logr",
"github.com/go-logr/zapr",
"github.com/gogo/protobuf/proto",
"github.com/golang/glog",
"github.com/golang/protobuf/jsonpb",
"github.com/golang/protobuf/proto",
"github.com/google/go-cmp/cmp",
@ -1388,7 +1405,9 @@
"go.opencensus.io/stats/view",
"go.opencensus.io/tag",
"go.opencensus.io/trace",
"go.uber.org/multierr",
"go.uber.org/zap",
"go.uber.org/zap/buffer",
"go.uber.org/zap/zapcore",
"go.uber.org/zap/zaptest",
"golang.org/x/net/context",

View File

@ -13,6 +13,8 @@ required = [
"knative.dev/test-infra/scripts",
"knative.dev/test-infra/tools/dep-collector",
"github.com/gogo/protobuf/proto",
"github.com/go-logr/logr",
"github.com/go-logr/zapr",
]
[[constraint]]

View File

@ -242,23 +242,38 @@ func (r conditionsImpl) ClearCondition(t ConditionType) error {
// MarkTrue sets the status of t to true, and then marks the happy condition to
// true if all other dependents are also true.
func (r conditionsImpl) MarkTrue(t ConditionType) {
// set the specified condition
// Save the original status of t.
org := r.GetCondition(t).DeepCopy()
orgTL := r.GetTopLevelCondition().DeepCopy()
// Set the specified condition.
r.SetCondition(Condition{
Type: t,
Status: corev1.ConditionTrue,
Severity: r.severity(t),
})
// check the dependents.
// Check the dependents.
for _, cond := range r.dependents {
c := r.GetCondition(cond)
// Failed or Unknown conditions trump true conditions
// Failed or Unknown conditions trump true conditions.
if !c.IsTrue() {
// Update the happy condition if the current ready condition is
// marked not ready because of this condition.
if org.Reason == orgTL.Reason && org.Message == orgTL.Message {
r.SetCondition(Condition{
Type: r.happy,
Status: c.Status,
Reason: c.Reason,
Message: c.Message,
Severity: r.severity(r.happy),
})
}
return
}
}
// set the happy condition
// Set the happy condition.
r.SetCondition(Condition{
Type: r.happy,
Status: corev1.ConditionTrue,

View File

@ -22,15 +22,12 @@ package test
import (
"bytes"
"flag"
"fmt"
"os"
"os/user"
"path"
"sync"
"text/template"
_ "github.com/golang/glog" // Needed if glog and klog are to coexist
"k8s.io/klog"
"knative.dev/pkg/test/logging"
)
@ -53,7 +50,6 @@ type EnvironmentFlags struct {
Kubeconfig string // Path to kubeconfig (defaults to ./kube/config)
Namespace string // K8s namespace (blank by default, to be overwritten by test suite)
IngressEndpoint string // Host to use for ingress endpoint
LogVerbose bool // Enable verbose logging
ImageTemplate string // Template to build the image reference (defaults to {{.Repository}}/{{.Name}}:{{.Tag}})
DockerRepo string // Docker repo (defaults to $KO_DOCKER_REPO)
Tag string // Tag for test images
@ -83,9 +79,6 @@ func initializeFlags() *EnvironmentFlags {
flag.StringVar(&f.IngressEndpoint, "ingressendpoint", "", "Provide a static endpoint url to the ingress server used during tests.")
flag.BoolVar(&f.LogVerbose, "logverbose", false,
"Set this flag to true if you would like to see verbose logging.")
flag.StringVar(&f.ImageTemplate, "imagetemplate", "{{.Repository}}/{{.Name}}:{{.Tag}}",
"Provide a template to generate the reference to an image from the test. Defaults to `{{.Repository}}/{{.Name}}:{{.Tag}}`.")
@ -95,45 +88,12 @@ func initializeFlags() *EnvironmentFlags {
flag.StringVar(&f.Tag, "tag", "latest", "Provide the version tag for the test images.")
klog.InitFlags(klogFlags)
flag.Set("v", klogDefaultLogLevel)
flag.Set("alsologtostderr", "true")
return &f
}
func printFlags() {
fmt.Print("Test Flags: {")
flag.CommandLine.VisitAll(func(f *flag.Flag) {
fmt.Printf("'%s': '%s', ", f.Name, f.Value.String())
})
fmt.Println("}")
}
// SetupLoggingFlags initializes the logging libraries at runtime
// TODO(coryrc): Remove once other repos are moved to call logging.InitializeLogger() directly
func SetupLoggingFlags() {
flagsSetupOnce.Do(func() {
// Sync the glog flags to klog
flag.CommandLine.VisitAll(func(f1 *flag.Flag) {
f2 := klogFlags.Lookup(f1.Name)
if f2 != nil {
value := f1.Value.String()
f2.Value.Set(value)
}
})
if Flags.LogVerbose {
// If klog verbosity is not set to a non-default value (via "-args -v=X"),
if flag.CommandLine.Lookup("v").Value.String() == klogDefaultLogLevel {
// set up verbosity for klog so round_trippers.go prints:
// URL, request headers, response headers, and partial response body
// See levels in vendor/k8s.io/client-go/transport/round_trippers.go:DebugWrappers for other options
klogFlags.Set("v", "8")
flag.Set("v", "8") // This is for glog, since glog=>klog sync is one-time
}
printFlags()
}
logging.InitializeLogger(Flags.LogVerbose)
})
logging.InitializeLogger()
}
// ImagePath is a helper function to transform an image name into an image reference that can be pulled.

View File

@ -0,0 +1 @@
memory_encoder.go coverage-excluded=true

View File

@ -0,0 +1,49 @@
/*
Copyright 2020 The Knative 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 logging assists setting up test logging and using leveled logging in tests.
The TLogger is designed to assist the test writer in creating more useful tests and
collecting log data in multiple streams, optimizing for human readability in one and
machine readability in another. It's designed to mimic the testing.T object rather closely and
use Zap logging semantics, both things already in use in Knative, to minimize the time developers
need to spend learning the tool.
Inspired by and uses go-logr.
Advantages
The TLogger enhances test design through subtle nudges and affordances:
* It encourages only logging with .V(), giving the writer a nudge to think about how important it is,
but without requiring them to fit it in a narrowly-defined category.
* Reduces boilerplate of carrying around context for errors in several different variables,
using .WithValues(), which results in more consistent and reusable code across the tests.
Porting
To port code from using testing.T to logging.TLogger, the interfaces knative.dev/pkg/test.T and
knative.dev/pkg/test.TLegacy have been created. All library functions should be refactored to use
one interface and all .Log() calls rewritten to use structured format, which works with testing and
TLogger. If a library function needs test functions not available even in test.TLegacy,
it's probably badly written.
Then any test can be incrementally rewritten to use TLogger, as it coexists with testing.T without issue.
*/
package logging

View File

@ -0,0 +1,91 @@
/*
Copyright 2020 The Knative 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 logging
import (
"fmt"
"github.com/davecgh/go-spew/spew"
)
// StructuredError is an error which can hold arbitrary key-value arguments.
//
// TODO(coryrc): The Structured Error is experimental and likely to be removed, but is currently in use in a refactored test.
type StructuredError interface {
error
GetValues() []interface{}
WithValues(...interface{}) StructuredError
DisableValuePrinting()
EnableValuePrinting()
}
type structuredError struct {
msg string
keysAndValues []interface{}
print bool
}
func keysAndValuesToSpewedMap(args ...interface{}) map[string]string {
m := make(map[string]string, len(args)/2)
for i := 0; i < len(args); i += 2 {
key, val := args[i], args[i+1]
if keyStr, ok := key.(string); ok {
m[keyStr] = spew.Sdump(val)
}
}
return m
}
// Implement `error` interface
func (e structuredError) Error() string {
// TODO(coryrc): accept zap.Field entries?
if e.print {
// %v for fmt.Sprintf does print keys sorted
return fmt.Sprintf("Error: %s\nContext:\n%v", e.msg, keysAndValuesToSpewedMap(e.keysAndValues...))
} else {
return e.msg
}
}
// GetValues gives you the structured key values in a plist
func (e structuredError) GetValues() []interface{} {
return e.keysAndValues
}
// DisableValuePrinting disables printing out the keys and values from the Error() method
func (e *structuredError) DisableValuePrinting() {
e.print = false
}
// EnableValuePrinting enables printing out the keys and values from the Error() method
func (e *structuredError) EnableValuePrinting() {
e.print = true
}
// Create a StructuredError. Gives a little better logging when given to a TLogger.
// This may prove to not be useful if users use the logger's WithValues() better.
func Error(msg string, keysAndValues ...interface{}) *structuredError {
return &structuredError{msg, keysAndValues, true}
}
// WithValues operates just like TLogger's WithValues but stores them in the error object.
func (e *structuredError) WithValues(keysAndValues ...interface{}) StructuredError {
newKAV := make([]interface{}, 0, len(keysAndValues)+len(e.keysAndValues))
newKAV = append(newKAV, e.keysAndValues...)
newKAV = append(newKAV, keysAndValues...)
return &structuredError{e.msg, newKAV, e.print}
}

View File

@ -0,0 +1,76 @@
// Copyright 2020 The Knative Authors
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Copying testingWriter from zaptest and allowing it to be disabled
package logging
import (
"bytes"
"errors"
"testing"
)
// testingWriter is a WriteSyncer that writes to the given testing.TB.
type testingWriter struct {
t *testing.T
// If true, the test will be marked as failed if this testingWriter is
// ever used.
markFailed bool
}
func newTestingWriter(t *testing.T) testingWriter {
return testingWriter{t: t}
}
// WithMarkFailed returns a copy of this testingWriter with markFailed set to
// the provided value.
func (w testingWriter) WithMarkFailed(v bool) testingWriter {
w.markFailed = v
return w
}
func (w testingWriter) Write(p []byte) (n int, err error) {
if w.t == nil {
return 0, errors.New("Write to buffer after test function completed")
}
n = len(p)
// Strip trailing newline because t.Log always adds one.
p = bytes.TrimRight(p, "\n")
// Note: t.Log is safe for concurrent use.
w.t.Logf("%s", p)
if w.markFailed {
w.t.Fail()
}
return n, nil
}
func (w testingWriter) Sync() error {
return nil
}
func (w *testingWriter) Disable() {
w.t = nil
}

View File

@ -21,15 +21,17 @@ package logging
import (
"context"
"fmt"
"flag"
"os"
"strings"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
"go.uber.org/zap"
"knative.dev/pkg/logging"
"go.uber.org/zap/zapcore"
)
const (
@ -44,8 +46,6 @@ const (
// FormatLogger is a printf style function for logging in tests.
type FormatLogger func(template string, args ...interface{})
var logger *zap.SugaredLogger
var exporter *zapMetricExporter
// zapMetricExporter is a stats and trace exporter that logs the
@ -80,29 +80,22 @@ func (e *zapMetricExporter) ExportSpan(vd *trace.SpanData) {
}
}
func newLogger(logLevel string) *zap.SugaredLogger {
configJSONTemplate := `{
"level": "%s",
"encoding": "console",
"outputPaths": ["stdout"],
"errorOutputPaths": ["stderr"],
"encoderConfig": {
"timeKey": "ts",
"messageKey": "message",
"levelKey": "level",
"nameKey": "logger",
"callerKey": "caller",
"messageKey": "msg",
"stacktraceKey": "stacktrace",
"lineEnding": "",
"levelEncoder": "",
"timeEncoder": "iso8601",
"durationEncoder": "",
"callerEncoder": ""
}
}`
configJSON := fmt.Sprintf(configJSONTemplate, logLevel)
l, _ := logging.NewLogger(string(configJSON), logLevel, zap.AddCallerSkip(1))
const (
logrZapDebugLevel = 3
)
func zapLevelFromLogrLevel(logrLevel int) zapcore.Level {
// Zap levels are -1, 0, 1, 2,... corresponding to DebugLevel, InfoLevel, WarnLevel, ErrorLevel,...
// zapr library just does zapLevel := -1*logrLevel; which means:
// 1. Info level is only active at 0 (versus 2 in klog being generally equivalent to Info)
// 2. Only verbosity of 0 and 1 map to valid Zap levels
// According to https://github.com/uber-go/zap/issues/713 custom levels (i.e. < -1) aren't guaranteed to work, so not using them (for now).
l := zap.InfoLevel
if logrLevel >= logrZapDebugLevel {
l = zap.DebugLevel
}
return l
}
@ -115,9 +108,9 @@ func InitializeMetricExporter(context string) {
trace.UnregisterExporter(exporter)
}
logger := logger.Named(context)
l := logger.Named(context).Sugar()
exporter = &zapMetricExporter{logger: logger}
exporter = &zapMetricExporter{logger: l}
view.RegisterExporter(exporter)
trace.RegisterExporter(exporter)
@ -125,12 +118,54 @@ func InitializeMetricExporter(context string) {
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
}
// InitializeLogger initializes the base logger
func InitializeLogger(logVerbose bool) {
logLevel := "info"
if logVerbose {
logLevel = "debug"
}
logger = newLogger(logLevel)
func printFlags() {
flagList := make([]interface{}, 0)
flag.CommandLine.VisitAll(func(f *flag.Flag) {
flagList = append(flagList, f.Name, f.Value.String())
})
logger.Sugar().Debugw("Test Flags", flagList...)
}
var (
zapCore zapcore.Core
logger *zap.Logger
verbosity int // Amount of log verbosity
loggerInitializeOnce = &sync.Once{}
)
// InitializeLogger initializes logging for Knative tests.
// It should be called prior to executing tests but after command-line flags have been processed.
// Recommend doing it in a TestMain().
func InitializeLogger() {
loggerInitializeOnce.Do(func() {
humanEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// Output streams
// TODO(coryrc): also open a log file if in Prow?
stdOut := zapcore.Lock(os.Stdout)
// Level function helper
zapLevel := zapLevelFromLogrLevel(verbosity)
isPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapLevel
})
// Assemble the output streams
zapCore = zapcore.NewTee(
// TODO(coryrc): log JSON output somewhere?
zapcore.NewCore(humanEncoder, stdOut, isPriority),
)
logger = zap.New(zapCore)
zap.ReplaceGlobals(logger) // Gets used by klog/glog proxy libraries
if verbosity > 2 {
printFlags()
}
})
}
func init() {
flag.IntVar(&verbosity, "verbosity", 2,
"Amount of verbosity, 0-10. See https://github.com/go-logr/logr#how-do-i-choose-my-v-levels and https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md")
}

View File

@ -0,0 +1,74 @@
// Copyright 2020 The Knative Authors
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package logging
import (
"time"
"go.uber.org/zap/zapcore"
)
// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like
// the MapObjectEncoder, it's not designed for production use.
type sliceArrayEncoder struct {
elems []interface{}
}
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
enc := &sliceArrayEncoder{}
err := v.MarshalLogArray(enc)
s.elems = append(s.elems, enc.elems)
return err
}
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
m := zapcore.NewMapObjectEncoder()
err := v.MarshalLogObject(m)
s.elems = append(s.elems, m.Fields)
return err
}
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
s.elems = append(s.elems, v)
return nil
}
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }

View File

@ -0,0 +1,196 @@
// Copyright 2020 The Knative Authors
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package logging
import (
"fmt"
"sort"
"strings"
"sync"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
. "go.uber.org/zap/zapcore"
)
var (
_pool = buffer.NewPool()
_sliceEncoderPool = sync.Pool{
New: func() interface{} {
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
},
}
)
func init() {
zap.RegisterEncoder("spew", func(encoderConfig EncoderConfig) (Encoder, error) {
return NewSpewEncoder(encoderConfig), nil
})
}
// NewSpewEncoder encodes logs using the spew library.
//
// The JSON encoder (also used by the console encoder) included in Zap can only print objects that
// can be serialized to JSON and doesn't print them in the most readable way. This spew encoder is
// designed to make human-readable log only and get the most information to the user on any data type.
//
// Code is mostly from console_encoder.go in zapcore.
func NewSpewEncoder(cfg EncoderConfig) *SpewEncoder {
enc := SpewEncoder{}
enc.MapObjectEncoder = NewMapObjectEncoder()
enc.EncoderConfig = &cfg
return &enc
}
// SpewEncoder implements zapcore.Encoder interface
type SpewEncoder struct {
*MapObjectEncoder
*EncoderConfig
}
// Implements zapcore.Encoder interface
func (enc *SpewEncoder) Clone() Encoder {
n := NewSpewEncoder(*(enc.EncoderConfig))
for k, v := range enc.Fields {
n.Fields[k] = v
}
return n
}
func getSliceEncoder() *sliceArrayEncoder {
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
}
func putSliceEncoder(e *sliceArrayEncoder) {
e.elems = e.elems[:0]
_sliceEncoderPool.Put(e)
}
// Implements zapcore.Encoder interface.
func (enc *SpewEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) {
line := _pool.Get()
// Could probably rewrite this portion and remove the copied
// memory_encoder.go from this folder
arr := getSliceEncoder()
defer putSliceEncoder(arr)
if enc.TimeKey != "" && enc.EncodeTime != nil {
enc.EncodeTime(ent.Time, arr)
}
if enc.LevelKey != "" && enc.EncodeLevel != nil {
enc.EncodeLevel(ent.Level, arr)
}
if ent.LoggerName != "" && enc.NameKey != "" {
nameEncoder := enc.EncodeName
if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = FullNameEncoder
}
nameEncoder(ent.LoggerName, arr)
}
if ent.Caller.Defined && enc.CallerKey != "" && enc.EncodeCaller != nil {
enc.EncodeCaller(ent.Caller, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendByte('\t')
}
fmt.Fprint(line, arr.elems[i])
}
// Add the message itself.
if enc.MessageKey != "" {
enc.addTabIfNecessary(line)
line.AppendString(ent.Message)
}
// Add any structured context.
enc.writeContext(line, fields)
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" && enc.StacktraceKey != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
if enc.LineEnding != "" {
line.AppendString(enc.LineEnding)
} else {
line.AppendString(DefaultLineEnding)
}
return line, nil
}
func (enc *SpewEncoder) writeContext(line *buffer.Buffer, extra []Field) {
if len(extra) == 0 && len(enc.Fields) == 0 {
return
}
// This could probably be more efficient, but .AddTo() is convenient
context := NewMapObjectEncoder()
for k, v := range enc.Fields {
context.Fields[k] = v
}
for i := range extra {
extra[i].AddTo(context)
}
enc.addTabIfNecessary(line)
line.AppendString("\nContext:\n")
var keys []string
for k := range context.Fields {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
line.AppendString(k)
line.AppendString(": ")
line.AppendString(stringify(context.Fields[k]))
line.TrimNewline()
line.AppendString("\n")
}
}
func stringify(a interface{}) string {
s, ok := a.(string)
if !ok {
s = strings.TrimSuffix(spewConfig.Sdump(a), "\n")
}
ret := strings.ReplaceAll(s, "\n", "\n ")
hasNewlines := s != ret
if hasNewlines {
return "\n " + ret
}
return s
}
func (enc *SpewEncoder) addTabIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendByte('\t')
}
}

View File

@ -0,0 +1,117 @@
// Copyright 2020 Knative Authors
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package logging
import (
"github.com/davecgh/go-spew/spew"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/multierr"
)
const (
_oddNumberErrMsg = "Ignored key without a value."
_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."
spewLevel1 = 2
spewLevel2 = 4
spewLevel3 = 6
)
var spewConfig *spew.ConfigState
func init() {
spewConfig = spew.NewDefaultConfig()
spewConfig.DisableCapacities = true
spewConfig.SortKeys = true
spewConfig.SpewKeys = true
spewConfig.ContinueOnMethod = true
}
func (o *TLogger) handleFields(args []interface{}) []zap.Field {
if len(args) == 0 {
return nil
}
s := o.l.Sugar()
// Allocate enough space for the worst case; if users pass only structured
// fields, we shouldn't penalize them with extra allocations.
fields := make([]zap.Field, 0, len(args))
var invalid invalidPairs
for i := 0; i < len(args); {
// This is a strongly-typed field. Consume it and move on.
if f, ok := args[i].(zap.Field); ok {
fields = append(fields, f)
i++
continue
}
// Make sure this element isn't a dangling key.
if i == len(args)-1 {
s.DPanic(_oddNumberErrMsg, zap.Any("ignored", args[i]))
break
}
// Consume this value and the next, treating them as a key-value pair. If the
// key isn't a string, add this pair to the slice of invalid pairs.
key, val := args[i], args[i+1]
if keyStr, ok := key.(string); !ok {
// Subsequent errors are likely, so allocate once up front.
if cap(invalid) == 0 {
invalid = make(invalidPairs, 0, len(args)/2)
}
invalid = append(invalid, invalidPair{i, key, val})
} else {
fields = append(fields, zap.Any(keyStr, val))
}
i += 2
}
// If we encountered any invalid key-value pairs, log an error.
if len(invalid) > 0 {
s.DPanic(_nonStringKeyErrMsg, zap.Array("invalid", invalid), zap.String("all_input", spew.Sprintf("%#+v", args)))
}
return fields
}
type invalidPair struct {
position int
key, value interface{}
}
func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddInt64("position", int64(p.position))
zap.Any("key", p.key).AddTo(enc)
zap.Any("value", p.value).AddTo(enc)
return nil
}
type invalidPairs []invalidPair
func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
var err error
for i := range ps {
err = multierr.Append(err, enc.AppendObject(ps[i]))
}
return err
}

View File

@ -0,0 +1,368 @@
/*
Copyright 2020 The Knative 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 logging
import (
"errors"
"fmt"
"testing"
"github.com/go-logr/logr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// TLogger is TLogger
type TLogger struct {
l *zap.Logger
level int
t *testing.T
errs map[string][]interface{} // For Collect()
dontFail bool
}
// V() returns an InfoLogger from go-logr.
//
// This should be the main way your tests log.
// Most frequent usage is used directly:
// t.V(2).Info("Something at regular level")
// But if something computationally difficult is to be done, can do:
// if l := t.V(8); l.Enabled() {
// x := somethingExpensive()
// l.Info("logging it", "expensiveThing", x)
// }
//
// Elsewhere in this documentation refers to a hypothetical .V(errorLevel) to simplify explanations.
// The V() function cannot write to the error level; the Error, ErrorIfErr, Fatal, and
// FatalIfErr methods are the only way to write to the error level.
func (o *TLogger) V(level int) logr.InfoLogger {
// Consider adding || (level <= logrZapDebugLevel && o.l.Core().Enabled(zapLevelFromLogrLevel(level)))
// Reason to add it is even if you ask for verbosity=1, in case of error you'll get up to verbosity=3 in the debug output
// but since zapTest uses Debug, you always get V(<=3) even when verbosity < 3
// Probable solution is to write to t.Log at Info level?
if level <= o.level {
return &infoLogger{
logrLevel: o.level,
t: o,
}
}
return disabledInfoLogger
}
// WithValues() acts like Zap's With() method.
// Consistent with logr.Logger.WithValues()
// Whenever anything is logged with the returned TLogger,
// it will act as if these keys and values were passed into every logging call.
func (o *TLogger) WithValues(keysAndValues ...interface{}) *TLogger {
return o.cloneWithNewLogger(o.l.With(o.handleFields(keysAndValues)...))
}
// WithName() acts like Zap's Named() method.
// Consistent with logr.Logger.WithName()
// Appends the name onto the current logger
func (o *TLogger) WithName(name string) *TLogger {
return o.cloneWithNewLogger(o.l.Named(name))
}
// Custom additions:
// ErrorIfErr fails the current test if the err != nil.
// Remaining arguments function as if passed to .V(errorLevel).Info() (were that a thing)
// Same signature as logr.Logger.Error() method, but as this is a test, it functions slightly differently.
func (o *TLogger) ErrorIfErr(err error, msg string, keysAndValues ...interface{}) {
if err != nil {
o.error(err, msg, keysAndValues)
o.fail()
}
}
// FatalIfErr is just like ErrorIfErr() but test execution stops immediately
func (o *TLogger) FatalIfErr(err error, msg string, keysAndValues ...interface{}) {
if err != nil {
o.error(err, msg, keysAndValues)
o.failNow()
}
}
// Error is essentially a .V(errorLevel).Info() followed by failing the test.
// Intended usage is Error(msg string, key-value alternating arguments)
// Same effect as testing.T.Error
// Generic definition for compatibility with test.T interface
// Implements test.T
func (o *TLogger) Error(stringThenKeysAndValues ...interface{}) {
// Using o.error to have consistent call depth for Error, FatalIfErr, Info, etc
o.error(o.errorWithRuntimeCheck(stringThenKeysAndValues...))
o.fail()
}
// Fatal is essentially a .V(errorLevel).Info() followed by failing and immediately stopping the test.
// Intended usage is Fatal(msg string, key-value alternating arguments)
// Same effect as testing.T.Fatal
// Generic definition for compatibility with test.TLegacy interface
// Implements test.TLegacy
func (o *TLogger) Fatal(stringThenKeysAndValues ...interface{}) {
o.error(o.errorWithRuntimeCheck(stringThenKeysAndValues...))
o.failNow()
}
func (o *TLogger) fail() {
if o.t != nil && !o.dontFail {
o.t.Fail()
}
}
func (o *TLogger) failNow() {
if o.t != nil && !o.dontFail {
o.t.FailNow()
}
}
func validateKeysAndValues(keysAndValues ...interface{}) bool {
length := len(keysAndValues)
for i := 0; i < length; {
_, isField := keysAndValues[i].(zapcore.Field)
_, isString := keysAndValues[i].(string)
if isField {
i += 1
} else if isString {
if i == length-1 {
return false
}
i += 2
} else {
return false
}
}
return true
}
func (o *TLogger) interfacesToFields(things ...interface{}) []interface{} {
o.V(5).Info("DEPRECATED Error/Fatal usage", zap.Stack("callstack"))
fields := make([]interface{}, 2*len(things))
for i, d := range things {
fields[i*2] = fmt.Sprintf("arg %d", i)
fields[i*2+1] = d
}
return fields
}
func (o *TLogger) errorWithRuntimeCheck(stringThenKeysAndValues ...interface{}) (error, string, []interface{}) {
if len(stringThenKeysAndValues) == 0 {
return nil, "", nil
} else {
s, isString := stringThenKeysAndValues[0].(string)
e, isError := stringThenKeysAndValues[0].(error)
if isString {
// Desired case (hopefully)
remainder := stringThenKeysAndValues[1:]
if !validateKeysAndValues(remainder...) {
remainder = o.interfacesToFields(remainder...)
}
return nil, s, remainder
} else if isError && len(stringThenKeysAndValues) == 1 {
return e, "", nil
} else {
return nil, "unstructured error", o.interfacesToFields(stringThenKeysAndValues...)
}
}
}
// Run a subtest. Just like testing.T.Run but creates a TLogger.
func (o *TLogger) Run(name string, f func(t *TLogger)) {
tfunc := func(ts *testing.T) {
tl, cancel := newTLogger(ts, o.level, o.dontFail)
defer cancel()
f(tl)
}
o.t.Run(name, tfunc)
}
// Name is just like testing.T.Name()
// Implements test.T
func (o *TLogger) Name() string {
return o.t.Name()
}
// Helper cannot work as an indirect call, so just do nothing :(
// Implements test.T
func (o *TLogger) Helper() {
}
// SkipNow immediately stops test execution
// Implements test.T
func (o *TLogger) SkipNow() {
o.t.SkipNow()
}
// Log is deprecated: only existing for test.T compatibility
// Please use leveled logging via .V().Info()
// Will panic if given data incompatible with Info() function
// Implements test.T
func (o *TLogger) Log(args ...interface{}) {
// This is complicated to ensure exactly 2 levels of indirection
i := o.V(2)
iL, ok := i.(*infoLogger)
if ok {
iL.indirectWrite(args[0].(string), args[1:]...)
}
}
// Parallel allows tests or subtests to run in parallel
// Just calls the testing.T.Parallel() under the hood
func (o *TLogger) Parallel() {
o.t.Parallel()
}
// Logf is deprecated: only existing for test.TLegacy compatibility
// Please use leveled logging via .V().Info()
// Implements test.TLegacy
func (o *TLogger) Logf(fmtS string, args ...interface{}) {
// This is complicated to ensure exactly 2 levels of indirection
iL, ok := o.V(2).(*infoLogger)
if ok {
iL.indirectWrite(fmt.Sprintf(fmtS, args...))
}
}
func (o *TLogger) error(err error, msg string, keysAndValues []interface{}) {
var newKAV []interface{}
var serr StructuredError
if errors.As(err, &serr) {
serr.DisableValuePrinting()
defer serr.EnableValuePrinting()
newLen := len(keysAndValues) + len(serr.GetValues())
newKAV = make([]interface{}, 0, newLen+2)
newKAV = append(newKAV, keysAndValues...)
newKAV = append(newKAV, serr.GetValues()...)
}
if err != nil {
if msg == "" { // This is used if just the error is given to .Error() or .Fatal()
msg = err.Error()
} else {
if newKAV == nil {
newKAV = make([]interface{}, 0, len(keysAndValues)+1)
newKAV = append(newKAV, keysAndValues...)
}
newKAV = append(newKAV, zap.Error(err))
}
}
if newKAV != nil {
keysAndValues = newKAV
}
if checkedEntry := o.l.Check(zap.ErrorLevel, msg); checkedEntry != nil {
checkedEntry.Write(o.handleFields(keysAndValues)...)
}
}
// Creation and Teardown
// Create a TLogger object using the global Zap logger and the current testing.T
// `defer` a call to second return value immediately after.
func NewTLogger(t *testing.T) (*TLogger, func()) {
return newTLogger(t, verbosity, false)
}
func newTLogger(t *testing.T, verbosity int, dontFail bool) (*TLogger, func()) {
testOptions := []zap.Option{
zap.AddCaller(),
zap.AddCallerSkip(2),
zap.Development(),
}
writer := newTestingWriter(t)
// Based off zap.NewDevelopmentEncoderConfig()
cfg := zapcore.EncoderConfig{
// Wanted keys can be anything except the empty string.
TimeKey: "",
LevelKey: "",
NameKey: "",
CallerKey: "C",
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
core := zapcore.NewCore(
NewSpewEncoder(cfg),
writer,
zapcore.DebugLevel,
)
if zapCore != nil {
core = zapcore.NewTee(
zapCore,
core,
// TODO(coryrc): Open new file (maybe creating JUnit!?) with test output?
)
}
log := zap.New(core, testOptions...).Named(t.Name())
tlogger := TLogger{
l: log,
level: verbosity,
t: t,
errs: make(map[string][]interface{}, 0),
dontFail: dontFail,
}
return &tlogger, func() {
tlogger.handleCollectedErrors()
// Sometimes goroutines exist after a test and they cause panics if they attempt to call t.Log().
// Prevent this panic by disabling writes to the testing.T (we'll still get them everywhere else).
writer.Disable()
}
}
func (o *TLogger) cloneWithNewLogger(l *zap.Logger) *TLogger {
t := TLogger{
l: l,
level: o.level,
t: o.t,
errs: o.errs,
dontFail: o.dontFail,
}
return &t
}
// Collect allows you to commingle multiple validations during one test execution.
// Under the hood, it creates a sub-test during cleanup and iterates through the collected values, printing them.
// If any are errors, it fails the subtest.
// Currently experimental and likely to be removed
func (o *TLogger) Collect(key string, value interface{}) {
list, has_key := o.errs[key]
if has_key {
list = append(list, value)
} else {
list = make([]interface{}, 1)
list[0] = value
}
o.errs[key] = list
}
func (o *TLogger) handleCollectedErrors() {
for name, list := range o.errs {
o.Run(name, func(t *TLogger) {
for _, item := range list {
_, isError := item.(error)
if isError {
t.Error(item)
} else {
t.V(3).Info(spewConfig.Sprint(item))
}
}
})
}
}

View File

@ -0,0 +1,47 @@
// Copyright 2020 Knative Authors
// Copyright 2018 Solly Ross
//
// 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.
// The useful parts of this file have been preserved
// from their origin at https://github.com/go-logr/zapr/tree/8f2487342d52a33a1793e50e3ca04bc1767aa65c
package logging
// noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing.
type noopInfoLogger struct{}
func (l *noopInfoLogger) Enabled() bool { return false }
func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {}
var disabledInfoLogger = &noopInfoLogger{}
// infoLogger is a logr.InfoLogger that uses Zap to log at a particular
// level.
type infoLogger struct {
logrLevel int
t *TLogger
}
func (i *infoLogger) Enabled() bool { return true }
func (i *infoLogger) Info(msg string, keysAndVals ...interface{}) {
i.indirectWrite(msg, keysAndVals...)
}
// This function just exists to have consistent 2-level call depth for Zap proxying
func (i *infoLogger) indirectWrite(msg string, keysAndVals ...interface{}) {
lvl := zapLevelFromLogrLevel(i.logrLevel)
if checkedEntry := i.t.l.Check(lvl, msg); checkedEntry != nil {
checkedEntry.Write(i.t.handleFields(keysAndVals)...)
}
}