Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

ns_test.gno

10.17 Kb · 375 lines
  1package ns
  2
  3import (
  4	"testing"
  5
  6	memfs "gno.land/p/nym-moultest123/x/plan9/memfs/v0"
  7	ninep "gno.land/p/nym-moultest123/x/plan9/ninep/v0"
  8)
  9
 10// newNs builds a namespace over a fresh ram tree seeded with files.
 11func newNs(t *testing.T, files ...string) (*Ns, *memfs.FS) {
 12	t.Helper()
 13	fs := memfs.New("glenda", 1)
 14	for _, f := range files {
 15		if err := fs.WriteFile(f, "contents of "+f+"\n", 1); err != nil {
 16			t.Fatalf("seed %s: %v", f, err)
 17		}
 18	}
 19	return New(fs.Root()), fs
 20}
 21
 22func names(ents []ninep.Stat) []string {
 23	out := []string{}
 24	for _, e := range ents {
 25		out = append(out, e.Name)
 26	}
 27	return out
 28}
 29
 30func eq(a, b []string) bool {
 31	if len(a) != len(b) {
 32		return false
 33	}
 34	for i := range a {
 35		if a[i] != b[i] {
 36			return false
 37		}
 38	}
 39	return true
 40}
 41
 42func TestResolveRoot(t *testing.T) {
 43	n, _ := newNs(t, "/bin/ls", "/bin/cat")
 44	st, err := n.Stat("/")
 45	if err != nil {
 46		t.Fatalf("stat root: %v", err)
 47	}
 48	if !st.IsDir() {
 49		t.Error("root is not a directory")
 50	}
 51	ents, err := n.ReadDir("/", false)
 52	if err != nil {
 53		t.Fatalf("readdir: %v", err)
 54	}
 55	if !eq(names(ents), []string{"bin"}) {
 56		t.Errorf("root listing: %v", names(ents))
 57	}
 58}
 59
 60func TestReadFileAndMissing(t *testing.T) {
 61	n, _ := newNs(t, "/bin/ls")
 62	got, err := n.ReadFile("/bin/ls")
 63	if err != nil {
 64		t.Fatalf("readfile: %v", err)
 65	}
 66	if got != "contents of /bin/ls\n" {
 67		t.Errorf("got %q", got)
 68	}
 69	if _, err := n.ReadFile("/bin/absent"); err != ninep.ErrNotExist {
 70		t.Errorf("missing: got %v, want ErrNotExist", err)
 71	}
 72}
 73
 74func TestDotDotIsLexical(t *testing.T) {
 75	n, _ := newNs(t, "/a/b/c")
 76	// /a/b/../b/c never asks a server about "..".
 77	if _, err := n.Stat("/a/b/../b/c"); err != nil {
 78		t.Fatalf("lexical dotdot: %v", err)
 79	}
 80	// Above the root stays at the root.
 81	if _, err := n.Stat("/../../a"); err != nil {
 82		t.Fatalf("dotdot above root: %v", err)
 83	}
 84}
 85
 86func TestBindReplace(t *testing.T) {
 87	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc")
 88	if err := n.Bind("/usr/glenda/bin", "/bin", MREPL); err != nil {
 89		t.Fatalf("bind: %v", err)
 90	}
 91	ents, _ := n.ReadDir("/bin", false)
 92	if !eq(names(ents), []string{"rc"}) {
 93		t.Errorf("after MREPL /bin should hold only rc, got %v", names(ents))
 94	}
 95	if _, err := n.Stat("/bin/ls"); err != ninep.ErrNotExist {
 96		t.Errorf("replaced file still reachable: %v", err)
 97	}
 98}
 99
100func TestBindAfterUnion(t *testing.T) {
101	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc")
102	if err := n.Bind("/usr/glenda/bin", "/bin", MAFTER); err != nil {
103		t.Fatalf("bind -a: %v", err)
104	}
105	ents, _ := n.ReadDir("/bin", false)
106	if !eq(names(ents), []string{"ls", "rc"}) {
107		t.Errorf("union order: got %v, want [ls rc]", names(ents))
108	}
109	if _, err := n.Stat("/bin/rc"); err != nil {
110		t.Errorf("unioned file not reachable: %v", err)
111	}
112	if _, err := n.Stat("/bin/ls"); err != nil {
113		t.Errorf("original file lost: %v", err)
114	}
115}
116
117func TestBindBeforeUnion(t *testing.T) {
118	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc")
119	if err := n.Bind("/usr/glenda/bin", "/bin", MBEFORE); err != nil {
120		t.Fatalf("bind -b: %v", err)
121	}
122	ents, _ := n.ReadDir("/bin", false)
123	if !eq(names(ents), []string{"rc", "ls"}) {
124		t.Errorf("union order: got %v, want [rc ls]", names(ents))
125	}
126}
127
128func TestUnionShadowingIsVisible(t *testing.T) {
129	// Both directories hold a file called "ls". Plan 9 concatenates, so the
130	// listing shows the duplicate and makes the shadowing visible.
131	fs := memfs.New("glenda", 1)
132	fs.WriteFile("/bin/ls", "system ls\n", 1)
133	fs.WriteFile("/usr/glenda/bin/ls", "my ls\n", 1)
134	n := New(fs.Root())
135
136	if err := n.Bind("/usr/glenda/bin", "/bin", MBEFORE); err != nil {
137		t.Fatalf("bind: %v", err)
138	}
139	ents, _ := n.ReadDir("/bin", false)
140	if !eq(names(ents), []string{"ls", "ls"}) {
141		t.Errorf("concatenation should show both: got %v", names(ents))
142	}
143	uniq, _ := n.ReadDir("/bin", true)
144	if !eq(names(uniq), []string{"ls"}) {
145		t.Errorf("unique listing: got %v", names(uniq))
146	}
147	// A walk reaches the first member, which is the one bound before.
148	got, err := n.ReadFile("/bin/ls")
149	if err != nil {
150		t.Fatalf("read: %v", err)
151	}
152	if got != "my ls\n" {
153		t.Errorf("shadowing: got %q, want my ls", got)
154	}
155}
156
157func TestUnionIsTopLevelOnly(t *testing.T) {
158	// /a and /b each hold a directory "sub", but only /a/sub/x exists.
159	// Unioning /b onto /a does NOT union /a/sub with /b/sub.
160	fs := memfs.New("glenda", 1)
161	fs.WriteFile("/a/sub/x", "x\n", 1)
162	fs.WriteFile("/b/sub/y", "y\n", 1)
163	n := New(fs.Root())
164	if err := n.Bind("/b", "/a", MAFTER); err != nil {
165		t.Fatalf("bind: %v", err)
166	}
167	if _, err := n.Stat("/a/sub/x"); err != nil {
168		t.Errorf("/a/sub/x should still resolve: %v", err)
169	}
170	if _, err := n.Stat("/a/sub/y"); err != ninep.ErrNotExist {
171		t.Errorf("unions are top level only, /a/sub/y must not resolve: %v", err)
172	}
173}
174
175func TestBindCapturesAChannelNotAName(t *testing.T) {
176	fs := memfs.New("glenda", 1)
177	fs.WriteFile("/one/f", "one\n", 1)
178	fs.WriteFile("/two/f", "two\n", 1)
179	n := New(fs.Root())
180
181	if err := n.Bind("/one", "/mnt", MREPL); err != nil {
182		// /mnt does not exist yet, so this must fail: see the next test.
183		fs.MkdirAll("/mnt", 1)
184		if err := n.Bind("/one", "/mnt", MREPL); err != nil {
185			t.Fatalf("bind: %v", err)
186		}
187	}
188	// Now rebind the SOURCE name elsewhere. The earlier bind captured the
189	// directory, so /mnt must not follow.
190	if err := n.Bind("/two", "/one", MREPL); err != nil {
191		t.Fatalf("rebind source: %v", err)
192	}
193	got, _ := n.ReadFile("/mnt/f")
194	if got != "one\n" {
195		t.Errorf("bind should have captured the channel: /mnt/f is %q", got)
196	}
197	got, _ = n.ReadFile("/one/f")
198	if got != "two\n" {
199		t.Errorf("/one/f is %q, want two", got)
200	}
201}
202
203func TestBindTargetMustExist(t *testing.T) {
204	n, _ := newNs(t, "/bin/ls")
205	if err := n.Bind("/bin", "/nowhere", MREPL); err != ninep.ErrNotExist {
206		t.Errorf("bind onto a missing name: got %v, want ErrNotExist", err)
207	}
208	if err := n.Bind("/nowhere", "/bin", MREPL); err != ninep.ErrNotExist {
209		t.Errorf("bind from a missing name: got %v, want ErrNotExist", err)
210	}
211}
212
213func TestCreateTargetNeedsMCREATE(t *testing.T) {
214	fs := memfs.New("glenda", 1)
215	fs.MkdirAll("/bin", 1)
216	fs.MkdirAll("/usr/glenda/bin", 1)
217	n := New(fs.Root())
218
219	// A plain directory takes creates with no flag at all.
220	if _, err := n.CreateTarget("/bin"); err != nil {
221		t.Fatalf("plain directory should accept a create: %v", err)
222	}
223
224	// Made into a union with no MCREATE anywhere, creation is refused.
225	if err := n.Bind("/usr/glenda/bin", "/bin", MAFTER); err != nil {
226		t.Fatalf("bind: %v", err)
227	}
228	if _, err := n.CreateTarget("/bin"); err != ninep.ErrNoCreate {
229		t.Errorf("union without MCREATE: got %v, want ErrNoCreate", err)
230	}
231
232	// Rebinding with MCREATE picks that member.
233	if err := n.Bind("/usr/glenda/bin", "/bin", MAFTER|MCREATE); err != nil {
234		t.Fatalf("bind -ac: %v", err)
235	}
236	target, err := n.CreateTarget("/bin")
237	if err != nil {
238		t.Fatalf("union with MCREATE: %v", err)
239	}
240	if _, err := target.Create("new", ninep.FilePerm, 2); err != nil {
241		t.Fatalf("create: %v", err)
242	}
243	// It landed in the MCREATE member, not in /bin's own directory.
244	if _, err := ninep.Walk(fs.Root(), ninep.Elems("/usr/glenda/bin/new")); err != nil {
245		t.Errorf("create went to the wrong member: %v", err)
246	}
247}
248
249func TestUnmount(t *testing.T) {
250	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc")
251	n.Bind("/usr/glenda/bin", "/bin", MAFTER)
252	ents, _ := n.ReadDir("/bin", false)
253	if len(ents) != 2 {
254		t.Fatalf("setup: %v", names(ents))
255	}
256
257	if err := n.Unmount("/usr/glenda/bin", "/bin"); err != nil {
258		t.Fatalf("unmount: %v", err)
259	}
260	ents, _ = n.ReadDir("/bin", false)
261	if !eq(names(ents), []string{"ls"}) {
262		t.Errorf("after unmount: %v", names(ents))
263	}
264	if err := n.Unmount("", "/bin"); err != ErrNotBound {
265		t.Errorf("unmount of an unbound name: got %v, want ErrNotBound", err)
266	}
267}
268
269func TestUnmountAll(t *testing.T) {
270	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc")
271	n.Bind("/usr/glenda/bin", "/bin", MAFTER)
272	if err := n.Unmount("", "/bin"); err != nil {
273		t.Fatalf("unmount all: %v", err)
274	}
275	ents, _ := n.ReadDir("/bin", false)
276	if !eq(names(ents), []string{"ls"}) {
277		t.Errorf("name should be restored to its original contents: %v", names(ents))
278	}
279	if len(n.Ops()) != 0 {
280		t.Errorf("ops should be gone: %v", n.Ops())
281	}
282}
283
284func TestCd(t *testing.T) {
285	n, _ := newNs(t, "/usr/glenda/lib/profile")
286	if err := n.Cd("/usr/glenda"); err != nil {
287		t.Fatalf("cd: %v", err)
288	}
289	if n.Cwd() != "/usr/glenda" {
290		t.Errorf("cwd: %q", n.Cwd())
291	}
292	got, err := n.ReadFile("lib/profile")
293	if err != nil {
294		t.Fatalf("relative read: %v", err)
295	}
296	if got == "" {
297		t.Error("relative read came back empty")
298	}
299	if err := n.Cd("lib/profile"); err != ninep.ErrNotDir {
300		t.Errorf("cd into a file: got %v, want ErrNotDir", err)
301	}
302	if err := n.Cd("/nowhere"); err != ninep.ErrNotExist {
303		t.Errorf("cd nowhere: got %v", err)
304	}
305}
306
307func TestStringMatchesNsFormat(t *testing.T) {
308	n, _ := newNs(t, "/bin/ls", "/usr/glenda/bin/rc", "/tmp/x")
309	n.Bind("/usr/glenda/bin", "/bin", MAFTER|MCREATE)
310	n.Bind("/tmp", "/usr/glenda", MBEFORE)
311	n.Cd("/usr/glenda")
312
313	want := "bind -ac /usr/glenda/bin /bin\n" +
314		"bind -b /tmp /usr/glenda\n" +
315		"cd /usr/glenda\n"
316	if got := n.String(); got != want {
317		t.Errorf("ns output:\ngot:\n%s\nwant:\n%s", got, want)
318	}
319}
320
321func TestFlagString(t *testing.T) {
322	tests := []struct {
323		flag Flag
324		want string
325	}{
326		{MREPL, ""},
327		{MBEFORE, "-b"},
328		{MAFTER, "-a"},
329		{MCREATE, "-c"},
330		{MAFTER | MCREATE, "-ac"},
331		{MBEFORE | MCREATE, "-bc"},
332	}
333	for _, tt := range tests {
334		if got := tt.flag.String(); got != tt.want {
335			t.Errorf("Flag(%d): got %q, want %q", uint32(tt.flag), got, tt.want)
336		}
337	}
338}
339
340func TestUnionWidthIsBounded(t *testing.T) {
341	fs := memfs.New("glenda", 1)
342	fs.MkdirAll("/bin", 1)
343	for i := 0; i < MaxUnion+2; i++ {
344		fs.MkdirAll("/d"+string(rune('a'+i)), 1)
345	}
346	n := New(fs.Root())
347	var err error
348	for i := 0; i < MaxUnion+2; i++ {
349		err = n.Bind("/d"+string(rune('a'+i)), "/bin", MAFTER)
350		if err != nil {
351			break
352		}
353	}
354	if err != ErrWideUnion {
355		t.Errorf("got %v, want ErrWideUnion", err)
356	}
357}
358
359func TestReadDirOnAFile(t *testing.T) {
360	n, _ := newNs(t, "/bin/ls")
361	if _, err := n.ReadDir("/bin/ls", false); err != ninep.ErrNotDir {
362		t.Errorf("got %v, want ErrNotDir", err)
363	}
364}
365
366func TestDepthIsBounded(t *testing.T) {
367	n, _ := newNs(t, "/a")
368	p := ""
369	for i := 0; i <= ninep.MaxDepth; i++ {
370		p += "/x"
371	}
372	if _, err := n.Stat(p); err != ninep.ErrTooDeep {
373		t.Errorf("got %v, want ErrTooDeep", err)
374	}
375}