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 }