github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/imports/wasi_snapshot_preview1/testdata/gotip/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/bananabytelabs/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  }`