github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/imports/wasi_snapshot_preview1/testdata/go/wasi.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/fs" 9 "net" 10 "net/http" 11 "os" 12 "strconv" 13 "strings" 14 "sync" 15 "syscall" 16 "time" 17 ) 18 19 func main() { 20 switch os.Args[1] { 21 case "ls": 22 var repeat bool 23 if len(os.Args) == 4 { 24 repeat = os.Args[3] == "repeat" 25 } 26 // Go doesn't open with O_DIRECTORY, so we don't end up with ENOTDIR, 27 // rather EBADF trying to read the directory later. 28 if err := mainLs(os.Args[2], repeat); errors.Is(err, syscall.EBADF) { 29 fmt.Println("ENOTDIR") 30 } else if err != nil { 31 panic(err) 32 } 33 case "stat": 34 if err := mainStat(); err != nil { 35 panic(err) 36 } 37 case "sock": 38 if err := mainSock(); err != nil { 39 panic(err) 40 } 41 case "nonblock": 42 if err := mainNonblock(os.Args[2], os.Args[3:]); err != nil { 43 panic(err) 44 } 45 } 46 47 // Handle go-specific additions 48 switch os.Args[1] { 49 case "http": 50 if err := mainHTTP(); err != nil { 51 panic(err) 52 } 53 case "stdin": 54 if err := mainStdin(); err != nil { 55 panic(err) 56 } 57 case "stdout": 58 mainStdout() 59 case "largestdout": 60 mainLargeStdout() 61 } 62 } 63 64 func mainLs(path string, repeat bool) error { 65 d, err := os.Open(path) 66 if err != nil { 67 return err 68 } 69 defer d.Close() 70 71 if err = printFileNames(d); err != nil { 72 return err 73 } else if repeat { 74 // rewind 75 if _, err = d.Seek(0, io.SeekStart); err != nil { 76 return err 77 } 78 return printFileNames(d) 79 } 80 return nil 81 } 82 83 func printFileNames(d *os.File) error { 84 if names, err := d.Readdirnames(-1); err != nil { 85 return err 86 } else { 87 for _, n := range names { 88 fmt.Println("./" + n) 89 } 90 } 91 return nil 92 } 93 94 func mainStat() error { 95 var isatty = func(name string, fd uintptr) error { 96 f := os.NewFile(fd, "") 97 if st, err := f.Stat(); err != nil { 98 return err 99 } else { 100 ttyMode := fs.ModeDevice | fs.ModeCharDevice 101 isatty := st.Mode()&ttyMode == ttyMode 102 fmt.Println(name, "isatty:", isatty) 103 return nil 104 } 105 } 106 107 for fd, name := range []string{"stdin", "stdout", "stderr", "/"} { 108 if err := isatty(name, uintptr(fd)); err != nil { 109 return err 110 } 111 } 112 return nil 113 } 114 115 // mainSock is an explicit test of a blocking socket. 116 func mainSock() error { 117 // Get a listener from the pre-opened file descriptor. 118 // The listener is the first pre-open, with a file-descriptor of 3. 119 f := os.NewFile(3, "") 120 l, err := net.FileListener(f) 121 defer f.Close() 122 if err != nil { 123 return err 124 } 125 defer l.Close() 126 127 // Accept a connection 128 conn, err := l.Accept() 129 if err != nil { 130 return err 131 } 132 defer conn.Close() 133 134 // Do a blocking read of up to 32 bytes. 135 // Note: the test should write: "wazero", so that's all we should read. 136 var buf [32]byte 137 n, err := conn.Read(buf[:]) 138 if err != nil { 139 return err 140 } 141 fmt.Println(string(buf[:n])) 142 return nil 143 } 144 145 // Adapted from nonblock.go 146 // https://github.com/golang/go/blob/0fcc70ecd56e3b5c214ddaee4065ea1139ae16b5/src/runtime/internal/wasitest/testdata/nonblock.go 147 func mainNonblock(mode string, files []string) error { 148 ready := make(chan struct{}) 149 150 var wg sync.WaitGroup 151 for _, path := range files { 152 f, err := os.Open(path) 153 if err != nil { 154 return err 155 } 156 switch mode { 157 case "open": 158 case "create": 159 fd := f.Fd() 160 if err = syscall.SetNonblock(int(fd), true); err != nil { 161 return err 162 } 163 f = os.NewFile(fd, path) 164 default: 165 return fmt.Errorf("invalid test mode") 166 } 167 168 spawnWait := make(chan struct{}) 169 170 wg.Add(1) 171 go func(f *os.File) { 172 defer f.Close() 173 defer wg.Done() 174 175 // Signal the routine has been spawned. 176 close(spawnWait) 177 178 // Wait until ready. 179 <-ready 180 181 var buf [256]byte 182 183 if n, err := f.Read(buf[:]); err != nil { 184 panic(err) 185 } else { 186 os.Stderr.Write(buf[:n]) 187 } 188 }(f) 189 190 // Spawn one goroutine at a time. 191 <-spawnWait 192 } 193 194 println("waiting") 195 close(ready) 196 wg.Wait() 197 return nil 198 } 199 200 // mainHTTP implicitly tests non-blocking sockets, as they are needed for 201 // middleware. 202 func mainHTTP() error { 203 // Get the file representing a pre-opened TCP socket. 204 // The socket (listener) is the first pre-open, with a file-descriptor of 205 // 3 because the host didn't add any pre-opened files. 206 listenerFD := 3 207 f := os.NewFile(uintptr(listenerFD), "") 208 209 // Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work 210 // in parallel. non-blocking allows the poller to park the go-routine 211 // accepting connections while work is done on one. 212 if err := syscall.SetNonblock(listenerFD, true); err != nil { 213 return err 214 } 215 216 // Convert the file representing the pre-opened socket to a listener, so 217 // that we can integrate it with HTTP middleware. 218 ln, err := net.FileListener(f) 219 defer f.Close() 220 if err != nil { 221 return err 222 } 223 defer ln.Close() 224 225 // Serve middleware that echos the request body to the response once, then quits. 226 h := &echoOnce{ch: make(chan struct{}, 1)} 227 go http.Serve(ln, h) 228 <-h.ch 229 return nil 230 } 231 232 type echoOnce struct { 233 ch chan struct{} 234 } 235 236 func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) { 237 // Copy up to 32 bytes from the request to the response, appending a newline. 238 // Note: the test should write: "wazero", so that's all we should read. 239 var buf [32]byte 240 if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF { 241 panic(err) 242 } else if n, err = w.Write(append(buf[:n], '\n')); err != nil { 243 panic(err) 244 } 245 // Once one request was served, close the channel. 246 close(e.ch) 247 } 248 249 // Reproducer for https://github.com/tetratelabs/wazero/issues/1538 250 func mainStdin() error { 251 go func() { 252 time.Sleep(1 * time.Second) 253 os.Stdout.WriteString("waiting for stdin...\n") 254 }() 255 256 b, err := io.ReadAll(os.Stdin) 257 if err != nil { 258 return err 259 } 260 os.Stdout.Write(b) 261 return nil 262 } 263 264 func mainStdout() { 265 os.Stdout.WriteString("test") 266 } 267 268 func mainLargeStdout() { 269 const ntest = 1024 270 271 var decls, calls bytes.Buffer 272 273 for i := 1; i <= ntest; i++ { 274 s := strconv.Itoa(i) 275 decls.WriteString(strings.Replace(decl, "$", s, -1)) 276 calls.WriteString(strings.Replace("call(test$)\n\t", "$", s, -1)) 277 } 278 279 program = strings.Replace(program, "$DECLS", decls.String(), 1) 280 program = strings.Replace(program, "$CALLS", calls.String(), 1) 281 fmt.Print(program) 282 } 283 284 var program = `package main 285 286 var count int 287 288 func call(f func() bool) { 289 if f() { 290 count++ 291 } 292 } 293 294 $DECLS 295 296 func main() { 297 $CALLS 298 if count != 0 { 299 println("failed", count, "case(s)") 300 } 301 } 302 ` 303 304 const decl = ` 305 type T$ [$]uint8 306 func test$() bool { 307 v := T${1} 308 return v == [$]uint8{2} || v != [$]uint8{1} 309 }`