全方位推进合理、高效、快速处理上下文、禁止顺手扩功能 smoke:收紧WASM烟测守卫
This commit is contained in:
@@ -188,5 +188,5 @@ cp build/wasm/cnc_sim.js web/public/cnc_sim.js
|
|||||||
cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm
|
cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm
|
||||||
cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js
|
cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js
|
||||||
cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm
|
cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm
|
||||||
node test-web-wasm-node-smoke.cjs
|
./test-web-wasm-node-smoke.sh
|
||||||
./test-web-wasm-browser-smoke.sh
|
./test-web-wasm-browser-smoke.sh
|
||||||
|
|||||||
@@ -281,7 +281,8 @@ Build and source-link guardrails:
|
|||||||
`build/wasm/cnc_sim.wasm` exactly once each.
|
`build/wasm/cnc_sim.wasm` exactly once each.
|
||||||
- `build-wasm.sh` must compare copied artifacts with `cmp -s` before running
|
- `build-wasm.sh` must compare copied artifacts with `cmp -s` before running
|
||||||
Node or browser smoke tests.
|
Node or browser smoke tests.
|
||||||
- `build-wasm.sh` must run the Node smoke before the browser smoke.
|
- `build-wasm.sh` must run `test-web-wasm-node-smoke.sh` before the browser
|
||||||
|
smoke so the Node prerequisite, syntax, and minimum-step checks are enforced.
|
||||||
- The default wasm manifest partition must remain explicitly checked for 25
|
- The default wasm manifest partition must remain explicitly checked for 25
|
||||||
wasm-safe core sources and 9 blocked sources.
|
wasm-safe core sources and 9 blocked sources.
|
||||||
- Manifest source listers must reject malformed lines, absolute source paths,
|
- Manifest source listers must reject malformed lines, absolute source paths,
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ if ! awk '
|
|||||||
index($0, "cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm") { cp_wasm_line = NR }
|
index($0, "cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm") { cp_wasm_line = NR }
|
||||||
index($0, "cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js") { cmp_js_line = NR }
|
index($0, "cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js") { cmp_js_line = NR }
|
||||||
index($0, "cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm") { cmp_wasm_line = NR }
|
index($0, "cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm") { cmp_wasm_line = NR }
|
||||||
index($0, "node test-web-wasm-node-smoke.cjs") { node_line = NR }
|
index($0, "./test-web-wasm-node-smoke.sh") { node_line = NR }
|
||||||
index($0, "./test-web-wasm-browser-smoke.sh") { browser_line = NR }
|
index($0, "./test-web-wasm-browser-smoke.sh") { browser_line = NR }
|
||||||
END {
|
END {
|
||||||
exit !(cp_js_line && cp_wasm_line && cmp_js_line && cmp_wasm_line && node_line && browser_line &&
|
exit !(cp_js_line && cp_wasm_line && cmp_js_line && cmp_wasm_line && node_line && browser_line &&
|
||||||
@@ -508,7 +508,7 @@ grep -F 'must check `cmake`, `emcmake`, `emcc`, and `node`' docs/linuxcnc-source
|
|||||||
grep -F 'must build `cnc_sim_wasm_runtime_probe` before' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'must build `cnc_sim_wasm_runtime_probe` before' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'must copy `build/wasm/cnc_sim.js` and' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'must copy `build/wasm/cnc_sim.js` and' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'must compare copied artifacts with `cmp -s` before running' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'must compare copied artifacts with `cmp -s` before running' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'must run the Node smoke before the browser smoke' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'must run `test-web-wasm-node-smoke.sh` before the browser' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'explicitly checked for 25' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'explicitly checked for 25' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'wasm-safe core sources and 9 blocked sources' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'wasm-safe core sources and 9 blocked sources' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
grep -F 'must reject malformed lines, absolute source paths,' docs/linuxcnc-source-policy.md >/dev/null
|
grep -F 'must reject malformed lines, absolute source paths,' docs/linuxcnc-source-policy.md >/dev/null
|
||||||
|
|||||||
@@ -48,15 +48,10 @@ if [[ -z "$chromium_bin" ]]; then
|
|||||||
elif [[ ! -x "$chromium_bin" ]]; then
|
elif [[ ! -x "$chromium_bin" ]]; then
|
||||||
missing+=("executable chromium at $chromium_bin")
|
missing+=("executable chromium at $chromium_bin")
|
||||||
fi
|
fi
|
||||||
required_files=(
|
cdp_driver=web/test-browser-wasm-smoke-cdp.py
|
||||||
web/public/cnc_sim.js
|
browser_smoke_modules=(
|
||||||
web/public/cnc_sim.wasm
|
|
||||||
web/public/linuxcnc_switchkins_remap_config_cases.json
|
|
||||||
web/src/wasm-core.js
|
web/src/wasm-core.js
|
||||||
web/src/app.js
|
web/src/app.js
|
||||||
web/index.html
|
|
||||||
web/styles.css
|
|
||||||
web/test-browser-wasm-smoke.html
|
|
||||||
web/test-browser-wasm-smoke-helpers.js
|
web/test-browser-wasm-smoke-helpers.js
|
||||||
web/test-browser-wasm-smoke-sections.js
|
web/test-browser-wasm-smoke-sections.js
|
||||||
web/test-browser-wasm-smoke-linuxcnc-sections.js
|
web/test-browser-wasm-smoke-linuxcnc-sections.js
|
||||||
@@ -68,6 +63,16 @@ required_files=(
|
|||||||
web/test-browser-wasm-smoke-opfs-policy-sections.js
|
web/test-browser-wasm-smoke-opfs-policy-sections.js
|
||||||
web/test-browser-wasm-smoke-app-sections.js
|
web/test-browser-wasm-smoke-app-sections.js
|
||||||
)
|
)
|
||||||
|
required_files=(
|
||||||
|
web/public/cnc_sim.js
|
||||||
|
web/public/cnc_sim.wasm
|
||||||
|
web/public/linuxcnc_switchkins_remap_config_cases.json
|
||||||
|
web/index.html
|
||||||
|
web/styles.css
|
||||||
|
web/test-browser-wasm-smoke.html
|
||||||
|
"$cdp_driver"
|
||||||
|
"${browser_smoke_modules[@]}"
|
||||||
|
)
|
||||||
|
|
||||||
for required in \
|
for required in \
|
||||||
node \
|
node \
|
||||||
@@ -168,20 +173,7 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for web_module in \
|
for web_module in "${browser_smoke_modules[@]}"; do
|
||||||
web/src/wasm-core.js \
|
|
||||||
web/src/app.js \
|
|
||||||
web/test-browser-wasm-smoke-helpers.js \
|
|
||||||
web/test-browser-wasm-smoke-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-linuxcnc-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-workspace-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-basic-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-parameter-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-mirror-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-directory-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-opfs-policy-sections.js \
|
|
||||||
web/test-browser-wasm-smoke-app-sections.js
|
|
||||||
do
|
|
||||||
if ! node --check "$web_module"; then
|
if ! node --check "$web_module"; then
|
||||||
echo "WASM browser smoke web module syntax check failed for $web_module" >&2
|
echo "WASM browser smoke web module syntax check failed for $web_module" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -323,18 +315,9 @@ probe_http_resource "/styles.css" "" "text/css"
|
|||||||
probe_http_resource "/public/cnc_sim.js" "" "text/javascript"
|
probe_http_resource "/public/cnc_sim.js" "" "text/javascript"
|
||||||
probe_http_resource "/public/cnc_sim.wasm" "" "application/wasm"
|
probe_http_resource "/public/cnc_sim.wasm" "" "application/wasm"
|
||||||
probe_http_resource "/public/linuxcnc_switchkins_remap_config_cases.json" "[" "application/json"
|
probe_http_resource "/public/linuxcnc_switchkins_remap_config_cases.json" "[" "application/json"
|
||||||
probe_http_resource "/src/app.js" "" "text/javascript"
|
for web_module in "${browser_smoke_modules[@]}"; do
|
||||||
probe_http_resource "/src/wasm-core.js" "" "text/javascript"
|
probe_http_resource "${web_module#web}" "" "text/javascript"
|
||||||
probe_http_resource "/test-browser-wasm-smoke-helpers.js" "" "text/javascript"
|
done
|
||||||
probe_http_resource "/test-browser-wasm-smoke-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-linuxcnc-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-workspace-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-basic-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-parameter-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-mirror-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-directory-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-opfs-policy-sections.js" "" "text/javascript"
|
|
||||||
probe_http_resource "/test-browser-wasm-smoke-app-sections.js" "" "text/javascript"
|
|
||||||
|
|
||||||
debug_port=$(pick_free_port)
|
debug_port=$(pick_free_port)
|
||||||
"$chromium_bin" \
|
"$chromium_bin" \
|
||||||
@@ -346,161 +329,7 @@ debug_port=$(pick_free_port)
|
|||||||
"http://127.0.0.1:$port/test-browser-wasm-smoke.html" >"$dom_log" 2>&1 &
|
"http://127.0.0.1:$port/test-browser-wasm-smoke.html" >"$dom_log" 2>&1 &
|
||||||
chromium_pid=$!
|
chromium_pid=$!
|
||||||
|
|
||||||
if ! python3 - "$debug_port" "$dom_log" "$virtual_time_budget" <<'PY'
|
if ! python3 "$cdp_driver" "$debug_port" "$dom_log" "$virtual_time_budget"; then
|
||||||
import base64
|
|
||||||
import http.client
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
debug_port = int(sys.argv[1])
|
|
||||||
dom_log = sys.argv[2]
|
|
||||||
timeout_ms = int(sys.argv[3])
|
|
||||||
deadline = time.monotonic() + (timeout_ms / 1000.0)
|
|
||||||
|
|
||||||
def http_json(path):
|
|
||||||
conn = http.client.HTTPConnection("127.0.0.1", debug_port, timeout=1)
|
|
||||||
try:
|
|
||||||
conn.request("GET", path)
|
|
||||||
response = conn.getresponse()
|
|
||||||
body = response.read()
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
if response.status != 200:
|
|
||||||
raise RuntimeError(f"{path} returned HTTP {response.status}")
|
|
||||||
return json.loads(body.decode("utf-8"))
|
|
||||||
|
|
||||||
def websocket_connect(url):
|
|
||||||
parsed = urlparse(url)
|
|
||||||
key = base64.b64encode(os.urandom(16)).decode("ascii")
|
|
||||||
sock = socket.create_connection((parsed.hostname, parsed.port), timeout=2)
|
|
||||||
request = (
|
|
||||||
f"GET {parsed.path} HTTP/1.1\r\n"
|
|
||||||
f"Host: {parsed.hostname}:{parsed.port}\r\n"
|
|
||||||
"Upgrade: websocket\r\n"
|
|
||||||
"Connection: Upgrade\r\n"
|
|
||||||
f"Sec-WebSocket-Key: {key}\r\n"
|
|
||||||
"Sec-WebSocket-Version: 13\r\n\r\n"
|
|
||||||
)
|
|
||||||
sock.sendall(request.encode("ascii"))
|
|
||||||
response = sock.recv(4096)
|
|
||||||
if b" 101 " not in response.split(b"\r\n", 1)[0]:
|
|
||||||
raise RuntimeError("DevTools websocket handshake failed")
|
|
||||||
sock.settimeout(1)
|
|
||||||
return sock
|
|
||||||
|
|
||||||
def websocket_send(sock, payload):
|
|
||||||
data = payload.encode("utf-8")
|
|
||||||
header = bytearray([0x81])
|
|
||||||
if len(data) < 126:
|
|
||||||
header.append(0x80 | len(data))
|
|
||||||
elif len(data) < 65536:
|
|
||||||
header.append(0x80 | 126)
|
|
||||||
header.extend(struct.pack("!H", len(data)))
|
|
||||||
else:
|
|
||||||
header.append(0x80 | 127)
|
|
||||||
header.extend(struct.pack("!Q", len(data)))
|
|
||||||
mask = os.urandom(4)
|
|
||||||
header.extend(mask)
|
|
||||||
masked = bytes(byte ^ mask[index % 4] for index, byte in enumerate(data))
|
|
||||||
sock.sendall(header + masked)
|
|
||||||
|
|
||||||
def recv_exact(sock, size):
|
|
||||||
chunks = []
|
|
||||||
remaining = size
|
|
||||||
while remaining:
|
|
||||||
chunk = sock.recv(remaining)
|
|
||||||
if not chunk:
|
|
||||||
raise RuntimeError("DevTools websocket closed")
|
|
||||||
chunks.append(chunk)
|
|
||||||
remaining -= len(chunk)
|
|
||||||
return b"".join(chunks)
|
|
||||||
|
|
||||||
def websocket_recv(sock):
|
|
||||||
first, second = recv_exact(sock, 2)
|
|
||||||
opcode = first & 0x0F
|
|
||||||
length = second & 0x7F
|
|
||||||
if length == 126:
|
|
||||||
length = struct.unpack("!H", recv_exact(sock, 2))[0]
|
|
||||||
elif length == 127:
|
|
||||||
length = struct.unpack("!Q", recv_exact(sock, 8))[0]
|
|
||||||
masked = bool(second & 0x80)
|
|
||||||
mask = recv_exact(sock, 4) if masked else b""
|
|
||||||
payload = recv_exact(sock, length)
|
|
||||||
if masked:
|
|
||||||
payload = bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
|
|
||||||
if opcode == 8:
|
|
||||||
raise RuntimeError("DevTools websocket closed")
|
|
||||||
if opcode != 1:
|
|
||||||
return None
|
|
||||||
return json.loads(payload.decode("utf-8"))
|
|
||||||
|
|
||||||
def find_page_ws_url():
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
try:
|
|
||||||
for target in http_json("/json/list"):
|
|
||||||
if target.get("type") == "page" and target.get("url", "").endswith("/test-browser-wasm-smoke.html"):
|
|
||||||
return target["webSocketDebuggerUrl"]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
time.sleep(0.1)
|
|
||||||
raise RuntimeError("timed out waiting for browser DevTools page target")
|
|
||||||
|
|
||||||
sock = websocket_connect(find_page_ws_url())
|
|
||||||
next_id = 0
|
|
||||||
|
|
||||||
def cdp(method, params=None):
|
|
||||||
global next_id
|
|
||||||
next_id += 1
|
|
||||||
message_id = next_id
|
|
||||||
websocket_send(sock, json.dumps({"id": message_id, "method": method, "params": params or {}}))
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
try:
|
|
||||||
message = websocket_recv(sock)
|
|
||||||
except TimeoutError:
|
|
||||||
continue
|
|
||||||
except socket.timeout:
|
|
||||||
continue
|
|
||||||
if message and message.get("id") == message_id:
|
|
||||||
if "error" in message:
|
|
||||||
raise RuntimeError(message["error"])
|
|
||||||
return message["result"]
|
|
||||||
raise RuntimeError(f"timed out waiting for DevTools response to {method}")
|
|
||||||
|
|
||||||
def evaluate(expression):
|
|
||||||
result = cdp("Runtime.evaluate", {"expression": expression, "returnByValue": True})
|
|
||||||
return result.get("result", {}).get("value", "")
|
|
||||||
|
|
||||||
try:
|
|
||||||
cdp("Runtime.enable")
|
|
||||||
text = ""
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
text = evaluate('document.querySelector("#result")?.textContent || ""')
|
|
||||||
if "browser wasm smoke passed" in text:
|
|
||||||
html = evaluate("document.documentElement.outerHTML")
|
|
||||||
with open(dom_log, "w", encoding="utf-8") as handle:
|
|
||||||
handle.write(html)
|
|
||||||
sys.exit(0)
|
|
||||||
if "browser wasm smoke failed:" in text:
|
|
||||||
print(text, file=sys.stderr)
|
|
||||||
html = evaluate("document.documentElement.outerHTML")
|
|
||||||
with open(dom_log, "w", encoding="utf-8") as handle:
|
|
||||||
handle.write(html)
|
|
||||||
sys.exit(1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
print(f"timed out waiting for browser smoke result; last status: {text}", file=sys.stderr)
|
|
||||||
html = evaluate("document.documentElement.outerHTML")
|
|
||||||
with open(dom_log, "w", encoding="utf-8") as handle:
|
|
||||||
handle.write(html)
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
|
||||||
sock.close()
|
|
||||||
PY
|
|
||||||
then
|
|
||||||
echo "browser WASM smoke failed or timed out" >&2
|
echo "browser WASM smoke failed or timed out" >&2
|
||||||
print_browser_failure_context
|
print_browser_failure_context
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
163
web/test-browser-wasm-smoke-cdp.py
Normal file
163
web/test-browser-wasm-smoke-cdp.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import base64
|
||||||
|
import http.client
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
debug_port = int(sys.argv[1])
|
||||||
|
dom_log = sys.argv[2]
|
||||||
|
timeout_ms = int(sys.argv[3])
|
||||||
|
deadline = time.monotonic() + (timeout_ms / 1000.0)
|
||||||
|
|
||||||
|
|
||||||
|
def http_json(path):
|
||||||
|
conn = http.client.HTTPConnection("127.0.0.1", debug_port, timeout=1)
|
||||||
|
try:
|
||||||
|
conn.request("GET", path)
|
||||||
|
response = conn.getresponse()
|
||||||
|
body = response.read()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
if response.status != 200:
|
||||||
|
raise RuntimeError(f"{path} returned HTTP {response.status}")
|
||||||
|
return json.loads(body.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def websocket_connect(url):
|
||||||
|
parsed = urlparse(url)
|
||||||
|
key = base64.b64encode(os.urandom(16)).decode("ascii")
|
||||||
|
sock = socket.create_connection((parsed.hostname, parsed.port), timeout=2)
|
||||||
|
request = (
|
||||||
|
f"GET {parsed.path} HTTP/1.1\r\n"
|
||||||
|
f"Host: {parsed.hostname}:{parsed.port}\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
f"Sec-WebSocket-Key: {key}\r\n"
|
||||||
|
"Sec-WebSocket-Version: 13\r\n\r\n"
|
||||||
|
)
|
||||||
|
sock.sendall(request.encode("ascii"))
|
||||||
|
response = sock.recv(4096)
|
||||||
|
if b" 101 " not in response.split(b"\r\n", 1)[0]:
|
||||||
|
raise RuntimeError("DevTools websocket handshake failed")
|
||||||
|
sock.settimeout(1)
|
||||||
|
return sock
|
||||||
|
|
||||||
|
|
||||||
|
def websocket_send(sock, payload):
|
||||||
|
data = payload.encode("utf-8")
|
||||||
|
header = bytearray([0x81])
|
||||||
|
if len(data) < 126:
|
||||||
|
header.append(0x80 | len(data))
|
||||||
|
elif len(data) < 65536:
|
||||||
|
header.append(0x80 | 126)
|
||||||
|
header.extend(struct.pack("!H", len(data)))
|
||||||
|
else:
|
||||||
|
header.append(0x80 | 127)
|
||||||
|
header.extend(struct.pack("!Q", len(data)))
|
||||||
|
mask = os.urandom(4)
|
||||||
|
header.extend(mask)
|
||||||
|
masked = bytes(byte ^ mask[index % 4] for index, byte in enumerate(data))
|
||||||
|
sock.sendall(header + masked)
|
||||||
|
|
||||||
|
|
||||||
|
def recv_exact(sock, size):
|
||||||
|
chunks = []
|
||||||
|
remaining = size
|
||||||
|
while remaining:
|
||||||
|
chunk = sock.recv(remaining)
|
||||||
|
if not chunk:
|
||||||
|
raise RuntimeError("DevTools websocket closed")
|
||||||
|
chunks.append(chunk)
|
||||||
|
remaining -= len(chunk)
|
||||||
|
return b"".join(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
def websocket_recv(sock):
|
||||||
|
first, second = recv_exact(sock, 2)
|
||||||
|
opcode = first & 0x0F
|
||||||
|
length = second & 0x7F
|
||||||
|
if length == 126:
|
||||||
|
length = struct.unpack("!H", recv_exact(sock, 2))[0]
|
||||||
|
elif length == 127:
|
||||||
|
length = struct.unpack("!Q", recv_exact(sock, 8))[0]
|
||||||
|
masked = bool(second & 0x80)
|
||||||
|
mask = recv_exact(sock, 4) if masked else b""
|
||||||
|
payload = recv_exact(sock, length)
|
||||||
|
if masked:
|
||||||
|
payload = bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
|
||||||
|
if opcode == 8:
|
||||||
|
raise RuntimeError("DevTools websocket closed")
|
||||||
|
if opcode != 1:
|
||||||
|
return None
|
||||||
|
return json.loads(payload.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def find_page_ws_url():
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
try:
|
||||||
|
for target in http_json("/json/list"):
|
||||||
|
if target.get("type") == "page" and target.get("url", "").endswith("/test-browser-wasm-smoke.html"):
|
||||||
|
return target["webSocketDebuggerUrl"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(0.1)
|
||||||
|
raise RuntimeError("timed out waiting for browser DevTools page target")
|
||||||
|
|
||||||
|
|
||||||
|
sock = websocket_connect(find_page_ws_url())
|
||||||
|
next_id = 0
|
||||||
|
|
||||||
|
|
||||||
|
def cdp(method, params=None):
|
||||||
|
global next_id
|
||||||
|
next_id += 1
|
||||||
|
message_id = next_id
|
||||||
|
websocket_send(sock, json.dumps({"id": message_id, "method": method, "params": params or {}}))
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
try:
|
||||||
|
message = websocket_recv(sock)
|
||||||
|
except TimeoutError:
|
||||||
|
continue
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
if message and message.get("id") == message_id:
|
||||||
|
if "error" in message:
|
||||||
|
raise RuntimeError(message["error"])
|
||||||
|
return message["result"]
|
||||||
|
raise RuntimeError(f"timed out waiting for DevTools response to {method}")
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate(expression):
|
||||||
|
result = cdp("Runtime.evaluate", {"expression": expression, "returnByValue": True})
|
||||||
|
return result.get("result", {}).get("value", "")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
cdp("Runtime.enable")
|
||||||
|
text = ""
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
text = evaluate('document.querySelector("#result")?.textContent || ""')
|
||||||
|
if "browser wasm smoke passed" in text:
|
||||||
|
html = evaluate("document.documentElement.outerHTML")
|
||||||
|
with open(dom_log, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write(html)
|
||||||
|
sys.exit(0)
|
||||||
|
if "browser wasm smoke failed:" in text:
|
||||||
|
print(text, file=sys.stderr)
|
||||||
|
html = evaluate("document.documentElement.outerHTML")
|
||||||
|
with open(dom_log, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write(html)
|
||||||
|
sys.exit(1)
|
||||||
|
time.sleep(0.2)
|
||||||
|
print(f"timed out waiting for browser smoke result; last status: {text}", file=sys.stderr)
|
||||||
|
html = evaluate("document.documentElement.outerHTML")
|
||||||
|
with open(dom_log, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write(html)
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
Reference in New Issue
Block a user