github.com/containerd/nerdctl@v1.7.7/pkg/logging/cri_logger_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  /*
    18  	Forked from https://github.com/kubernetes/kubernetes/blob/a66aad2d80dacc70025f95a8f97d2549ebd3208c/pkg/kubelet/kuberuntime/logs/logs_test.go
    19  	Copyright The Kubernetes Authors.
    20  	Licensed under the Apache License, Version 2.0
    21  */
    22  
    23  package logging
    24  
    25  import (
    26  	"bufio"
    27  	"bytes"
    28  	"fmt"
    29  	"io"
    30  	"os"
    31  	"reflect"
    32  	"testing"
    33  	"time"
    34  )
    35  
    36  func TestReadLogs(t *testing.T) {
    37  	file, err := os.CreateTemp("", "TestFollowLogs")
    38  	if err != nil {
    39  		t.Fatalf("unable to create temp file")
    40  	}
    41  	defer os.Remove(file.Name())
    42  	file.WriteString(`2016-10-06T00:17:09.669794202Z stdout F line1` + "\n")
    43  	file.WriteString(`2016-10-06T00:17:10.669794202Z stdout F line2` + "\n")
    44  	file.WriteString(`2016-10-06T00:17:11.669794202Z stdout F line3` + "\n")
    45  
    46  	stopChan := make(chan os.Signal)
    47  	testCases := []struct {
    48  		name           string
    49  		logViewOptions LogViewOptions
    50  		expected       string
    51  	}{
    52  		{
    53  			name: "default log options should output all lines",
    54  			logViewOptions: LogViewOptions{
    55  				LogPath: file.Name(),
    56  				Tail:    0,
    57  			},
    58  			expected: "line1\nline2\nline3\n",
    59  		},
    60  		{
    61  			name: "using Tail 2 should output last 2 lines",
    62  			logViewOptions: LogViewOptions{
    63  				LogPath: file.Name(),
    64  				Tail:    2,
    65  			},
    66  			expected: "line2\nline3\n",
    67  		},
    68  		{
    69  			name: "using Tail 4 should output all lines when the log has less than 4 lines",
    70  			logViewOptions: LogViewOptions{
    71  				LogPath: file.Name(),
    72  				Tail:    4,
    73  			},
    74  			expected: "line1\nline2\nline3\n",
    75  		},
    76  		{
    77  			name: "using Tail 0 should output all",
    78  			logViewOptions: LogViewOptions{
    79  				LogPath: file.Name(),
    80  				Tail:    0,
    81  			},
    82  			expected: "line1\nline2\nline3\n",
    83  		},
    84  	}
    85  	for _, tc := range testCases {
    86  		t.Run(tc.name, func(t *testing.T) {
    87  			stdoutBuf := bytes.NewBuffer(nil)
    88  			stderrBuf := bytes.NewBuffer(nil)
    89  			err = ReadLogs(&tc.logViewOptions, stdoutBuf, stderrBuf, stopChan)
    90  
    91  			if err != nil {
    92  				t.Fatal(err.Error())
    93  			}
    94  			if stderrBuf.Len() > 0 {
    95  				t.Fatalf("Stderr: %v", stderrBuf.String())
    96  			}
    97  			if actual := stdoutBuf.String(); tc.expected != actual {
    98  				t.Fatalf("Actual output does not match expected.\nActual:  %v\nExpected: %v\n", actual, tc.expected)
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  func TestParseLog(t *testing.T) {
   105  	timestamp, err := time.Parse(time.RFC3339Nano, "2016-10-20T18:39:20.57606443Z")
   106  
   107  	if err != nil {
   108  		t.Fatalf("Parse Time err %s", err.Error())
   109  	}
   110  	logmsg := &logMessage{}
   111  	for c, test := range []struct {
   112  		line string
   113  		msg  *logMessage
   114  		err  bool
   115  	}{
   116  		{ // CRI log format stdout
   117  			line: "2016-10-20T18:39:20.57606443Z stdout F cri stdout test log\n",
   118  			msg: &logMessage{
   119  				timestamp: timestamp,
   120  				stream:    Stdout,
   121  				log:       []byte("cri stdout test log\n"),
   122  			},
   123  		},
   124  		{ // CRI log format stderr
   125  			line: "2016-10-20T18:39:20.57606443Z stderr F cri stderr test log\n",
   126  			msg: &logMessage{
   127  				timestamp: timestamp,
   128  				stream:    Stderr,
   129  				log:       []byte("cri stderr test log\n"),
   130  			},
   131  		},
   132  		{ // Unsupported Log format
   133  			line: "unsupported log format test log\n",
   134  			msg:  &logMessage{},
   135  			err:  true,
   136  		},
   137  		{ // Partial CRI log line
   138  			line: "2016-10-20T18:39:20.57606443Z stdout P cri stdout partial test log\n",
   139  			msg: &logMessage{
   140  				timestamp: timestamp,
   141  				stream:    Stdout,
   142  				log:       []byte("cri stdout partial test log"),
   143  			},
   144  		},
   145  		{ // Partial CRI log line with multiple log tags.
   146  			line: "2016-10-20T18:39:20.57606443Z stdout P:TAG1:TAG2 cri stdout partial test log\n",
   147  			msg: &logMessage{
   148  				timestamp: timestamp,
   149  				stream:    Stdout,
   150  				log:       []byte("cri stdout partial test log"),
   151  			},
   152  		},
   153  	} {
   154  		t.Logf("TestCase #%d: %+v", c, test)
   155  
   156  		err = ParseCRILog([]byte(test.line), logmsg)
   157  		if err != nil {
   158  			if test.err {
   159  				continue
   160  			}
   161  			t.Errorf("ParseCRILog err %s ", err.Error())
   162  		}
   163  
   164  		if !reflect.DeepEqual(test.msg, logmsg) {
   165  			t.Errorf("ParseCRILog failed, msg is %#v,test.msg is %#v", logmsg, test.msg)
   166  		}
   167  
   168  	}
   169  }
   170  
   171  func TestReadLogsLimitsWithTimestamps(t *testing.T) {
   172  	logLineFmt := "2022-10-29T16:10:22.592603036-05:00 stdout P %v\n"
   173  	logLineNewLine := "2022-10-29T16:10:22.592603036-05:00 stdout F \n"
   174  
   175  	tmpfile, err := os.CreateTemp("", "log.*.txt")
   176  	if err != nil {
   177  		t.Fatalf("unable to create temp file")
   178  	}
   179  
   180  	stopChan := make(chan os.Signal)
   181  
   182  	count := 10000
   183  
   184  	for i := 0; i < count; i++ {
   185  		tmpfile.WriteString(fmt.Sprintf(logLineFmt, i))
   186  	}
   187  	tmpfile.WriteString(logLineNewLine)
   188  
   189  	for i := 0; i < count; i++ {
   190  		tmpfile.WriteString(fmt.Sprintf(logLineFmt, i))
   191  	}
   192  	tmpfile.WriteString(logLineNewLine)
   193  
   194  	// two lines are in the buffer
   195  
   196  	defer os.Remove(tmpfile.Name()) // clean up
   197  
   198  	tmpfile.Close()
   199  
   200  	var buf bytes.Buffer
   201  	w := io.MultiWriter(&buf)
   202  
   203  	err = ReadLogs(&LogViewOptions{LogPath: tmpfile.Name(), Tail: 0, Timestamps: true}, w, w, stopChan)
   204  	if err != nil {
   205  		t.Errorf("ReadLogs file %s failed %s", tmpfile.Name(), err.Error())
   206  	}
   207  
   208  	lineCount := 0
   209  	scanner := bufio.NewScanner(bytes.NewReader(buf.Bytes()))
   210  	for scanner.Scan() {
   211  		lineCount++
   212  
   213  		// Split the line
   214  		ts, logline, _ := bytes.Cut(scanner.Bytes(), []byte(" "))
   215  
   216  		// Verification
   217  		//   1. The timestamp should exist
   218  		//   2. The last item in the log should be 9999
   219  		_, err = time.Parse(time.RFC3339, string(ts))
   220  		if err != nil {
   221  			t.Errorf("timestamp not found, err: %s", err.Error())
   222  		}
   223  
   224  		if !bytes.HasSuffix(logline, []byte("9999")) {
   225  			t.Errorf("the complete log found, err: %s", err.Error())
   226  		}
   227  	}
   228  
   229  	if lineCount != 2 {
   230  		t.Errorf("should have two lines, lineCount= %d", lineCount)
   231  	}
   232  }