github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/imports/wasi_snapshot_preview1/poll_test.go (about)

     1  package wasi_snapshot_preview1_test
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	wazero "github.com/wasilibs/wazerox"
    11  	"github.com/wasilibs/wazerox/api"
    12  	experimentalsys "github.com/wasilibs/wazerox/experimental/sys"
    13  	"github.com/wasilibs/wazerox/internal/fsapi"
    14  	"github.com/wasilibs/wazerox/internal/sys"
    15  	"github.com/wasilibs/wazerox/internal/testing/require"
    16  	"github.com/wasilibs/wazerox/internal/wasip1"
    17  	"github.com/wasilibs/wazerox/internal/wasm"
    18  	sysapi "github.com/wasilibs/wazerox/sys"
    19  )
    20  
    21  func Test_pollOneoff(t *testing.T) {
    22  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
    23  	defer r.Close(testCtx)
    24  
    25  	mem := []byte{
    26  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
    27  		wasip1.EventTypeClock, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // event type and padding
    28  		wasip1.ClockIDMonotonic, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // clockID
    29  		0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // timeout (ns)
    30  		0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // precision (ns)
    31  		0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // flags (relative)
    32  		'?', // stopped after encoding
    33  	}
    34  
    35  	expectedMem := []byte{
    36  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
    37  		byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
    38  		wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
    39  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    40  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, '?', // stopped after encoding
    41  	}
    42  
    43  	in := uint32(0)    // past in
    44  	out := uint32(128) // past in
    45  	nsubscriptions := uint32(1)
    46  	resultNevents := uint32(512) // past out
    47  
    48  	maskMemory(t, mod, 1024)
    49  	mod.Memory().Write(in, mem)
    50  
    51  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PollOneoffName, uint64(in), uint64(out), uint64(nsubscriptions),
    52  		uint64(resultNevents))
    53  	require.Equal(t, `
    54  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1)
    55  <== (nevents=1,errno=ESUCCESS)
    56  `, "\n"+log.String())
    57  
    58  	outMem, ok := mod.Memory().Read(out, uint32(len(expectedMem)))
    59  	require.True(t, ok)
    60  	require.Equal(t, expectedMem, outMem)
    61  
    62  	nevents, ok := mod.Memory().ReadUint32Le(resultNevents)
    63  	require.True(t, ok)
    64  	require.Equal(t, nsubscriptions, nevents)
    65  }
    66  
    67  func Test_pollOneoff_Errors(t *testing.T) {
    68  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
    69  	defer r.Close(testCtx)
    70  
    71  	tests := []struct {
    72  		name                                   string
    73  		in, out, nsubscriptions, resultNevents uint32
    74  		mem                                    []byte // at offset in
    75  		expectedErrno                          wasip1.Errno
    76  		expectedMem                            []byte // at offset out
    77  		expectedLog                            string
    78  	}{
    79  		{
    80  			name:           "in out of range",
    81  			in:             wasm.MemoryPageSize,
    82  			nsubscriptions: 1,
    83  			out:            128, // past in
    84  			resultNevents:  512, // past out
    85  			expectedErrno:  wasip1.ErrnoFault,
    86  			expectedLog: `
    87  ==> wasi_snapshot_preview1.poll_oneoff(in=65536,out=128,nsubscriptions=1)
    88  <== (nevents=,errno=EFAULT)
    89  `,
    90  		},
    91  		{
    92  			name:           "out out of range",
    93  			out:            wasm.MemoryPageSize,
    94  			resultNevents:  512, // past out
    95  			nsubscriptions: 1,
    96  			expectedErrno:  wasip1.ErrnoFault,
    97  			expectedLog: `
    98  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=65536,nsubscriptions=1)
    99  <== (nevents=,errno=EFAULT)
   100  `,
   101  		},
   102  		{
   103  			name:           "resultNevents out of range",
   104  			resultNevents:  wasm.MemoryPageSize,
   105  			nsubscriptions: 1,
   106  			expectedErrno:  wasip1.ErrnoFault,
   107  			expectedLog: `
   108  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=0,nsubscriptions=1)
   109  <== (nevents=,errno=EFAULT)
   110  `,
   111  		},
   112  		{
   113  			name:          "nsubscriptions zero",
   114  			out:           128, // past in
   115  			resultNevents: 512, // past out
   116  			expectedErrno: wasip1.ErrnoInval,
   117  			expectedLog: `
   118  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=0)
   119  <== (nevents=,errno=EINVAL)
   120  `,
   121  		},
   122  	}
   123  
   124  	for _, tt := range tests {
   125  		tc := tt
   126  		t.Run(tc.name, func(t *testing.T) {
   127  			defer log.Reset()
   128  
   129  			maskMemory(t, mod, 1024)
   130  
   131  			if tc.mem != nil {
   132  				mod.Memory().Write(tc.in, tc.mem)
   133  			}
   134  
   135  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PollOneoffName, uint64(tc.in), uint64(tc.out),
   136  				uint64(tc.nsubscriptions), uint64(tc.resultNevents))
   137  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   138  
   139  			out, ok := mod.Memory().Read(tc.out, uint32(len(tc.expectedMem)))
   140  			require.True(t, ok)
   141  			require.Equal(t, tc.expectedMem, out)
   142  
   143  			// Events should be written on success regardless of nested failure.
   144  			if tc.expectedErrno == wasip1.ErrnoSuccess {
   145  				nevents, ok := mod.Memory().ReadUint32Le(tc.resultNevents)
   146  				require.True(t, ok)
   147  				require.Equal(t, uint32(1), nevents)
   148  				_ = nevents
   149  			}
   150  		})
   151  	}
   152  }
   153  
   154  func Test_pollOneoff_Stdin(t *testing.T) {
   155  	w, r, err := os.Pipe()
   156  	require.NoError(t, err)
   157  	defer w.Close()
   158  	defer r.Close()
   159  	_, _ = w.Write([]byte("wazero"))
   160  
   161  	tests := []struct {
   162  		name                                   string
   163  		in, out, nsubscriptions, resultNevents uint32
   164  		mem                                    []byte // at offset in
   165  		stdin                                  fsapi.File
   166  		expectedErrno                          wasip1.Errno
   167  		expectedMem                            []byte // at offset out
   168  		expectedLog                            string
   169  		expectedNevents                        uint32
   170  	}{
   171  		{
   172  			name:            "Read without explicit timeout (no tty)",
   173  			nsubscriptions:  1,
   174  			expectedNevents: 1,
   175  			stdin:           &sys.StdinFile{Reader: strings.NewReader("test")},
   176  			mem:             fdReadSub,
   177  			expectedErrno:   wasip1.ErrnoSuccess,
   178  			out:             128, // past in
   179  			resultNevents:   512, // past out
   180  			expectedMem: []byte{
   181  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   182  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   183  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   184  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   185  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   186  				0x0, 0x0,
   187  
   188  				'?', // stopped after encoding
   189  			},
   190  			expectedLog: `
   191  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1)
   192  <== (nevents=1,errno=ESUCCESS)
   193  `,
   194  		},
   195  		{
   196  			name:            "20ms timeout, fdread on tty (buffer ready): both events are written",
   197  			nsubscriptions:  2,
   198  			expectedNevents: 2,
   199  			stdin:           &ttyStdinFile{StdinFile: sys.StdinFile{Reader: strings.NewReader("test")}},
   200  			mem: concat(
   201  				clockNsSub(20*1000*1000),
   202  				fdReadSub,
   203  			),
   204  			expectedErrno: wasip1.ErrnoSuccess,
   205  			out:           128, // past in
   206  			resultNevents: 512, // past out
   207  			expectedMem: []byte{
   208  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   209  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   210  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   211  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   212  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   213  				0x0, 0x0,
   214  
   215  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   216  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   217  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   218  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   219  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   220  				0x0, 0x0,
   221  
   222  				'?', // stopped after encoding
   223  			},
   224  			expectedLog: `
   225  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2)
   226  <== (nevents=2,errno=ESUCCESS)
   227  `,
   228  		},
   229  		{
   230  			name:            "0ns timeout, fdread on tty (buffer ready): both are written",
   231  			nsubscriptions:  2,
   232  			expectedNevents: 2,
   233  			stdin:           &ttyStdinFile{StdinFile: sys.StdinFile{Reader: strings.NewReader("test")}},
   234  			mem: concat(
   235  				clockNsSub(20*1000*1000),
   236  				fdReadSub,
   237  			),
   238  			expectedErrno: wasip1.ErrnoSuccess,
   239  			out:           128, // past in
   240  			resultNevents: 512, // past out
   241  			expectedMem: []byte{
   242  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   243  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   244  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   245  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   246  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   247  				0x0, 0x0,
   248  
   249  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   250  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   251  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   252  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   253  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   254  				0x0, 0x0,
   255  
   256  				'?', // stopped after encoding
   257  			},
   258  			expectedLog: `
   259  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2)
   260  <== (nevents=2,errno=ESUCCESS)
   261  `,
   262  		},
   263  		{
   264  			name:            "0ns timeout, fdread on regular file: both events are written",
   265  			nsubscriptions:  2,
   266  			expectedNevents: 2,
   267  			stdin:           &sys.StdinFile{Reader: strings.NewReader("test")},
   268  			mem: concat(
   269  				clockNsSub(20*1000*1000),
   270  				fdReadSub,
   271  			),
   272  			expectedErrno: wasip1.ErrnoSuccess,
   273  			out:           128, // past in
   274  			resultNevents: 512, // past out
   275  			expectedMem: []byte{
   276  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   277  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   278  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   279  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   280  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   281  				0x0, 0x0,
   282  
   283  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   284  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   285  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   286  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   287  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   288  				0x0, 0x0,
   289  
   290  				'?', // stopped after encoding
   291  			},
   292  			expectedLog: `
   293  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2)
   294  <== (nevents=2,errno=ESUCCESS)
   295  `,
   296  		},
   297  		{
   298  			name:            "1ns timeout, fdread on regular file: both events are written",
   299  			nsubscriptions:  2,
   300  			expectedNevents: 2,
   301  			stdin:           &sys.StdinFile{Reader: strings.NewReader("test")},
   302  			mem: concat(
   303  				clockNsSub(20*1000*1000),
   304  				fdReadSub,
   305  			),
   306  			expectedErrno: wasip1.ErrnoSuccess,
   307  			out:           128, // past in
   308  			resultNevents: 512, // past out
   309  			expectedMem: []byte{
   310  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   311  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   312  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   313  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   314  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   315  				0x0, 0x0,
   316  
   317  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   318  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   319  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   320  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   321  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   322  				0x0, 0x0,
   323  
   324  				'?', // stopped after encoding
   325  			},
   326  			expectedLog: `
   327  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2)
   328  <== (nevents=2,errno=ESUCCESS)
   329  `,
   330  		},
   331  		{
   332  			name:            "20ms timeout, fdread on blocked tty: only clock event is written",
   333  			nsubscriptions:  2,
   334  			expectedNevents: 1,
   335  			stdin:           &neverReadyTtyStdinFile{StdinFile: sys.StdinFile{Reader: newBlockingReader(t)}},
   336  			mem: concat(
   337  				clockNsSub(20*1000*1000),
   338  				fdReadSub,
   339  			),
   340  
   341  			expectedErrno: wasip1.ErrnoSuccess,
   342  			out:           128, // past in
   343  			resultNevents: 512, // past out
   344  			expectedMem: []byte{
   345  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   346  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   347  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   348  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   349  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   350  				0x0, 0x0,
   351  
   352  				// 32 empty bytes
   353  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   354  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   355  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   356  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   357  
   358  				'?', // stopped after encoding
   359  			},
   360  			expectedLog: `
   361  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2)
   362  <== (nevents=1,errno=ESUCCESS)
   363  `,
   364  		},
   365  		{
   366  			name:            "pollable pipe, multiple subs, events returned out of order",
   367  			nsubscriptions:  3,
   368  			expectedNevents: 3,
   369  			mem: concat(
   370  				fdReadSub,
   371  				clockNsSub(20*1000*1000),
   372  				// Illegal file fd with custom user data to recognize it in the event buffer.
   373  				fdReadSubFdWithUserData(100, []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77})),
   374  			stdin:         &sys.StdinFile{Reader: w},
   375  			expectedErrno: wasip1.ErrnoSuccess,
   376  			out:           128, // past in
   377  			resultNevents: 512, // past out
   378  			expectedMem: []byte{
   379  				// Clock is acknowledged first.
   380  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   381  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   382  				wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   383  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   384  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   385  				0x0, 0x0,
   386  
   387  				// Then an illegal file with custom user data.
   388  				0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // userdata
   389  				byte(wasip1.ErrnoBadf), 0x0, // errno is 16 bit
   390  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   391  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   392  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   393  				0x0, 0x0,
   394  
   395  				// Stdin pipes are delayed to invoke sysfs.poll
   396  				// thus, they are written back last.
   397  				0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   398  				byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   399  				wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   400  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   401  				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   402  				0x0, 0x0,
   403  
   404  				'?', // stopped after encoding
   405  			},
   406  			expectedLog: `
   407  ==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=3)
   408  <== (nevents=3,errno=ESUCCESS)
   409  `,
   410  		},
   411  	}
   412  
   413  	for _, tt := range tests {
   414  		tc := tt
   415  		t.Run(tc.name, func(t *testing.T) {
   416  			mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
   417  			defer r.Close(testCtx)
   418  			defer log.Reset()
   419  
   420  			setStdin(t, mod, tc.stdin)
   421  
   422  			maskMemory(t, mod, 1024)
   423  			if tc.mem != nil {
   424  				mod.Memory().Write(tc.in, tc.mem)
   425  			}
   426  
   427  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PollOneoffName, uint64(tc.in), uint64(tc.out),
   428  				uint64(tc.nsubscriptions), uint64(tc.resultNevents))
   429  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   430  
   431  			out, ok := mod.Memory().Read(tc.out, uint32(len(tc.expectedMem)))
   432  			require.True(t, ok)
   433  			require.Equal(t, tc.expectedMem, out)
   434  
   435  			// Events should be written on success regardless of nested failure.
   436  			if tc.expectedErrno == wasip1.ErrnoSuccess {
   437  				nevents, ok := mod.Memory().ReadUint32Le(tc.resultNevents)
   438  				require.True(t, ok)
   439  				require.Equal(t, tc.expectedNevents, nevents)
   440  				_ = nevents
   441  			}
   442  		})
   443  	}
   444  }
   445  
   446  func setStdin(t *testing.T, mod api.Module, stdin fsapi.File) {
   447  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   448  	f, ok := fsc.LookupFile(sys.FdStdin)
   449  	require.True(t, ok)
   450  	f.File = stdin
   451  }
   452  
   453  func Test_pollOneoff_Zero(t *testing.T) {
   454  	poller := &pollStdinFile{StdinFile: sys.StdinFile{Reader: strings.NewReader("test")}, ready: true}
   455  
   456  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
   457  	defer r.Close(testCtx)
   458  	defer log.Reset()
   459  
   460  	setStdin(t, mod, poller)
   461  
   462  	maskMemory(t, mod, 1024)
   463  
   464  	out := uint32(128)
   465  	nsubscriptions := 2
   466  	resultNevents := uint32(512)
   467  
   468  	mod.Memory().Write(0,
   469  		concat(
   470  			clockNsSub(20*1000*1000),
   471  			fdReadSub,
   472  		),
   473  	)
   474  
   475  	expectedMem := []byte{
   476  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   477  		byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   478  		wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   479  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   480  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   481  
   482  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   483  		byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   484  		wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum
   485  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // pad to 32
   486  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   487  		0x0, 0x0,
   488  
   489  		'?', // stopped after encoding
   490  	}
   491  
   492  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PollOneoffName, uint64(0), uint64(out),
   493  		uint64(nsubscriptions), uint64(resultNevents))
   494  
   495  	outMem, ok := mod.Memory().Read(out, uint32(len(expectedMem)))
   496  	require.True(t, ok)
   497  	require.Equal(t, expectedMem, outMem)
   498  
   499  	// Events should be written on success regardless of nested failure.
   500  	nevents, ok := mod.Memory().ReadUint32Le(resultNevents)
   501  	require.True(t, ok)
   502  	require.Equal(t, uint32(2), nevents)
   503  
   504  	// second run: simulate no more data on the fd
   505  	poller.ready = false
   506  
   507  	mod.Memory().Write(0,
   508  		concat(
   509  			clockNsSub(20*1000*1000),
   510  			fdReadSub,
   511  		),
   512  	)
   513  
   514  	expectedMem = []byte{
   515  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   516  		byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit
   517  		wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum
   518  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   519  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   520  
   521  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   522  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   523  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   524  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   525  
   526  		'?', // stopped after encoding
   527  	}
   528  
   529  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PollOneoffName, uint64(0), uint64(out),
   530  		uint64(nsubscriptions), uint64(resultNevents))
   531  
   532  	outMem, ok = mod.Memory().Read(out, uint32(len(expectedMem)))
   533  	require.True(t, ok)
   534  	require.Equal(t, expectedMem, outMem)
   535  
   536  	nevents, ok = mod.Memory().ReadUint32Le(resultNevents)
   537  	require.True(t, ok)
   538  	require.Equal(t, uint32(1), nevents)
   539  }
   540  
   541  func concat(bytes ...[]byte) []byte {
   542  	var res []byte
   543  	for i := range bytes {
   544  		res = append(res, bytes[i]...)
   545  	}
   546  	return res
   547  }
   548  
   549  // subscription for a given timeout in ns
   550  func clockNsSub(ns uint64) []byte {
   551  	return []byte{
   552  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   553  		wasip1.EventTypeClock, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // event type and padding
   554  		wasip1.ClockIDMonotonic, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   555  		byte(ns), byte(ns >> 8), byte(ns >> 16), byte(ns >> 24),
   556  		byte(ns >> 32), byte(ns >> 40), byte(ns >> 48), byte(ns >> 56), // timeout (ns)
   557  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // precision (ns)
   558  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // flags
   559  	}
   560  }
   561  
   562  // subscription for an EventTypeFdRead on a given fd
   563  func fdReadSubFd(fd byte) []byte {
   564  	return []byte{
   565  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata
   566  		wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   567  		fd, 0x0, 0x0, 0x0, // valid readable FD
   568  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   569  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   570  		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   571  		0x0, 0x0, 0x0, 0x0, // pad to 32 bytes
   572  	}
   573  }
   574  
   575  func fdReadSubFdWithUserData(fd byte, userdata []byte) []byte {
   576  	return concat(
   577  		userdata,
   578  		[]byte{
   579  			wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   580  			fd, 0x0, 0x0, 0x0, // valid readable FD
   581  			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   582  			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   583  			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   584  			0x0, 0x0, 0x0, 0x0, // pad to 32 bytes
   585  		})
   586  }
   587  
   588  // subscription for an EventTypeFdRead on stdin
   589  var fdReadSub = fdReadSubFd(byte(sys.FdStdin))
   590  
   591  // ttyStat returns fs.ModeCharDevice | fs.ModeCharDevice as an approximation
   592  // for isatty.
   593  //
   594  // See go-isatty for a more specific approach:
   595  // https://github.com/mattn/go-isatty/blob/v0.0.18/isatty_tcgets.go#LL11C1-L12C1
   596  type ttyStat struct{}
   597  
   598  // Stat implements the same method as documented on sys.File
   599  func (ttyStat) Stat() (sysapi.Stat_t, experimentalsys.Errno) {
   600  	return sysapi.Stat_t{
   601  		Mode:  fs.ModeDevice | fs.ModeCharDevice,
   602  		Nlink: 1,
   603  	}, 0
   604  }
   605  
   606  type ttyStdinFile struct {
   607  	sys.StdinFile
   608  	ttyStat
   609  }
   610  
   611  type neverReadyTtyStdinFile struct {
   612  	sys.StdinFile
   613  	ttyStat
   614  }
   615  
   616  // Poll implements the same method as documented on sys.File
   617  func (neverReadyTtyStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
   618  	if flag != fsapi.POLLIN {
   619  		return false, experimentalsys.ENOTSUP
   620  	}
   621  	switch {
   622  	case timeoutMillis <= 0:
   623  		return
   624  	}
   625  	time.Sleep(time.Duration(timeoutMillis) * time.Millisecond)
   626  	return false, 0
   627  }
   628  
   629  type pollStdinFile struct {
   630  	sys.StdinFile
   631  	ttyStat
   632  	ready bool
   633  }
   634  
   635  // Poll implements the same method as documented on sys.File
   636  func (p *pollStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
   637  	if flag != fsapi.POLLIN {
   638  		return false, experimentalsys.ENOTSUP
   639  	}
   640  	return p.ready, 0
   641  }