github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/poll_test.go (about)

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