github.com/Aoi-hosizora/ahlib-more@v1.5.1-0.20230404072844-256112befaf6/xrotation/xrotation_test.go (about)

     1  package xrotation
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/Aoi-hosizora/ahlib/xtesting"
     6  	"github.com/Aoi-hosizora/ahlib/xtime"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"testing"
    12  	"time"
    13  )
    14  
    15  func TestOptions(t *testing.T) {
    16  	t.Run("errors", func(t *testing.T) {
    17  		_, err := New("")
    18  		xtesting.NotNil(t, err)
    19  		_, err = New("test.log")
    20  		xtesting.Nil(t, err)
    21  		_, err = New("test.log%")
    22  		xtesting.NotNil(t, err)
    23  		_, err = New("[x-]", WithForceNewFile(true))
    24  		xtesting.NotNil(t, err)
    25  		_, err = New("test.log", WithRotationMaxAge(1), WithRotationMaxCount(0))
    26  		xtesting.Nil(t, err)
    27  		_, err = New("test.log", WithRotationMaxAge(0), WithRotationMaxCount(1))
    28  		xtesting.Nil(t, err)
    29  		_, err = New("test.log", WithRotationMaxAge(1), WithRotationMaxCount(1))
    30  		xtesting.NotNil(t, err)
    31  	})
    32  
    33  	t.Run("values", func(t *testing.T) {
    34  		rl, err := New("test.log")
    35  		xtesting.Nil(t, err)
    36  		xtesting.Equal(t, rl.namePattern, "test.log")
    37  		xtesting.Equal(t, rl.globPattern, "test.log")
    38  		xtesting.Equal(t, rl.option.symlinkFilename, "")
    39  		xtesting.Equal(t, xtime.LocationDuration(rl.option.nowClock.Now().Location()), xtime.LocationDuration(time.Local))
    40  		xtesting.Equal(t, rl.option.forceNewFile, false)
    41  		xtesting.Equal(t, rl.option.rotationTime, 24*time.Hour)
    42  		xtesting.Equal(t, rl.option.rotationSize, int64(0))
    43  		xtesting.Equal(t, rl.option.rotationMaxAge, 7*24*time.Hour)
    44  		xtesting.Equal(t, rl.option.rotationMaxCount, int32(0))
    45  
    46  		rl, err = New(
    47  			"x", WithSymlinkFilename(""), WithSymlinkFilename("test.curr.log"), WithClock(nil),
    48  			WithForceNewFile(false), WithRotationTime(-1), WithRotationSize(-1), WithRotationMaxAge(-1), WithRotationMaxCount(-1),
    49  		)
    50  		xtesting.Nil(t, err)
    51  		xtesting.Equal(t, rl.namePattern, "x")
    52  		xtesting.Equal(t, rl.globPattern, "x")
    53  		xtesting.Equal(t, rl.option.symlinkFilename, "test.curr.log")
    54  		xtesting.Equal(t, xtime.LocationDuration(rl.option.nowClock.Now().Location()), xtime.LocationDuration(time.Local))
    55  		xtesting.Equal(t, rl.option.forceNewFile, false)
    56  		xtesting.Equal(t, rl.option.rotationTime, 24*time.Hour)
    57  		xtesting.Equal(t, rl.option.rotationSize, int64(0))
    58  		xtesting.Equal(t, rl.option.rotationMaxAge, 7*24*time.Hour)
    59  		xtesting.Equal(t, rl.option.rotationMaxCount, int32(0))
    60  
    61  		rl, err = New(
    62  			"test-%Y%m%d.log", WithSymlinkFilename(""), WithClock(xtime.UTC), WithForceNewFile(true),
    63  			WithRotationTime(time.Hour), WithRotationSize(256), WithRotationMaxAge(time.Hour*15*24), WithRotationMaxCount(0),
    64  		)
    65  		xtesting.Nil(t, err)
    66  		xtesting.Equal(t, rl.namePattern, "test-%Y%m%d.log")
    67  		xtesting.Equal(t, rl.globPattern, "test-*.log")
    68  		xtesting.Equal(t, rl.option.symlinkFilename, "")
    69  		xtesting.Equal(t, xtime.LocationDuration(rl.option.nowClock.Now().Location()), time.Duration(0))
    70  		xtesting.Equal(t, rl.option.forceNewFile, true)
    71  		xtesting.Equal(t, rl.option.rotationTime, time.Hour)
    72  		xtesting.Equal(t, rl.option.rotationSize, int64(256))
    73  		xtesting.Equal(t, rl.option.rotationMaxAge, 15*24*time.Hour)
    74  		xtesting.Equal(t, rl.option.rotationMaxCount, int32(0))
    75  	})
    76  }
    77  
    78  func removeLoggers() {
    79  	matches, _ := filepath.Glob("*.log*")
    80  	for _, match := range matches {
    81  		err := os.Remove(match)
    82  		if err != nil {
    83  			log.Printf("removeLoggers: os.Remove failed on `%s`", match)
    84  		}
    85  	}
    86  	err := os.RemoveAll("./_test")
    87  	if err != nil {
    88  		log.Printf("removeLoggers: os.Remove failed on `%s`", "./_test")
    89  	}
    90  }
    91  
    92  func testFileContent(t *testing.T, name, content string) {
    93  	xtesting.SetExtraSkip(1)
    94  	defer xtesting.SetExtraSkip(0)
    95  	bs, err := ioutil.ReadFile(name)
    96  	xtesting.Nil(t, err)
    97  	if err == nil {
    98  		xtesting.Equal(t, string(bs), content)
    99  	}
   100  }
   101  
   102  func testFileExistence(t *testing.T, name string, exist bool) {
   103  	xtesting.SetExtraSkip(1)
   104  	defer xtesting.SetExtraSkip(0)
   105  	_, err := os.Stat(name)
   106  	if exist {
   107  		xtesting.Nil(t, err)
   108  	} else {
   109  		xtesting.True(t, os.IsNotExist(err))
   110  	}
   111  }
   112  
   113  func TestSimpleWrite(t *testing.T) {
   114  	t.Run("strftime", func(t *testing.T) {
   115  		removeLoggers()
   116  		defer removeLoggers()
   117  
   118  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   119  		pNow := &now
   120  		clock := xtime.CustomClock(pNow)
   121  		rl, _ := New("logger.%Y%m%d.log", WithRotationTime(time.Hour*24), WithClock(clock))
   122  		xtesting.Nil(t, rl.Close()) // <- r.currFile == nil
   123  
   124  		rl, _ = New("logger.%Y%m%d.log", WithRotationTime(time.Hour*24), WithClock(clock))
   125  		_, err := fmt.Fprintf(rl, "hello world 1") // <- create a new file, logger.01.log
   126  		xtesting.Nil(t, err)
   127  		testFileExistence(t, "logger.20010101.log", true)
   128  		testFileContent(t, "logger.20010101.log", "hello world 1")
   129  
   130  		*pNow = time.Date(2002, 2, 2, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   131  		_, err = fmt.Fprintf(rl, "hello world 2") // <- create a new file, logger.02.log
   132  		xtesting.Nil(t, err)
   133  		testFileExistence(t, "logger.20010101.log", true)
   134  		testFileExistence(t, "logger.20020202.log", true)
   135  		testFileContent(t, "logger.20010101.log", "hello world 1")
   136  		testFileContent(t, "logger.20020202.log", "hello world 2")
   137  
   138  		_, err = fmt.Fprintf(rl, "hello world 3") // <- use logger.02.log
   139  		testFileContent(t, "logger.20020202.log", "hello world 2hello world 3")
   140  		xtesting.Nil(t, rl.Close()) // normal close
   141  		xtesting.Nil(t, rl.currFile)
   142  		xtesting.Zero(t, rl.currBasename)
   143  		xtesting.Zero(t, rl.currGeneration)
   144  		xtesting.Zero(t, rl.currFilename)
   145  		_, err = fmt.Fprintf(rl, "hello world 4") // <- normally use logger.02.log
   146  		testFileContent(t, "logger.20020202.log", "hello world 2hello world 3hello world 4")
   147  		xtesting.Nil(t, rl.Close()) // normal close
   148  	})
   149  
   150  	t.Run("reopen and ForceNewFile", func(t *testing.T) {
   151  		removeLoggers()
   152  		defer removeLoggers()
   153  
   154  		rl, _ := New("logger.log", WithRotationTime(time.Hour*24), WithRotationSize(15), WithForceNewFile(false))
   155  		_, err := fmt.Fprintf(rl, "hello world 1\n") // <- 14, create logger.log
   156  		xtesting.Nil(t, err)
   157  		testFileExistence(t, "logger.log", true)
   158  		testFileContent(t, "logger.log", "hello world 1\n")
   159  		xtesting.Nil(t, rl.Close())
   160  
   161  		rl, _ = New("logger.log", WithRotationTime(time.Hour*24), WithRotationSize(15), WithForceNewFile(false))
   162  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use logger.log
   163  		xtesting.Nil(t, err)
   164  		testFileExistence(t, "logger.log", true)
   165  		testFileExistence(t, "logger.log_1", false)
   166  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\n")
   167  		xtesting.Nil(t, rl.Close())
   168  
   169  		rl, _ = New("logger.log", WithRotationTime(time.Hour*24), WithRotationSize(15), WithForceNewFile(false))
   170  		_, err = fmt.Fprintf(rl, "hello world 3\n") // <- create logger.log_1
   171  		xtesting.Nil(t, err)
   172  		testFileExistence(t, "logger.log_1", true)
   173  		testFileExistence(t, "logger.log_2", false)
   174  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\n")
   175  		testFileContent(t, "logger.log_1", "hello world 3\n")
   176  		xtesting.Nil(t, rl.Close())
   177  
   178  		rl, _ = New("logger.log", WithRotationTime(time.Hour*24), WithRotationSize(15), WithForceNewFile(true))
   179  		_, err = fmt.Fprintf(rl, "hello world 4\n") // <- create logger.log_2
   180  		xtesting.Nil(t, err)
   181  		testFileExistence(t, "logger.log_2", true)
   182  		testFileExistence(t, "logger.log_3", false)
   183  		testFileContent(t, "logger.log_1", "hello world 3\n")
   184  		testFileContent(t, "logger.log_2", "hello world 4\n")
   185  		xtesting.Nil(t, rl.Close())
   186  	})
   187  }
   188  
   189  func TestWrite(t *testing.T) {
   190  	t.Run("some write demos", func(t *testing.T) {
   191  		removeLoggers()
   192  		defer removeLoggers()
   193  
   194  		rl, _ := New("logger.log", WithRotationSize(15))
   195  		xtesting.Equal(t, rl.CurrentFilename(), "")
   196  		_, err := fmt.Fprintf(rl, "hello world 1\n") // <- 14, create logger.log
   197  		xtesting.Nil(t, err)
   198  		testFileExistence(t, "logger.log", true)
   199  		testFileExistence(t, "logger.log_1", false)
   200  		testFileContent(t, "logger.log", "hello world 1\n")
   201  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   202  
   203  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use logger.log
   204  		xtesting.Nil(t, err)
   205  		testFileExistence(t, "logger.log", true)
   206  		testFileExistence(t, "logger.log_1", false)
   207  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\n")
   208  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   209  
   210  		_, err = fmt.Fprintf(rl, "hello world 3\n") // <- create: logger.log_1
   211  		xtesting.Nil(t, err)
   212  		testFileExistence(t, "logger.log_1", true)
   213  		testFileExistence(t, "logger.log_2", false)
   214  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\n")
   215  		testFileContent(t, "logger.log_1", "hello world 3\n")
   216  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log_1")
   217  		xtesting.Nil(t, rl.Close())
   218  
   219  		rl, _ = New("logger.log", WithRotationSize(29))
   220  		xtesting.Equal(t, rl.CurrentFilename(), "")
   221  		_, err = fmt.Fprintf(rl, "hello world 4\n") // <- use logger.log
   222  		xtesting.Nil(t, err)
   223  		testFileExistence(t, "logger.log", true)
   224  		testFileExistence(t, "logger.log_1", true)
   225  		testFileExistence(t, "logger.log_2", false)
   226  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\nhello world 4\n")
   227  		testFileContent(t, "logger.log_1", "hello world 3\n")
   228  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   229  
   230  		_, err = fmt.Fprintf(rl, "hello world 5\n") // <- create: logger.log_2
   231  		xtesting.Nil(t, err)
   232  		testFileExistence(t, "logger.log_2", true)
   233  		testFileExistence(t, "logger.log_3", false)
   234  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\nhello world 4\n")
   235  		testFileContent(t, "logger.log_1", "hello world 3\n")
   236  		testFileContent(t, "logger.log_2", "hello world 5\n")
   237  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log_2")
   238  		xtesting.Nil(t, rl.Close())
   239  
   240  		rl, _ = New("logger.log", WithRotationSize(29))
   241  		xtesting.Equal(t, rl.CurrentFilename(), "")
   242  		_, err = fmt.Fprintf(rl, "hello world 6\n") // <- create: logger.log_3
   243  		xtesting.Nil(t, err)
   244  		testFileExistence(t, "logger.log_3", true)
   245  		testFileExistence(t, "logger.log_4", false)
   246  		testFileContent(t, "logger.log_2", "hello world 5\n")
   247  		testFileContent(t, "logger.log_3", "hello world 6\n")
   248  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log_3")
   249  		xtesting.Nil(t, rl.Close())
   250  	})
   251  
   252  	t.Run("simple rotate", func(t *testing.T) {
   253  		removeLoggers()
   254  		defer removeLoggers()
   255  
   256  		rl, _ := New("logger.log", WithRotationSize(15))
   257  		xtesting.Nil(t, rl.Rotate()) // <- create logger.log
   258  		testFileExistence(t, "logger.log", true)
   259  		testFileContent(t, "logger.log", "")
   260  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   261  		xtesting.Nil(t, rl.Rotate()) // <- use logger.log
   262  		testFileExistence(t, "logger.log", true)
   263  		testFileExistence(t, "logger.log_1", false)
   264  		testFileContent(t, "logger.log", "")
   265  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   266  		xtesting.Nil(t, rl.Close())
   267  
   268  		rl, _ = New("logger.log", WithRotationSize(15))
   269  		xtesting.Nil(t, rl.Rotate()) // <- use logger.log
   270  		testFileExistence(t, "logger.log", true)
   271  		testFileExistence(t, "logger.log_1", false)
   272  		testFileContent(t, "logger.log", "")
   273  		xtesting.Equal(t, rl.CurrentFilename(), "logger.log")
   274  		_, err := fmt.Fprintf(rl, "hello world 1\n") // <- write to logger.log
   275  		xtesting.Nil(t, err)
   276  		testFileContent(t, "logger.log", "hello world 1\n")
   277  		xtesting.Nil(t, rl.Rotate()) // <- use logger.log
   278  		testFileExistence(t, "logger.log", true)
   279  		testFileExistence(t, "logger.log_1", false)
   280  		testFileContent(t, "logger.log", "hello world 1\n")
   281  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- write to logger.log
   282  		xtesting.Nil(t, err)
   283  		testFileContent(t, "logger.log", "hello world 1\nhello world 2\n")
   284  		xtesting.Nil(t, rl.Rotate()) // <- create logger_1.log
   285  		testFileExistence(t, "logger.log_1", true)
   286  		testFileExistence(t, "logger.log_2", false)
   287  		testFileContent(t, "logger.log_1", "") // <- empty logger.log_1
   288  		xtesting.Nil(t, rl.Close())
   289  
   290  		rl, _ = New("logger.log", WithRotationSize(15))
   291  		xtesting.Nil(t, rl.Rotate()) // <- create logger.log_2
   292  		testFileExistence(t, "logger.log_1", true)
   293  		testFileExistence(t, "logger.log_2", true)
   294  		testFileContent(t, "logger.log_2", "")
   295  		_, err = fmt.Fprintf(rl, "hello world 3\n") // <- write to logger.log_2
   296  		testFileContent(t, "logger.log_1", "")
   297  		testFileContent(t, "logger.log_2", "hello world 3\n")
   298  		xtesting.Nil(t, rl.Close())
   299  	})
   300  }
   301  
   302  func TestRotate(t *testing.T) {
   303  	t.Run("max age", func(t *testing.T) {
   304  		removeLoggers()
   305  		defer removeLoggers()
   306  
   307  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   308  		pNow := &now
   309  		clock := xtime.CustomClock(pNow)
   310  		rl, _ := New("logger.%Y%m%d.log", WithRotationSize(15), WithRotationTime(time.Hour*24), WithRotationMaxAge(3*24*time.Hour), WithClock(clock))
   311  
   312  		*pNow = xtime.SetDay(now, 1)              // 1d
   313  		_, _ = fmt.Fprintf(rl, "hello world 1\n") // <- logger.01.log
   314  		_, _ = fmt.Fprintf(rl, "hello world 2\n")
   315  		_, _ = fmt.Fprintf(rl, "hello world 3\n") // <- logger.01.log_1
   316  		_, _ = fmt.Fprintf(rl, "hello world 4\n")
   317  		_, _ = fmt.Fprintf(rl, "hello world 5\n") // <- logger.01.log_2
   318  		_ = os.Chtimes("logger.20010101.log", now, now)
   319  		_ = os.Chtimes("logger.20010101.log_1", now, now)
   320  		_ = os.Chtimes("logger.20010101.log_2", now, now)
   321  		*pNow = xtime.SetDay(now, 2)              // 2d
   322  		_, _ = fmt.Fprintf(rl, "hello world 6\n") // <- logger.02.log
   323  		_, _ = fmt.Fprintf(rl, "hello world 7\n")
   324  		_, _ = fmt.Fprintf(rl, "hello world 8\n") // <- logger.02.log_1
   325  		_ = os.Chtimes("logger.20010102.log", now, now)
   326  		_ = os.Chtimes("logger.20010102.log_1", now, now)
   327  		*pNow = xtime.SetDay(now, 3)              // 3d
   328  		_, _ = fmt.Fprintf(rl, "hello world 9\n") // <- logger.03.log
   329  		_ = os.Chtimes("logger.20010103.log", now, now)
   330  		testFileExistence(t, "logger.20010101.log", true)
   331  		testFileExistence(t, "logger.20010101.log_1", true)
   332  		testFileExistence(t, "logger.20010101.log_2", true)
   333  		testFileExistence(t, "logger.20010102.log", true)
   334  		testFileExistence(t, "logger.20010102.log_1", true)
   335  		testFileExistence(t, "logger.20010103.log", true)
   336  
   337  		*pNow = xtime.SetHour(now, 1)
   338  		*pNow = xtime.SetDay(now, 4) // 4d1h
   339  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.01.log
   340  		testFileExistence(t, "logger.20010101.log", false)
   341  		testFileExistence(t, "logger.20010101.log_1", false)
   342  		testFileExistence(t, "logger.20010101.log_2", false)
   343  		testFileExistence(t, "logger.20010102.log", true)
   344  		testFileExistence(t, "logger.20010102.log_1", true)
   345  		testFileExistence(t, "logger.20010103.log", true)
   346  		*pNow = xtime.SetDay(now, 5)         // 5d1h
   347  		_, _ = fmt.Fprintf(rl, "for rotate") // <- delete logger.02.log
   348  		testFileExistence(t, "logger.20010101.log", false)
   349  		testFileExistence(t, "logger.20010101.log_1", false)
   350  		testFileExistence(t, "logger.20010101.log_2", false)
   351  		testFileExistence(t, "logger.20010102.log", false)
   352  		testFileExistence(t, "logger.20010102.log_1", false)
   353  		testFileExistence(t, "logger.20010103.log", true)
   354  		*pNow = xtime.SetDay(now, 6) // 6d1h
   355  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.03.log
   356  		testFileExistence(t, "logger.20010101.log", false)
   357  		testFileExistence(t, "logger.20010101.log_1", false)
   358  		testFileExistence(t, "logger.20010101.log_2", false)
   359  		testFileExistence(t, "logger.20010102.log", false)
   360  		testFileExistence(t, "logger.20010102.log_1", false)
   361  		testFileExistence(t, "logger.20010103.log", false)
   362  		_ = rl.Close()
   363  	})
   364  
   365  	t.Run("max count", func(t *testing.T) {
   366  		removeLoggers()
   367  		defer removeLoggers()
   368  
   369  		now := time.Date(2001, 1, 1, 1, 1, 1, 0, time.FixedZone("", 8*60*60))
   370  		pNow := &now
   371  		clock := xtime.CustomClock(pNow)
   372  		rl, _ := New("logger.%Y%m%d.log", WithRotationSize(15), WithRotationTime(time.Hour*24), WithRotationMaxCount(3), WithClock(clock))
   373  
   374  		*pNow = xtime.SetDay(now, 1)              // 1d
   375  		_, _ = fmt.Fprintf(rl, "hello world 1\n") // <- logger.01.log
   376  		_, _ = fmt.Fprintf(rl, "hello world 2\n")
   377  		_, _ = fmt.Fprintf(rl, "hello world 3\n") // <- logger.01.log_1
   378  		_, _ = fmt.Fprintf(rl, "hello world 4\n")
   379  		_, _ = fmt.Fprintf(rl, "hello world 5\n") // <- logger.01.log_2
   380  		*pNow = xtime.SetDay(now, 2)              // 2d
   381  		_, _ = fmt.Fprintf(rl, "hello world 6\n") // <- logger.02.log
   382  		_, _ = fmt.Fprintf(rl, "hello world 7\n")
   383  		_, _ = fmt.Fprintf(rl, "hello world 8\n") // <- logger.02.log_1
   384  		*pNow = xtime.SetDay(now, 3)              // 3d
   385  		_, _ = fmt.Fprintf(rl, "hello world 9\n") // <- logger.03.log
   386  		testFileExistence(t, "logger.20010101.log", true)
   387  		testFileExistence(t, "logger.20010101.log_1", true)
   388  		testFileExistence(t, "logger.20010101.log_2", true)
   389  		testFileExistence(t, "logger.20010102.log", true)
   390  		testFileExistence(t, "logger.20010102.log_1", true)
   391  		testFileExistence(t, "logger.20010103.log", true)
   392  
   393  		*pNow = xtime.SetHour(now, 1)
   394  		*pNow = xtime.SetDay(now, 4) // 4d1h
   395  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.01.log
   396  		testFileExistence(t, "logger.20010101.log", false)
   397  		testFileExistence(t, "logger.20010101.log_1", false)
   398  		testFileExistence(t, "logger.20010101.log_2", false)
   399  		testFileExistence(t, "logger.20010102.log", true)
   400  		testFileExistence(t, "logger.20010102.log_1", true)
   401  		testFileExistence(t, "logger.20010103.log", true)
   402  		*pNow = xtime.SetDay(now, 5)         // 5d1h
   403  		_, _ = fmt.Fprintf(rl, "for rotate") // <- delete logger.02.log
   404  		testFileExistence(t, "logger.20010101.log", false)
   405  		testFileExistence(t, "logger.20010101.log_1", false)
   406  		testFileExistence(t, "logger.20010101.log_2", false)
   407  		testFileExistence(t, "logger.20010102.log", false)
   408  		testFileExistence(t, "logger.20010102.log_1", false)
   409  		testFileExistence(t, "logger.20010103.log", true)
   410  		*pNow = xtime.SetDay(now, 6) // 6d1h
   411  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.03.log
   412  		testFileExistence(t, "logger.20010101.log", false)
   413  		testFileExistence(t, "logger.20010101.log_1", false)
   414  		testFileExistence(t, "logger.20010101.log_2", false)
   415  		testFileExistence(t, "logger.20010102.log", false)
   416  		testFileExistence(t, "logger.20010102.log_1", false)
   417  		testFileExistence(t, "logger.20010103.log", false)
   418  
   419  		f1, err := os.OpenFile("logger.20010104.log", os.O_APPEND, 0644)
   420  		xtesting.Nil(t, err)
   421  		f2, err := os.OpenFile("logger.20010105.log", os.O_APPEND, 0644)
   422  		xtesting.Nil(t, err)
   423  		*pNow = xtime.SetDay(now, 7) // 7d1h
   424  		_, err = fmt.Fprintf(rl, "")
   425  		xtesting.Nil(t, err)            // <- Warning: failed to remove logger.04.log
   426  		xtesting.NotNil(t, rl.Rotate()) // <- need to rotate, error: failed to remove logger.04.log
   427  		testFileExistence(t, "logger.20010104.log", true)
   428  		*pNow = xtime.SetDay(now, 8)    // 8d1h
   429  		xtesting.NotNil(t, rl.Rotate()) // <- error: failed to remove logger.04.log, logger.05.log
   430  		testFileExistence(t, "logger.20010104.log", true)
   431  		testFileExistence(t, "logger.20010105.log", true)
   432  		xtesting.Nil(t, f1.Close())
   433  		xtesting.Nil(t, f2.Close())
   434  		*pNow = xtime.SetDay(now, 9) // 9d1h
   435  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.04.log and logger.05.log
   436  		testFileExistence(t, "logger.20010104.log", false)
   437  		testFileExistence(t, "logger.20010105.log", false)
   438  		_ = rl.Close()
   439  	})
   440  }
   441  
   442  func TestInDifferentDir(t *testing.T) {
   443  	t.Run("some write demos", func(t *testing.T) {
   444  		removeLoggers()
   445  		defer removeLoggers()
   446  
   447  		rl, _ := New("./_test/_test/logger.log", WithRotationSize(15))
   448  		_, _ = fmt.Fprintf(rl, "hello world 1\n") // <- create logger.log
   449  		_, _ = fmt.Fprintf(rl, "hello world 2\n") // <- use logger.log
   450  		_, _ = fmt.Fprintf(rl, "hello world 3\n") // <- create: logger.log_1
   451  		testFileExistence(t, "./_test/_test/logger.log", true)
   452  		testFileExistence(t, "./_test/_test/logger.log_1", true)
   453  		testFileExistence(t, "./_test/_test/logger.log_2", false)
   454  		testFileContent(t, "./_test/_test/logger.log", "hello world 1\nhello world 2\n")
   455  		testFileContent(t, "./_test/_test/logger.log_1", "hello world 3\n")
   456  		xtesting.Equal(t, rl.CurrentFilename(), "./_test/_test/logger.log_1")
   457  		xtesting.Nil(t, rl.Close())
   458  
   459  		rl, _ = New("./_test/_test/logger.log", WithRotationSize(29))
   460  		_, _ = fmt.Fprintf(rl, "hello world 4\n") // <- use logger.log
   461  		_, _ = fmt.Fprintf(rl, "hello world 5\n") // <- create: logger.log_2
   462  		testFileExistence(t, "./_test/_test/logger.log", true)
   463  		testFileExistence(t, "./_test/_test/logger.log_1", true)
   464  		testFileExistence(t, "./_test/_test/logger.log_2", true)
   465  		testFileExistence(t, "./_test/_test/logger.log_3", false)
   466  		testFileContent(t, "./_test/_test/logger.log", "hello world 1\nhello world 2\nhello world 4\n")
   467  		testFileContent(t, "./_test/_test/logger.log_1", "hello world 3\n")
   468  		testFileContent(t, "./_test/_test/logger.log_2", "hello world 5\n")
   469  		xtesting.Equal(t, rl.CurrentFilename(), "./_test/_test/logger.log_2")
   470  		xtesting.Nil(t, rl.Close())
   471  	})
   472  
   473  	t.Run("max age", func(t *testing.T) {
   474  		removeLoggers()
   475  		defer removeLoggers()
   476  
   477  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   478  		pNow := &now
   479  		clock := xtime.CustomClock(pNow)
   480  		rl, _ := New("./_test/_test/logger.%Y%m%d.log", WithRotationSize(15), WithRotationTime(time.Hour*24), WithRotationMaxAge(2*24*time.Hour), WithClock(clock))
   481  
   482  		*pNow = xtime.SetDay(now, 1)              // 1d
   483  		_, _ = fmt.Fprintf(rl, "hello world 1\n") // <- logger.01.log
   484  		_, _ = fmt.Fprintf(rl, "hello world 2\n")
   485  		_, _ = fmt.Fprintf(rl, "hello world 3\n") // <- logger.01.log_1
   486  		_ = os.Chtimes("./_test/_test/logger.20010101.log", now, now)
   487  		_ = os.Chtimes("./_test/_test/logger.20010101.log_1", now, now)
   488  		*pNow = xtime.SetDay(now, 2)              // 2d
   489  		_, _ = fmt.Fprintf(rl, "hello world 4\n") // <- logger.02.log
   490  		_ = os.Chtimes("./_test/_test/logger.20010102.log", now, now)
   491  		testFileExistence(t, "./_test/_test/logger.20010101.log", true)
   492  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", true)
   493  		testFileExistence(t, "./_test/_test/logger.20010102.log", true)
   494  
   495  		*pNow = xtime.SetHour(now, 1)
   496  		*pNow = xtime.SetDay(now, 3) // 3d1h
   497  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.01.log
   498  		testFileExistence(t, "./_test/_test/logger.20010101.log", false)
   499  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", false)
   500  		testFileExistence(t, "./_test/_test/logger.20010102.log", true)
   501  		*pNow = xtime.SetDay(now, 4)         // 4d1h
   502  		_, _ = fmt.Fprintf(rl, "for rotate") // <- delete logger.02.log
   503  		testFileExistence(t, "./_test/_test/logger.20010101.log", false)
   504  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", false)
   505  		testFileExistence(t, "./_test/_test/logger.20010102.log", false)
   506  		_ = rl.Close()
   507  	})
   508  
   509  	t.Run("max count", func(t *testing.T) {
   510  		removeLoggers()
   511  		defer removeLoggers()
   512  
   513  		now := time.Date(2001, 1, 1, 1, 1, 1, 0, time.FixedZone("", 8*60*60))
   514  		pNow := &now
   515  		clock := xtime.CustomClock(pNow)
   516  		rl, _ := New("./_test/_test/logger.%Y%m%d.log", WithRotationSize(15), WithRotationTime(time.Hour*24), WithRotationMaxCount(2), WithClock(clock))
   517  
   518  		*pNow = xtime.SetDay(now, 1)              // 1d
   519  		_, _ = fmt.Fprintf(rl, "hello world 1\n") // <- logger.01.log
   520  		_, _ = fmt.Fprintf(rl, "hello world 2\n")
   521  		_, _ = fmt.Fprintf(rl, "hello world 3\n") // <- logger.01.log_1
   522  		*pNow = xtime.SetDay(now, 2)              // 2d
   523  		_, _ = fmt.Fprintf(rl, "hello world 4\n") // <- logger.02.log
   524  		testFileExistence(t, "./_test/_test/logger.20010101.log", true)
   525  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", true)
   526  		testFileExistence(t, "./_test/_test/logger.20010102.log", true)
   527  
   528  		*pNow = xtime.SetHour(now, 1)
   529  		*pNow = xtime.SetDay(now, 3) // 3d1h
   530  		xtesting.Nil(t, rl.Rotate()) // <- delete logger.01.log
   531  		testFileExistence(t, "./_test/_test/logger.20010101.log", false)
   532  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", false)
   533  		testFileExistence(t, "./_test/_test/logger.20010102.log", true)
   534  		*pNow = xtime.SetDay(now, 4)         // 4d1h
   535  		_, _ = fmt.Fprintf(rl, "for rotate") // <- delete logger.02.log
   536  		testFileExistence(t, "./_test/_test/logger.20010101.log", false)
   537  		testFileExistence(t, "./_test/_test/logger.20010101.log_1", false)
   538  		testFileExistence(t, "./_test/_test/logger.20010102.log", false)
   539  		_ = rl.Close()
   540  	})
   541  
   542  	t.Run("cover errors", func(t *testing.T) {
   543  		removeLoggers()
   544  		now := time.Date(2001, 1, 1, 1, 1, 1, 0, time.FixedZone("", 8*60*60))
   545  		pNow := &now
   546  		clock := xtime.CustomClock(pNow)
   547  
   548  		rl, _ := New("./_test/logger.%Y%m%d.log", WithRotationTime(time.Hour*24), WithClock(clock))
   549  		*pNow = xtime.SetDay(now, 1)
   550  		_t_testHookMkdir = func() {
   551  			f, err := os.OpenFile("_test", os.O_CREATE, 0644)
   552  			xtesting.Nil(t, err)
   553  			xtesting.Nil(t, f.Close())
   554  		}
   555  		xtesting.NotNil(t, rl.Rotate()) // Rotate: MkdirAll failed
   556  		removeLoggers()
   557  		_t_testHookMkdir = func() {
   558  			err := os.MkdirAll("./_test/logger.20010101.log", 0755)
   559  			xtesting.Nil(t, err)
   560  		}
   561  		_, err := fmt.Fprintf(rl, "test OpenFile")
   562  		xtesting.NotNil(t, err) // OpenFile failed
   563  		_ = rl.Close()
   564  		_t_testHookMkdir = nil
   565  		removeLoggers()
   566  	})
   567  }
   568  
   569  func TestSymlink(t *testing.T) {
   570  	t.Run("same directory", func(t *testing.T) {
   571  		removeLoggers()
   572  		defer removeLoggers()
   573  
   574  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   575  		pNow := &now
   576  		clock := xtime.CustomClock(pNow)
   577  		rl, _ := New("logger.%Y%m%d.log", WithSymlinkFilename("logger.current.log"), WithRotationSize(15), WithRotationMaxCount(2), WithClock(clock))
   578  		_, err := fmt.Fprintf(rl, "hello world 1\n") // <- create: logger.01.log
   579  		xtesting.Nil(t, err)
   580  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use: logger.01.log
   581  		xtesting.Nil(t, err)
   582  		testFileContent(t, "logger.20010101.log", "hello world 1\nhello world 2\n")
   583  		testFileExistence(t, "logger.current.log", true)
   584  		fi, err := os.Lstat("logger.current.log")
   585  		xtesting.Nil(t, err)
   586  		xtesting.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink)
   587  		testFileContent(t, "logger.current.log", "hello world 1\nhello world 2\n")
   588  
   589  		_, err = fmt.Fprintf(rl, "hello world 3\n") // <- create: logger.01.log_1
   590  		xtesting.Nil(t, err)
   591  		testFileContent(t, "logger.20010101.log_1", "hello world 3\n")
   592  		testFileContent(t, "logger.current.log", "hello world 3\n")
   593  		*pNow = xtime.SetDay(now, 2)                // 2d
   594  		_, err = fmt.Fprintf(rl, "hello world 4\n") // <- create: logger.02.log
   595  		testFileContent(t, "logger.20010102.log", "hello world 4\n")
   596  		testFileContent(t, "logger.current.log", "hello world 4\n")
   597  		*pNow = xtime.SetDay(now, 3)                // 3d
   598  		_, err = fmt.Fprintf(rl, "hello world 5\n") // <- create: logger.03.log, need rotate
   599  		xtesting.Nil(t, err)
   600  		testFileExistence(t, "logger.20010101.log", false)
   601  		testFileExistence(t, "logger.20010101.log_1", false)
   602  		testFileExistence(t, "logger.20010102.log", true)
   603  		testFileContent(t, "logger.20010103.log", "hello world 5\n")
   604  		testFileContent(t, "logger.current.log", "hello world 5\n")
   605  		_ = rl.Close()
   606  		removeLoggers()
   607  
   608  		rl, _ = New("logger.%Y%m%d.log", WithSymlinkFilename("logger.current.log"), WithRotationSize(15), WithRotationMaxAge(time.Hour*24*2), WithClock(clock), WithForceNewFile(true))
   609  		*pNow = xtime.SetDay(now, 1)                // 1d
   610  		_, err = fmt.Fprintf(rl, "hello world 1\n") // <- create: logger.01.log
   611  		xtesting.Nil(t, err)
   612  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use: logger.01.log
   613  		xtesting.Nil(t, err)
   614  		_ = os.Chtimes("logger.20010101.log", now, now)
   615  		testFileContent(t, "logger.20010101.log", "hello world 1\nhello world 2\n")
   616  		testFileContent(t, "logger.current.log", "hello world 1\nhello world 2\n")
   617  
   618  		_, err = fmt.Fprintf(rl, "hello world 3\n") // <- create: logger.01.log_1
   619  		xtesting.Nil(t, err)
   620  		_ = os.Chtimes("logger.20010101.log_1", now, now)
   621  		testFileContent(t, "logger.20010101.log_1", "hello world 3\n")
   622  		testFileContent(t, "logger.current.log", "hello world 3\n")
   623  		*pNow = xtime.SetDay(now, 2)                // 2d
   624  		_, err = fmt.Fprintf(rl, "hello world 4\n") // <- create: logger.02.log
   625  		_ = os.Chtimes("logger.20010102.log", now, now)
   626  		testFileContent(t, "logger.20010102.log", "hello world 4\n")
   627  		testFileContent(t, "logger.current.log", "hello world 4\n")
   628  		*pNow = xtime.SetHour(now, 1)
   629  		*pNow = xtime.SetDay(now, 3) // 3d1h
   630  		f, err := os.OpenFile("logger.20010103.log_symlink", os.O_CREATE, 0644)
   631  		xtesting.Nil(t, err) // <- fake symlink, need to be deleted
   632  		xtesting.Nil(t, f.Close())
   633  		_, err = fmt.Fprintf(rl, "hello world 5\n") // <- create: logger.03.log, need rotate
   634  		xtesting.Nil(t, err)
   635  		testFileExistence(t, "logger.20010101.log", false)
   636  		testFileExistence(t, "logger.20010101.log_1", false)
   637  		testFileExistence(t, "logger.20010102.log", true)
   638  		testFileContent(t, "logger.20010103.log", "hello world 5\n")
   639  		testFileContent(t, "logger.current.log", "hello world 5\n")
   640  		_ = rl.Close()
   641  	})
   642  
   643  	t.Run("different directory", func(t *testing.T) {
   644  		removeLoggers()
   645  		defer removeLoggers()
   646  
   647  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   648  		pNow := &now
   649  		clock := xtime.CustomClock(pNow)
   650  		rl, _ := New("_test/logger.%Y%m%d.log", WithSymlinkFilename("_test/_test/logger.current.log"), WithRotationSize(15), WithClock(clock))
   651  		_, err := fmt.Fprintf(rl, "hello world 1\n") // <- create: logger.01.log
   652  		xtesting.Nil(t, err)
   653  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use: logger.01.log
   654  		xtesting.Nil(t, err)
   655  		testFileContent(t, "_test/logger.20010101.log", "hello world 1\nhello world 2\n")
   656  		testFileExistence(t, "_test/_test/logger.current.log", true)
   657  		testFileContent(t, "_test/_test/logger.current.log", "hello world 1\nhello world 2\n")
   658  		_ = rl.Close()
   659  		removeLoggers()
   660  
   661  		rl, _ = New("_test/_test/logger.%Y%m%d.log", WithSymlinkFilename("_test/logger.current.log"), WithRotationSize(15), WithClock(clock))
   662  		_, err = fmt.Fprintf(rl, "hello world 1\n") // <- create: logger.01.log
   663  		xtesting.Nil(t, err)
   664  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use: logger.01.log
   665  		xtesting.Nil(t, err)
   666  		testFileContent(t, "_test/_test/logger.20010101.log", "hello world 1\nhello world 2\n")
   667  		testFileExistence(t, "_test/logger.current.log", true)
   668  		testFileContent(t, "_test/logger.current.log", "hello world 1\nhello world 2\n")
   669  		_ = rl.Close()
   670  		removeLoggers()
   671  
   672  		rl, _ = New("_test/logger.%Y%m%d.log", WithSymlinkFilename("_test/logger.current.log"), WithRotationSize(15), WithClock(clock))
   673  		_, err = fmt.Fprintf(rl, "hello world 1\n") // <- create: logger.01.log
   674  		xtesting.Nil(t, err)
   675  		_, err = fmt.Fprintf(rl, "hello world 2\n") // <- use: logger.01.log
   676  		xtesting.Nil(t, err)
   677  		testFileContent(t, "_test/logger.20010101.log", "hello world 1\nhello world 2\n")
   678  		testFileExistence(t, "_test/logger.current.log", true)
   679  		testFileContent(t, "_test/logger.current.log", "hello world 1\nhello world 2\n")
   680  		_ = rl.Close()
   681  		removeLoggers()
   682  	})
   683  
   684  	t.Run("cover errors", func(t *testing.T) {
   685  		removeLoggers()
   686  		defer removeLoggers()
   687  
   688  		now := time.Date(2001, 1, 1, 0, 0, 0, 0, time.FixedZone("", 8*60*60))
   689  		pNow := &now
   690  		clock := xtime.CustomClock(pNow)
   691  		rl, _ := New("logger.%Y%m%d.log", WithSymlinkFilename("_test/logger.current.log"), WithRotationSize(15), WithClock(clock))
   692  		*pNow = xtime.SetDay(now, 1)
   693  		_t_testHookSymlink[0] = func() string {
   694  			f, err := os.OpenFile("_test", os.O_CREATE, 0644)
   695  			xtesting.Nil(t, err)
   696  			xtesting.Nil(t, f.Close())
   697  			return ""
   698  		}
   699  		xtesting.Nil(t, rl.Rotate()) // MkdirAll failed
   700  		_t_testHookSymlink[0] = nil
   701  
   702  		*pNow = xtime.SetDay(now, 2)
   703  		_t_testHookSymlink[1] = func() string {
   704  			return ".." // hack
   705  		}
   706  		xtesting.Nil(t, rl.Rotate()) // Rel failed
   707  		_t_testHookSymlink[1] = nil
   708  
   709  		*pNow = xtime.SetDay(now, 3)
   710  		_t_testHookSymlink[2] = func() string {
   711  			xtesting.Nil(t, os.MkdirAll("logger.20010103.log_symlink", 0755))
   712  			return ""
   713  		}
   714  		xtesting.Nil(t, rl.Rotate()) // Symlink failed
   715  
   716  		_ = os.RemoveAll("_test")
   717  		*pNow = xtime.SetDay(now, 4)
   718  		_t_testHookSymlink[2] = func() string {
   719  			xtesting.Nil(t, os.MkdirAll("_test/logger.current.log", 0755))
   720  			return ""
   721  		}
   722  		xtesting.Nil(t, rl.Rotate()) // Rename failed
   723  		_t_testHookSymlink[2] = nil
   724  
   725  		_ = rl.Close()
   726  	})
   727  }