github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/logs/logs_linux_test.go (about) 1 package logs 2 3 import ( 4 "bytes" 5 "io" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/sirupsen/logrus" 11 ) 12 13 const msgErr = `"level":"error"` 14 15 func TestLoggingToFile(t *testing.T) { 16 l := runLogForwarding(t) 17 18 msg := `"level":"info","msg":"kitten"` 19 logToLogWriter(t, l, msg) 20 finish(t, l) 21 check(t, l, msg, msgErr) 22 } 23 24 func TestLogForwardingDoesNotStopOnJsonDecodeErr(t *testing.T) { 25 l := runLogForwarding(t) 26 27 logToLogWriter(t, l, `"invalid-json-with-kitten"`) 28 checkWait(t, l, msgErr, "") 29 30 truncateLogFile(t, l.file) 31 32 msg := `"level":"info","msg":"puppy"` 33 logToLogWriter(t, l, msg) 34 finish(t, l) 35 check(t, l, msg, msgErr) 36 } 37 38 func TestLogForwardingDoesNotStopOnLogLevelParsingErr(t *testing.T) { 39 l := runLogForwarding(t) 40 41 msg := `"level":"alert","msg":"puppy"` 42 logToLogWriter(t, l, msg) 43 checkWait(t, l, msgErr, msg) 44 45 truncateLogFile(t, l.file) 46 47 msg = `"level":"info","msg":"puppy"` 48 logToLogWriter(t, l, msg) 49 finish(t, l) 50 check(t, l, msg, msgErr) 51 } 52 53 func TestLogForwardingStopsAfterClosingTheWriter(t *testing.T) { 54 l := runLogForwarding(t) 55 56 msg := `"level":"info","msg":"sync"` 57 logToLogWriter(t, l, msg) 58 59 // Do not use finish() here as we check done pipe ourselves. 60 l.w.Close() 61 select { 62 case <-l.done: 63 case <-time.After(10 * time.Second): 64 t.Fatal("log forwarding did not stop after closing the pipe") 65 } 66 67 check(t, l, msg, msgErr) 68 } 69 70 func logToLogWriter(t *testing.T, l *log, message string) { 71 t.Helper() 72 _, err := l.w.Write([]byte("{" + message + "}\n")) 73 if err != nil { 74 t.Fatalf("failed to write %q to log writer: %v", message, err) 75 } 76 } 77 78 type log struct { 79 w io.WriteCloser 80 file *os.File 81 done chan error 82 } 83 84 func runLogForwarding(t *testing.T) *log { 85 t.Helper() 86 logR, logW, err := os.Pipe() 87 if err != nil { 88 t.Fatal(err) 89 } 90 t.Cleanup(func() { 91 logR.Close() 92 logW.Close() 93 }) 94 95 tempFile, err := os.CreateTemp("", "") 96 if err != nil { 97 t.Fatal(err) 98 } 99 t.Cleanup(func() { 100 tempFile.Close() 101 os.Remove(tempFile.Name()) 102 }) 103 104 logrus.SetOutput(tempFile) 105 logrus.SetFormatter(&logrus.JSONFormatter{}) 106 doneForwarding := ForwardLogs(logR) 107 108 return &log{w: logW, done: doneForwarding, file: tempFile} 109 } 110 111 func finish(t *testing.T, l *log) { 112 t.Helper() 113 l.w.Close() 114 if err := <-l.done; err != nil { 115 t.Fatalf("ForwardLogs: %v", err) 116 } 117 } 118 119 func truncateLogFile(t *testing.T, file *os.File) { 120 t.Helper() 121 122 err := file.Truncate(0) 123 if err != nil { 124 t.Fatalf("failed to truncate log file: %v", err) 125 } 126 } 127 128 // check checks that the file contains txt and does not contain notxt. 129 func check(t *testing.T, l *log, txt, notxt string) { 130 t.Helper() 131 contents, err := os.ReadFile(l.file.Name()) 132 if err != nil { 133 t.Fatal(err) 134 } 135 if txt != "" && !bytes.Contains(contents, []byte(txt)) { 136 t.Fatalf("%s does not contain %s", contents, txt) 137 } 138 if notxt != "" && bytes.Contains(contents, []byte(notxt)) { 139 t.Fatalf("%s does contain %s", contents, notxt) 140 } 141 } 142 143 // checkWait is like check, but if the file is empty, 144 // it waits until it's not. 145 func checkWait(t *testing.T, l *log, txt string, notxt string) { 146 t.Helper() 147 const ( 148 delay = 100 * time.Millisecond 149 iter = 3 150 ) 151 for i := 0; ; i++ { 152 st, err := l.file.Stat() 153 if err != nil { 154 t.Fatal(err) 155 } 156 if st.Size() > 0 { 157 break 158 } 159 if i == iter { 160 t.Fatalf("waited %s for file %s to be non-empty but it still is", iter*delay, l.file.Name()) 161 } 162 time.Sleep(delay) 163 } 164 165 check(t, l, txt, notxt) 166 }