github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/futimens_test.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"os"
     5  	"path"
     6  	"runtime"
     7  	"syscall"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/tetratelabs/wazero/internal/platform"
    12  	"github.com/tetratelabs/wazero/internal/testing/require"
    13  )
    14  
    15  func TestUtimens(t *testing.T) {
    16  	t.Run("doesn't exist", func(t *testing.T) {
    17  		err := Utimens("nope", nil, true)
    18  		require.EqualErrno(t, syscall.ENOENT, err)
    19  
    20  		err = Utimens("nope", nil, false)
    21  		if SupportsSymlinkNoFollow {
    22  			require.EqualErrno(t, syscall.ENOENT, err)
    23  		} else {
    24  			require.EqualErrno(t, syscall.ENOSYS, err)
    25  		}
    26  	})
    27  	testUtimens(t, false)
    28  }
    29  
    30  func testUtimens(t *testing.T, futimes bool) {
    31  	// Note: This sets microsecond granularity because Windows doesn't support
    32  	// nanosecond.
    33  	//
    34  	// Negative isn't tested as most platforms don't return consistent results.
    35  	tests := []struct {
    36  		name  string
    37  		times *[2]syscall.Timespec
    38  	}{
    39  		{
    40  			name: "nil",
    41  		},
    42  		{
    43  			name: "a=omit,m=omit",
    44  			times: &[2]syscall.Timespec{
    45  				{Sec: 123, Nsec: UTIME_OMIT},
    46  				{Sec: 123, Nsec: UTIME_OMIT},
    47  			},
    48  		},
    49  		{
    50  			name: "a=now,m=omit",
    51  			times: &[2]syscall.Timespec{
    52  				{Sec: 123, Nsec: UTIME_NOW},
    53  				{Sec: 123, Nsec: UTIME_OMIT},
    54  			},
    55  		},
    56  		{
    57  			name: "a=omit,m=now",
    58  			times: &[2]syscall.Timespec{
    59  				{Sec: 123, Nsec: UTIME_OMIT},
    60  				{Sec: 123, Nsec: UTIME_NOW},
    61  			},
    62  		},
    63  		{
    64  			name: "a=now,m=now",
    65  			times: &[2]syscall.Timespec{
    66  				{Sec: 123, Nsec: UTIME_NOW},
    67  				{Sec: 123, Nsec: UTIME_NOW},
    68  			},
    69  		},
    70  		{
    71  			name: "a=now,m=set",
    72  			times: &[2]syscall.Timespec{
    73  				{Sec: 123, Nsec: UTIME_NOW},
    74  				{Sec: 123, Nsec: 4 * 1e3},
    75  			},
    76  		},
    77  		{
    78  			name: "a=set,m=now",
    79  			times: &[2]syscall.Timespec{
    80  				{Sec: 123, Nsec: 4 * 1e3},
    81  				{Sec: 123, Nsec: UTIME_NOW},
    82  			},
    83  		},
    84  		{
    85  			name: "a=set,m=omit",
    86  			times: &[2]syscall.Timespec{
    87  				{Sec: 123, Nsec: 4 * 1e3},
    88  				{Sec: 123, Nsec: UTIME_OMIT},
    89  			},
    90  		},
    91  		{
    92  			name: "a=omit,m=set",
    93  			times: &[2]syscall.Timespec{
    94  				{Sec: 123, Nsec: UTIME_OMIT},
    95  				{Sec: 123, Nsec: 4 * 1e3},
    96  			},
    97  		},
    98  		{
    99  			name: "a=set,m=set",
   100  			times: &[2]syscall.Timespec{
   101  				{Sec: 123, Nsec: 4 * 1e3},
   102  				{Sec: 223, Nsec: 5 * 1e3},
   103  			},
   104  		},
   105  	}
   106  	for _, fileType := range []string{"dir", "file", "link", "link-follow"} {
   107  		for _, tt := range tests {
   108  			tc := tt
   109  			fileType := fileType
   110  			name := fileType + " " + tc.name
   111  			symlinkNoFollow := fileType == "link"
   112  
   113  			// symlinkNoFollow is invalid for file descriptor based operations,
   114  			// because the default for open is to follow links. You can't avoid
   115  			// this. O_NOFOLLOW is used only to return ELOOP on a link.
   116  			if futimes && symlinkNoFollow {
   117  				continue
   118  			}
   119  
   120  			t.Run(name, func(t *testing.T) {
   121  				tmpDir := t.TempDir()
   122  				file := path.Join(tmpDir, "file")
   123  				err := os.WriteFile(file, []byte{}, 0o700)
   124  				require.NoError(t, err)
   125  
   126  				link := file + "-link"
   127  				require.NoError(t, os.Symlink(file, link))
   128  
   129  				dir := path.Join(tmpDir, "dir")
   130  				err = os.Mkdir(dir, 0o700)
   131  				require.NoError(t, err)
   132  
   133  				var path, statPath string
   134  				switch fileType {
   135  				case "dir":
   136  					path = dir
   137  					statPath = dir
   138  				case "file":
   139  					path = file
   140  					statPath = file
   141  				case "link":
   142  					path = link
   143  					statPath = link
   144  				case "link-follow":
   145  					path = link
   146  					statPath = file
   147  				default:
   148  					panic(tc)
   149  				}
   150  
   151  				oldSt, errno := lstat(statPath)
   152  				require.EqualErrno(t, 0, errno)
   153  
   154  				if !futimes {
   155  					err = Utimens(path, tc.times, !symlinkNoFollow)
   156  					if symlinkNoFollow && !SupportsSymlinkNoFollow {
   157  						require.EqualErrno(t, syscall.ENOSYS, err)
   158  						return
   159  					}
   160  					require.EqualErrno(t, 0, errno)
   161  				} else {
   162  					flag := syscall.O_RDWR
   163  					if path == dir {
   164  						flag = syscall.O_RDONLY
   165  						if runtime.GOOS == "windows" {
   166  							// windows requires O_RDWR, which is invalid for directories
   167  							t.Skip("windows cannot update timestamps on a dir")
   168  						}
   169  					}
   170  
   171  					f := requireOpenFile(t, path, flag, 0)
   172  
   173  					errno = f.Utimens(tc.times)
   174  					require.EqualErrno(t, 0, f.Close())
   175  					require.EqualErrno(t, 0, errno)
   176  				}
   177  
   178  				newSt, errno := lstat(statPath)
   179  				require.EqualErrno(t, 0, errno)
   180  
   181  				if platform.CompilerSupported() {
   182  					if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
   183  						require.Equal(t, oldSt.Atim, newSt.Atim)
   184  					} else if tc.times == nil || tc.times[0].Nsec == UTIME_NOW {
   185  						now := time.Now().UnixNano()
   186  						require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
   187  					} else {
   188  						require.Equal(t, tc.times[0].Nano(), newSt.Atim)
   189  					}
   190  				}
   191  
   192  				// When compiler isn't supported, we can still check mtim.
   193  				if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
   194  					require.Equal(t, oldSt.Mtim, newSt.Mtim)
   195  				} else if tc.times == nil || tc.times[1].Nsec == UTIME_NOW {
   196  					now := time.Now().UnixNano()
   197  					require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
   198  				} else {
   199  					require.Equal(t, tc.times[1].Nano(), newSt.Mtim)
   200  				}
   201  			})
   202  		}
   203  	}
   204  }