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

6.23 Kb · 209 lines
  1package ns
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8	"gno.land/p/nt/uassert/v0"
  9
 10	ninep "gno.land/p/moul/x/plan9/ninep/v0"
 11	synfs "gno.land/p/moul/x/plan9/synfs/v0"
 12)
 13
 14// probe is a tiny read-only server, standing in for another realm's tree.
 15func probe(name, contents string) ninep.File {
 16	t := synfs.New(name, "sys", func() int64 { return 1 })
 17	t.Root().Add("value", func() string { return contents })
 18	return t.Root()
 19}
 20
 21func TestDemoNamespaceIsSeeded(t *testing.T) {
 22	seedDemo()
 23	out, err := Run(DemoKey, "ls -u /bin")
 24	if err != nil {
 25		t.Fatalf("ls: %v", err)
 26	}
 27	if out != "ls\nrc\n" {
 28		t.Errorf("the demo /bin should be a union of two directories: %q", out)
 29	}
 30	if got := Namespace(DemoKey); !strings.Contains(got, "bind -ac /usr/glenda/bin /bin") {
 31		t.Errorf("mount table: %q", got)
 32	}
 33	if got, _ := Run(DemoKey, "cat /tmp/greeting"); got != "hello from a namespace\n" {
 34		t.Errorf("greeting: %q", got)
 35	}
 36}
 37
 38func TestRunIsReadOnly(t *testing.T) {
 39	seedDemo()
 40	if _, err := Run(DemoKey, "echo x > /tmp/f"); err == nil {
 41		t.Fatal("Render's shell must refuse a write")
 42	}
 43	if _, err := Run(DemoKey, "rm /tmp/greeting"); err == nil {
 44		t.Fatal("Render's shell must refuse a remove")
 45	}
 46	if _, err := Run("nobody", "ls /"); err == nil {
 47		t.Fatal("an unknown namespace should not be created by a read")
 48	}
 49}
 50
 51func TestPostAndBind(cur realm, t *testing.T) {
 52	services.Remove("probe")
 53	Post(cross(cur), "probe", probe("probe", "42"))
 54
 55	found := false
 56	for _, s := range Services() {
 57		if s == "probe" {
 58			found = true
 59		}
 60	}
 61	if !found {
 62		t.Fatalf("probe is not in /srv: %v", Services())
 63	}
 64
 65	// A fresh namespace sees it through /srv without importing anything.
 66	spaces.Remove("tester")
 67	spaceFor("tester")
 68	defer spaces.Remove("tester")
 69	out, err := Run("tester", "cat /srv/probe/value")
 70	if err != nil {
 71		t.Fatalf("cat through /srv: %v", err)
 72	}
 73	if out != "42" {
 74		t.Errorf("got %q, want 42", out)
 75	}
 76	services.Remove("probe")
 77}
 78
 79func TestExecWritesToTheCallersOwnNamespace(cur realm, t *testing.T) {
 80	alice := testutils.TestAddress("alice")
 81	bob := testutils.TestAddress("bob")
 82	spaces.Remove(alice.String())
 83	spaces.Remove(bob.String())
 84
 85	testing.SetRealm(testing.NewUserRealm(alice))
 86	Exec(cross(cur), "echo 'alice was here' > /tmp/note")
 87
 88	testing.SetRealm(testing.NewUserRealm(bob))
 89	Exec(cross(cur), "echo 'bob was here' > /tmp/note")
 90
 91	got, err := Run(alice.String(), "cat /tmp/note")
 92	if err != nil {
 93		t.Fatalf("alice: %v", err)
 94	}
 95	if got != "alice was here\n" {
 96		t.Errorf("alice's namespace: %q", got)
 97	}
 98	if got, _ = Run(bob.String(), "cat /tmp/note"); got != "bob was here\n" {
 99		t.Errorf("bob's namespace: %q", got)
100	}
101
102	// Reset throws a namespace away; the next use rebuilds the default.
103	testing.SetRealm(testing.NewUserRealm(alice))
104	Reset(cross(cur))
105	if _, err := Run(alice.String(), "cat /tmp/note"); err == nil {
106		t.Error("the namespace should be gone after Reset")
107	}
108	spaces.Remove(alice.String())
109	spaces.Remove(bob.String())
110}
111
112func TestExecBindsIntoTheCallersNamespace(cur realm, t *testing.T) {
113	services.Remove("probe")
114	Post(cross(cur), "probe", probe("probe", "42"))
115	defer services.Remove("probe")
116
117	erin := testutils.TestAddress("erin")
118	spaces.Remove(erin.String())
119	testing.SetRealm(testing.NewUserRealm(erin))
120	Exec(cross(cur), "bind /srv/probe /dev")
121
122	if got := mustRun(t, erin.String(), "cat /dev/value"); got != "42" {
123		t.Errorf("read through the mount: %q", got)
124	}
125	if ns := Namespace(erin.String()); !strings.Contains(ns, "bind /srv/probe /dev") {
126		t.Errorf("mount table: %q", ns)
127	}
128	spaces.Remove(erin.String())
129}
130
131func TestSrvListingUsesThePostedName(cur realm, t *testing.T) {
132	services.Remove("aliased")
133	// The server calls its own root "probe"; /srv must show it as "aliased".
134	Post(cross(cur), "aliased", probe("probe", "x"))
135	defer services.Remove("aliased")
136
137	spaces.Remove("lister")
138	spaceFor("lister")
139	out := mustRun(t, "lister", "ls /srv")
140	if !strings.Contains(out, "aliased") {
141		t.Errorf("ls /srv: %q", out)
142	}
143	if strings.Contains(out, "probe") {
144		t.Errorf("/srv leaked the server's own root name: %q", out)
145	}
146	spaces.Remove("lister")
147}
148
149func mustRun(t *testing.T, key, line string) string {
150	t.Helper()
151	out, err := Run(key, line)
152	if err != nil {
153		t.Fatalf("%s: %v", line, err)
154	}
155	return out
156}
157
158// --- the boundary. Three properties the suite would be unsafe without. -------
159
160// A /srv name belongs to the realm that posted it: first come, first served,
161// and then locked.
162func TestPostRejectsASecondOwner(cur realm, t *testing.T) {
163	services.Remove("taken")
164	Post(cross(cur), "taken", probe("taken", "first"))
165	defer services.Remove("taken")
166
167	testing.SetRealm(testing.NewCodeRealm("gno.land/r/other/thing/v0"))
168	uassert.AbortsWithMessage(t, cur,
169		"post: taken is already posted by gno.land/r/moul/x/plan9/ns/v0",
170		func() { Post(cross(cur), "taken", probe("taken", "second")) })
171}
172
173// Grafting another realm's tree into your namespace gives you reads and
174// nothing else. ninep.File has no mutating method, so there is no route from a
175// mount to a write against the realm that posted it.
176func TestMountedTreesAreReadOnly(cur realm, t *testing.T) {
177	services.Remove("probe")
178	Post(cross(cur), "probe", probe("probe", "read me"))
179	defer services.Remove("probe")
180
181	frank := testutils.TestAddress("frank")
182	spaces.Remove(frank.String())
183	defer spaces.Remove(frank.String())
184
185	testing.SetRealm(testing.NewUserRealm(frank))
186	Exec(cross(cur), "bind /srv/probe /dev")
187	uassert.Equal(t, "read me", mustRun(t, frank.String(), "cat /dev/value"))
188	uassert.AbortsWithMessage(t, cur, "echo: read-only file server",
189		func() { Exec(cross(cur), "echo nope > /dev/value") })
190}
191
192// A command line stops at the first error and the error leaves the realm as an
193// abort, so a half-applied Exec reverts with its transaction instead of
194// leaving a namespace nobody asked for.
195func TestExecAbortsAtTheFirstError(cur realm, t *testing.T) {
196	grace := testutils.TestAddress("grace")
197	spaces.Remove(grace.String())
198	defer spaces.Remove(grace.String())
199
200	testing.SetRealm(testing.NewUserRealm(grace))
201	uassert.AbortsWithMessage(t, cur, "cat: file does not exist",
202		func() { Exec(cross(cur), "echo ok > /tmp/a; cat /absent; echo never > /tmp/b") })
203}
204
205func TestRenderUnknownCommand(t *testing.T) {
206	if got := Render("nope/x"); !strings.Contains(got, "404") {
207		t.Errorf("got %q", got)
208	}
209}