dev_test.gno
4.49 Kb · 166 lines
1package dev
2
3import (
4 "chain/runtime"
5 "strconv"
6 "strings"
7 "testing"
8
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11
12 ninep "gno.land/p/moul/x/plan9/ninep/v0"
13
14 nsrealm "gno.land/r/moul/x/plan9/ns/v1"
15)
16
17func read(t *testing.T, name string) string {
18 t.Helper()
19 f, err := tree.Root().Walk(name)
20 if err != nil {
21 t.Fatalf("walk %s: %v", name, err)
22 }
23 data, err := ninep.ReadAll(f)
24 if err != nil {
25 t.Fatalf("read %s: %v", name, err)
26 }
27 return data
28}
29
30func TestDevicesFollowTheChain(t *testing.T) {
31 if got, want := read(t, "sysname"), runtime.ChainID(); got != want {
32 t.Errorf("sysname: got %q, want %q", got, want)
33 }
34 if got, want := read(t, "domain"), runtime.ChainDomain(); got != want {
35 t.Errorf("domain: got %q, want %q", got, want)
36 }
37 if got, want := read(t, "height"), strconv.FormatInt(runtime.ChainHeight(), 10); got != want {
38 t.Errorf("height: got %q, want %q", got, want)
39 }
40
41 // A device is code, not storage: advance the chain and the file follows.
42 before := read(t, "height")
43 testing.SkipHeights(5)
44 after := read(t, "height")
45 if before == after {
46 t.Errorf("height did not move: %q", after)
47 }
48 b, _ := strconv.ParseInt(before, 10, 64)
49 a, _ := strconv.ParseInt(after, 10, 64)
50 if a-b != 5 {
51 t.Errorf("height moved by %d, want 5", a-b)
52 }
53}
54
55func TestRandomIsBlockDeterministic(t *testing.T) {
56 a := read(t, "random")
57 if len(a) != 64 {
58 t.Errorf("a sha256 in hex is 64 characters, got %d", len(a))
59 }
60 if a != read(t, "random") {
61 t.Error("two reads in the same block must agree, or Render is a consensus bug")
62 }
63 testing.SkipHeights(1)
64 if a == read(t, "random") {
65 t.Error("the value should change with the block")
66 }
67}
68
69func TestNullAndZero(t *testing.T) {
70 if got := read(t, "null"); got != "" {
71 t.Errorf("null: %q", got)
72 }
73 f, _ := tree.Root().Walk("zero")
74 got, _ := f.Read(0, 4)
75 if got != "\x00\x00\x00\x00" {
76 t.Errorf("zero: %q", got)
77 }
78 // An endless file must not answer an unbounded read with an endless value.
79 if got, _ := f.Read(0, -1); got != "" {
80 t.Errorf("unbounded read of /dev/zero: %d bytes", len(got))
81 }
82 if got, _ := f.Read(0, 1<<20); len(got) != 4096 {
83 t.Errorf("a huge read should cap at 4096, got %d", len(got))
84 }
85}
86
87func TestSessionReportsAPlainKey(t *testing.T) {
88 got := read(t, "session")
89 if got != "session no\nscope full\n" {
90 t.Errorf("a plain key has no session scope: %q", got)
91 }
92}
93
94func TestDriversListsEveryDevice(t *testing.T) {
95 got := read(t, "drivers")
96 ents, err := tree.Root().ReadDir()
97 if err != nil {
98 t.Fatalf("readdir: %v", err)
99 }
100 for _, e := range ents {
101 if !strings.Contains(got, e.Name+"\t") {
102 t.Errorf("/dev/drivers does not document %q", e.Name)
103 }
104 }
105 if len(ents) != len(docs) {
106 t.Errorf("%d devices but %d documented", len(ents), len(docs))
107 }
108}
109
110func TestTreeIsReadOnly(t *testing.T) {
111 var f ninep.File = tree.Root()
112 if _, ok := f.(ninep.Mutable); ok {
113 t.Error("a device tree handed to other realms must not be Mutable")
114 }
115}
116
117// TestPostedAtDeployTime is the cross-realm claim the whole suite rests on:
118// this realm pushed an interface value into another realm at init, and that
119// realm can call it back later from a read.
120func TestPostedAtDeployTime(t *testing.T) {
121 found := false
122 for _, s := range nsrealm.Services() {
123 if s == "dev" {
124 found = true
125 }
126 }
127 if !found {
128 t.Fatalf("dev is not posted in /srv: %v", nsrealm.Services())
129 }
130}
131
132func TestAFreshNamespaceHasDevBound(cur realm, t *testing.T) {
133 user := testutils.TestAddress("glenda")
134 testing.SetRealm(testing.NewUserRealm(user))
135 nsrealm.Reset(cross(cur))
136 // Touch the namespace so it is built with /dev already bound.
137 nsrealm.Exec(cross(cur), "echo hi > /tmp/hi")
138
139 out, err := nsrealm.Run(user.String(), "ls /dev")
140 if err != nil {
141 t.Fatalf("ls /dev: %v", err)
142 }
143 for _, d := range docs {
144 if !strings.Contains(out, d[0]) {
145 t.Errorf("ls /dev is missing %q: %q", d[0], out)
146 }
147 }
148 got, err := nsrealm.Run(user.String(), "cat /dev/sysname")
149 if err != nil {
150 t.Fatalf("cat: %v", err)
151 }
152 if got != runtime.ChainID() {
153 t.Errorf("reading this realm's device through another realm's namespace: %q", got)
154 }
155 // And the mount table says where it came from.
156 if ns := nsrealm.Namespace(user.String()); !strings.Contains(ns, "bind /srv/dev /dev") {
157 t.Errorf("mount table: %q", ns)
158 }
159}
160
161// The device name comes from the URL: escaped in the heading.
162func TestDeviceNameIsEscaped(t *testing.T) {
163 // No slash in the payload: Render splits the path on "/".
164 out := Render("cat/x` [claim](evil.example) `")
165 uassert.False(t, strings.Contains(out, "[claim](evil.example)"), out)
166}