github.com/netdata/go.d.plugin@v0.58.1/pkg/logs/reader_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package logs
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestReader_Read(t *testing.T) {
    19  	reader, teardown := prepareTestReader(t)
    20  	defer teardown()
    21  
    22  	r := testReader{bufio.NewReader(reader)}
    23  	filename := reader.CurrentFilename()
    24  	numLogs := 5
    25  	var sum int
    26  
    27  	for i := 0; i < 10; i++ {
    28  		appendLogs(t, filename, time.Millisecond*10, numLogs)
    29  		n, err := r.readUntilEOF()
    30  		sum += n
    31  
    32  		assert.Equal(t, io.EOF, err)
    33  		assert.Equal(t, numLogs*(i+1), sum)
    34  	}
    35  }
    36  
    37  func TestReader_Read_HandleFileRotation(t *testing.T) {
    38  	reader, teardown := prepareTestReader(t)
    39  	defer teardown()
    40  
    41  	r := testReader{bufio.NewReader(reader)}
    42  	filename := reader.CurrentFilename()
    43  	numLogs := 5
    44  	rotateFile(t, filename)
    45  	appendLogs(t, filename, time.Millisecond*10, numLogs)
    46  
    47  	n, err := r.readUntilEOFTimes(maxEOF)
    48  	assert.Equal(t, io.EOF, err)
    49  	assert.Equal(t, 0, n)
    50  
    51  	appendLogs(t, filename, time.Millisecond*10, numLogs)
    52  	n, err = r.readUntilEOF()
    53  	assert.Equal(t, io.EOF, err)
    54  	assert.Equal(t, numLogs, n)
    55  }
    56  
    57  func TestReader_Read_HandleFileRotationWithDelay(t *testing.T) {
    58  	reader, teardown := prepareTestReader(t)
    59  	defer teardown()
    60  
    61  	r := testReader{bufio.NewReader(reader)}
    62  	filename := reader.CurrentFilename()
    63  	_ = os.Remove(filename)
    64  
    65  	// trigger reopen first time
    66  	n, err := r.readUntilEOFTimes(maxEOF)
    67  	assert.Equal(t, ErrNoMatchedFile, err)
    68  	assert.Equal(t, 0, n)
    69  
    70  	f, err := os.Create(filename)
    71  	require.NoError(t, err)
    72  	_ = f.Close()
    73  
    74  	// trigger reopen 2nd time
    75  	n, err = r.readUntilEOF()
    76  	assert.Equal(t, io.EOF, err)
    77  	assert.Equal(t, 0, n)
    78  
    79  	numLogs := 5
    80  	appendLogs(t, filename, time.Millisecond*10, numLogs)
    81  	n, err = r.readUntilEOF()
    82  	assert.Equal(t, io.EOF, err)
    83  	assert.Equal(t, numLogs, n)
    84  }
    85  
    86  func TestReader_Close(t *testing.T) {
    87  	reader, teardown := prepareTestReader(t)
    88  	defer teardown()
    89  
    90  	assert.NoError(t, reader.Close())
    91  	assert.Nil(t, reader.file)
    92  }
    93  
    94  func TestReader_Close_NilFile(t *testing.T) {
    95  	var r Reader
    96  	assert.NoError(t, r.Close())
    97  }
    98  
    99  func TestOpen(t *testing.T) {
   100  	tempFileName1 := prepareTempFile(t, "*-web_log-open-test-1.log")
   101  	tempFileName2 := prepareTempFile(t, "*-web_log-open-test-2.log")
   102  	tempFileName3 := prepareTempFile(t, "*-web_log-open-test-3.log")
   103  	defer func() {
   104  		_ = os.Remove(tempFileName1)
   105  		_ = os.Remove(tempFileName2)
   106  		_ = os.Remove(tempFileName3)
   107  	}()
   108  
   109  	makePath := func(s string) string {
   110  		return filepath.Join(os.TempDir(), s)
   111  	}
   112  
   113  	tests := []struct {
   114  		name    string
   115  		path    string
   116  		exclude string
   117  		err     bool
   118  	}{
   119  		{
   120  			name: "match without exclude",
   121  			path: makePath("*-web_log-open-test-[1-3].log"),
   122  		},
   123  		{
   124  			name:    "match with exclude",
   125  			path:    makePath("*-web_log-open-test-[1-3].log"),
   126  			exclude: makePath("*-web_log-open-test-[2-3].log"),
   127  		},
   128  		{
   129  			name:    "exclude everything",
   130  			path:    makePath("*-web_log-open-test-[1-3].log"),
   131  			exclude: makePath("*"),
   132  			err:     true,
   133  		},
   134  		{
   135  			name: "no match",
   136  			path: makePath("*-web_log-no-match-test-[1-3].log"),
   137  			err:  true,
   138  		},
   139  		{
   140  			name: "bad path pattern",
   141  			path: "[qw",
   142  			err:  true,
   143  		},
   144  		{
   145  			name: "bad exclude path pattern",
   146  			path: "[qw",
   147  			err:  true,
   148  		},
   149  	}
   150  
   151  	for _, tt := range tests {
   152  		t.Run(tt.name, func(t *testing.T) {
   153  			r, err := Open(tt.path, tt.exclude, nil)
   154  
   155  			if tt.err {
   156  				assert.Error(t, err)
   157  			} else {
   158  				assert.NoError(t, err)
   159  				assert.NotNil(t, r.file)
   160  				_ = r.Close()
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  func TestReader_CurrentFilename(t *testing.T) {
   167  	reader, teardown := prepareTestReader(t)
   168  	defer teardown()
   169  
   170  	assert.Equal(t, reader.file.Name(), reader.CurrentFilename())
   171  }
   172  
   173  type testReader struct {
   174  	*bufio.Reader
   175  }
   176  
   177  func (r *testReader) readUntilEOF() (n int, err error) {
   178  	for {
   179  		_, err = r.ReadBytes('\n')
   180  		if err != nil {
   181  			break
   182  		}
   183  		n++
   184  	}
   185  	return n, err
   186  }
   187  
   188  func (r *testReader) readUntilEOFTimes(times int) (sum int, err error) {
   189  	var n int
   190  	for i := 0; i < times; i++ {
   191  		n, err = r.readUntilEOF()
   192  		if err != io.EOF {
   193  			break
   194  		}
   195  		sum += n
   196  	}
   197  	return sum, err
   198  }
   199  
   200  func prepareTempFile(t *testing.T, pattern string) string {
   201  	t.Helper()
   202  	f, err := os.CreateTemp("", pattern)
   203  	require.NoError(t, err)
   204  	return f.Name()
   205  }
   206  
   207  func prepareTestReader(t *testing.T) (reader *Reader, teardown func()) {
   208  	t.Helper()
   209  	filename := prepareTempFile(t, "*-web_log-test.log")
   210  	f, err := os.Open(filename)
   211  	require.NoError(t, err)
   212  
   213  	teardown = func() {
   214  		_ = os.Remove(filename)
   215  		_ = reader.file.Close()
   216  	}
   217  	reader = &Reader{
   218  		file: f,
   219  		path: filename,
   220  	}
   221  	return reader, teardown
   222  }
   223  
   224  func rotateFile(t *testing.T, filename string) {
   225  	t.Helper()
   226  	require.NoError(t, os.Remove(filename))
   227  	f, err := os.Create(filename)
   228  	require.NoError(t, err)
   229  	_ = f.Close()
   230  }
   231  
   232  func appendLogs(t *testing.T, filename string, interval time.Duration, numOfLogs int) {
   233  	t.Helper()
   234  	base := filepath.Base(filename)
   235  	file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND, os.ModeAppend)
   236  	require.NoError(t, err)
   237  	require.NotNil(t, file)
   238  	defer func() { _ = file.Close() }()
   239  
   240  	for i := 0; i < numOfLogs; i++ {
   241  		_, err = fmt.Fprintln(file, "line", i, "filename", base)
   242  		require.NoError(t, err)
   243  		time.Sleep(interval)
   244  	}
   245  }