github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/tests/wasm/setup_test.go (about)

     1  package wasm
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os/exec"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/chromedp/cdproto/cdp"
    16  	"github.com/chromedp/cdproto/runtime"
    17  	"github.com/chromedp/chromedp"
    18  )
    19  
    20  func run(t *testing.T, cmdline string) error {
    21  	args := strings.Fields(cmdline)
    22  	return runargs(t, args...)
    23  }
    24  
    25  func runargs(t *testing.T, args ...string) error {
    26  	cmd := exec.Command(args[0], args[1:]...)
    27  	b, err := cmd.CombinedOutput()
    28  	t.Logf("Command: %s; err=%v; full output:\n%s", strings.Join(args, " "), err, b)
    29  	if err != nil {
    30  		return err
    31  	}
    32  	return nil
    33  }
    34  
    35  func chromectx(t *testing.T) context.Context {
    36  	// looks for locally installed Chrome
    37  	ctx, ccancel := chromedp.NewContext(context.Background(), chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf))
    38  	t.Cleanup(ccancel)
    39  
    40  	// Wait for browser to be ready.
    41  	err := chromedp.Run(ctx)
    42  	if err != nil {
    43  		t.Fatalf("failed to start browser: %s", err.Error())
    44  	}
    45  
    46  	ctx, tcancel := context.WithTimeout(ctx, 30*time.Second)
    47  	t.Cleanup(tcancel)
    48  
    49  	return ctx
    50  }
    51  
    52  func startServer(t *testing.T) (string, *httptest.Server) {
    53  	tmpDir := t.TempDir()
    54  
    55  	fsh := http.FileServer(http.Dir(tmpDir))
    56  	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    57  
    58  		if r.URL.Path == "/wasm_exec.js" {
    59  			http.ServeFile(w, r, "../../targets/wasm_exec.js")
    60  			return
    61  		}
    62  
    63  		if r.URL.Path == "/run" {
    64  			fmt.Fprintf(w, `<!doctype html>
    65  <html>
    66  <head>
    67  <title>Test</title>
    68  <meta charset="utf-8"/>
    69  </head>
    70  <body>
    71  <div id="main"></div>
    72  <pre id="log"></pre>
    73  <script>
    74  window.wasmLogOutput  = [];
    75  (function() {
    76  	var logdiv = document.getElementById('log');
    77  	var cl = console.log;
    78  	console.log = function() {
    79  		var a = [];
    80  		for (var i = 0; i < arguments.length; i++) {
    81  			a.push(arguments[i]);
    82  		}
    83  		var line = a.join(' ') + "\n";
    84  		window.wasmLogOutput.push(line);
    85  		var ret = cl.apply(console, arguments)
    86  		var el = document.createElement('span');
    87  		el.innerText = line;
    88  		logdiv.appendChild(el);
    89  		return ret
    90  	}
    91  })()
    92  </script>
    93  <script src="/wasm_exec.js"></script>
    94  <script>
    95  var wasmSupported = (typeof WebAssembly === "object");
    96  if (wasmSupported) {
    97  	var mainWasmReq = fetch("/%s").then(function(res) {
    98  		if (res.ok) {
    99  			const go = new Go();
   100  			WebAssembly.instantiateStreaming(res, go.importObject).then((result) => {
   101  				go.run(result.instance);
   102  			});		
   103  		} else {
   104  			res.text().then(function(txt) {
   105  				var el = document.getElementById("main");
   106  				el.style = 'font-family: monospace; background: black; color: red; padding: 10px';
   107  				el.innerText = txt;
   108  			})
   109  		}
   110  	})
   111  } else {
   112  	document.getElementById("main").innerHTML = 'This application requires WebAssembly support.  Please upgrade your browser.';
   113  }
   114  </script>
   115  </body>
   116  </html>`, r.FormValue("file"))
   117  			return
   118  		}
   119  
   120  		fsh.ServeHTTP(w, r)
   121  	})
   122  
   123  	server := httptest.NewServer(h)
   124  	t.Logf("Started server at %q for dir: %s", server.URL, tmpDir)
   125  	t.Cleanup(server.Close)
   126  
   127  	return tmpDir, server
   128  }
   129  
   130  // waitLog blocks until the log output equals the text provided (ignoring whitespace before and after)
   131  func waitLog(logText string) chromedp.QueryAction {
   132  	return waitInnerTextTrimEq("#log", strings.TrimSpace(logText))
   133  }
   134  
   135  // waitLogRe blocks until the log output matches this regular expression
   136  func waitLogRe(restr string) chromedp.QueryAction {
   137  	return waitInnerTextMatch("#log", regexp.MustCompile(restr))
   138  }
   139  
   140  // waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific text pattern (ignoring whitespace before and after)
   141  func waitInnerTextTrimEq(sel string, innerText string) chromedp.QueryAction {
   142  	return waitInnerTextMatch(sel, regexp.MustCompile(`^\s*`+regexp.QuoteMeta(innerText)+`\s*$`))
   143  }
   144  
   145  // waitInnerTextMatch will wait for the innerText of the specified element to match a specific regexp pattern
   146  func waitInnerTextMatch(sel string, re *regexp.Regexp) chromedp.QueryAction {
   147  
   148  	return chromedp.Query(sel, func(s *chromedp.Selector) {
   149  
   150  		chromedp.WaitFunc(func(ctx context.Context, cur *cdp.Frame, execCtx runtime.ExecutionContextID, ids ...cdp.NodeID) ([]*cdp.Node, error) {
   151  
   152  			nodes := make([]*cdp.Node, len(ids))
   153  			cur.RLock()
   154  			for i, id := range ids {
   155  				nodes[i] = cur.Nodes[id]
   156  				if nodes[i] == nil {
   157  					cur.RUnlock()
   158  					// not yet ready
   159  					return nil, nil
   160  				}
   161  			}
   162  			cur.RUnlock()
   163  
   164  			var ret string
   165  			err := chromedp.EvaluateAsDevTools("document.querySelector('"+sel+"').innerText", &ret).Do(ctx)
   166  			if err != nil {
   167  				return nodes, err
   168  			}
   169  			if !re.MatchString(ret) {
   170  				// log.Printf("found text: %s", ret)
   171  				return nodes, errors.New("unexpected value: " + ret)
   172  			}
   173  
   174  			// log.Printf("NodeValue: %#v", nodes[0])
   175  
   176  			// return nil, errors.New("not ready yet")
   177  			return nodes, nil
   178  		})(s)
   179  
   180  	})
   181  
   182  }