Files
sanderling/conformance/testdata/generate.py
T

122 lines
4.3 KiB
Python

#!/usr/bin/env python3
# Regenerates the canned gate-analyzer fixtures. Each fixture is a minimal but
# format-accurate run directory (trace.jsonl + output.log + exit_status) that
# isolates one gate's pass or fail condition. Run from this directory:
# python3 generate.py
import json
import os
from datetime import datetime, timedelta, timezone
HERE = os.path.dirname(os.path.abspath(__file__))
BASE = datetime(2026, 6, 6, 12, 0, 0, tzinfo=timezone(timedelta(hours=5, minutes=30)))
def element(resource_id, text="", element_class="android.widget.EditText"):
return {
"resourceId": resource_id,
"class": element_class,
"editable": True,
"bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135},
"attrs": {"resource-id": resource_id, "class": element_class, "text": text},
}
def login_screen(email_text="", password_text=""):
return {
"elements": [
element("LoginScreen", element_class="android.view.View"),
element("LoginEmail", email_text),
element("LoginPassword", password_text),
element("LoginSubmit", element_class="android.view.View"),
]
}
def input_action(field, text):
return {
"kind": "InputText",
"text": text,
"selector": f"testTag:LoginScreen > testTag:{field}",
}
def step(index, gap_ms, hierarchy, next_action=None):
timestamp = (BASE + timedelta(milliseconds=index * gap_ms)).isoformat()
record = {"step": index, "timestamp": timestamp, "hierarchy": hierarchy}
if next_action is not None:
record["next_action"] = next_action
return record
def write_run(name, steps, exit_status="0", output_lines=None):
directory = os.path.join(HERE, name)
os.makedirs(directory, exist_ok=True)
with open(os.path.join(directory, "trace.jsonl"), "w") as handle:
for record in steps:
handle.write(json.dumps(record) + "\n")
with open(os.path.join(directory, "exit_status"), "w") as handle:
handle.write(exit_status)
lines = output_lines if output_lines is not None else ["run complete\n"]
with open(os.path.join(directory, "output.log"), "w") as handle:
handle.writelines(lines)
# A clean run: empty login fields first, single (not doubled) typed values in
# the following snapshots, sub-second step gaps.
def healthy_steps(gap_ms=600):
return [
step(1, gap_ms, login_screen("", ""), input_action("LoginEmail", "[email protected]")),
step(2, gap_ms, login_screen("[email protected]", ""), input_action("LoginPassword", "ledger123")),
step(3, gap_ms, login_screen("[email protected]", "ledger123")),
step(4, gap_ms, login_screen("[email protected]", "ledger123")),
]
BENIGN_NOISE = [
"companion pid=4242 listening on 127.0.0.1:51000\n",
"objc[4242]: Class FBSDKError is implemented in both /a (0x1) and /b (0x2). "
"One of the two will be used. Which one is undefined.\n",
"WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
# An incidental ERROR substring inside a longer word: the G2 scan is
# word-bounded, so this must not trip the gate.
"archived previous runs under runs/ERRORS_archive\n",
]
write_run("pass", healthy_steps())
write_run("g1-nonzero-exit", healthy_steps(), exit_status="1")
write_run("g2-benign-only", healthy_steps(), output_lines=BENIGN_NOISE + ["run complete\n"])
write_run(
"g2-real-error",
healthy_steps(),
output_lines=BENIGN_NOISE
+ ["error: hierarchy fetch failed: companion connection lost\n"],
)
# G3: first snapshot already carries text in the login fields (clear-state
# failed to wipe the previous session).
write_run(
"g3-dirty-field",
[
step(1, 600, login_screen("[email protected]", "leftover"), input_action("LoginEmail", "[email protected]")),
step(2, 600, login_screen("[email protected]", "leftover")),
],
)
# G4: after typing into LoginEmail the next snapshot shows the value appended to
# itself (append-vs-replace / double-paste regression).
write_run(
"g4-doubled-text",
[
step(1, 600, login_screen("", ""), input_action("LoginEmail", "[email protected]")),
step(2, 600, login_screen("[email protected]@folio.app", "")),
],
)
# G5: step gaps far exceed the 2.5s ceiling, driving p95 over the limit.
write_run("g5-slow-p95", healthy_steps(gap_ms=4000))
print("fixtures written")