blob: 936a96fc95161d1bd0183ec9e93283e965db446e [file] [log] [blame]
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package oswrapper
import (
"io"
"io/fs"
"os"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestUsableAsOsWrapper(t *testing.T) {
f := func(osWrapper OSWrapper) {
_ = osWrapper.Environ()
}
wrapper := CreateMemMapOSWrapper()
f(wrapper)
}
func TestEnviron(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
environ := wrapper.Environ()
require.Equal(t, []string{}, environ)
wrapper.MemMapEnvironProvider.Environment = map[string]string{
"HOME": "/tmp",
"PWD": "/local",
}
environ = wrapper.Environ()
require.Equal(t, []string{"HOME=/tmp", "PWD=/local"}, environ)
}
func TestGetenv(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
require.Equal(t, "", wrapper.Getenv("HOME"))
wrapper.MemMapEnvironProvider.Environment = map[string]string{
"HOME": "/tmp",
}
require.Equal(t, "/tmp", wrapper.Getenv("HOME"))
}
func TestGetwd(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
wd, err := wrapper.Getwd()
require.NoErrorf(t, err, "Failed to get wd: %v", err)
require.Equal(t, "/", wd)
wrapper.MemMapEnvironProvider.Environment = map[string]string{
"PWD": "/local",
}
wd, err = wrapper.Getwd()
require.NoErrorf(t, err, "Failed to get wd: %v", err)
require.Equal(t, "/local", wd)
}
func TestUserHomeDir(t *testing.T) {
tests := []struct {
name string
environment map[string]string
want string
}{
{
name: "Default",
environment: map[string]string{},
want: "/",
},
{
name: "POSIX",
environment: map[string]string{
"HOME": "/posix",
"USERPROFILE": "/windows",
},
want: "/posix",
},
{
name: "Windows",
environment: map[string]string{
"USERPROFILE": "/windows",
},
want: "/windows",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
wrapper.MemMapEnvironProvider.Environment = testCase.environment
home, err := wrapper.UserHomeDir()
require.NoErrorf(t, err, "Failed to get home dir: %v", err)
require.Equal(t, testCase.want, home)
})
}
}
func TestOpen_Nonexistent(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
_, err := wrapper.Open("/foo.txt")
require.ErrorContains(t, err, "open /foo.txt: file does not exist")
}
func TestOpenFile_Nonexistent(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
_, err := wrapper.OpenFile("/foo.txt", os.O_RDONLY, 0o700)
require.ErrorContains(t, err, "open /foo.txt: file does not exist")
}
func TestReadFile_Nonexistent(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
_, err := wrapper.ReadFile("/foo.txt")
require.ErrorContains(t, err, "open /foo.txt: file does not exist")
}
func TestStat_Nonexistent(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
_, err := wrapper.Stat("/foo.txt")
require.ErrorContains(t, err, "open /foo.txt: file does not exist")
}
func TestWalk_Nonexistent(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
walkfunc := func(root string, info fs.FileInfo, err error) error {
require.Equal(t, "/nonexistent", root)
require.Equal(t, nil, info)
require.ErrorContains(t, err, "open /nonexistent: file does not exist")
return err
}
err := wrapper.Walk("/nonexistent", walkfunc)
require.ErrorContains(t, err, "open /nonexistent: file does not exist")
}
func TestWriteFile_Open(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.WriteFile("/foo.txt", []byte("content"), 0o700)
require.NoErrorf(t, err, "Failed to write file: %v", err)
file, err := wrapper.Open("/foo.txt")
require.NoErrorf(t, err, "Failed to open file: %v", err)
buffer := make([]byte, 7)
bytesRead, err := file.Read(buffer)
require.NoErrorf(t, err, "Failed to read from file: %v", err)
require.Equal(t, []byte("content"), buffer)
require.Equal(t, 7, bytesRead)
bytesRead, err = file.Read(buffer)
require.Error(t, io.EOF, "Did not get EOF")
require.Equal(t, 0, bytesRead)
}
func TestCreate_Walk_NonexistentDirectory(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
_, err := wrapper.Create("/parent/foo.txt")
require.NoErrorf(t, err, "Got error creating file without parent dir: %v", err)
type input struct {
root string
info fs.FileInfo
err error
}
inputs := []input{}
walkfunc := func(root string, info fs.FileInfo, err error) error {
inputs = append(inputs, input{root: root, info: info, err: err})
return err
}
err = wrapper.Walk("/parent", walkfunc)
require.NoErrorf(t, err, "Got error walking: %v", err)
require.Equal(t, 2, len(inputs))
require.Equal(t, "/parent", inputs[0].root)
require.True(t, inputs[0].info.IsDir())
require.Equal(t, "/parent/foo.txt", inputs[1].root)
require.False(t, inputs[1].info.IsDir())
}
func TestCreate_ReadFile(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
file, err := wrapper.Create("/foo.txt")
require.NoErrorf(t, err, "Failed to create file: %v", err)
bytesWritten, err := file.Write([]byte("asdf"))
require.NoErrorf(t, err, "Failed to write to file: %v", err)
require.Equal(t, 4, bytesWritten)
contents, err := wrapper.ReadFile("/foo.txt")
require.NoErrorf(t, err, "Failed to read file: %v", err)
require.Equal(t, []byte("asdf"), contents)
}
func TestMkdir_Exists(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.Mkdir("/parent", 0o700)
require.NoErrorf(t, err, "Got error creating directory: %v", err)
err = wrapper.Mkdir("/parent", 0o700)
require.ErrorContains(t, err, "mkdir /parent: file already exists")
}
func TestMkdir_MkdirAll(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.Mkdir("/parent", 0o700)
require.NoErrorf(t, err, "Error creating parent: %v", err)
err = wrapper.MkdirAll("/parent/child/grandchild", 0o600)
require.NoErrorf(t, err, "Error creating grandchild: %v", err)
type input struct {
root string
info fs.FileInfo
err error
}
inputs := []input{}
walkfunc := func(root string, info fs.FileInfo, err error) error {
inputs = append(inputs, input{root: root, info: info, err: err})
return err
}
err = wrapper.Walk("/parent", walkfunc)
require.NoErrorf(t, err, "Got error walking: %v", err)
require.Equal(t, len(inputs), 3)
require.Equal(t, "/parent", inputs[0].root)
require.True(t, inputs[0].info.IsDir())
require.Equal(t, os.FileMode(0o700)|fs.ModeDir, inputs[0].info.Mode())
require.Equal(t, "/parent/child", inputs[1].root)
require.True(t, inputs[1].info.IsDir())
require.Equal(t, os.FileMode(0o600)|fs.ModeDir, inputs[1].info.Mode())
require.Equal(t, "/parent/child/grandchild", inputs[2].root)
require.True(t, inputs[2].info.IsDir())
require.Equal(t, os.FileMode(0o600)|fs.ModeDir, inputs[2].info.Mode())
}
func TestMkdirAll_Exists(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.MkdirAll("/parent", 0o700)
require.NoErrorf(t, err, "Got error creating directory: %v", err)
err = wrapper.MkdirAll("/parent", 0o700)
require.NoErrorf(t, err, "Got error creating directory second time: %v", err)
}
func TestMkdirTemp(t *testing.T) {
tests := []struct {
name string
pattern string
re string
}{
{
name: "Default behavior",
pattern: "tempdir",
re: "/tmp/tempdir\\d+",
},
{
name: "Single star front",
pattern: "*tempdir",
re: "/tmp/\\d+tempdir",
},
{
name: "Single star middle",
pattern: "temp*dir",
re: "/tmp/temp\\d+dir",
},
{
name: "Multiple stars",
pattern: "temp*dir*",
re: "/tmp/temp\\*dir\\d+",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
dir, err := wrapper.MkdirTemp("/tmp", testCase.pattern)
require.NoErrorf(t, err, "Error creating temporary directory: %v", err)
re, err := regexp.Compile(testCase.re)
require.NoErrorf(t, err, "Error compiling regexp: %s", err)
require.True(t, re.Match([]byte(dir)))
})
}
}
func TestRemove(t *testing.T) {
tests := []struct {
name string
filesToCreate []string
dirsToCreate []string
toRemove string
wantErr bool
wantErrMsg string
}{
{
name: "Non-existent",
toRemove: "/foo.txt",
wantErr: true,
wantErrMsg: "remove /foo.txt: file does not exist",
},
{
name: "File",
filesToCreate: []string{"/foo.txt"},
toRemove: "/foo.txt",
},
{
name: "Empty dir",
dirsToCreate: []string{"/foo"},
toRemove: "/foo",
},
{
// This differs from the real os implementation, as removing a non-empty
// dir is supposed to fail.
name: "Non-empty dir",
dirsToCreate: []string{"/foo/bar"},
toRemove: "/foo",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
for _, f := range testCase.filesToCreate {
_, err := wrapper.Create(f)
require.NoErrorf(t, err, "Failed to create file: %v", err)
}
for _, d := range testCase.dirsToCreate {
err := wrapper.MkdirAll(d, 0o700)
require.NoErrorf(t, err, "Failed to create directory: %v", err)
}
err := wrapper.Remove(testCase.toRemove)
if testCase.wantErr {
require.ErrorContains(t, err, testCase.wantErrMsg)
} else {
require.NoErrorf(t, err, "Got unexpected error: %v")
}
// Make sure things were actually deleted.
walkfunc := func(root string, info fs.FileInfo, err error) error {
require.Equal(t, "/", root)
return err
}
err = wrapper.Walk("/", walkfunc)
require.NoErrorf(t, err, "Error walking: %v", err)
})
}
}
func TestRemoveAll(t *testing.T) {
tests := []struct {
name string
filesToCreate []string
dirsToCreate []string
toRemove string
}{
{
name: "Non-existent",
toRemove: "/foo.txt",
},
{
name: "File",
filesToCreate: []string{"/foo.txt"},
toRemove: "/foo.txt",
},
{
name: "Empty dir",
dirsToCreate: []string{"/foo"},
toRemove: "/foo",
},
{
// This differs from the real os implementation, as removing a non-empty
// dir is supposed to fail.
name: "Non-empty dir",
dirsToCreate: []string{"/foo/bar"},
toRemove: "/foo",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
for _, f := range testCase.filesToCreate {
_, err := wrapper.Create(f)
require.NoErrorf(t, err, "Failed to create file: %v", err)
}
for _, d := range testCase.dirsToCreate {
err := wrapper.MkdirAll(d, 0o700)
require.NoErrorf(t, err, "Failed to create directory: %v", err)
}
err := wrapper.RemoveAll(testCase.toRemove)
require.NoErrorf(t, err, "Got unexpected error: %v")
// Make sure things were actually deleted.
walkfunc := func(root string, info fs.FileInfo, err error) error {
require.Equal(t, "/", root)
return err
}
err = wrapper.Walk("/", walkfunc)
require.NoErrorf(t, err, "Error walking: %v", err)
})
}
}
func TestWriteFile_OpenFile(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.WriteFile("/foo.txt", []byte("content"), 0o700)
require.NoErrorf(t, err, "Failed to write file: %v", err)
file, err := wrapper.OpenFile("/foo.txt", os.O_RDONLY, 0o700)
require.NoErrorf(t, err, "Failed to open file: %v", err)
buffer := make([]byte, 7)
bytesRead, err := file.Read(buffer)
require.NoErrorf(t, err, "Failed to read from file: %v", err)
require.Equal(t, []byte("content"), buffer)
require.Equal(t, 7, bytesRead)
bytesRead, err = file.Read(buffer)
require.Errorf(t, io.EOF, "Did not get EOF: %v", err)
require.Equal(t, 0, bytesRead)
}
func TestWriteFile_ReadFile(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
err := wrapper.WriteFile("/foo.txt", []byte("content"), 0o700)
require.NoErrorf(t, err, "Failed to write file: %v", err)
contents, err := wrapper.ReadFile("/foo.txt")
require.NoErrorf(t, err, "Failed to read file: %v", err)
require.Equal(t, []byte("content"), contents)
}
func TestWriteFile_Stat(t *testing.T) {
wrapper := CreateMemMapOSWrapper()
approxWriteTime := time.Now()
err := wrapper.WriteFile("/foo.txt", []byte("content"), 0o700)
require.NoErrorf(t, err, "Failed to write file: %v", err)
fileInfo, err := wrapper.Stat("/foo.txt")
require.NoErrorf(t, err, "Got error stat-ing file: %v", err)
require.Equal(t, "foo.txt", fileInfo.Name())
require.Equal(t, int64(7), fileInfo.Size())
require.Equal(t, os.FileMode(0o700), fileInfo.Mode())
// Assert that the actual mod time is within 10ms of when we requested the
// write.
require.LessOrEqual(t, fileInfo.ModTime().UnixMilli()-approxWriteTime.UnixMilli(), int64(10))
require.False(t, fileInfo.IsDir())
}