|
| 1 | +"use strict"; |
| 2 | +/* eslint-disable no-console, global-require */ |
| 3 | +const dns = require("dns").promises; |
| 4 | +const path = require("path"); |
| 5 | +const childProcess = require("child_process"); |
| 6 | +const http = require("http"); |
| 7 | +const https = require("https"); |
| 8 | +const os = require("os"); |
| 9 | + |
| 10 | +const wptDir = path.resolve(__dirname, "tests"); |
| 11 | + |
| 12 | +const configPaths = { |
| 13 | + default: path.resolve(__dirname, "wpt-config.json"), |
| 14 | + toUpstream: path.resolve(__dirname, "tuwpt-config.json") |
| 15 | +}; |
| 16 | + |
| 17 | +const configs = { |
| 18 | + default: require(configPaths.default), |
| 19 | + toUpstream: require(configPaths.toUpstream) |
| 20 | +}; |
| 21 | + |
| 22 | +exports.start = async ({ toUpstream = false } = {}) => { |
| 23 | + const configType = toUpstream ? "toUpstream" : "default"; |
| 24 | + const configPath = configPaths[configType]; |
| 25 | + const config = configs[configType]; |
| 26 | + |
| 27 | + try { |
| 28 | + await dns.lookup("web-platform.test"); |
| 29 | + } catch { |
| 30 | + throw new Error("Host entries not present for web platform tests. See " + |
| 31 | + "https://web-platform-tests.org/running-tests/from-local-system.html#system-setup"); |
| 32 | + } |
| 33 | + |
| 34 | + const configArg = path.relative(path.resolve(wptDir), configPath); |
| 35 | + const args = ["./wpt.py", "serve", "--config", configArg]; |
| 36 | + const subprocess = childProcess.spawn("python", args, { |
| 37 | + cwd: wptDir, |
| 38 | + stdio: ["inherit", "pipe", "pipe"] |
| 39 | + }); |
| 40 | + |
| 41 | + subprocess.stdout.filter(nonSpammyWPTLog).pipe(process.stdout); |
| 42 | + subprocess.stderr.filter(nonSpammyWPTLog).pipe(process.stderr); |
| 43 | + |
| 44 | + return new Promise((resolve, reject) => { |
| 45 | + subprocess.on("error", e => { |
| 46 | + reject(new Error("Error starting python server process:", e.message)); |
| 47 | + }); |
| 48 | + |
| 49 | + resolve(Promise.all([ |
| 50 | + pollForServer(`http://${config.browser_host}:${config.ports.http[0]}/`), |
| 51 | + pollForServer(`https://${config.browser_host}:${config.ports.https[0]}/`), |
| 52 | + pollForServer(`http://${config.browser_host}:${config.ports.ws[0]}/`), |
| 53 | + pollForServer(`https://${config.browser_host}:${config.ports.wss[0]}/`) |
| 54 | + ]).then(urls => ({ urls, subprocess }))); |
| 55 | + }); |
| 56 | +}; |
| 57 | + |
| 58 | +exports.kill = subprocess => { |
| 59 | + if (os.platform() === "win32") { |
| 60 | + // subprocess.kill() doesn't seem to be able to kill descendant processes on Windows, at least with whatever's going |
| 61 | + // on inside the web-platform-tests Python. Use this technique instead. |
| 62 | + childProcess.spawnSync("taskkill", ["/F", "/T", "/PID", subprocess.pid], { detached: true, windowsHide: true }); |
| 63 | + } else { |
| 64 | + // SIGINT is necessary so that the Python script can clean up its subprocesses. |
| 65 | + subprocess.kill("SIGINT"); |
| 66 | + } |
| 67 | +}; |
| 68 | + |
| 69 | +function pollForServer(url, lastLogTime = Date.now()) { |
| 70 | + const agent = url.startsWith("https") ? new https.Agent({ rejectUnauthorized: false }) : null; |
| 71 | + const { request } = url.startsWith("https") ? https : http; |
| 72 | + |
| 73 | + // Using raw Node.js http/https modules is gross, but it's not worth pulling in something like node-fetch for just |
| 74 | + // this one part of the test codebase. |
| 75 | + return new Promise((resolve, reject) => { |
| 76 | + const req = request(url, { method: "HEAD", agent }, res => { |
| 77 | + if (res.statusCode < 200 || res.statusCode > 299) { |
| 78 | + reject(new Error(`Unexpected status=${res.statusCode}`)); |
| 79 | + } else { |
| 80 | + resolve(url); |
| 81 | + } |
| 82 | + }); |
| 83 | + |
| 84 | + req.on("error", reject); |
| 85 | + req.end(); |
| 86 | + }).catch(err => { |
| 87 | + // Only log every 5 seconds to be less spammy. |
| 88 | + if (Date.now() - lastLogTime >= 5000) { |
| 89 | + console.log(`WPT server at ${url} is not up yet (${err.message}); trying again`); |
| 90 | + lastLogTime = Date.now(); |
| 91 | + } |
| 92 | + |
| 93 | + return new Promise(resolve => { |
| 94 | + setTimeout(() => resolve(pollForServer(url, lastLogTime)), 500); |
| 95 | + }); |
| 96 | + }); |
| 97 | +} |
| 98 | + |
| 99 | +function nonSpammyWPTLog(buffer) { |
| 100 | + const string = buffer.toString("utf-8"); |
| 101 | + |
| 102 | + // Subprocess shutdown is uninteresting. |
| 103 | + if (string.includes("Status of subprocess")) { |
| 104 | + return false; |
| 105 | + } |
| 106 | + if (string.includes("INFO - Stopped")) { |
| 107 | + return false; |
| 108 | + } |
| 109 | + |
| 110 | + // We'll get one message for each server startup. We don't need four more. |
| 111 | + if (string.includes("INFO - Close on:") || |
| 112 | + string.includes("INFO - Bind on:") || |
| 113 | + string.includes("INFO - Listen on:") || |
| 114 | + string.includes("INFO - Create socket on:")) { |
| 115 | + return false; |
| 116 | + } |
| 117 | + |
| 118 | + // Probing / on the ws and wss ports will cause a fallback, and log some messages about it. |
| 119 | + // Those are expected and are uninteresting. |
| 120 | + if (/wss? on port \d+\] INFO - (No handler|Fallback to|"HEAD \/ HTTP)/.test(string)) { |
| 121 | + return false; |
| 122 | + } |
| 123 | + |
| 124 | + return true; |
| 125 | +} |
0 commit comments