blob: 12640483b1048d3093d6236de303d73ca03ed224 [file] [edit]
package logs
import (
"bytes"
"io"
"os"
"testing"
"time"
"github.com/sirupsen/logrus"
)
const msgErr = `"level":"error"`
func TestLoggingToFile(t *testing.T) {
l := runLogForwarding(t)
msg := `"level":"info","msg":"kitten"`
logToLogWriter(t, l, msg)
finish(t, l)
check(t, l, msg, msgErr)
}
func TestLogForwardingDoesNotStopOnJsonDecodeErr(t *testing.T) {
l := runLogForwarding(t)
logToLogWriter(t, l, `"invalid-json-with-kitten"`)
checkWait(t, l, msgErr, "")
truncateLogFile(t, l.file)
msg := `"level":"info","msg":"puppy"`
logToLogWriter(t, l, msg)
finish(t, l)
check(t, l, msg, msgErr)
}
func TestLogForwardingDoesNotStopOnLogLevelParsingErr(t *testing.T) {
l := runLogForwarding(t)
msg := `"level":"alert","msg":"puppy"`
logToLogWriter(t, l, msg)
checkWait(t, l, msgErr, msg)
truncateLogFile(t, l.file)
msg = `"level":"info","msg":"puppy"`
logToLogWriter(t, l, msg)
finish(t, l)
check(t, l, msg, msgErr)
}
func TestLogForwardingStopsAfterClosingTheWriter(t *testing.T) {
l := runLogForwarding(t)
msg := `"level":"info","msg":"sync"`
logToLogWriter(t, l, msg)
// Do not use finish() here as we check done pipe ourselves.
l.w.Close()
select {
case <-l.done:
case <-time.After(10 * time.Second):
t.Fatal("log forwarding did not stop after closing the pipe")
}
check(t, l, msg, msgErr)
}
func logToLogWriter(t *testing.T, l *log, message string) {
t.Helper()
_, err := l.w.Write([]byte("{" + message + "}\n"))
if err != nil {
t.Fatalf("failed to write %q to log writer: %v", message, err)
}
}
type log struct {
w io.WriteCloser
file *os.File
done chan error
}
func runLogForwarding(t *testing.T) *log {
t.Helper()
logR, logW, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
logR.Close()
logW.Close()
})
tempFile, err := os.CreateTemp("", "")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
tempFile.Close()
os.Remove(tempFile.Name())
})
logrus.SetOutput(tempFile)
logrus.SetFormatter(&logrus.JSONFormatter{})
doneForwarding := ForwardLogs(logR)
return &log{w: logW, done: doneForwarding, file: tempFile}
}
func finish(t *testing.T, l *log) {
t.Helper()
l.w.Close()
if err := <-l.done; err != nil {
t.Fatalf("ForwardLogs: %v", err)
}
}
func truncateLogFile(t *testing.T, file *os.File) {
t.Helper()
err := file.Truncate(0)
if err != nil {
t.Fatalf("failed to truncate log file: %v", err)
}
}
// check checks that the file contains txt and does not contain notxt.
func check(t *testing.T, l *log, txt, notxt string) {
t.Helper()
contents, err := os.ReadFile(l.file.Name())
if err != nil {
t.Fatal(err)
}
if txt != "" && !bytes.Contains(contents, []byte(txt)) {
t.Fatalf("%s does not contain %s", contents, txt)
}
if notxt != "" && bytes.Contains(contents, []byte(notxt)) {
t.Fatalf("%s does contain %s", contents, notxt)
}
}
// checkWait is like check, but if the file is empty,
// it waits until it's not.
func checkWait(t *testing.T, l *log, txt string, notxt string) {
t.Helper()
const (
delay = 100 * time.Millisecond
iter = 3
)
for i := 0; ; i++ {
st, err := l.file.Stat()
if err != nil {
t.Fatal(err)
}
if st.Size() > 0 {
break
}
if i == iter {
t.Fatalf("waited %s for file %s to be non-empty but it still is", iter*delay, l.file.Name())
}
time.Sleep(delay)
}
check(t, l, txt, notxt)
}