1package synfs23import (4 "strconv"5 "strings"6 "testing"78 ninep "gno.land/p/moul/x/plan9/ninep/v0"9)1011func fixedClock(h int64) func() int64 { return func() int64 { return h } }1213func TestTreeShape(t *testing.T) {14 tr := New("dev", "sys", fixedClock(42))15 tr.Root().16 Add("sysname", func() string { return "gnoland-1" }).17 Add("height", func() string { return "42" })1819 st := tr.Root().Stat()20 if st.Name != "dev" {21 t.Errorf("root name %q", st.Name)22 }23 if !st.IsDir() {24 t.Error("root should be a directory")25 }26 if st.Mtime != 42 {27 t.Errorf("mtime %d, want 42", st.Mtime)28 }29 if st.Mode.String() != "dr-xr-xr-x" {30 t.Errorf("mode %q, want dr-xr-xr-x", st.Mode.String())31 }32}3334func TestReadComputed(t *testing.T) {35 height := int64(100)36 tr := New("dev", "sys", func() int64 { return height })37 tr.Root().Add("height", func() string { return strconv.FormatInt(height, 10) })3839 f, err := tr.Root().Walk("height")40 if err != nil {41 t.Fatalf("walk: %v", err)42 }43 got, _ := ninep.ReadAll(f)44 if got != "100" {45 t.Errorf("got %q", got)46 }47 // The generator runs on every read, so the file follows the chain.48 height = 10149 got, _ = ninep.ReadAll(f)50 if got != "101" {51 t.Errorf("after the block advanced: got %q", got)52 }53 if v := f.Stat().Qid.Version; v != 0 {54 t.Errorf("a synthetic file pins version 0, got %d", v)55 }56}5758func TestReadWindow(t *testing.T) {59 tr := New("dev", "sys", nil)60 tr.Root().Add("msg", func() string { return "hello world" })61 f, _ := tr.Root().Walk("msg")6263 tests := []struct {64 off, count int6465 want string66 }{67 {0, -1, "hello world"},68 {0, 5, "hello"},69 {6, 5, "world"},70 {99, 5, ""},71 }72 for _, tt := range tests {73 got, err := f.Read(tt.off, tt.count)74 if err != nil {75 t.Fatalf("read: %v", err)76 }77 if got != tt.want {78 t.Errorf("Read(%d,%d): got %q, want %q", tt.off, tt.count, got, tt.want)79 }80 }81}8283func TestAddRangeEndlessFile(t *testing.T) {84 // /dev/zero has no end: it must serve the window rather than a value.85 tr := New("dev", "sys", nil)86 tr.Root().AddRange("zero", 0444, func(off, count int64) (string, error) {87 if count < 0 {88 count = 089 }90 return strings.Repeat("0", int(count)), nil91 })92 f, _ := tr.Root().Walk("zero")93 got, _ := f.Read(0, 4)94 if got != "0000" {95 t.Errorf("got %q", got)96 }97 got, _ = f.Read(0, -1)98 if got != "" {99 t.Errorf("an unbounded read of an endless file must not hang or grow: got %q", got)100 }101}102103func TestReadDirIsOrdered(t *testing.T) {104 tr := New("dev", "sys", fixedClock(1))105 tr.Root().106 Add("zero", func() string { return "" }).107 Add("null", func() string { return "" }).108 Add("time", func() string { return "" })109110 ents, err := tr.Root().ReadDir()111 if err != nil {112 t.Fatalf("readdir: %v", err)113 }114 want := []string{"null", "time", "zero"}115 if len(ents) != len(want) {116 t.Fatalf("got %d entries", len(ents))117 }118 for i, w := range want {119 if ents[i].Name != w {120 t.Errorf("entry %d: got %q, want %q", i, ents[i].Name, w)121 }122 }123 if got := tr.Root().Names(); len(got) != 3 || got[0] != "null" {124 t.Errorf("Names: %v", got)125 }126}127128func TestSubdirectories(t *testing.T) {129 tr := New("proc", "sys", fixedClock(7))130 sub := tr.NewDir("1")131 sub.Add("status", func() string { return "running" })132 tr.Root().AddDir(sub)133134 f, err := ninep.Walk(tr.Root(), ninep.Elems("/1/status"))135 if err != nil {136 t.Fatalf("walk: %v", err)137 }138 got, _ := ninep.ReadAll(f)139 if got != "running" {140 t.Errorf("got %q", got)141 }142}143144func TestErrors(t *testing.T) {145 tr := New("dev", "sys", nil)146 tr.Root().Add("null", func() string { return "" })147148 if _, err := tr.Root().Read(0, -1); err != ninep.ErrIsDir {149 t.Errorf("read a directory: got %v", err)150 }151 if _, err := tr.Root().Walk("absent"); err != ninep.ErrNotExist {152 t.Errorf("walk missing: got %v", err)153 }154 if _, err := tr.Root().Walk("a/b"); err != ninep.ErrBadName {155 t.Errorf("walk bad name: got %v", err)156 }157 f, _ := tr.Root().Walk("null")158 if _, err := f.Walk("x"); err != ninep.ErrNotDir {159 t.Errorf("walk through a file: got %v", err)160 }161 if _, err := f.ReadDir(); err != ninep.ErrNotDir {162 t.Errorf("readdir a file: got %v", err)163 }164}165166func TestSyntheticTreeIsReadOnly(t *testing.T) {167 // The whole point: a synthetic tree implements File and NOT Mutable, so168 // it is safe to hand to a namespace owned by somebody else.169 tr := New("dev", "sys", nil)170 var f ninep.File = tr.Root()171 if _, ok := f.(ninep.Mutable); ok {172 t.Error("a synthetic tree must not be Mutable")173 }174}175Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.