github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/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  	"github.com/bananabytelabs/wazero"
    18  	"github.com/bananabytelabs/wazero/api"
    19  	"github.com/bananabytelabs/wazero/imports/wasi_snapshot_preview1"
    20  	"github.com/bananabytelabs/wazero/internal/testing/require"
    21  	"github.com/bananabytelabs/wazero/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  }