Files
wazero/internal/platform/futimens_test.go
2023-05-01 12:33:40 +08:00

246 lines
5.8 KiB
Go

package platform
import (
"os"
"path"
"runtime"
"syscall"
"testing"
"time"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestUtimens(t *testing.T) {
t.Run("doesn't exist", func(t *testing.T) {
err := Utimens("nope", nil, true)
require.EqualErrno(t, syscall.ENOENT, err)
err = Utimens("nope", nil, false)
if SupportsSymlinkNoFollow {
require.EqualErrno(t, syscall.ENOENT, err)
} else {
require.EqualErrno(t, syscall.ENOSYS, err)
}
})
testFutimens(t, true)
}
func testFutimens(t *testing.T, usePath bool) {
// Note: This sets microsecond granularity because Windows doesn't support
// nanosecond.
//
// Negative isn't tested as most platforms don't return consistent results.
tests := []struct {
name string
times *[2]syscall.Timespec
}{
{
name: "nil",
},
{
name: "a=omit,m=omit",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_OMIT},
{Sec: 123, Nsec: UTIME_OMIT},
},
},
{
name: "a=now,m=omit",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_NOW},
{Sec: 123, Nsec: UTIME_OMIT},
},
},
{
name: "a=omit,m=now",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_OMIT},
{Sec: 123, Nsec: UTIME_NOW},
},
},
{
name: "a=now,m=now",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_NOW},
{Sec: 123, Nsec: UTIME_NOW},
},
},
{
name: "a=now,m=set",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_NOW},
{Sec: 123, Nsec: 4 * 1e3},
},
},
{
name: "a=set,m=now",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: 4 * 1e3},
{Sec: 123, Nsec: UTIME_NOW},
},
},
{
name: "a=set,m=omit",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: 4 * 1e3},
{Sec: 123, Nsec: UTIME_OMIT},
},
},
{
name: "a=omit,m=set",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: UTIME_OMIT},
{Sec: 123, Nsec: 4 * 1e3},
},
},
{
name: "a=set,m=set",
times: &[2]syscall.Timespec{
{Sec: 123, Nsec: 4 * 1e3},
{Sec: 223, Nsec: 5 * 1e3},
},
},
}
for _, fileType := range []string{"dir", "file", "link", "link-follow"} {
for _, tt := range tests {
tc := tt
fileType := fileType
name := fileType + " " + tc.name
symlinkNoFollow := fileType == "link"
// symlinkNoFollow is invalid for file descriptor based operations,
// because the default for open is to follow links. You can't avoid
// this. O_NOFOLLOW is used only to return ELOOP on a link.
if !usePath && symlinkNoFollow {
continue
}
t.Run(name, func(t *testing.T) {
tmpDir := t.TempDir()
file := path.Join(tmpDir, "file")
err := os.WriteFile(file, []byte{}, 0o700)
require.NoError(t, err)
link := file + "-link"
require.NoError(t, os.Symlink(file, link))
dir := path.Join(tmpDir, "dir")
err = os.Mkdir(dir, 0o700)
require.NoError(t, err)
var path, statPath string
switch fileType {
case "dir":
path = dir
statPath = dir
case "file":
path = file
statPath = file
case "link":
path = link
statPath = link
case "link-follow":
path = link
statPath = file
default:
panic(tc)
}
oldSt, errno := Lstat(statPath)
require.Zero(t, errno)
if usePath {
err = Utimens(path, tc.times, !symlinkNoFollow)
if symlinkNoFollow && !SupportsSymlinkNoFollow {
require.EqualErrno(t, syscall.ENOSYS, err)
return
}
require.Zero(t, errno)
} else {
flag := syscall.O_RDWR
if path == dir {
flag = syscall.O_RDONLY
if runtime.GOOS == "windows" {
// windows requires O_RDWR, which is invalid for directories
t.Skip("windows cannot update timestamps on a dir")
}
}
f, errno := OpenFile(path, flag, 0)
require.Zero(t, errno)
errno = UtimensFile(f.File(), tc.times)
require.Zero(t, f.Close())
require.Zero(t, errno)
}
newSt, errno := Lstat(statPath)
require.Zero(t, errno)
if CompilerSupported() {
if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
require.Equal(t, oldSt.Atim, newSt.Atim)
} else if tc.times == nil || tc.times[0].Nsec == UTIME_NOW {
now := time.Now().UnixNano()
require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
} else {
require.Equal(t, tc.times[0].Nano(), newSt.Atim)
}
}
// When compiler isn't supported, we can still check mtim.
if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
require.Equal(t, oldSt.Mtim, newSt.Mtim)
} else if tc.times == nil || tc.times[1].Nsec == UTIME_NOW {
now := time.Now().UnixNano()
require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
} else {
require.Equal(t, tc.times[1].Nano(), newSt.Mtim)
}
})
}
}
}
func TestUtimensFile(t *testing.T) {
switch runtime.GOOS {
case "linux", "darwin": // supported
case "freebsd": // TODO: support freebsd w/o CGO
case "windows":
if !IsGo120 {
t.Skip("windows only works after Go 1.20") // TODO: possibly 1.19 ;)
}
default: // expect ENOSYS and callers need to fall back to Utimens
t.Skip("unsupported GOOS", runtime.GOOS)
}
testFutimens(t, false)
t.Run("closed file", func(t *testing.T) {
file := path.Join(t.TempDir(), "file")
err := os.WriteFile(file, []byte{}, 0o700)
require.NoError(t, err)
fileF, errno := OpenFile(file, syscall.O_RDWR, 0)
require.Zero(t, errno)
require.Zero(t, fileF.Close())
errno = UtimensFile(fileF.File(), nil)
require.EqualErrno(t, syscall.EBADF, errno)
})
t.Run("closed dir", func(t *testing.T) {
dir := path.Join(t.TempDir(), "dir")
err := os.Mkdir(dir, 0o700)
require.NoError(t, err)
dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
require.Zero(t, errno)
require.Zero(t, dirF.Close())
err = UtimensFile(dirF.File(), nil)
require.EqualErrno(t, syscall.EBADF, err)
})
}