github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/logging/stat_test.go (about)

     1  package logging_test
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/datawire/dlib/dlog"
    15  	"github.com/telepresenceio/telepresence/v2/pkg/client"
    16  	"github.com/telepresenceio/telepresence/v2/pkg/client/logging"
    17  	"github.com/telepresenceio/telepresence/v2/pkg/dos"
    18  )
    19  
    20  var osHasBTime = true
    21  
    22  func TestFStat(t *testing.T) {
    23  	btimeIsCTime := testFStat(t, runtime.GOOS == "linux")
    24  	if btimeIsCTime && osHasBTime {
    25  		// The kernel supports btime, but the filesystem doesn't.  Set TMPDIR to be
    26  		// $HOME/tmp, on the assumption that $HOME is on a "big boy" filesystem and thus
    27  		// supports btime.
    28  		t.Run("tmpdirInHome", func(t *testing.T) {
    29  			os.Setenv("TMPDIR", filepath.Join(os.Getenv("HOME"), "tmp"))
    30  			err := os.Mkdir(os.Getenv("TMPDIR"), 0o777)
    31  			if err != nil && !errors.Is(err, os.ErrExist) {
    32  				t.Fatal(err)
    33  			}
    34  			testFStat(t, false)
    35  		})
    36  	}
    37  }
    38  
    39  func testFStat(t *testing.T, okIfBTimeIsCTime bool) (btimeIsCTime bool) {
    40  	const (
    41  		fsVsClockLeeway = 1 * time.Second // many filesystems only have second precision
    42  		minDelta        = 2 * time.Second
    43  	)
    44  
    45  	ctx := dlog.NewTestContext(t, false)
    46  	ctx = client.WithEnv(ctx, &client.Env{})
    47  	filename := filepath.Join(t.TempDir(), "stamp.txt")
    48  	withFile := func(flags int, fn func(dos.File)) (time.Time, time.Time) {
    49  		before := time.Now()
    50  		time.Sleep(fsVsClockLeeway)
    51  		file, err := dos.OpenFile(ctx, filename, flags, 0o666)
    52  		require.NoError(t, err)
    53  		require.NotNil(t, file)
    54  		fn(file)
    55  		require.NoError(t, file.Close())
    56  		time.Sleep(fsVsClockLeeway)
    57  		after := time.Now()
    58  		return before, after
    59  	}
    60  
    61  	// btime
    62  	bBefore, bAfter := withFile(os.O_CREATE|os.O_RDWR, func(file dos.File) {})
    63  
    64  	time.Sleep(minDelta)
    65  
    66  	// mtime
    67  	mBefore, mAfter := withFile(os.O_RDWR, func(file dos.File) {
    68  		_, err := io.WriteString(file, "#!/bin/sh\n")
    69  		require.NoError(t, err)
    70  	})
    71  
    72  	time.Sleep(minDelta)
    73  
    74  	// ctime
    75  	cBefore := time.Now()
    76  	time.Sleep(fsVsClockLeeway)
    77  	require.NoError(t, os.Chmod(filename, 0o777))
    78  	time.Sleep(fsVsClockLeeway)
    79  	cAfter := time.Now()
    80  
    81  	// stat
    82  	var stat logging.SysInfo
    83  	withFile(os.O_RDWR, func(file dos.File) {
    84  		var err error
    85  		stat, err = logging.FStat(file)
    86  		require.NoError(t, err)
    87  	})
    88  
    89  	// validate
    90  	assertInRange := func(before, after, x time.Time, msg string) {
    91  		if x.Before(before) || x.After(after) {
    92  			t.Errorf("%s: %v: not in range [%v, %v]", msg, x, before, after)
    93  		} else {
    94  			t.Logf("%s: %v", msg, x)
    95  		}
    96  	}
    97  
    98  	if okIfBTimeIsCTime && stat.BirthTime() == stat.ChangeTime() {
    99  		btimeIsCTime = true
   100  		t.Logf("btime: %v (spoofed with ctime)", stat.BirthTime())
   101  	} else {
   102  		assertInRange(bBefore, bAfter, stat.BirthTime(), "btime")
   103  	}
   104  	assertInRange(mBefore, mAfter, stat.ModifyTime(), "mtime")
   105  	if runtime.GOOS == "windows" {
   106  		cBefore, cAfter = mBefore, mAfter
   107  	}
   108  	assertInRange(cBefore, cAfter, stat.ChangeTime(), "ctime")
   109  	return btimeIsCTime
   110  }