github.heygears.com/openimsdk/tools@v0.0.49/log/file-rotatelogs/rotatelogs_test.go (about)

     1  package rotatelogs_test
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/jonboulle/clockwork"
    15  	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    16  	"github.com/pkg/errors"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestSatisfiesIOWriter(t *testing.T) {
    21  	var w io.Writer
    22  	w, _ = rotatelogs.New("/foo/bar")
    23  	_ = w
    24  }
    25  
    26  func TestSatisfiesIOCloser(t *testing.T) {
    27  	var c io.Closer
    28  	c, _ = rotatelogs.New("/foo/bar")
    29  	_ = c
    30  }
    31  
    32  func TestLogRotate(t *testing.T) {
    33  	testCases := []struct {
    34  		Name        string
    35  		FixArgs     func([]rotatelogs.Option, string) []rotatelogs.Option
    36  		CheckExtras func(*testing.T, *rotatelogs.RotateLogs, string) bool
    37  	}{
    38  		{
    39  			Name: "Basic Usage",
    40  		},
    41  		{
    42  			Name: "With Symlink",
    43  			FixArgs: func(options []rotatelogs.Option, dir string) []rotatelogs.Option {
    44  				linkName := filepath.Join(dir, "log")
    45  
    46  				return append(options, rotatelogs.WithLinkName(linkName))
    47  			},
    48  			CheckExtras: func(t *testing.T, rl *rotatelogs.RotateLogs, dir string) bool {
    49  				linkName := filepath.Join(dir, "log")
    50  				linkDest, err := os.Readlink(linkName)
    51  				if !assert.NoError(t, err, `os.Readlink(%#v) should succeed`, linkName) {
    52  					return false
    53  				}
    54  
    55  				expectedLinkDest := filepath.Base(rl.CurrentFileName())
    56  				t.Logf("expecting relative link: %s", expectedLinkDest)
    57  
    58  				return assert.Equal(t, linkDest, expectedLinkDest, `Symlink destination should  match expected filename (%#v != %#v)`, expectedLinkDest, linkDest)
    59  			},
    60  		},
    61  		{
    62  			Name: "With Symlink (multiple levels)",
    63  			FixArgs: func(options []rotatelogs.Option, dir string) []rotatelogs.Option {
    64  				linkName := filepath.Join(dir, "nest1", "nest2", "log")
    65  
    66  				return append(options, rotatelogs.WithLinkName(linkName))
    67  			},
    68  			CheckExtras: func(t *testing.T, rl *rotatelogs.RotateLogs, dir string) bool {
    69  				linkName := filepath.Join(dir, "nest1", "nest2", "log")
    70  				linkDest, err := os.Readlink(linkName)
    71  				if !assert.NoError(t, err, `os.Readlink(%#v) should succeed`, linkName) {
    72  					return false
    73  				}
    74  
    75  				expectedLinkDest := filepath.Join("..", "..", filepath.Base(rl.CurrentFileName()))
    76  				t.Logf("expecting relative link: %s", expectedLinkDest)
    77  
    78  				return assert.Equal(t, linkDest, expectedLinkDest, `Symlink destination should  match expected filename (%#v != %#v)`, expectedLinkDest, linkDest)
    79  			},
    80  		},
    81  	}
    82  
    83  	for i, tc := range testCases {
    84  		i := i   // avoid lint errors
    85  		tc := tc // avoid lint errors
    86  		t.Run(tc.Name, func(t *testing.T) {
    87  			dir, err := ioutil.TempDir("", fmt.Sprintf("file-rotatelogs-test%d", i))
    88  			if !assert.NoError(t, err, "creating temporary directory should succeed") {
    89  				return
    90  			}
    91  			defer os.RemoveAll(dir)
    92  
    93  			// Change current time, so we can safely purge old logs
    94  			dummyTime := time.Now().Add(-7 * 24 * time.Hour)
    95  			dummyTime = dummyTime.Add(time.Duration(-1 * dummyTime.Nanosecond()))
    96  			clock := clockwork.NewFakeClockAt(dummyTime)
    97  
    98  			options := []rotatelogs.Option{rotatelogs.WithClock(clock), rotatelogs.WithMaxAge(24 * time.Hour)}
    99  			if fn := tc.FixArgs; fn != nil {
   100  				options = fn(options, dir)
   101  			}
   102  
   103  			rl, err := rotatelogs.New(filepath.Join(dir, "log%Y%m%d%H%M%S"), options...)
   104  			if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   105  				return
   106  			}
   107  			defer rl.Close()
   108  
   109  			str := "Hello, World"
   110  			n, err := rl.Write([]byte(str))
   111  			if !assert.NoError(t, err, "rl.Write should succeed") {
   112  				return
   113  			}
   114  
   115  			if !assert.Len(t, str, n, "rl.Write should succeed") {
   116  				return
   117  			}
   118  
   119  			fn := rl.CurrentFileName()
   120  			if fn == "" {
   121  				t.Errorf("Could not get filename %s", fn)
   122  			}
   123  
   124  			content, err := ioutil.ReadFile(fn)
   125  			if err != nil {
   126  				t.Errorf("Failed to read file %s: %s", fn, err)
   127  			}
   128  
   129  			if string(content) != str {
   130  				t.Errorf(`File content does not match (was "%s")`, content)
   131  			}
   132  
   133  			err = os.Chtimes(fn, dummyTime, dummyTime)
   134  			if err != nil {
   135  				t.Errorf("Failed to change access/modification times for %s: %s", fn, err)
   136  			}
   137  
   138  			fi, err := os.Stat(fn)
   139  			if err != nil {
   140  				t.Errorf("Failed to stat %s: %s", fn, err)
   141  			}
   142  
   143  			if !fi.ModTime().Equal(dummyTime) {
   144  				t.Errorf("Failed to chtime for %s (expected %s, got %s)", fn, fi.ModTime(), dummyTime)
   145  			}
   146  
   147  			clock.Advance(7 * 24 * time.Hour)
   148  
   149  			// This next Write() should trigger Rotate()
   150  			rl.Write([]byte(str))
   151  			newfn := rl.CurrentFileName()
   152  			if newfn == fn {
   153  				t.Errorf(`New file name and old file name should not match ("%s" != "%s")`, fn, newfn)
   154  			}
   155  
   156  			content, err = ioutil.ReadFile(newfn)
   157  			if err != nil {
   158  				t.Errorf("Failed to read file %s: %s", newfn, err)
   159  			}
   160  
   161  			if string(content) != str {
   162  				t.Errorf(`File content does not match (was "%s")`, content)
   163  			}
   164  
   165  			time.Sleep(time.Second)
   166  
   167  			// fn was declared above, before mocking CurrentTime
   168  			// Old files should have been unlinked
   169  			_, err = os.Stat(fn)
   170  			if !assert.Error(t, err, "os.Stat should have failed") {
   171  				return
   172  			}
   173  
   174  			if fn := tc.CheckExtras; fn != nil {
   175  				if !fn(t, rl, dir) {
   176  					return
   177  				}
   178  			}
   179  		})
   180  	}
   181  }
   182  
   183  func CreateRotationTestFile(dir string, base time.Time, d time.Duration, n int) {
   184  	timestamp := base
   185  	for i := 0; i < n; i++ {
   186  		// %Y%m%d%H%M%S
   187  		suffix := timestamp.Format("20060102150405")
   188  		path := filepath.Join(dir, "log"+suffix)
   189  		ioutil.WriteFile(path, []byte("rotation test file\n"), os.ModePerm)
   190  		os.Chtimes(path, timestamp, timestamp)
   191  		timestamp = timestamp.Add(d)
   192  	}
   193  }
   194  
   195  func TestLogRotationCount(t *testing.T) {
   196  	dir, err := ioutil.TempDir("", "file-rotatelogs-rotationcount-test")
   197  	if !assert.NoError(t, err, "creating temporary directory should succeed") {
   198  		return
   199  	}
   200  	defer os.RemoveAll(dir)
   201  
   202  	dummyTime := time.Now().Add(-7 * 24 * time.Hour)
   203  	dummyTime = dummyTime.Add(time.Duration(-1 * dummyTime.Nanosecond()))
   204  	clock := clockwork.NewFakeClockAt(dummyTime)
   205  
   206  	t.Run("Either maxAge or rotationCount should be set", func(t *testing.T) {
   207  		rl, err := rotatelogs.New(
   208  			filepath.Join(dir, "log%Y%m%d%H%M%S"),
   209  			rotatelogs.WithClock(clock),
   210  			rotatelogs.WithMaxAge(time.Duration(0)),
   211  			rotatelogs.WithRotationCount(0),
   212  		)
   213  		if !assert.NoError(t, err, `Both of maxAge and rotationCount is disabled`) {
   214  			return
   215  		}
   216  		defer rl.Close()
   217  	})
   218  
   219  	t.Run("Either maxAge or rotationCount should be set", func(t *testing.T) {
   220  		rl, err := rotatelogs.New(
   221  			filepath.Join(dir, "log%Y%m%d%H%M%S"),
   222  			rotatelogs.WithClock(clock),
   223  			rotatelogs.WithMaxAge(1),
   224  			rotatelogs.WithRotationCount(1),
   225  		)
   226  		if !assert.Error(t, err, `Both of maxAge and rotationCount is enabled`) {
   227  			return
   228  		}
   229  		if rl != nil {
   230  			defer rl.Close()
   231  		}
   232  	})
   233  
   234  	t.Run("Only latest log file is kept", func(t *testing.T) {
   235  		rl, err := rotatelogs.New(
   236  			filepath.Join(dir, "log%Y%m%d%H%M%S"),
   237  			rotatelogs.WithClock(clock),
   238  			rotatelogs.WithMaxAge(-1),
   239  			rotatelogs.WithRotationCount(1),
   240  		)
   241  		if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   242  			return
   243  		}
   244  		defer rl.Close()
   245  
   246  		n, err := rl.Write([]byte("dummy"))
   247  		if !assert.NoError(t, err, "rl.Write should succeed") {
   248  			return
   249  		}
   250  		if !assert.Len(t, "dummy", n, "rl.Write should succeed") {
   251  			return
   252  		}
   253  		time.Sleep(time.Second)
   254  		files, _ := filepath.Glob(filepath.Join(dir, "log*"))
   255  		if !assert.Equal(t, 1, len(files), "Only latest log is kept") {
   256  			return
   257  		}
   258  	})
   259  
   260  	t.Run("Old log files are purged except 2 log files", func(t *testing.T) {
   261  		CreateRotationTestFile(dir, dummyTime, time.Hour, 5)
   262  		rl, err := rotatelogs.New(
   263  			filepath.Join(dir, "log%Y%m%d%H%M%S"),
   264  			rotatelogs.WithClock(clock),
   265  			rotatelogs.WithMaxAge(-1),
   266  			rotatelogs.WithRotationCount(2),
   267  		)
   268  		if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   269  			return
   270  		}
   271  		defer rl.Close()
   272  
   273  		n, err := rl.Write([]byte("dummy"))
   274  		if !assert.NoError(t, err, "rl.Write should succeed") {
   275  			return
   276  		}
   277  		if !assert.Len(t, "dummy", n, "rl.Write should succeed") {
   278  			return
   279  		}
   280  		time.Sleep(time.Second)
   281  		files, _ := filepath.Glob(filepath.Join(dir, "log*"))
   282  		if !assert.Equal(t, 2, len(files), "One file is kept") {
   283  			return
   284  		}
   285  	})
   286  }
   287  
   288  func TestLogSetOutput(t *testing.T) {
   289  	dir, err := ioutil.TempDir("", "file-rotatelogs-test")
   290  	if err != nil {
   291  		t.Errorf("Failed to create temporary directory: %s", err)
   292  	}
   293  	defer os.RemoveAll(dir)
   294  
   295  	rl, err := rotatelogs.New(filepath.Join(dir, "log%Y%m%d%H%M%S"))
   296  	if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   297  		return
   298  	}
   299  	defer rl.Close()
   300  
   301  	log.SetOutput(rl)
   302  	defer log.SetOutput(os.Stderr)
   303  
   304  	str := "Hello, World"
   305  	log.Print(str)
   306  
   307  	fn := rl.CurrentFileName()
   308  	if fn == "" {
   309  		t.Errorf("Could not get filename %s", fn)
   310  	}
   311  
   312  	content, err := ioutil.ReadFile(fn)
   313  	if err != nil {
   314  		t.Errorf("Failed to read file %s: %s", fn, err)
   315  	}
   316  
   317  	if !strings.Contains(string(content), str) {
   318  		t.Errorf(`File content does not contain "%s" (was "%s")`, str, content)
   319  	}
   320  }
   321  
   322  func TestGHIssue16(t *testing.T) {
   323  	defer func() {
   324  		if v := recover(); v != nil {
   325  			assert.NoError(t, errors.Errorf("%s", v), "error should be nil")
   326  		}
   327  	}()
   328  
   329  	dir, err := ioutil.TempDir("", "file-rotatelogs-gh16")
   330  	if !assert.NoError(t, err, `creating temporary directory should succeed`) {
   331  		return
   332  	}
   333  	defer os.RemoveAll(dir)
   334  
   335  	rl, err := rotatelogs.New(
   336  		filepath.Join(dir, "log%Y%m%d%H%M%S"),
   337  		rotatelogs.WithLinkName("./test.log"),
   338  		rotatelogs.WithRotationTime(10*time.Second),
   339  		rotatelogs.WithRotationCount(3),
   340  		rotatelogs.WithMaxAge(-1),
   341  	)
   342  	if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   343  		return
   344  	}
   345  
   346  	if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") {
   347  		return
   348  	}
   349  	defer rl.Close()
   350  }
   351  
   352  func TestRotationGenerationalNames(t *testing.T) {
   353  	dir, err := ioutil.TempDir("", "file-rotatelogs-generational")
   354  	if !assert.NoError(t, err, `creating temporary directory should succeed`) {
   355  		return
   356  	}
   357  	defer os.RemoveAll(dir)
   358  
   359  	t.Run("Rotate over unchanged pattern", func(t *testing.T) {
   360  		rl, err := rotatelogs.New(
   361  			filepath.Join(dir, "unchanged-pattern.log"),
   362  		)
   363  		if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   364  			return
   365  		}
   366  
   367  		seen := map[string]struct{}{}
   368  		for i := 0; i < 10; i++ {
   369  			rl.Write([]byte("Hello, World!"))
   370  			if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") {
   371  				return
   372  			}
   373  
   374  			// Because every call to Rotate should yield a new log file,
   375  			// and the previous files already exist, the filenames should share
   376  			// the same prefix and have a unique suffix
   377  			fn := filepath.Base(rl.CurrentFileName())
   378  			if !assert.True(t, strings.HasPrefix(fn, "unchanged-pattern.log"), "prefix for all filenames should match") {
   379  				return
   380  			}
   381  			rl.Write([]byte("Hello, World!"))
   382  			suffix := strings.TrimPrefix(fn, "unchanged-pattern.log")
   383  			expectedSuffix := fmt.Sprintf(".%d", i+1)
   384  			if !assert.True(t, suffix == expectedSuffix, "expected suffix %s found %s", expectedSuffix, suffix) {
   385  				return
   386  			}
   387  			assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName())
   388  			stat, err := os.Stat(rl.CurrentFileName())
   389  			if err == nil {
   390  				if !assert.True(t, stat.Size() == 13, "file %s size is %d, expected 13", rl.CurrentFileName(), stat.Size()) {
   391  					return
   392  				}
   393  			} else {
   394  				assert.Failf(t, "could not stat file %s", rl.CurrentFileName())
   395  
   396  				return
   397  			}
   398  
   399  			if _, ok := seen[suffix]; !assert.False(t, ok, `filename suffix %s should be unique`, suffix) {
   400  				return
   401  			}
   402  			seen[suffix] = struct{}{}
   403  		}
   404  		defer rl.Close()
   405  	})
   406  	t.Run("Rotate over pattern change over every second", func(t *testing.T) {
   407  		rl, err := rotatelogs.New(
   408  			filepath.Join(dir, "every-second-pattern-%Y%m%d%H%M%S.log"),
   409  			rotatelogs.WithRotationTime(time.Nanosecond),
   410  		)
   411  		if !assert.NoError(t, err, `rotatelogs.New should succeed`) {
   412  			return
   413  		}
   414  
   415  		for i := 0; i < 10; i++ {
   416  			time.Sleep(time.Second)
   417  			rl.Write([]byte("Hello, World!"))
   418  			if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") {
   419  				return
   420  			}
   421  
   422  			// because every new Write should yield a new logfile,
   423  			// every rorate should be create a filename ending with a .1
   424  			if !assert.True(t, strings.HasSuffix(rl.CurrentFileName(), ".1"), "log name should end with .1") {
   425  				return
   426  			}
   427  		}
   428  		defer rl.Close()
   429  	})
   430  }
   431  
   432  type ClockFunc func() time.Time
   433  
   434  func (f ClockFunc) Now() time.Time {
   435  	return f()
   436  }
   437  
   438  func TestGHIssue23(t *testing.T) {
   439  	dir, err := ioutil.TempDir("", "file-rotatelogs-generational")
   440  	if !assert.NoError(t, err, `creating temporary directory should succeed`) {
   441  		return
   442  	}
   443  	defer os.RemoveAll(dir)
   444  
   445  	for _, locName := range []string{"Asia/Tokyo", "Pacific/Honolulu"} {
   446  		locName := locName
   447  		loc, _ := time.LoadLocation(locName)
   448  		tests := []struct {
   449  			Expected string
   450  			Clock    rotatelogs.Clock
   451  		}{
   452  			{
   453  				Expected: filepath.Join(dir, strings.ToLower(strings.Replace(locName, "/", "_", -1))+".201806010000.log"),
   454  				Clock: ClockFunc(func() time.Time {
   455  					return time.Date(2018, 6, 1, 3, 18, 0, 0, loc)
   456  				}),
   457  			},
   458  			{
   459  				Expected: filepath.Join(dir, strings.ToLower(strings.Replace(locName, "/", "_", -1))+".201712310000.log"),
   460  				Clock: ClockFunc(func() time.Time {
   461  					return time.Date(2017, 12, 31, 23, 52, 0, 0, loc)
   462  				}),
   463  			},
   464  		}
   465  		for _, test := range tests {
   466  			test := test
   467  			t.Run(fmt.Sprintf("location = %s, time = %s", locName, test.Clock.Now().Format(time.RFC3339)), func(t *testing.T) {
   468  				template := strings.ToLower(strings.Replace(locName, "/", "_", -1)) + ".%Y%m%d%H%M.log"
   469  				rl, err := rotatelogs.New(
   470  					filepath.Join(dir, template),
   471  					rotatelogs.WithClock(test.Clock), // we're not using WithLocation, but it's the same thing
   472  				)
   473  				if !assert.NoError(t, err, "rotatelogs.New should succeed") {
   474  					return
   475  				}
   476  
   477  				t.Logf("expected %s", test.Expected)
   478  				rl.Rotate()
   479  				if !assert.Equal(t, test.Expected, rl.CurrentFileName(), "file names should match") {
   480  					return
   481  				}
   482  			})
   483  		}
   484  	}
   485  }
   486  
   487  func TestForceNewFile(t *testing.T) {
   488  	dir, err := ioutil.TempDir("", "file-rotatelogs-force-new-file")
   489  	if !assert.NoError(t, err, `creating temporary directory should succeed`) {
   490  		return
   491  	}
   492  	defer os.RemoveAll(dir)
   493  
   494  	t.Run("Force a new file", func(t *testing.T) {
   495  		rl, err := rotatelogs.New(
   496  			filepath.Join(dir, "force-new-file.log"),
   497  			rotatelogs.ForceNewFile(),
   498  		)
   499  		if !assert.NoError(t, err, "rotatelogs.New should succeed") {
   500  			return
   501  		}
   502  		rl.Write([]byte("Hello, World!"))
   503  		rl.Close()
   504  
   505  		for i := 0; i < 10; i++ {
   506  			baseFn := filepath.Join(dir, "force-new-file.log")
   507  			rl, err := rotatelogs.New(
   508  				baseFn,
   509  				rotatelogs.ForceNewFile(),
   510  			)
   511  			if !assert.NoError(t, err, "rotatelogs.New should succeed") {
   512  				return
   513  			}
   514  			rl.Write([]byte("Hello, World"))
   515  			rl.Write([]byte(fmt.Sprintf("%d", i)))
   516  			rl.Close()
   517  
   518  			fn := filepath.Base(rl.CurrentFileName())
   519  			suffix := strings.TrimPrefix(fn, "force-new-file.log")
   520  			expectedSuffix := fmt.Sprintf(".%d", i+1)
   521  			if !assert.True(t, suffix == expectedSuffix, "expected suffix %s found %s", expectedSuffix, suffix) {
   522  				return
   523  			}
   524  			assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName())
   525  			content, err := ioutil.ReadFile(rl.CurrentFileName())
   526  			if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) {
   527  				return
   528  			}
   529  			str := fmt.Sprintf("Hello, World%d", i)
   530  			if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) {
   531  				return
   532  			}
   533  
   534  			assert.FileExists(t, baseFn, "file does not exist %s", baseFn)
   535  			content, err = ioutil.ReadFile(baseFn)
   536  			if !assert.NoError(t, err, "ioutil.ReadFile should succeed") {
   537  				return
   538  			}
   539  			if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) {
   540  				return
   541  			}
   542  		}
   543  	})
   544  
   545  	t.Run("Force a new file with Rotate", func(t *testing.T) {
   546  		baseFn := filepath.Join(dir, "force-new-file-rotate.log")
   547  		rl, err := rotatelogs.New(
   548  			baseFn,
   549  			rotatelogs.ForceNewFile(),
   550  		)
   551  		if !assert.NoError(t, err, "rotatelogs.New should succeed") {
   552  			return
   553  		}
   554  		rl.Write([]byte("Hello, World!"))
   555  
   556  		for i := 0; i < 10; i++ {
   557  			if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") {
   558  				return
   559  			}
   560  			rl.Write([]byte("Hello, World"))
   561  			rl.Write([]byte(fmt.Sprintf("%d", i)))
   562  			assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName())
   563  			content, err := ioutil.ReadFile(rl.CurrentFileName())
   564  			if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) {
   565  				return
   566  			}
   567  			str := fmt.Sprintf("Hello, World%d", i)
   568  			if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) {
   569  				return
   570  			}
   571  
   572  			assert.FileExists(t, baseFn, "file does not exist %s", baseFn)
   573  			content, err = ioutil.ReadFile(baseFn)
   574  			if !assert.NoError(t, err, "ioutil.ReadFile should succeed") {
   575  				return
   576  			}
   577  			if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) {
   578  				return
   579  			}
   580  		}
   581  	})
   582  }