synfs_test.gno
4.33 Kb · 174 lines
1package synfs
2
3import (
4 "strconv"
5 "strings"
6 "testing"
7
8 ninep "gno.land/p/moul/x/plan9/ninep/v0"
9)
10
11func fixedClock(h int64) func() int64 { return func() int64 { return h } }
12
13func 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" })
18
19 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}
33
34func 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) })
38
39 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 = 101
49 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}
57
58func 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")
62
63 tests := []struct {
64 off, count int64
65 want string
66 }{
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}
82
83func 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 = 0
89 }
90 return strings.Repeat("0", int(count)), nil
91 })
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}
102
103func 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 "" })
109
110 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}
127
128func 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)
133
134 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}
143
144func TestErrors(t *testing.T) {
145 tr := New("dev", "sys", nil)
146 tr.Root().Add("null", func() string { return "" })
147
148 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}
165
166func TestSyntheticTreeIsReadOnly(t *testing.T) {
167 // The whole point: a synthetic tree implements File and NOT Mutable, so
168 // 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}