package virtfs import ( "context" "os" "path/filepath" "testing" ) func TestTreeMkdirAll(t *testing.T) { tmpDir := t.TempDir() tree := &Tree{Tree: []string{tmpDir}, perm: 0755} err := tree.MkdirAll("a/b/c") if err != nil { t.Fatalf("MkdirAll: %v", err) } info, err := os.Stat(filepath.Join(tmpDir, "a", "b", "c")) if err != nil { t.Fatalf("Stat after MkdirAll: %v", err) } if !info.IsDir() { t.Error("expected directory") } } func TestTreeMkdirAllNoBackingDir(t *testing.T) { tree := &Tree{perm: 0755} err := tree.MkdirAll("somedir") if err == nil { t.Fatal("expected error for nil backing dir") } } func TestTreeWithCustomFunctions(t *testing.T) { listed := false tree := &Tree{perm: 0755} tree.listFn = func(_ []string) ([]os.DirEntry, error) { listed = true return []os.DirEntry{fileEntry("a", 0644)}, nil } entries, err := tree.List() if err != nil { t.Fatalf("List: %v", err) } if !listed { t.Error("listFn not called") } if len(entries) != 1 || entries[0].Name() != "a" { t.Errorf("entries = %v", entries) } } func TestTreeOpen(t *testing.T) { tree := &Tree{perm: 0644} tree.openFn = func(_ []string, name string) (File, error) { return &fileConfig{ StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name}, nil }, ReadFn: func() ([]byte, error) { return []byte("content"), nil }, WriteFn: func([]byte) error { return nil }, BlockingReadFn: func(context.Context, string) ([]byte, string, error) { return nil, "", nil }, }, nil } f, err := tree.Open("test") if err != nil { t.Fatalf("Open: %v", err) } data, err := f.Read() if err != nil { t.Fatalf("Read: %v", err) } if string(data) != "content" { t.Errorf("Read = %q", data) } } func TestTreeOpenNotFound(t *testing.T) { tree := &Tree{perm: 0644} _, err := tree.Open("nonexistent") if err == nil { t.Fatal("expected error") } } func TestTreeStat(t *testing.T) { tree := &Tree{perm: 0755} tree.statFn = func(_ []string, name string) (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name, Mode_: 0644, Size_: 42}, nil } fi, err := tree.Stat("hello") if err != nil { t.Fatalf("Stat: %v", err) } if fi.Size() != 42 { t.Errorf("Size = %d", fi.Size()) } } func TestTreeStatOpenerFallback(t *testing.T) { tree := &Tree{perm: 0755} closed := false tree.openFn = func(_ []string, name string) (File, error) { return &fileConfig{ StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name, Size_: 99}, nil }, CloseFn: func() error { closed = true return nil }, ReadFn: func() ([]byte, error) { return nil, nil }, WriteFn: func([]byte) error { return nil }, BlockingReadFn: func(context.Context, string) ([]byte, string, error) { return nil, "", nil }, }, nil } fi, err := tree.Stat("x") if err != nil { t.Fatalf("Stat: %v", err) } if fi.Size() != 99 { t.Errorf("Size = %d; want 99", fi.Size()) } if !closed { t.Error("Stat did not close fallback-opened file") } } func TestTreeChildDelegation(t *testing.T) { child := &Tree{perm: 0755} child.openFn = func(_ []string, name string) (File, error) { return &fileConfig{ StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name}, nil }, ReadFn: func() ([]byte, error) { return []byte("child:" + name), nil }, WriteFn: func([]byte) error { return nil }, BlockingReadFn: func(context.Context, string) ([]byte, string, error) { return nil, "", nil }, }, nil } child.listFn = func(_ []string) ([]os.DirEntry, error) { return []os.DirEntry{fileEntry("inner", 0644)}, nil } parent := &Tree{perm: 0755, children: make(map[string]*Tree)} parent.children["sub"] = child // Open delegated to child f, err := parent.Open("sub/inner") if err != nil { t.Fatalf("Open: %v", err) } data, _ := f.Read() if string(data) != "child:inner" { t.Errorf("Read = %q", data) } // List parent includes mounted child entries, err := parent.List() if err != nil { t.Fatalf("List: %v", err) } found := false for _, e := range entries { if e.Name() == "sub" && e.IsDir() { found = true } } if !found { t.Error("mounted child not in List") } // Stat child directory fi, err := parent.Stat("sub") if err != nil { t.Fatalf("Stat child: %v", err) } if !fi.IsDir() { t.Error("child should be dir") } } func TestTreeChildMkdirAll(t *testing.T) { tmpDir := t.TempDir() child := &Tree{Tree: []string{tmpDir}, perm: 0755} parent := &Tree{perm: 0755, children: make(map[string]*Tree)} parent.children["sub"] = child err := parent.MkdirAll("sub/new/deep/path") if err != nil { t.Fatalf("MkdirAll: %v", err) } _, err = os.Stat(filepath.Join(tmpDir, "new", "deep", "path")) if err != nil { t.Fatalf("directory not created: %v", err) } } func TestTreeCreate(t *testing.T) { created := "" tree := &Tree{perm: 0644} tree.createFn = func(_ []string, name string, _ os.FileMode) error { created = name return nil } if err := tree.Create("newfile"); err != nil { t.Fatalf("Create: %v", err) } if created != "newfile" { t.Errorf("created = %q", created) } } func TestTreeDelete(t *testing.T) { deleted := "" tree := &Tree{perm: 0644} tree.deleteFn = func(_ []string, name string) error { deleted = name return nil } if err := tree.Delete("old"); err != nil { t.Fatalf("Delete: %v", err) } if deleted != "old" { t.Errorf("deleted = %q", deleted) } } func TestTreeRename(t *testing.T) { var oldN, newN string tree := &Tree{perm: 0644} tree.renameFn = func(_ []string, old, new string) error { oldN, newN = old, new return nil } if err := tree.Rename("a", "b"); err != nil { t.Fatalf("Rename: %v", err) } if oldN != "a" || newN != "b" { t.Errorf("rename(%q, %q)", oldN, newN) } } func TestTreeReaddir(t *testing.T) { tree := &Tree{perm: 0755} tree.readdirFn = func(_ []string, name string) ([]os.DirEntry, error) { return []os.DirEntry{fileEntry("child", 0644)}, nil } entries, err := tree.Readdir("subdir") if err != nil { t.Fatalf("Readdir: %v", err) } if len(entries) != 1 || entries[0].Name() != "child" { t.Errorf("entries = %v", entries) } } func TestReaddirNotFound(t *testing.T) { tree := &Tree{perm: 0755} _, err := tree.Readdir("nowhere") if err == nil { t.Fatal("expected error") } } func TestGenerateHelpModes(t *testing.T) { spec := DirNode("/", FileNode("ro", 0444, Doc("read only file")), FileNode("rw", 0644, Doc("read write file"), Write(func(_ []byte) error { return nil })), FileNode("ctl", 0200, Doc("control file"), Rdwr(func(_ context.Context, _ []byte) ([]byte, error) { return nil, nil })), ) help := GenerateHelp(spec) if help == "" { t.Fatal("empty help") } }