Files
wazero/internal/platform/futimens_test.go
Crypt Keeper 36bf277534 sysfs: requires all methods to return syscall.Errno (#1264)
This forces all syscall functions, notably filesystem, to return numeric
codes as opposed to mapping in two different areas. The result of this
change is better consolidation in call sites of `sysfs.FS`, while
further refactoring is needed to address consolidation of file errors.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-03-22 07:47:57 +01: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, err)
} 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, tc.times)
require.NoError(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.NoError(t, fileF.Close())
errno = UtimensFile(fileF, 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.NoError(t, dirF.Close())
err = UtimensFile(dirF, nil)
require.EqualErrno(t, syscall.EBADF, err)
})
}