syncthing/lib/fs/fakefs_test.go
Jakob Borg adce6fa473
all: Support syncing ownership (fixes #1329) (#8434)
This adds support for syncing ownership on Unixes and on Windows. The
scanner always picks up ownership information, but it is not applied
unless the new folder option "Sync Ownership" is set.

Ownership data is stored in a new FileInfo field called "platform data". This
is intended to hold further platform-specific data in the future
(specifically, extended attributes), which is why the whole design is a
bit overkill for just ownership.
2022-07-26 08:24:58 +02:00

926 lines
19 KiB
Go

// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package fs
import (
"bytes"
"fmt"
"io"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"testing"
"time"
)
func TestFakeFS(t *testing.T) {
// Test some basic aspects of the fakeFS
fs := newFakeFilesystem("/foo/bar/baz")
// MkdirAll
err := fs.MkdirAll("dira/dirb", 0755)
if err != nil {
t.Fatal(err)
}
_, err = fs.Stat("dira/dirb")
if err != nil {
t.Fatal(err)
}
// Mkdir
err = fs.Mkdir("dira/dirb/dirc", 0755)
if err != nil {
t.Fatal(err)
}
_, err = fs.Stat("dira/dirb/dirc")
if err != nil {
t.Fatal(err)
}
// Create
fd, err := fs.Create("/dira/dirb/test")
if err != nil {
t.Fatal(err)
}
// Write
_, err = fd.Write([]byte("hello"))
if err != nil {
t.Fatal(err)
}
// Stat on fd
info, err := fd.Stat()
if err != nil {
t.Fatal(err)
}
if info.Name() != "test" {
t.Error("wrong name:", info.Name())
}
if info.Size() != 5 {
t.Error("wrong size:", info.Size())
}
// Stat on fs
info, err = fs.Stat("dira/dirb/test")
if err != nil {
t.Fatal(err)
}
if info.Name() != "test" {
t.Error("wrong name:", info.Name())
}
if info.Size() != 5 {
t.Error("wrong size:", info.Size())
}
// Seek
_, err = fd.Seek(1, io.SeekStart)
if err != nil {
t.Fatal(err)
}
// Read
bs0, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
if len(bs0) != 4 {
t.Error("wrong number of bytes:", len(bs0))
}
// Read again, same data hopefully
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
t.Fatal(err)
}
bs1, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(bs0, bs1[1:]) {
t.Error("wrong data")
}
// Create symlink
if err := fs.CreateSymlink("foo", "dira/dirb/symlink"); err != nil {
t.Fatal(err)
}
if str, err := fs.ReadSymlink("dira/dirb/symlink"); err != nil {
t.Fatal(err)
} else if str != "foo" {
t.Error("Wrong symlink destination", str)
}
// Chown
if err := fs.Lchown("dira", "1234", "5678"); err != nil {
t.Fatal(err)
}
if info, err := fs.Lstat("dira"); err != nil {
t.Fatal(err)
} else if info.Owner() != 1234 || info.Group() != 5678 {
t.Error("Wrong owner/group")
}
}
func testFakeFSRead(t *testing.T, fs Filesystem) {
// Test some basic aspects of the fakeFS
// Create
fd, _ := fs.Create("test")
defer fd.Close()
fd.Truncate(3 * 1 << randomBlockShift)
// Read
fd.Seek(0, io.SeekStart)
bs0, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
if len(bs0) != 3*1<<randomBlockShift {
t.Error("wrong number of bytes:", len(bs0))
}
// Read again, starting at an odd offset
fd.Seek(0, io.SeekStart)
buf0 := make([]byte, 12345)
n, _ := fd.Read(buf0)
if n != len(buf0) {
t.Fatal("short read")
}
buf1, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
if len(buf1) != 3*1<<randomBlockShift-len(buf0) {
t.Error("wrong number of bytes:", len(buf1))
}
bs1 := append(buf0, buf1...)
if !bytes.Equal(bs0, bs1) {
t.Error("data mismatch")
}
// Read large block with ReadAt
bs2 := make([]byte, 3*1<<randomBlockShift)
_, err = fd.ReadAt(bs2, 0)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(bs0, bs2) {
t.Error("data mismatch")
}
}
type testFS struct {
name string
fs Filesystem
}
type test struct {
name string
impl func(t *testing.T, fs Filesystem)
}
func TestFakeFSCaseSensitive(t *testing.T) {
var tests = []test{
{"Read", testFakeFSRead},
{"OpenFile", testFakeFSOpenFile},
{"RemoveAll", testFakeFSRemoveAll},
{"Remove", testFakeFSRemove},
{"Rename", testFakeFSRename},
{"Mkdir", testFakeFSMkdir},
{"SameFile", testFakeFSSameFile},
{"DirNames", testDirNames},
{"FileName", testFakeFSFileName},
}
var filesystems = []testFS{
{"fakeFS", newFakeFilesystem("/foo")},
}
testDir, sensitive := createTestDir(t)
if sensitive {
filesystems = append(filesystems, testFS{runtime.GOOS, newBasicFilesystem(testDir)})
}
runTests(t, tests, filesystems)
}
func TestFakeFSCaseInsensitive(t *testing.T) {
var tests = []test{
{"Read", testFakeFSRead},
{"OpenFile", testFakeFSOpenFile},
{"RemoveAll", testFakeFSRemoveAll},
{"Remove", testFakeFSRemove},
{"Mkdir", testFakeFSMkdir},
{"SameFile", testFakeFSSameFile},
{"DirNames", testDirNames},
{"FileName", testFakeFSFileName},
{"GeneralInsens", testFakeFSCaseInsensitive},
{"MkdirAllInsens", testFakeFSCaseInsensitiveMkdirAll},
{"StatInsens", testFakeFSStatInsens},
{"RenameInsens", testFakeFSRenameInsensitive},
{"MkdirInsens", testFakeFSMkdirInsens},
{"OpenFileInsens", testFakeFSOpenFileInsens},
{"RemoveAllInsens", testFakeFSRemoveAllInsens},
{"RemoveInsens", testFakeFSRemoveInsens},
{"SameFileInsens", testFakeFSSameFileInsens},
{"CreateInsens", testFakeFSCreateInsens},
{"FileNameInsens", testFakeFSFileNameInsens},
}
var filesystems = []testFS{
{"fakeFS", newFakeFilesystem("/foobar?insens=true")},
}
testDir, sensitive := createTestDir(t)
if !sensitive {
filesystems = append(filesystems, testFS{runtime.GOOS, newBasicFilesystem(testDir)})
}
runTests(t, tests, filesystems)
}
func createTestDir(t *testing.T) (string, bool) {
t.Helper()
testDir := t.TempDir()
if fd, err := os.Create(filepath.Join(testDir, ".stfolder")); err != nil {
t.Fatalf("could not create .stfolder: %s", err)
} else {
fd.Close()
}
var sensitive bool
if f, err := os.Open(filepath.Join(testDir, ".STfolder")); err != nil {
sensitive = true
} else {
defer f.Close()
}
return testDir, sensitive
}
func runTests(t *testing.T, tests []test, filesystems []testFS) {
for _, filesystem := range filesystems {
for _, test := range tests {
name := fmt.Sprintf("%s_%s", test.name, filesystem.name)
t.Run(name, func(t *testing.T) {
test.impl(t, filesystem.fs)
if err := cleanup(filesystem.fs); err != nil {
t.Errorf("cleanup failed: %s", err)
}
})
}
}
}
func testFakeFSCaseInsensitive(t *testing.T, fs Filesystem) {
bs1 := []byte("test")
err := fs.Mkdir("/fUbar", 0755)
if err != nil {
t.Fatal(err)
}
fd1, err := fs.Create("fuBAR/SISYPHOS")
if err != nil {
t.Fatalf("could not create file: %s", err)
}
defer fd1.Close()
_, err = fd1.Write(bs1)
if err != nil {
t.Fatal(err)
}
// Try reading from the same file with different filenames
fd2, err := fs.Open("Fubar/Sisyphos")
if err != nil {
t.Fatalf("could not open file by its case-differing filename: %s", err)
}
defer fd2.Close()
if _, err := fd2.Seek(0, io.SeekStart); err != nil {
t.Fatal(err)
}
bs2, err := io.ReadAll(fd2)
if err != nil {
t.Fatal(err)
}
if len(bs1) != len(bs2) {
t.Errorf("wrong number of bytes, expected %d, got %d", len(bs1), len(bs2))
}
}
func testFakeFSCaseInsensitiveMkdirAll(t *testing.T, fs Filesystem) {
err := fs.MkdirAll("/fOO/Bar/bAz", 0755)
if err != nil {
t.Fatal(err)
}
fd, err := fs.OpenFile("/foo/BaR/BaZ/tESt", os.O_CREATE, 0644)
if err != nil {
t.Fatal(err)
}
if err = fd.Close(); err != nil {
t.Fatal(err)
}
if err = fs.Rename("/FOO/BAR/baz/tesT", "/foo/baR/BAZ/Qux"); err != nil {
t.Fatal(err)
}
}
func testDirNames(t *testing.T, fs Filesystem) {
filenames := []string{"fOO", "Bar", "baz"}
for _, filename := range filenames {
if fd, err := fs.Create("/" + filename); err != nil {
t.Errorf("Could not create %s: %s", filename, err)
} else {
fd.Close()
}
}
assertDir(t, fs, "/", filenames)
}
func assertDir(t *testing.T, fs Filesystem, directory string, filenames []string) {
t.Helper()
got, err := fs.DirNames(directory)
if err != nil {
t.Fatal(err)
}
if path.Clean(directory) == "/" {
filenames = append(filenames, ".stfolder")
}
sort.Strings(filenames)
sort.Strings(got)
if len(filenames) != len(got) {
t.Errorf("want %s, got %s", filenames, got)
return
}
for i := range filenames {
if filenames[i] != got[i] {
t.Errorf("want %s, got %s", filenames, got)
return
}
}
}
func testFakeFSStatInsens(t *testing.T, fs Filesystem) {
// this is to test that neither fs.Stat nor fd.Stat change the filename
// both in directory and in previous Stat results
fd1, err := fs.Create("aAa")
if err != nil {
t.Fatal(err)
}
defer fd1.Close()
info1, err := fs.Stat("AAA")
if err != nil {
t.Fatal(err)
}
if _, err = fs.Stat("AaA"); err != nil {
t.Fatal(err)
}
info2, err := fd1.Stat()
if err != nil {
t.Fatal(err)
}
fd2, err := fs.Open("aaa")
if err != nil {
t.Fatal(err)
}
defer fd2.Close()
if _, err = fd2.Stat(); err != nil {
t.Fatal(err)
}
if info1.Name() != "AAA" {
t.Errorf("want AAA, got %s", info1.Name())
}
if info2.Name() != "aAa" {
t.Errorf("want aAa, got %s", info2.Name())
}
assertDir(t, fs, "/", []string{"aAa"})
}
func testFakeFSFileName(t *testing.T, fs Filesystem) {
var testCases = []struct {
create string
open string
}{
{"bar", "bar"},
}
for _, testCase := range testCases {
if fd, err := fs.Create(testCase.create); err != nil {
t.Fatal(err)
} else {
fd.Close()
}
fd, err := fs.Open(testCase.open)
if err != nil {
t.Fatal(err)
}
defer fd.Close()
if got := fd.Name(); got != testCase.open {
t.Errorf("want %s, got %s", testCase.open, got)
}
}
}
func testFakeFSFileNameInsens(t *testing.T, fs Filesystem) {
var testCases = []struct {
create string
open string
}{
{"BaZ", "bAz"},
}
for _, testCase := range testCases {
fd, err := fs.Create(testCase.create)
if err != nil {
t.Fatal(err)
}
fd.Close()
fd, err = fs.Open(testCase.open)
if err != nil {
t.Fatal(err)
}
defer fd.Close()
if got := fd.Name(); got != testCase.open {
t.Errorf("want %s, got %s", testCase.open, got)
}
}
}
func testFakeFSRename(t *testing.T, fs Filesystem) {
if err := fs.MkdirAll("/foo/bar/baz", 0755); err != nil {
t.Fatal(err)
}
fd, err := fs.Create("/foo/bar/baz/qux")
if err != nil {
t.Fatal(err)
}
fd.Close()
if err := fs.Rename("/foo/bar/baz/qux", "/foo/notthere/qux"); err == nil {
t.Errorf("rename to non-existent dir gave no error")
}
if err := fs.MkdirAll("/baz/bar/foo", 0755); err != nil {
t.Fatal(err)
}
if err := fs.Rename("/foo/bar/baz/qux", "/baz/bar/foo/qux"); err != nil {
t.Fatal(err)
}
var dirs = []struct {
dir string
files []string
}{
{dir: "/", files: []string{"foo", "baz"}},
{dir: "/foo", files: []string{"bar"}},
{dir: "/foo/bar/baz", files: []string{}},
{dir: "/baz/bar/foo", files: []string{"qux"}},
}
for _, dir := range dirs {
assertDir(t, fs, dir.dir, dir.files)
}
if err := fs.Rename("/baz/bar/foo", "/baz/bar/FOO"); err != nil {
t.Fatal(err)
}
assertDir(t, fs, "/baz/bar", []string{"FOO"})
assertDir(t, fs, "/baz/bar/FOO", []string{"qux"})
}
func testFakeFSRenameInsensitive(t *testing.T, fs Filesystem) {
if err := fs.MkdirAll("/baz/bar/foo", 0755); err != nil {
t.Fatal(err)
}
if err := fs.MkdirAll("/foO/baR/baZ", 0755); err != nil {
t.Fatal(err)
}
fd, err := fs.Create("/BAZ/BAR/FOO/QUX")
if err != nil {
t.Fatal(err)
}
fd.Close()
if err := fs.Rename("/Baz/bAr/foO/QuX", "/Foo/Bar/Baz/qUUx"); err != nil {
t.Fatal(err)
}
var dirs = []struct {
dir string
files []string
}{
{dir: "/", files: []string{"foO", "baz"}},
{dir: "/foo", files: []string{"baR"}},
{dir: "/foo/bar/baz", files: []string{"qUUx"}},
{dir: "/baz/bar/foo", files: []string{}},
}
for _, dir := range dirs {
assertDir(t, fs, dir.dir, dir.files)
}
// not checking on darwin due to https://github.com/golang/go/issues/35222
if runtime.GOOS != "darwin" {
if err := fs.Rename("/foo/bar/BAZ", "/FOO/BAR/bAz"); err != nil {
t.Errorf("Could not perform in-place case-only directory rename: %s", err)
}
assertDir(t, fs, "/foo/bar", []string{"bAz"})
assertDir(t, fs, "/fOO/bAr/baz", []string{"qUUx"})
}
if err := fs.Rename("foo/bar/baz/quux", "foo/bar/BaZ/Quux"); err != nil {
t.Errorf("File rename failed: %s", err)
}
assertDir(t, fs, "/FOO/BAR/BAZ", []string{"Quux"})
}
func testFakeFSMkdir(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/foo", 0755); err != nil {
t.Fatal(err)
}
if _, err := fs.Stat("/foo"); err != nil {
t.Fatal(err)
}
if err := fs.Mkdir("/foo", 0755); err == nil {
t.Errorf("got no error while creating existing directory")
}
}
func testFakeFSMkdirInsens(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/foo", 0755); err != nil {
t.Fatal(err)
}
if _, err := fs.Stat("/Foo"); err != nil {
t.Fatal(err)
}
if err := fs.Mkdir("/FOO", 0755); err == nil {
t.Errorf("got no error while creating existing directory")
}
}
func testFakeFSOpenFile(t *testing.T, fs Filesystem) {
fd, err := fs.OpenFile("foobar", os.O_RDONLY, 0664)
if err == nil {
fd.Close()
t.Fatalf("got no error opening a non-existing file")
}
fd, err = fs.OpenFile("foobar", os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
fd, err = fs.OpenFile("foobar", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0664)
if err == nil {
fd.Close()
t.Fatalf("created an existing file while told not to")
}
fd, err = fs.OpenFile("foobar", os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
fd, err = fs.OpenFile("foobar", os.O_RDWR, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
}
func testFakeFSOpenFileInsens(t *testing.T, fs Filesystem) {
fd, err := fs.OpenFile("FooBar", os.O_RDONLY, 0664)
if err == nil {
fd.Close()
t.Fatalf("got no error opening a non-existing file")
}
fd, err = fs.OpenFile("fOObar", os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
fd, err = fs.OpenFile("fOoBaR", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0664)
if err == nil {
fd.Close()
t.Fatalf("created an existing file while told not to")
}
fd, err = fs.OpenFile("FoObAr", os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
fd, err = fs.OpenFile("FOOBAR", os.O_RDWR, 0664)
if err != nil {
t.Fatal(err)
}
fd.Close()
}
func testFakeFSRemoveAll(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/foo", 0755); err != nil {
t.Fatal(err)
}
filenames := []string{"bar", "baz", "qux"}
for _, filename := range filenames {
fd, err := fs.Create("/foo/" + filename)
if err != nil {
t.Fatalf("Could not create %s: %s", filename, err)
} else {
fd.Close()
}
}
if err := fs.RemoveAll("/foo"); err != nil {
t.Fatal(err)
}
if _, err := fs.Stat("/foo"); err == nil {
t.Errorf("this should be an error, as file doesn not exist anymore")
}
if err := fs.RemoveAll("/foo/bar"); err != nil {
t.Errorf("real systems don't return error here")
}
}
func testFakeFSRemoveAllInsens(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/Foo", 0755); err != nil {
t.Fatal(err)
}
filenames := []string{"bar", "baz", "qux"}
for _, filename := range filenames {
fd, err := fs.Create("/FOO/" + filename)
if err != nil {
t.Fatalf("Could not create %s: %s", filename, err)
}
fd.Close()
}
if err := fs.RemoveAll("/fOo"); err != nil {
t.Errorf("Could not remove dir: %s", err)
}
if _, err := fs.Stat("/foo"); err == nil {
t.Errorf("this should be an error, as file doesn not exist anymore")
}
if err := fs.RemoveAll("/foO/bAr"); err != nil {
t.Errorf("real systems don't return error here")
}
}
func testFakeFSRemove(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/Foo", 0755); err != nil {
t.Fatal(err)
}
fd, err := fs.Create("/Foo/Bar")
if err != nil {
t.Fatal(err)
} else {
fd.Close()
}
if err := fs.Remove("/Foo"); err == nil {
t.Errorf("not empty, should give error")
}
if err := fs.Remove("/Foo/Bar"); err != nil {
t.Fatal(err)
}
if err := fs.Remove("/Foo"); err != nil {
t.Fatal(err)
}
}
func testFakeFSRemoveInsens(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/Foo", 0755); err != nil {
t.Fatal(err)
}
fd, err := fs.Create("/Foo/Bar")
if err != nil {
t.Fatal(err)
}
fd.Close()
if err := fs.Remove("/FOO"); err == nil {
t.Errorf("not empty, should give error")
}
if err := fs.Remove("/Foo/BaR"); err != nil {
t.Fatal(err)
}
if err := fs.Remove("/FoO"); err != nil {
t.Fatal(err)
}
}
func testFakeFSSameFile(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/Foo", 0755); err != nil {
t.Fatal(err)
}
filenames := []string{"Bar", "Baz", "/Foo/Bar"}
for _, filename := range filenames {
if fd, err := fs.Create(filename); err != nil {
t.Fatalf("Could not create %s: %s", filename, err)
} else {
fd.Close()
if runtime.GOOS == "windows" {
time.Sleep(1 * time.Millisecond)
}
}
}
testCases := []struct {
f1 string
f2 string
want bool
}{
{"Bar", "Baz", false},
{"Bar", "/Foo/Bar", false},
{"Bar", "Bar", true},
}
for _, test := range testCases {
assertSameFile(t, fs, test.f1, test.f2, test.want)
}
}
func testFakeFSSameFileInsens(t *testing.T, fs Filesystem) {
if err := fs.Mkdir("/Foo", 0755); err != nil {
t.Fatal(err)
}
filenames := []string{"Bar", "Baz"}
for _, filename := range filenames {
fd, err := fs.Create(filename)
if err != nil {
t.Errorf("Could not create %s: %s", filename, err)
}
fd.Close()
}
testCases := []struct {
f1 string
f2 string
want bool
}{
{"bAr", "baZ", false},
{"baz", "BAZ", true},
}
for _, test := range testCases {
assertSameFile(t, fs, test.f1, test.f2, test.want)
}
}
func assertSameFile(t *testing.T, fs Filesystem, f1, f2 string, want bool) {
t.Helper()
fi1, err := fs.Stat(f1)
if err != nil {
t.Fatal(err)
}
fi2, err := fs.Stat(f2)
if err != nil {
t.Fatal(err)
}
got := fs.SameFile(fi1, fi2)
if got != want {
t.Errorf("for \"%s\" and \"%s\" want SameFile %v, got %v", f1, f2, want, got)
}
}
func testFakeFSCreateInsens(t *testing.T, fs Filesystem) {
fd1, err := fs.Create("FOO")
if err != nil {
t.Fatal(err)
}
defer fd1.Close()
fd2, err := fs.Create("fOo")
if err != nil {
t.Fatal(err)
}
defer fd2.Close()
if fd1.Name() != "FOO" {
t.Errorf("name of the file created as \"FOO\" is %s", fd1.Name())
}
if fd2.Name() != "fOo" {
t.Errorf("name of created file \"fOo\" is %s", fd2.Name())
}
// one would expect DirNames to show the last wrapperType, but in fact it shows
// the original one
assertDir(t, fs, "/", []string{"FOO"})
}
func TestReadWriteContent(t *testing.T) {
fs := newFakeFilesystem("foo?content=true")
fd, err := fs.Create("file")
if err != nil {
t.Fatal(err)
}
if _, err := fd.Write([]byte("foo")); err != nil {
t.Fatal(err)
}
if _, err := fd.WriteAt([]byte("bar"), 5); err != nil {
t.Fatal(err)
}
expected := []byte("foo\x00\x00bar")
buf := make([]byte, len(expected)-1)
n, err := fd.ReadAt(buf, 1) // note offset one byte
if err != nil {
t.Fatal(err)
}
if n != len(expected)-1 {
t.Fatal("wrong number of bytes read")
}
if !bytes.Equal(buf[:n], expected[1:]) {
fmt.Printf("%d %q\n", n, buf[:n])
t.Error("wrong data in file")
}
}
func cleanup(fs Filesystem) error {
filenames, _ := fs.DirNames("/")
for _, filename := range filenames {
if filename != ".stfolder" {
if err := fs.RemoveAll(filename); err != nil {
return err
}
}
}
return nil
}