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

ns_test.gno

10.15 Kb · 375 lines
  1package ns
  2
  3import (
  4	"testing"
  5
  6	memfs "gno.land/p/moul/x/plan9/memfs/v0"
  7	ninep "gno.land/p/moul/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}