github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/rotate_log_test.go (about)

     1  package xlog
     2  
     3  import (
     4  	"archive/zip"
     5  	"context"
     6  	"errors"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/google/safeopen"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/benz9527/xboot/lib/id"
    19  )
    20  
    21  func TestParseFileSizeUnit(t *testing.T) {
    22  	testcases := []struct {
    23  		size        string
    24  		expected    uint64
    25  		expectedErr bool
    26  	}{
    27  		{
    28  			"abcMB",
    29  			0,
    30  			true,
    31  		},
    32  		{
    33  			"_GB",
    34  			0,
    35  			true,
    36  		},
    37  		{
    38  			"TB",
    39  			0,
    40  			true,
    41  		},
    42  		{
    43  			"Y",
    44  			0,
    45  			true,
    46  		},
    47  		{
    48  			"100B",
    49  			100 * uint64(B),
    50  			false,
    51  		},
    52  		{
    53  			"100KB",
    54  			100 * uint64(KB),
    55  			false,
    56  		},
    57  		{
    58  			"100MB",
    59  			100 * uint64(MB),
    60  			false,
    61  		},
    62  		{
    63  			"100b",
    64  			100 * uint64(B),
    65  			false,
    66  		},
    67  		{
    68  			"100kb",
    69  			100 * uint64(KB),
    70  			false,
    71  		},
    72  		{
    73  			"100mb",
    74  			100 * uint64(MB),
    75  			false,
    76  		},
    77  		{
    78  			"100kB",
    79  			100 * uint64(KB),
    80  			false,
    81  		},
    82  		{
    83  			"100Mb",
    84  			100 * uint64(MB),
    85  			false,
    86  		},
    87  		{
    88  			"100Kb",
    89  			100 * uint64(KB),
    90  			false,
    91  		},
    92  		{
    93  			"100mB",
    94  			100 * uint64(MB),
    95  			false,
    96  		},
    97  	}
    98  	for _, tc := range testcases {
    99  		actual, err := parseFileSize(tc.size)
   100  		if tc.expectedErr {
   101  			require.Error(t, err)
   102  			continue
   103  		}
   104  		require.NoError(t, err)
   105  		require.Equal(t, tc.expected, actual)
   106  	}
   107  }
   108  
   109  func TestParseFileAgeUnit(t *testing.T) {
   110  	testcases := []struct {
   111  		age         string
   112  		expected    time.Duration
   113  		expectedErr bool
   114  	}{
   115  		{
   116  			"1s",
   117  			1 * time.Second,
   118  			false,
   119  		},
   120  		{
   121  			"1sec",
   122  			1 * time.Second,
   123  			false,
   124  		},
   125  		{
   126  			"1S",
   127  			0,
   128  			true,
   129  		},
   130  		{
   131  			"_S",
   132  			0,
   133  			true,
   134  		},
   135  		{
   136  			"_Sec",
   137  			0,
   138  			true,
   139  		},
   140  		{
   141  			"1m",
   142  			0,
   143  			true,
   144  		},
   145  		{
   146  			"1min",
   147  			1 * time.Minute,
   148  			false,
   149  		},
   150  		{
   151  			"1H",
   152  			1 * time.Hour,
   153  			false,
   154  		},
   155  		{
   156  			"1hour",
   157  			1 * time.Hour,
   158  			false,
   159  		},
   160  		{
   161  			"2hours",
   162  			2 * time.Hour,
   163  			false,
   164  		},
   165  		{
   166  			"2Hours",
   167  			2 * time.Hour,
   168  			false,
   169  		},
   170  		{
   171  			"1D",
   172  			1 * time.Duration(Day),
   173  			false,
   174  		},
   175  		{
   176  			"1d",
   177  			1 * time.Duration(Day),
   178  			false,
   179  		},
   180  		{
   181  			"1day",
   182  			1 * time.Duration(Day),
   183  			false,
   184  		},
   185  		{
   186  			"2days",
   187  			2 * time.Duration(Day),
   188  			false,
   189  		},
   190  		{
   191  			"2Days",
   192  			2 * time.Duration(Day),
   193  			false,
   194  		},
   195  	}
   196  	for _, tc := range testcases {
   197  		actual, err := parseFileAge(tc.age)
   198  		if tc.expectedErr {
   199  			require.Error(t, err)
   200  			continue
   201  		}
   202  		require.NoError(t, err)
   203  		require.Equal(t, tc.expected, actual)
   204  	}
   205  }
   206  
   207  func testRotateLogWriteRunCore(t *testing.T, log *rotateLog) {
   208  	err := log.initialize()
   209  	require.NoError(t, err)
   210  
   211  	for i := 0; i < 100; i++ {
   212  		data := []byte(strconv.Itoa(i) + " " + time.Now().UTC().Format(backupDateTimeFormat) + " xlog rolling log write test!\n")
   213  		_, err = log.Write(data)
   214  		require.NoError(t, err)
   215  	}
   216  	time.Sleep(1 * time.Second)
   217  	err = log.Close()
   218  	require.NoError(t, err)
   219  }
   220  
   221  func TestRotateLog_Write_Compress(t *testing.T) {
   222  	nano, err := id.ClassicNanoID(6)
   223  	require.NoError(t, err)
   224  	rngLogSuffix := "_" + nano() + "_xlog"
   225  	rngLogZipSuffix := rngLogSuffix + "s"
   226  	log := &rotateLog{
   227  		fileMaxSize:       "1KB",
   228  		filename:          filepath.Base(os.Args[0]) + rngLogSuffix + ".log",
   229  		fileCompressible:  true,
   230  		fileMaxBackups:    4,
   231  		fileMaxAge:        "3day",
   232  		fileCompressBatch: 2,
   233  		fileZipName:       filepath.Base(os.Args[0]) + rngLogZipSuffix + ".zip",
   234  		filePath:          os.TempDir(),
   235  		ctx:               context.TODO(),
   236  	}
   237  	loop := 2
   238  	for i := 0; i < loop; i++ {
   239  		testRotateLogWriteRunCore(t, log)
   240  	}
   241  	reader, err := zip.OpenReader(filepath.Join(log.filePath, log.fileZipName))
   242  	require.NoError(t, err)
   243  	require.LessOrEqual(t, int((loop-1)*log.fileMaxBackups), len(reader.File))
   244  	reader.Close()
   245  	removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log")
   246  	require.LessOrEqual(t, log.fileMaxBackups+1, removed)
   247  	removed = testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogZipSuffix, ".zip")
   248  	require.Equal(t, 1, removed)
   249  }
   250  
   251  func TestRotateLog_Write_Delete(t *testing.T) {
   252  	nano, err := id.ClassicNanoID(6)
   253  	require.NoError(t, err)
   254  	rngLogSuffix := "_" + nano() + "_xlog"
   255  	log := &rotateLog{
   256  		fileMaxSize:      "1KB",
   257  		filename:         filepath.Base(os.Args[0]) + rngLogSuffix + ".log",
   258  		fileCompressible: false,
   259  		fileMaxBackups:   4,
   260  		fileMaxAge:       "3day",
   261  		filePath:         os.TempDir(),
   262  		ctx:              context.TODO(),
   263  	}
   264  	loop := 2
   265  	for i := 0; i < loop; i++ {
   266  		testRotateLogWriteRunCore(t, log)
   267  	}
   268  	removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log")
   269  	require.Equal(t, log.fileMaxBackups+1, removed)
   270  }
   271  
   272  func testCleanLogFiles(t *testing.T, path, namePrefix, nameSuffix string) int {
   273  	// Walk through the log files and find the expired ones.
   274  	entries, err := os.ReadDir(path)
   275  	logInfos := make([]os.FileInfo, 0, 16)
   276  	if err == nil && len(entries) > 0 {
   277  		for _, entry := range entries {
   278  			if !entry.IsDir() {
   279  				filename := entry.Name()
   280  				if strings.HasPrefix(filename, namePrefix) && strings.HasSuffix(filename, nameSuffix) {
   281  					if info, err := entry.Info(); err == nil && info != nil {
   282  						logInfos = append(logInfos, info)
   283  					}
   284  				}
   285  			} else {
   286  				if entry.Name() == namePrefix+nameSuffix {
   287  					if info, err := entry.Info(); err == nil && info != nil {
   288  						logInfos = append(logInfos, info)
   289  					}
   290  				}
   291  			}
   292  		}
   293  	}
   294  	for _, logInfo := range logInfos {
   295  		_ = os.Remove(filepath.Join(path, logInfo.Name()))
   296  	}
   297  	return len(logInfos)
   298  }
   299  
   300  func TestRotateLog_Write_PermissionDeniedAccess(t *testing.T) {
   301  	rf, err := safeopen.CreateBeneath(os.TempDir(), "rpda.log")
   302  	require.NoError(t, err)
   303  	err = rf.Close()
   304  	require.NoError(t, err)
   305  
   306  	err = os.Chmod(filepath.Join(os.TempDir(), "rpda.log"), 0o400)
   307  	require.NoError(t, err)
   308  
   309  	rf, err = safeopen.OpenFileBeneath(os.TempDir(), "rpda.log", os.O_WRONLY|os.O_APPEND, 0o666)
   310  	require.Error(t, err) // Access denied.
   311  	require.Nil(t, rf)
   312  
   313  	ctx, cancel := context.WithCancel(context.TODO())
   314  	log := RotateLog(nil, &FileCoreConfig{})
   315  	require.Nil(t, log)
   316  	log = RotateLog(ctx, nil)
   317  	require.Nil(t, log)
   318  
   319  	log = RotateLog(ctx, &FileCoreConfig{
   320  		FileMaxSize:      "1KB",
   321  		Filename:         "rpda.log",
   322  		FilePath:         os.TempDir(),
   323  		FileCompressible: false,
   324  		FileMaxAge:       "100days",
   325  		FileMaxBackups:   4,
   326  	})
   327  
   328  	_, err = log.Write([]byte("rotate log permission denied access!"))
   329  	require.Error(t, err) // Access denied.
   330  	cancel()
   331  	err = log.Close()
   332  	require.NoError(t, err)
   333  	time.Sleep(20 * time.Millisecond)
   334  	_, err = log.Write([]byte("rotate log permission denied access!"))
   335  	require.True(t, errors.Is(err, io.EOF))
   336  
   337  	removed := testCleanLogFiles(t, os.TempDir(), "rpda", ".log")
   338  	require.Equal(t, 1, removed)
   339  }
   340  
   341  func TestRotateLog_Write_Dir(t *testing.T) {
   342  	err := os.Mkdir(filepath.Join(os.TempDir(), "rpda2.log"), 0o600)
   343  	require.NoError(t, err)
   344  
   345  	log := &rotateLog{
   346  		fileMaxSize:      "1KB",
   347  		filename:         "rpda2.log",
   348  		fileCompressible: false,
   349  		fileMaxBackups:   4,
   350  		fileMaxAge:       "3day",
   351  		filePath:         os.TempDir(),
   352  		ctx:              context.TODO(),
   353  	}
   354  
   355  	_, err = log.Write([]byte("rotate log write dir!"))
   356  	require.Error(t, err)
   357  	err = log.Close()
   358  	require.NoError(t, err)
   359  
   360  	removed := testCleanLogFiles(t, os.TempDir(), "rpda2", ".log")
   361  	require.Equal(t, 1, removed)
   362  }
   363  
   364  func TestRotateLog_Write_OtherErrors(t *testing.T) {
   365  	log := &rotateLog{
   366  		fileMaxSize:      "1KB",
   367  		filename:         "rpda3.log",
   368  		fileCompressible: false,
   369  		fileMaxBackups:   4,
   370  		fileMaxAge:       "3day",
   371  		filePath:         os.TempDir(),
   372  		ctx:              context.TODO(),
   373  	}
   374  
   375  	err := log.openOrCreate()
   376  	require.NoError(t, err)
   377  	err = log.Close()
   378  	require.NoError(t, err)
   379  
   380  	log.filePath = "abc"
   381  	err = log.openOrCreate()
   382  	require.Error(t, err)
   383  
   384  	removed := testCleanLogFiles(t, os.TempDir(), "rpda3", ".log")
   385  	require.Equal(t, 1, removed)
   386  }