1package memfs23import (4 "testing"56 ninep "gno.land/p/moul/x/plan9/ninep/v0"7)89func TestRootStat(t *testing.T) {10 fs := New("glenda", 100)11 s := fs.Root().Stat()12 if s.Name != "/" {13 t.Errorf("root name: got %q", s.Name)14 }15 if !s.IsDir() {16 t.Error("root should be a directory")17 }18 if s.Length != 0 {19 t.Errorf("a directory reports length 0 in 9P, got %d", s.Length)20 }21 if s.Uid != "glenda" || s.Gid != "glenda" {22 t.Errorf("owner: got %q/%q", s.Uid, s.Gid)23 }24 if s.Mtime != 100 {25 t.Errorf("mtime: got %d, want 100", s.Mtime)26 }27}2829func TestCreateWalkRead(t *testing.T) {30 fs := New("glenda", 1)31 root := fs.RootMutable()3233 f, err := root.Create("greeting", ninep.FilePerm, 2)34 if err != nil {35 t.Fatalf("create: %v", err)36 }37 m := f.(ninep.Mutable)38 n, err := m.Write(0, "hello world", 3)39 if err != nil {40 t.Fatalf("write: %v", err)41 }42 if n != 11 {43 t.Errorf("wrote %d bytes, want 11", n)44 }4546 got, err := root.Walk("greeting")47 if err != nil {48 t.Fatalf("walk: %v", err)49 }50 data, err := ninep.ReadAll(got)51 if err != nil {52 t.Fatalf("read: %v", err)53 }54 if data != "hello world" {55 t.Errorf("read back %q", data)56 }57 if l := got.Stat().Length; l != 11 {58 t.Errorf("length %d, want 11", l)59 }60}6162func TestWalkErrors(t *testing.T) {63 fs := New("glenda", 1)64 root := fs.RootMutable()65 root.Create("file", ninep.FilePerm, 1)6667 if _, err := root.Walk("absent"); err != ninep.ErrNotExist {68 t.Errorf("missing name: got %v, want ErrNotExist", err)69 }70 if _, err := root.Walk("a/b"); err != ninep.ErrBadName {71 t.Errorf("slash in name: got %v, want ErrBadName", err)72 }73 f, _ := root.Walk("file")74 if _, err := f.Walk("x"); err != ninep.ErrNotDir {75 t.Errorf("walk through a file: got %v, want ErrNotDir", err)76 }77 if _, err := f.ReadDir(); err != ninep.ErrNotDir {78 t.Errorf("readdir of a file: got %v, want ErrNotDir", err)79 }80 if _, err := root.Read(0, -1); err != ninep.ErrIsDir {81 t.Errorf("read of a directory: got %v, want ErrIsDir", err)82 }83}8485func TestReadDirIsOrdered(t *testing.T) {86 fs := New("glenda", 1)87 root := fs.RootMutable()88 for _, name := range []string{"zulu", "alpha", "mike", "bravo"} {89 if _, err := root.Create(name, ninep.FilePerm, 1); err != nil {90 t.Fatalf("create %s: %v", name, err)91 }92 }93 ents, err := root.ReadDir()94 if err != nil {95 t.Fatalf("readdir: %v", err)96 }97 want := []string{"alpha", "bravo", "mike", "zulu"}98 if len(ents) != len(want) {99 t.Fatalf("got %d entries, want %d", len(ents), len(want))100 }101 for i, w := range want {102 if ents[i].Name != w {103 t.Errorf("entry %d: got %q, want %q", i, ents[i].Name, w)104 }105 }106}107108func TestCreateDuplicate(t *testing.T) {109 fs := New("glenda", 1)110 root := fs.RootMutable()111 root.Create("x", ninep.FilePerm, 1)112 if _, err := root.Create("x", ninep.FilePerm, 1); err != ninep.ErrExist {113 t.Errorf("got %v, want ErrExist", err)114 }115 if _, err := root.Create("", ninep.FilePerm, 1); err != ninep.ErrBadName {116 t.Errorf("empty name: got %v, want ErrBadName", err)117 }118 if _, err := root.Create("..", ninep.FilePerm, 1); err != ninep.ErrBadName {119 t.Errorf("dotdot: got %v, want ErrBadName", err)120 }121}122123func TestRemove(t *testing.T) {124 fs := New("glenda", 1)125 root := fs.RootMutable()126 root.Create("file", ninep.FilePerm, 1)127 d, _ := root.Create("dir", ninep.DirPerm, 1)128 d.(ninep.Mutable).Create("inner", ninep.FilePerm, 1)129130 if err := root.Remove("absent"); err != ninep.ErrNotExist {131 t.Errorf("remove missing: got %v", err)132 }133 if err := root.Remove("dir"); err != ninep.ErrNotEmpty {134 t.Errorf("remove non-empty dir: got %v, want ErrNotEmpty", err)135 }136 if err := d.(ninep.Mutable).Remove("inner"); err != nil {137 t.Fatalf("remove inner: %v", err)138 }139 if err := root.Remove("dir"); err != nil {140 t.Fatalf("remove emptied dir: %v", err)141 }142 if err := root.Remove("file"); err != nil {143 t.Fatalf("remove file: %v", err)144 }145 ents, _ := root.ReadDir()146 if len(ents) != 0 {147 t.Errorf("root should be empty, has %d entries", len(ents))148 }149}150151func TestWriteOffsets(t *testing.T) {152 tests := []struct {153 name string154 start string155 off int64156 data string157 want string158 }{159 {"append at end", "abc", 3, "def", "abcdef"},160 {"overwrite middle", "abcdef", 2, "XY", "abXYef"},161 {"overwrite past end", "abc", 2, "XYZ", "abXYZ"},162 {"from zero", "abcdef", 0, "ZZ", "ZZcdef"},163 {"gap is nul filled", "ab", 4, "Z", "ab\x00\x00Z"},164 {"negative offset clamps", "abc", -2, "Z", "Zbc"},165 }166 for _, tt := range tests {167 fs := New("glenda", 1)168 root := fs.RootMutable()169 f, _ := root.Create("f", ninep.FilePerm, 1)170 m := f.(ninep.Mutable)171 m.Write(0, tt.start, 1)172 m.Write(tt.off, tt.data, 2)173 got, _ := ninep.ReadAll(f)174 if got != tt.want {175 t.Errorf("%s: got %q, want %q", tt.name, got, tt.want)176 }177 }178}179180func TestAppendOnlyIgnoresOffset(t *testing.T) {181 fs := New("glenda", 1)182 root := fs.RootMutable()183 f, _ := root.Create("log", ninep.DMAPPEND|0644, 1)184 m := f.(ninep.Mutable)185 m.Write(0, "one\n", 1)186 m.Write(0, "two\n", 2) // offset 0, but append-only pins it to the end187 got, _ := ninep.ReadAll(f)188 if got != "one\ntwo\n" {189 t.Errorf("got %q", got)190 }191}192193func TestTruncate(t *testing.T) {194 fs := New("glenda", 1)195 root := fs.RootMutable()196 f, _ := root.Create("f", ninep.FilePerm, 1)197 m := f.(ninep.Mutable)198 m.Write(0, "abcdef", 1)199200 m.Truncate(3, 2)201 if got, _ := ninep.ReadAll(f); got != "abc" {202 t.Errorf("shrink: got %q", got)203 }204 m.Truncate(5, 3)205 if got, _ := ninep.ReadAll(f); got != "abc\x00\x00" {206 t.Errorf("grow: got %q", got)207 }208 m.Truncate(0, 4)209 if got, _ := ninep.ReadAll(f); got != "" {210 t.Errorf("zero: got %q", got)211 }212}213214func TestQidVersionAndPath(t *testing.T) {215 fs := New("glenda", 1)216 root := fs.RootMutable()217 a, _ := root.Create("a", ninep.FilePerm, 1)218 b, _ := root.Create("b", ninep.FilePerm, 1)219220 if a.Stat().Qid.Path == b.Stat().Qid.Path {221 t.Error("two files share a qid path")222 }223 v0 := a.Stat().Qid.Version224 a.(ninep.Mutable).Write(0, "x", 2)225 if a.Stat().Qid.Version != v0+1 {226 t.Errorf("version did not advance on write: %d -> %d", v0, a.Stat().Qid.Version)227 }228 if a.Stat().Mtime != 2 {229 t.Errorf("mtime: got %d, want 2", a.Stat().Mtime)230 }231}232233func TestMkdirAllAndWriteFile(t *testing.T) {234 fs := New("glenda", 1)235 if _, err := fs.MkdirAll("/usr/glenda/lib", 1); err != nil {236 t.Fatalf("mkdirall: %v", err)237 }238 // idempotent239 if _, err := fs.MkdirAll("/usr/glenda", 1); err != nil {240 t.Fatalf("mkdirall again: %v", err)241 }242 if err := fs.WriteFile("/usr/glenda/lib/profile", "font=pelm\n", 2); err != nil {243 t.Fatalf("writefile: %v", err)244 }245 f, err := ninep.Walk(fs.Root(), ninep.Elems("/usr/glenda/lib/profile"))246 if err != nil {247 t.Fatalf("walk: %v", err)248 }249 if got, _ := ninep.ReadAll(f); got != "font=pelm\n" {250 t.Errorf("got %q", got)251 }252 // rewriting replaces rather than appends253 if err := fs.WriteFile("/usr/glenda/lib/profile", "font=lucida\n", 3); err != nil {254 t.Fatalf("rewrite: %v", err)255 }256 f, _ = ninep.Walk(fs.Root(), ninep.Elems("/usr/glenda/lib/profile"))257 if got, _ := ninep.ReadAll(f); got != "font=lucida\n" {258 t.Errorf("rewrite got %q", got)259 }260}261262func TestMkdirAllThroughAFileFails(t *testing.T) {263 fs := New("glenda", 1)264 fs.WriteFile("/etc", "not a directory", 1)265 if _, err := fs.MkdirAll("/etc/passwd", 2); err != ninep.ErrNotDir {266 t.Errorf("got %v, want ErrNotDir", err)267 }268}269270func TestWalkDepthLimit(t *testing.T) {271 fs := New("glenda", 1)272 elems := []string{}273 for i := 0; i <= ninep.MaxDepth; i++ {274 elems = append(elems, "x")275 }276 if _, err := ninep.Walk(fs.Root(), elems); err != ninep.ErrTooDeep {277 t.Errorf("got %v, want ErrTooDeep", err)278 }279}280Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.