You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by zr...@apache.org on 2023/09/27 01:01:59 UTC

[trafficcontrol] branch master updated: Add a way to use a testing.T for our go-log library in tests (#6882)

This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 5c1d852c55 Add a way to use a testing.T for our go-log library in tests (#6882)
5c1d852c55 is described below

commit 5c1d852c5563132b6f0bcc3b7b557516ede1623b
Author: ocket8888 <oc...@apache.org>
AuthorDate: Tue Sep 26 19:01:52 2023 -0600

    Add a way to use a testing.T for our go-log library in tests (#6882)
    
    (cherry picked from commit 43ba2b72f8e7684de379694466b431d95ed1a998)
---
 lib/go-log/test.logger.go      |  62 +++++++++++
 lib/go-log/test.logger_test.go | 242 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 304 insertions(+)

diff --git a/lib/go-log/test.logger.go b/lib/go-log/test.logger.go
new file mode 100644
index 0000000000..99ac93152f
--- /dev/null
+++ b/lib/go-log/test.logger.go
@@ -0,0 +1,62 @@
+package log
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+*/
+
+import "testing"
+
+type testErrorLogger struct {
+	t testing.TB
+}
+
+func (tl testErrorLogger) Write(data []byte) (int, error) {
+	tl.t.Error(string(data))
+	return len(data), nil
+}
+func (tl testErrorLogger) Close() error {
+	return nil
+}
+
+type testLogger struct {
+	t testing.TB
+}
+
+func (tl testLogger) Write(data []byte) (int, error) {
+	tl.t.Log(string(data))
+	return len(data), nil
+}
+func (tl testLogger) Close() error {
+	return nil
+}
+
+// InitTestingLogging initializes all logging functions to write their outputs
+// to the logging output of the given testing context. If warningsAreErrors is
+// true, warnings are logged using t.Error - marking the test as failed.
+// Otherwise, errors are logged using t.Log. In either case, any errors logged
+// cause the test to be marked as failed and are logged using t.Error, and the
+// Info, Event, and Debug streams are always logged using t.Log.
+func InitTestingLogging(tb testing.TB, warningsAreErrors bool) {
+	tb.Helper()
+	errWriter := testErrorLogger{t: tb}
+	if writer := (testLogger{t: tb}); warningsAreErrors {
+		Init(writer, errWriter, errWriter, writer, writer)
+	} else {
+		Init(writer, errWriter, writer, writer, writer)
+	}
+}
diff --git a/lib/go-log/test.logger_test.go b/lib/go-log/test.logger_test.go
new file mode 100644
index 0000000000..96dbd63291
--- /dev/null
+++ b/lib/go-log/test.logger_test.go
@@ -0,0 +1,242 @@
+package log
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+*/
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+	"testing"
+	"time"
+)
+
+type mockTester struct {
+	logbuf       string
+	errbuf       string
+	failed       bool
+	helperCalled bool
+	*testing.T
+}
+
+/*** testing.TB method overrides ***/
+func (m *mockTester) Helper() {
+	m.helperCalled = true
+}
+func (m *mockTester) Failed() bool {
+	return m.failed
+}
+func (m *mockTester) Error(args ...any) {
+	m.failed = true
+	m.errbuf = fmt.Sprint(args...)
+}
+func (m *mockTester) Log(args ...any) {
+	m.logbuf = fmt.Sprint(args...)
+}
+
+/*** None of these should ever be called ***/
+func (m *mockTester) Fail() {
+	m.failed = true
+	m.T.Error("'Fail' called")
+}
+func (m *mockTester) FailNow() {
+	m.failed = true
+	m.T.Error("'FailNow' called")
+}
+func (m *mockTester) Fatal(args ...any) {
+	m.failed = true
+	m.T.Error("'Fatal' called")
+}
+func (m *mockTester) Errorf(fmtstr string, args ...any) {
+	m.failed = true
+	m.T.Error("'Errorf' called")
+}
+func (m *mockTester) Fatalf(fmtstr string, args ...any) {
+	m.failed = true
+	m.T.Error("'Fatalf' called")
+}
+func (m *mockTester) Logf(fmtstr string, args ...any) {
+	m.failed = true
+	m.T.Error("'Logf' called")
+}
+func (m *mockTester) Name() string {
+	m.failed = true
+	m.T.Error("'Name' called")
+	return ""
+}
+func (m *mockTester) Skip(args ...any) {
+	m.failed = true
+	m.T.Error("'Skip' called")
+}
+func (m *mockTester) Skipf(fmtstr string, args ...any) {
+	m.failed = true
+	m.T.Error("'Skipf' called")
+}
+func (m *mockTester) SkipNow() {
+	m.failed = true
+	m.T.Error("'SkipNow' called")
+}
+func (m *mockTester) Skipped() bool {
+	m.failed = true
+	m.T.Error("'Skipped' called")
+	return false
+}
+
+const datePattern = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z`
+
+func mkPattern(prefix, msg string) string {
+	msg = strings.ReplaceAll(msg, `\`, `\\`)
+	msg = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(msg, "[", `\[`), "]", `\]`), "(", `\(`), ")", `\)`)
+	msg = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(msg, "{", `\{`), "}", `\}`), ".", `\.`), "^", `\^`)
+	msg = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(msg, "$", `\$`), "+", `\+`), "*", `\*`), "?", `\?`)
+
+	return "^" + prefix + "test\\.logger_test\\.go:\\d+: " + datePattern + ": " + msg + "\n$"
+}
+
+func matches(pattern, str string, t *testing.T) bool {
+	match, err := regexp.Match(pattern, []byte(str))
+	if err != nil {
+		t.Errorf("failed to compile pattern '%s': %v", pattern, err)
+		return false
+	}
+	return match
+}
+
+func TestInitTestLogging(t *testing.T) {
+	m := mockTester{T: t}
+	InitTestingLogging(&m, false)
+	msg := "testing message"
+	Debugln(msg)
+	pattern := mkPattern(DebugPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+	Debugf("formatted: %d, %+v, %t", 1, []byte{1}, true)
+	msg = "formatted: 1, [1], true"
+	pattern = mkPattern(DebugPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+
+	msg = "testing message"
+	Infoln(msg)
+	pattern = mkPattern(InfoPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+	Infof("formatted: %d, %+v, %t", 1, []byte{1}, true)
+	msg = "formatted: 1, [1], true"
+	pattern = mkPattern(InfoPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+
+	msg = "testing message"
+	Warnln(msg)
+	pattern = mkPattern(WarnPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+	Warnf("formatted: %d, %+v, %t", 1, []byte{1}, true)
+	msg = "formatted: 1, [1], true"
+	pattern = mkPattern(WarnPrefix, msg)
+	if !matches(pattern, m.logbuf, t) {
+		t.Errorf("expected last log message to match '%s', got: '%s'", pattern, m.logbuf)
+	}
+
+	msg = "testing message"
+	EventRaw(msg)
+	if m.logbuf != msg+"\n" {
+		t.Errorf("expected last log message to be exactly '%s', got: '%s'", msg, m.logbuf)
+	}
+	Eventf(time.Time{}, "formatted: %d, %+v, %t", 1, []byte{1}, true)
+	msg = "formatted: 1, [1], true"
+	pattern = fmt.Sprintf(eventFormat, eventTime(time.Time{}), "formatted: 1, [1], true\n")
+	if pattern != m.logbuf {
+		t.Errorf("expected last log message to be exactly '%s', got: '%s'", pattern, m.logbuf)
+	}
+
+	if m.Failed() {
+		t.Error("none of the non-error logging functions should have caused a failure")
+	}
+	if m.errbuf != "" {
+		t.Error("none of the non-error logging functions should have populated the error buffer:", m.errbuf)
+	}
+
+	m.logbuf = ""
+	msg = "testing message"
+	Errorln(msg)
+	if m.logbuf != "" {
+		t.Error("error should've been logged to the error stream, not the logging stream")
+		m.logbuf = ""
+	}
+	if !m.Failed() {
+		t.Error("error logging should have caused a failure")
+	} else {
+		m.failed = false
+	}
+	pattern = mkPattern(ErrPrefix, msg)
+	if !matches(pattern, m.errbuf, t) {
+		t.Errorf("expected last error message to match '%s', got: '%s'", pattern, m.errbuf)
+	}
+	Errorf("formatted: %d, %+v, %t", 1, []byte{1}, true)
+	if m.logbuf != "" {
+		t.Error("error should've been logged to the error stream, not the logging stream")
+	}
+	if !m.Failed() {
+		t.Error("error logging should have caused a failure")
+	}
+	msg = "formatted: 1, [1], true"
+	pattern = mkPattern(ErrPrefix, msg)
+	if !matches(pattern, m.errbuf, t) {
+		t.Errorf("expected last error message to match '%s', got: '%s'", pattern, m.errbuf)
+	}
+}
+
+func TestLoggingWarningsAsErrors(t *testing.T) {
+	m := mockTester{T: t}
+	InitTestingLogging(&m, true)
+	msg := "testing message"
+	Warnln(msg)
+	if m.logbuf != "" {
+		t.Error("warning should've been logged to the error stream, not the logging stream")
+		m.logbuf = ""
+	}
+	if !m.Failed() {
+		t.Error("warning logging should have caused a failure")
+	} else {
+		m.failed = false
+	}
+	pattern := mkPattern(WarnPrefix, msg)
+	if !matches(pattern, m.errbuf, t) {
+		t.Errorf("expected last error message to match '%s', got: '%s'", pattern, m.errbuf)
+	}
+	Warnf("formatted: %d, %+v, %t", 1, []byte{1}, true)
+	if m.logbuf != "" {
+		t.Error("warning should've been logged to the error stream, not the logging stream")
+	}
+	if !m.Failed() {
+		t.Error("warning logging should have caused a failure")
+	}
+	msg = "formatted: 1, [1], true"
+	pattern = mkPattern(WarnPrefix, msg)
+	if !matches(pattern, m.errbuf, t) {
+		t.Errorf("expected last error message to match '%s', got: '%s'", pattern, m.errbuf)
+	}
+}