Files

299 lines
6.0 KiB
Go

// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unionfs
import (
"bytes"
"os"
"path/filepath"
"reflect"
"syscall"
"testing"
"github.com/hanzoai/go-fuse/v2/fs"
"github.com/hanzoai/go-fuse/v2/fuse"
"github.com/hanzoai/go-fuse/v2/internal/testutil"
"github.com/hanzoai/go-fuse/v2/posixtest"
)
type testCase struct {
dir string
mnt string
server *fuse.Server
rw string
ro string
root *unionFSRoot
}
func (tc *testCase) Clean() {
if tc.server != nil {
tc.server.Unmount()
tc.server = nil
}
}
func newTestCase(t *testing.T, populate bool) *testCase {
t.Helper()
dir := t.TempDir()
dirs := []string{"ro", "rw", "mnt"}
if populate {
dirs = append(dirs, "ro/dir")
}
for _, d := range dirs {
if err := os.Mkdir(filepath.Join(dir, d), 0755); err != nil {
t.Fatal("Mkdir", err)
}
}
opts := fs.Options{}
opts.Debug = testutil.VerboseTest()
tc := &testCase{
dir: dir,
mnt: dir + "/mnt",
rw: dir + "/rw",
ro: dir + "/ro",
}
tc.root = &unionFSRoot{
roots: []string{tc.rw, tc.ro},
}
server, err := fs.Mount(tc.mnt, tc.root, &opts)
if err != nil {
t.Fatal("Mount", err)
}
tc.server = server
if populate {
if err := os.WriteFile(tc.ro+"/dir/ro-file", []byte("bla"), 0644); err != nil {
t.Fatal(err)
}
}
return tc
}
func TestBasic(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if fi, err := os.Lstat(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatal(err)
} else if fi.Size() != 3 {
t.Errorf("got size %d, want 3", fi.Size())
}
}
func TestDelete(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatal(err)
}
if _, err := os.Lstat(tc.ro + "/dir/ro-file"); err != nil {
t.Fatal(err)
}
c, err := os.ReadFile(filepath.Join(tc.rw, delDir, filePathHash("dir/ro-file")))
if err != nil {
t.Fatal(err)
}
if got, want := string(c), "dir/ro-file"; got != want {
t.Errorf("got %q want %q", got, want)
}
}
func TestDeleteMarker(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
tc.root.delPath(path)
var st syscall.Stat_t
if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != syscall.ENOENT {
t.Fatalf("Lstat before: %v", err)
}
if errno := tc.root.rmMarker(path); errno != 0 {
t.Fatalf("rmMarker: %v", errno)
}
if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
}
func TestCreateDeletions(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if _, err := syscall.Creat(filepath.Join(tc.mnt, delDir), 0644); err != syscall.EPERM {
t.Fatalf("got err %v, want EPERM", err)
}
}
func TestCreate(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
if err := syscall.Unlink(filepath.Join(tc.mnt, path)); err != nil {
t.Fatalf("Unlink: %v", err)
}
want := []byte{42}
if err := os.WriteFile(filepath.Join(tc.mnt, path), want, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := os.ReadFile(filepath.Join(tc.mnt, path)); err != nil {
t.Fatalf("WriteFile: %v", err)
} else if !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
func TestPromote(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
mPath := filepath.Join(tc.mnt, path)
want := []byte{42}
if err := os.WriteFile(mPath, want, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := os.ReadFile(mPath); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
func TestDeleteRevert(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
mPath := filepath.Join(tc.mnt, path)
if err := os.WriteFile(mPath, []byte{42}, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
var st syscall.Stat_t
if err := syscall.Lstat(mPath, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
} else if st.Size != 1 {
t.Fatalf("Stat: want size 1, got %#v", st)
}
if err := syscall.Unlink(mPath); err != nil {
t.Fatalf("Unlink: %v", err)
}
if err := syscall.Lstat(mPath, &st); err != syscall.ENOENT {
t.Fatalf("Lstat after: got %v, want ENOENT", err)
}
}
func TestReaddirRoot(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatalf("Remove: %v", err)
}
f, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("Readdirnames: %v", err)
}
got := map[string]bool{}
want := map[string]bool{"dir": true}
for _, nm := range names {
got[nm] = true
}
if !reflect.DeepEqual(want, got) {
t.Errorf("got %v want %v", got, want)
}
}
func TestReaddir(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if err := os.WriteFile(tc.ro+"/dir/file2", nil, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := os.Mkdir(tc.rw+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := os.WriteFile(tc.rw+"/dir/file3", nil, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatalf("Remove: %v", err)
}
res, err := os.ReadDir(tc.mnt + "/dir")
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
got := map[string]bool{}
want := map[string]bool{
"file2": true,
"file3": true,
}
for _, fi := range res {
got[fi.Name()] = true
}
if !reflect.DeepEqual(want, got) {
t.Errorf("got %v want %v", got, want)
}
}
func TestPosix(t *testing.T) {
cases := []string{
"SymlinkReadlink",
"FileBasic",
"TruncateFile",
"TruncateNoFile",
"FdLeak",
// "MkdirRmdir",
// "NlinkZero",
"ParallelFileOpen",
// "Link",
"ReadDir",
}
for _, nm := range cases {
f := posixtest.All[nm]
t.Run(nm, func(t *testing.T) {
tc := newTestCase(t, false)
defer tc.Clean()
f(t, tc.mnt)
})
}
}
func init() {
syscall.Umask(0)
}