github.com/rawahars/moby@v24.0.4+incompatible/pkg/tailfile/tailfile_test.go (about)

     1  package tailfile // import "github.com/docker/docker/pkg/tailfile"
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"gotest.tools/v3/assert"
    14  )
    15  
    16  func TestTailFile(t *testing.T) {
    17  	f, err := os.CreateTemp("", "tail-test")
    18  	if err != nil {
    19  		t.Fatal(err)
    20  	}
    21  	defer f.Close()
    22  	defer os.RemoveAll(f.Name())
    23  	testFile := []byte(`first line
    24  second line
    25  third line
    26  fourth line
    27  fifth line
    28  next first line
    29  next second line
    30  next third line
    31  next fourth line
    32  next fifth line
    33  last first line
    34  next first line
    35  next second line
    36  next third line
    37  next fourth line
    38  next fifth line
    39  next first line
    40  next second line
    41  next third line
    42  next fourth line
    43  next fifth line
    44  last second line
    45  last third line
    46  last fourth line
    47  last fifth line
    48  truncated line`)
    49  	if _, err := f.Write(testFile); err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	if _, err := f.Seek(0, io.SeekStart); err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	expected := []string{"last fourth line", "last fifth line"}
    56  	res, err := TailFile(f, 2)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	if len(res) != len(expected) {
    61  		t.Fatalf("\nexpected:\n%s\n\nactual:\n%s", expected, res)
    62  	}
    63  	for i, l := range res {
    64  		if expected[i] != string(l) {
    65  			t.Fatalf("Expected line %q, got %q", expected[i], l)
    66  		}
    67  	}
    68  }
    69  
    70  func TestTailFileManyLines(t *testing.T) {
    71  	f, err := os.CreateTemp("", "tail-test")
    72  	if err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	defer f.Close()
    76  	defer os.RemoveAll(f.Name())
    77  	testFile := []byte(`first line
    78  second line
    79  truncated line`)
    80  	if _, err := f.Write(testFile); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	if _, err := f.Seek(0, io.SeekStart); err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	expected := []string{"first line", "second line"}
    87  	res, err := TailFile(f, 10000)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	if len(expected) != len(res) {
    92  		t.Fatalf("\nexpected:\n%s\n\nactual:\n%s", expected, res)
    93  	}
    94  	for i, l := range res {
    95  		if expected[i] != string(l) {
    96  			t.Fatalf("Expected line %s, got %s", expected[i], l)
    97  		}
    98  	}
    99  }
   100  
   101  func TestTailEmptyFile(t *testing.T) {
   102  	f, err := os.CreateTemp("", "tail-test")
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	defer f.Close()
   107  	defer os.RemoveAll(f.Name())
   108  	res, err := TailFile(f, 10000)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	if len(res) != 0 {
   113  		t.Fatal("Must be empty slice from empty file")
   114  	}
   115  }
   116  
   117  func TestTailNegativeN(t *testing.T) {
   118  	f, err := os.CreateTemp("", "tail-test")
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	defer f.Close()
   123  	defer os.RemoveAll(f.Name())
   124  	testFile := []byte(`first line
   125  second line
   126  truncated line`)
   127  	if _, err := f.Write(testFile); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	if _, err := f.Seek(0, io.SeekStart); err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	if _, err := TailFile(f, -1); err != ErrNonPositiveLinesNumber {
   134  		t.Fatalf("Expected ErrNonPositiveLinesNumber, got %v", err)
   135  	}
   136  	if _, err := TailFile(f, 0); err != ErrNonPositiveLinesNumber {
   137  		t.Fatalf("Expected ErrNonPositiveLinesNumber, got %s", err)
   138  	}
   139  }
   140  
   141  func BenchmarkTail(b *testing.B) {
   142  	f, err := os.CreateTemp("", "tail-test")
   143  	if err != nil {
   144  		b.Fatal(err)
   145  	}
   146  	defer f.Close()
   147  	defer os.RemoveAll(f.Name())
   148  	for i := 0; i < 10000; i++ {
   149  		if _, err := f.Write([]byte("tailfile pretty interesting line\n")); err != nil {
   150  			b.Fatal(err)
   151  		}
   152  	}
   153  	b.ResetTimer()
   154  	for i := 0; i < b.N; i++ {
   155  		if _, err := TailFile(f, 1000); err != nil {
   156  			b.Fatal(err)
   157  		}
   158  	}
   159  }
   160  
   161  func TestNewTailReader(t *testing.T) {
   162  	t.Parallel()
   163  	ctx := context.Background()
   164  
   165  	for dName, delim := range map[string][]byte{
   166  		"no delimiter":          {},
   167  		"single byte delimiter": {'\n'},
   168  		"2 byte delimiter":      []byte(";\n"),
   169  		"4 byte delimiter":      []byte("####"),
   170  		"8 byte delimiter":      []byte("########"),
   171  		"12 byte delimiter":     []byte("############"),
   172  	} {
   173  		t.Run(dName, func(t *testing.T) {
   174  			delim := delim
   175  			t.Parallel()
   176  
   177  			s1 := "Hello world."
   178  			s2 := "Today is a fine day."
   179  			s3 := "So long, and thanks for all the fish!"
   180  			s4 := strings.Repeat("a", blockSize/2) // same as block size
   181  			s5 := strings.Repeat("a", blockSize)   // just to make sure
   182  			s6 := strings.Repeat("a", blockSize*2) // bigger than block size
   183  			s7 := strings.Repeat("a", blockSize-1) // single line same as block
   184  
   185  			s8 := `{"log":"Don't panic!\n","stream":"stdout","time":"2018-04-04T20:28:44.7207062Z"}`
   186  			jsonTest := make([]string, 0, 20)
   187  			for i := 0; i < 20; i++ {
   188  				jsonTest = append(jsonTest, s8)
   189  			}
   190  
   191  			for _, test := range []struct {
   192  				desc string
   193  				data []string
   194  			}{
   195  				{desc: "one small entry", data: []string{s1}},
   196  				{desc: "several small entries", data: []string{s1, s2, s3}},
   197  				{desc: "various sizes", data: []string{s1, s2, s3, s4, s5, s1, s2, s3, s7, s6}},
   198  				{desc: "multiple lines with one more than block", data: []string{s5, s5, s5, s5, s5}},
   199  				{desc: "multiple lines much bigger than block", data: []string{s6, s6, s6, s6, s6}},
   200  				{desc: "multiple lines same as block", data: []string{s4, s4, s4, s4, s4}},
   201  				{desc: "single line same as block", data: []string{s7}},
   202  				{desc: "single line half block", data: []string{s4}},
   203  				{desc: "single line twice block", data: []string{s6}},
   204  				{desc: "json encoded values", data: jsonTest},
   205  				{desc: "no lines", data: []string{}},
   206  				{desc: "same length as delimiter", data: []string{strings.Repeat("a", len(delim))}},
   207  			} {
   208  				t.Run(test.desc, func(t *testing.T) {
   209  					test := test
   210  					t.Parallel()
   211  
   212  					max := len(test.data)
   213  					if max > 10 {
   214  						max = 10
   215  					}
   216  
   217  					s := strings.Join(test.data, string(delim))
   218  					if len(test.data) > 0 {
   219  						s += string(delim)
   220  					}
   221  
   222  					for i := 1; i <= max; i++ {
   223  						t.Run(fmt.Sprintf("%d lines", i), func(t *testing.T) {
   224  							i := i
   225  							t.Parallel()
   226  
   227  							r := strings.NewReader(s)
   228  							tr, lines, err := NewTailReaderWithDelimiter(ctx, r, i, delim)
   229  							if len(delim) == 0 {
   230  								assert.Assert(t, err != nil)
   231  								assert.Assert(t, lines == 0)
   232  								return
   233  							}
   234  							assert.NilError(t, err)
   235  							assert.Check(t, lines == i, "%d -- %d", lines, i)
   236  
   237  							b, err := io.ReadAll(tr)
   238  							assert.NilError(t, err)
   239  
   240  							expectLines := test.data[len(test.data)-i:]
   241  							assert.Check(t, len(expectLines) == i)
   242  							expect := strings.Join(expectLines, string(delim)) + string(delim)
   243  							assert.Check(t, string(b) == expect, "\n%v\n%v", b, []byte(expect))
   244  						})
   245  					}
   246  
   247  					t.Run("request more lines than available", func(t *testing.T) {
   248  						t.Parallel()
   249  
   250  						r := strings.NewReader(s)
   251  						tr, lines, err := NewTailReaderWithDelimiter(ctx, r, len(test.data)*2, delim)
   252  						if len(delim) == 0 {
   253  							assert.Assert(t, err != nil)
   254  							assert.Assert(t, lines == 0)
   255  							return
   256  						}
   257  						if len(test.data) == 0 {
   258  							assert.Assert(t, err == ErrNonPositiveLinesNumber, err)
   259  							return
   260  						}
   261  
   262  						assert.NilError(t, err)
   263  						assert.Check(t, lines == len(test.data), "%d -- %d", lines, len(test.data))
   264  						b, err := io.ReadAll(tr)
   265  						assert.NilError(t, err)
   266  						assert.Check(t, bytes.Equal(b, []byte(s)), "\n%v\n%v", b, []byte(s))
   267  					})
   268  				})
   269  			}
   270  		})
   271  	}
   272  	t.Run("truncated last line", func(t *testing.T) {
   273  		t.Run("more than available", func(t *testing.T) {
   274  			tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 3)
   275  			assert.NilError(t, err)
   276  			assert.Check(t, nLines == 2, nLines)
   277  
   278  			rdr := bufio.NewReader(tail)
   279  			data, _, err := rdr.ReadLine()
   280  			assert.NilError(t, err)
   281  			assert.Check(t, string(data) == "a", string(data))
   282  
   283  			data, _, err = rdr.ReadLine()
   284  			assert.NilError(t, err)
   285  			assert.Check(t, string(data) == "b", string(data))
   286  
   287  			_, _, err = rdr.ReadLine()
   288  			assert.Assert(t, err == io.EOF, err)
   289  		})
   290  	})
   291  	t.Run("truncated last line", func(t *testing.T) {
   292  		t.Run("exact", func(t *testing.T) {
   293  			tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 2)
   294  			assert.NilError(t, err)
   295  			assert.Check(t, nLines == 2, nLines)
   296  
   297  			rdr := bufio.NewReader(tail)
   298  			data, _, err := rdr.ReadLine()
   299  			assert.NilError(t, err)
   300  			assert.Check(t, string(data) == "a", string(data))
   301  
   302  			data, _, err = rdr.ReadLine()
   303  			assert.NilError(t, err)
   304  			assert.Check(t, string(data) == "b", string(data))
   305  
   306  			_, _, err = rdr.ReadLine()
   307  			assert.Assert(t, err == io.EOF, err)
   308  		})
   309  	})
   310  
   311  	t.Run("truncated last line", func(t *testing.T) {
   312  		t.Run("one line", func(t *testing.T) {
   313  			tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 1)
   314  			assert.NilError(t, err)
   315  			assert.Check(t, nLines == 1, nLines)
   316  
   317  			rdr := bufio.NewReader(tail)
   318  			data, _, err := rdr.ReadLine()
   319  			assert.NilError(t, err)
   320  			assert.Check(t, string(data) == "b", string(data))
   321  
   322  			_, _, err = rdr.ReadLine()
   323  			assert.Assert(t, err == io.EOF, err)
   324  		})
   325  	})
   326  }