2018-01-03 21:53:45 +01:00
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2022-10-16 11:32:38 +02:00
|
|
|
"errors"
|
2022-12-02 19:36:43 +01:00
|
|
|
"io"
|
2018-01-03 21:53:45 +01:00
|
|
|
"os"
|
2019-04-28 20:55:31 +01:00
|
|
|
"path"
|
2018-01-03 21:53:45 +01:00
|
|
|
"sort"
|
2019-01-06 14:31:11 +01:00
|
|
|
"strings"
|
2018-01-03 21:53:45 +01:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/restic/restic/internal/test"
|
|
|
|
)
|
|
|
|
|
|
|
|
func verifyFileContentOpenFile(t testing.TB, fs FS, filename string, want []byte) {
|
2024-11-02 20:27:38 +01:00
|
|
|
f, err := fs.OpenFile(filename, O_RDONLY, false)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2022-12-02 19:36:43 +01:00
|
|
|
buf, err := io.ReadAll(f)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !cmp.Equal(want, buf) {
|
|
|
|
t.Error(cmp.Diff(want, buf))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func verifyDirectoryContents(t testing.TB, fs FS, dir string, want []string) {
|
2024-11-02 20:27:38 +01:00
|
|
|
f, err := fs.OpenFile(dir, O_RDONLY, false)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
entries, err := f.Readdirnames(-1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-06-30 23:20:32 +03:00
|
|
|
sort.Strings(want)
|
|
|
|
sort.Strings(entries)
|
2018-01-03 21:53:45 +01:00
|
|
|
|
|
|
|
if !cmp.Equal(want, entries) {
|
|
|
|
t.Error(cmp.Diff(want, entries))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-03 16:01:59 +01:00
|
|
|
func checkFileInfo(t testing.TB, fi *ExtendedFileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) {
|
2024-11-30 16:58:04 +01:00
|
|
|
if fi.Mode.IsDir() != isdir {
|
|
|
|
t.Errorf("IsDir returned %t, want %t", fi.Mode.IsDir(), isdir)
|
2018-01-03 21:53:45 +01:00
|
|
|
}
|
|
|
|
|
2024-11-30 16:58:04 +01:00
|
|
|
if fi.Mode != mode {
|
|
|
|
t.Errorf("Mode has wrong value, want 0%o, got 0%o", mode, fi.Mode)
|
2018-01-03 21:53:45 +01:00
|
|
|
}
|
|
|
|
|
2025-04-11 21:43:31 +02:00
|
|
|
if !fi.ModTime.Equal(modtime) {
|
2024-11-30 16:58:04 +01:00
|
|
|
t.Errorf("ModTime has wrong value, want %v, got %v", modtime, fi.ModTime)
|
2018-01-03 21:53:45 +01:00
|
|
|
}
|
|
|
|
|
2024-11-30 16:58:04 +01:00
|
|
|
if path.Base(fi.Name) != fi.Name {
|
|
|
|
t.Errorf("Name is not base, want %q, got %q", path.Base(fi.Name), fi.Name)
|
2019-04-28 20:55:31 +01:00
|
|
|
}
|
|
|
|
|
2024-11-30 16:58:04 +01:00
|
|
|
if fi.Name != path.Base(filename) {
|
|
|
|
t.Errorf("Name has wrong value, want %q, got %q", path.Base(filename), fi.Name)
|
2018-01-03 21:53:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:37:32 +02:00
|
|
|
type fsTest []struct {
|
|
|
|
name string
|
|
|
|
f func(t *testing.T, fs FS)
|
|
|
|
}
|
2018-01-03 21:53:45 +01:00
|
|
|
|
2025-04-11 21:37:32 +02:00
|
|
|
func createReadDirTest(fpath, filename string) fsTest {
|
|
|
|
return fsTest{
|
2018-01-03 21:53:45 +01:00
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "Readdirnames-slash-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:37:32 +02:00
|
|
|
verifyDirectoryContents(t, fs, "/"+fpath, []string{filename})
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "Readdirnames-current-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:37:32 +02:00
|
|
|
verifyDirectoryContents(t, fs, path.Clean(fpath), []string{filename})
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
2025-04-11 21:37:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createFileTest(filename string, now time.Time, data []byte) fsTest {
|
|
|
|
return fsTest{
|
2018-01-03 21:53:45 +01:00
|
|
|
{
|
|
|
|
name: "file/OpenFile",
|
|
|
|
f: func(t *testing.T, fs FS) {
|
|
|
|
verifyFileContentOpenFile(t, fs, filename, data)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "file/Lstat",
|
|
|
|
f: func(t *testing.T, fs FS) {
|
|
|
|
fi, err := fs.Lstat(filename)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkFileInfo(t, fi, filename, now, 0644, false)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "file/Stat",
|
|
|
|
f: func(t *testing.T, fs FS) {
|
2024-11-02 20:27:38 +01:00
|
|
|
f, err := fs.OpenFile(filename, O_RDONLY, true)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fi, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkFileInfo(t, fi, filename, now, 0644, false)
|
|
|
|
},
|
|
|
|
},
|
2025-04-11 21:37:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:43:31 +02:00
|
|
|
func createDirTest(fpath string, now time.Time) fsTest {
|
2025-04-11 21:37:32 +02:00
|
|
|
return fsTest{
|
2018-01-03 21:53:45 +01:00
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "dir/Lstat-slash-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:37:32 +02:00
|
|
|
fi, err := fs.Lstat("/" + fpath)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:43:31 +02:00
|
|
|
checkFileInfo(t, fi, "/"+fpath, now, os.ModeDir|0755, true)
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "dir/Lstat-current-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:37:32 +02:00
|
|
|
fi, err := fs.Lstat("./" + fpath)
|
2018-01-03 21:53:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:43:31 +02:00
|
|
|
checkFileInfo(t, fi, "/"+fpath, now, os.ModeDir|0755, true)
|
2019-04-28 20:58:10 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "dir/Lstat-error-not-exist-" + fpath,
|
2019-04-28 20:58:10 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:37:32 +02:00
|
|
|
_, err := fs.Lstat(fpath + "/other")
|
2022-10-16 11:32:38 +02:00
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
2019-04-28 20:58:10 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "dir/Open-slash-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:49:25 +02:00
|
|
|
fi := fsStatDir(t, fs, "/"+fpath)
|
2025-04-11 21:43:31 +02:00
|
|
|
checkFileInfo(t, fi, "/"+fpath, now, os.ModeDir|0755, true)
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2025-04-11 21:37:32 +02:00
|
|
|
name: "dir/Open-current-" + fpath,
|
2018-01-03 21:53:45 +01:00
|
|
|
f: func(t *testing.T, fs FS) {
|
2025-04-11 21:49:25 +02:00
|
|
|
fi := fsStatDir(t, fs, "./"+fpath)
|
2025-04-11 21:43:31 +02:00
|
|
|
checkFileInfo(t, fi, "/"+fpath, now, os.ModeDir|0755, true)
|
2018-01-03 21:53:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2025-04-11 21:37:32 +02:00
|
|
|
}
|
|
|
|
|
2025-04-11 21:49:25 +02:00
|
|
|
func fsStatDir(t *testing.T, fs FS, fpath string) *ExtendedFileInfo {
|
|
|
|
f, err := fs.OpenFile(fpath, O_RDONLY, false)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fi, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return fi
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:37:32 +02:00
|
|
|
func TestFSReader(t *testing.T) {
|
|
|
|
data := test.Random(55, 1<<18+588)
|
|
|
|
now := time.Now()
|
|
|
|
filename := "foobar"
|
|
|
|
|
|
|
|
tests := createReadDirTest("", filename)
|
|
|
|
tests = append(tests, createFileTest(filename, now, data)...)
|
2025-04-11 21:43:31 +02:00
|
|
|
tests = append(tests, createDirTest("", now)...)
|
2018-01-03 21:53:45 +01:00
|
|
|
|
|
|
|
for _, test := range tests {
|
2025-04-11 21:37:32 +02:00
|
|
|
fs := NewReader(filename, io.NopCloser(bytes.NewReader(data)), ReaderOptions{
|
|
|
|
Mode: 0644,
|
|
|
|
Size: int64(len(data)),
|
|
|
|
ModTime: now,
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
test.f(t, fs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2018-01-03 21:53:45 +01:00
|
|
|
|
2025-04-11 21:37:32 +02:00
|
|
|
func TestFSReaderNested(t *testing.T) {
|
|
|
|
data := test.Random(55, 1<<18+588)
|
|
|
|
now := time.Now()
|
|
|
|
filename := "foo/sub/bar"
|
|
|
|
|
|
|
|
tests := createReadDirTest("", "foo")
|
|
|
|
tests = append(tests, createReadDirTest("foo", "sub")...)
|
|
|
|
tests = append(tests, createReadDirTest("foo/sub", "bar")...)
|
|
|
|
tests = append(tests, createFileTest(filename, now, data)...)
|
2025-04-11 21:43:31 +02:00
|
|
|
tests = append(tests, createDirTest("", now)...)
|
|
|
|
tests = append(tests, createDirTest("foo", now)...)
|
|
|
|
tests = append(tests, createDirTest("foo/sub", now)...)
|
2025-04-11 21:37:32 +02:00
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
fs := NewReader(filename, io.NopCloser(bytes.NewReader(data)), ReaderOptions{
|
2018-01-03 21:53:45 +01:00
|
|
|
Mode: 0644,
|
|
|
|
Size: int64(len(data)),
|
|
|
|
ModTime: now,
|
2025-04-11 21:37:32 +02:00
|
|
|
})
|
2018-01-03 21:53:45 +01:00
|
|
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
test.f(t, fs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-01-06 14:31:11 +01:00
|
|
|
|
2019-04-28 20:58:10 +01:00
|
|
|
func TestFSReaderDir(t *testing.T) {
|
|
|
|
data := test.Random(55, 1<<18+588)
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
name string
|
|
|
|
filename string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Lstat-absolute",
|
|
|
|
filename: "/path/to/foobar",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Lstat-relative",
|
|
|
|
filename: "path/to/foobar",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2025-04-11 21:37:32 +02:00
|
|
|
fs := NewReader(test.filename, io.NopCloser(bytes.NewReader(data)), ReaderOptions{
|
2019-04-28 20:58:10 +01:00
|
|
|
Mode: 0644,
|
|
|
|
Size: int64(len(data)),
|
|
|
|
ModTime: now,
|
2025-04-11 21:37:32 +02:00
|
|
|
})
|
2019-04-28 20:58:10 +01:00
|
|
|
|
2025-04-11 21:37:32 +02:00
|
|
|
dir := path.Dir(test.filename)
|
2019-04-28 20:58:10 +01:00
|
|
|
for {
|
|
|
|
if dir == "/" || dir == "." {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
fi, err := fs.Lstat(dir)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2025-04-11 21:43:31 +02:00
|
|
|
checkFileInfo(t, fi, dir, now, os.ModeDir|0755, true)
|
2019-04-28 20:58:10 +01:00
|
|
|
|
|
|
|
dir = path.Dir(dir)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-06 14:31:11 +01:00
|
|
|
func TestFSReaderMinFileSize(t *testing.T) {
|
|
|
|
var tests = []struct {
|
|
|
|
name string
|
|
|
|
data string
|
|
|
|
allowEmpty bool
|
|
|
|
readMustErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "regular",
|
|
|
|
data: "foobar",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "empty",
|
|
|
|
data: "",
|
|
|
|
allowEmpty: false,
|
|
|
|
readMustErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "empty2",
|
|
|
|
data: "",
|
|
|
|
allowEmpty: true,
|
|
|
|
readMustErr: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2025-04-11 21:37:32 +02:00
|
|
|
fs := NewReader("testfile", io.NopCloser(strings.NewReader(test.data)), ReaderOptions{
|
2019-01-06 14:31:11 +01:00
|
|
|
Mode: 0644,
|
|
|
|
ModTime: time.Now(),
|
|
|
|
AllowEmptyFile: test.allowEmpty,
|
2025-04-11 21:37:32 +02:00
|
|
|
})
|
2019-01-06 14:31:11 +01:00
|
|
|
|
2024-11-02 20:27:38 +01:00
|
|
|
f, err := fs.OpenFile("testfile", O_RDONLY, false)
|
2019-01-06 14:31:11 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2022-12-02 19:36:43 +01:00
|
|
|
buf, err := io.ReadAll(f)
|
2019-01-06 14:31:11 +01:00
|
|
|
if test.readMustErr {
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error not found, got nil")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if string(buf) != test.data {
|
|
|
|
t.Fatalf("wrong data returned, want %q, got %q", test.data, string(buf))
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|