github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/imports/wasi_snapshot_preview1/wasi_stdlib_unix_test.go (about) 1 //go:build unix 2 3 package wasi_snapshot_preview1_test 4 5 import ( 6 "bufio" 7 "bytes" 8 "fmt" 9 "io" 10 "math/rand" 11 "os" 12 "path/filepath" 13 "strings" 14 "syscall" 15 "testing" 16 17 wazero "github.com/wasilibs/wazerox" 18 "github.com/wasilibs/wazerox/api" 19 "github.com/wasilibs/wazerox/imports/wasi_snapshot_preview1" 20 "github.com/wasilibs/wazerox/internal/testing/require" 21 "github.com/wasilibs/wazerox/sys" 22 ) 23 24 func Test_NonblockingFile(t *testing.T) { 25 const fifo = "/test-fifo" 26 tempDir := t.TempDir() 27 fifoAbsPath := tempDir + fifo 28 29 moduleConfig := wazero.NewModuleConfig(). 30 WithArgs("wasi", "nonblock", fifo). 31 WithFSConfig(wazero.NewFSConfig().WithDirMount(tempDir, "/")). 32 WithSysNanosleep() 33 34 err := syscall.Mkfifo(fifoAbsPath, 0o666) 35 require.NoError(t, err) 36 37 ch := make(chan string, 2) 38 go func() { 39 ch <- compileAndRunWithPreStart(t, testCtx, moduleConfig, wasmZigCc, func(t *testing.T, mod api.Module) { 40 // Send a dummy string to signal that initialization has completed. 41 ch <- "ready" 42 }) 43 }() 44 45 // Wait for the dummy value, then start the sleep. 46 require.Equal(t, "ready", <-ch) 47 48 // The test writes a few dots on the console until the pipe has data ready 49 // for reading. So, so we wait to ensure those dots are printed. 50 sleepALittle() 51 52 f, err := os.OpenFile(fifoAbsPath, os.O_APPEND|os.O_WRONLY, 0) 53 require.NoError(t, err) 54 n, err := f.Write([]byte("wazero")) 55 require.NoError(t, err) 56 require.NotEqual(t, 0, n) 57 console := <-ch 58 lines := strings.Split(console, "\n") 59 60 // Check if the first line starts with at least one dot. 61 require.True(t, strings.HasPrefix(lines[0], ".")) 62 require.Equal(t, "wazero", lines[1]) 63 } 64 65 type fifo struct { 66 file *os.File 67 path string 68 } 69 70 func Test_NonblockGotip(t *testing.T) { 71 // - Create `numFifos` FIFOs. 72 // - Instantiate `wasmGotip` with the names of the FIFO in the order of creation 73 // - The test binary opens the FIFOs in the given order and spawns a goroutine for each 74 // - The unit test writes to the FIFO in reverse order. 75 // - Each goroutine reads from the given FIFO and writes the contents to stderr 76 // 77 // The test verifies that the output order matches the write order (i.e. reverse order). 78 // 79 // If I/O was blocking, all goroutines would be blocked waiting for one read call 80 // to return, and the output order wouldn't match. 81 // 82 // Adapted from https://github.com/golang/go/blob/0fcc70ecd56e3b5c214ddaee4065ea1139ae16b5/src/runtime/internal/wasitest/nonblock_test.go 83 84 if wasmGotip == nil { 85 t.Skip("skipping because wasi.go was not compiled (gotip missing or compilation error)") 86 } 87 const numFifos = 8 88 89 for _, mode := range []string{"open", "create"} { 90 t.Run(mode, func(t *testing.T) { 91 tempDir := t.TempDir() 92 93 args := []string{"wasi", "nonblock", mode} 94 fifos := make([]*fifo, numFifos) 95 for i := range fifos { 96 tempFile := fmt.Sprintf("wasip1-nonblock-fifo-%d-%d", rand.Uint32(), i) 97 path := filepath.Join(tempDir, tempFile) 98 err := syscall.Mkfifo(path, 0o666) 99 require.NoError(t, err) 100 101 file, err := os.OpenFile(path, os.O_RDWR, 0) 102 require.NoError(t, err) 103 defer file.Close() 104 105 args = append(args, tempFile) 106 fifos[len(fifos)-i-1] = &fifo{file, path} 107 } 108 109 pr, pw := io.Pipe() 110 defer pw.Close() 111 112 var consoleBuf bytes.Buffer 113 114 moduleConfig := wazero.NewModuleConfig(). 115 WithArgs(args...). 116 WithFSConfig( // Mount the tempDir as root. 117 wazero.NewFSConfig().WithDirMount(tempDir, "/")). 118 WithStderr(pw). // Write Stderr to pw 119 WithStdout(&consoleBuf). 120 WithStartFunctions(). 121 WithSysNanosleep() 122 123 ch := make(chan string, 1) 124 go func() { 125 r := wazero.NewRuntime(testCtx) 126 defer r.Close(testCtx) 127 128 _, err := wasi_snapshot_preview1.Instantiate(testCtx, r) 129 require.NoError(t, err) 130 131 mod, err := r.InstantiateWithConfig(testCtx, wasmGotip, moduleConfig) // clear 132 require.NoError(t, err) 133 134 _, err = mod.ExportedFunction("_start").Call(testCtx) 135 if exitErr, ok := err.(*sys.ExitError); ok { 136 require.Zero(t, exitErr.ExitCode(), consoleBuf.String()) 137 } 138 ch <- consoleBuf.String() 139 }() 140 141 scanner := bufio.NewScanner(pr) 142 require.True(t, scanner.Scan(), fmt.Sprintf("expected line: %s", scanner.Err())) 143 require.Equal(t, "waiting", scanner.Text(), fmt.Sprintf("unexpected output: %s", scanner.Text())) 144 145 for _, fifo := range fifos { 146 _, err := fifo.file.WriteString(fifo.path + "\n") 147 require.NoError(t, err) 148 require.True(t, scanner.Scan(), fmt.Sprintf("expected line: %s", scanner.Err())) 149 require.Equal(t, fifo.path, scanner.Text(), fmt.Sprintf("unexpected line: %s", scanner.Text())) 150 } 151 152 s := <-ch 153 require.Equal(t, "", s) 154 }) 155 } 156 }