github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/tailfile/tailfile_test.go (about)

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