feat: add typed error message templates and stable replay snapshots
This commit is contained in:
59
scripts/verifyErrorParamsRedis.py
Normal file
59
scripts/verifyErrorParamsRedis.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test template replay against an isolated, bounded local Redis container."""
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
def run(args, **kwargs):
|
||||
return subprocess.run(args, check=True, capture_output=True, text=True, **kwargs).stdout.strip()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--report", type=Path, default=ROOT / "performance/errorParamsRedis.json")
|
||||
args = parser.parse_args()
|
||||
started = time.monotonic()
|
||||
container = run(["docker", "run", "--pull=never", "--rm", "-d", "--cpus", "1", "--memory", "128m",
|
||||
"--pids-limit", "64", "-p", "127.0.0.1::6379", "redis:7-alpine",
|
||||
"redis-server", "--save", "", "--appendonly", "no", "--maxmemory", "64mb",
|
||||
"--maxmemory-policy", "noeviction"])
|
||||
print("Started isolated Redis (1 CPU, 128 MiB); loopback only", flush=True)
|
||||
try:
|
||||
image = run(["docker", "inspect", "--format", "{{.Image}}", container])
|
||||
address = run(["docker", "port", container, "6379/tcp"])
|
||||
for _ in range(30):
|
||||
try:
|
||||
if run(["docker", "exec", container, "redis-cli", "ping"]) == "PONG":
|
||||
break
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
raise RuntimeError("Redis did not become ready")
|
||||
env = dict(os.environ, GO_OMNI_REDIS_TEST_ADDR=address)
|
||||
output = run(["go", "test", "-race", "-count=1", "-json", "-timeout=120s",
|
||||
"./middleware/redis"], cwd=ROOT, env=env, timeout=150)
|
||||
events = [json.loads(line) for line in output.splitlines() if line.startswith("{")]
|
||||
passed = [e["Test"] for e in events if e.get("Action") == "pass" and "Test" in e]
|
||||
skipped = [e["Test"] for e in events if e.get("Action") == "skip" and "Test" in e]
|
||||
assert "TestTemplateErrorRedisReplay" in passed and not skipped, (passed, skipped)
|
||||
args.report.write_text(json.dumps({
|
||||
"passed": True, "image": image, "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"goVersion": run(["go", "version"]), "durationSeconds": round(time.monotonic() - started, 2),
|
||||
"passedTests": passed, "skippedTests": skipped,
|
||||
"checks": ["templateReplay", "int64CodePrecisionThroughLua", "catalogChangeKeepsSnapshot",
|
||||
"noPersistedCauseOrParams", "http200Failure", "dataNull", "oneBusinessExecution"],
|
||||
"cpuLimit": 1, "memoryMiB": 128, "loopbackOnly": True,
|
||||
}, ensure_ascii=False, indent=2) + "\n")
|
||||
print("PASS isolated Redis template replay and shared store regressions", flush=True)
|
||||
finally:
|
||||
run(["docker", "rm", "-f", container])
|
||||
print("Temporary Redis container removed", flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
80
scripts/verifyErrorParamsRelease.py
Normal file
80
scripts/verifyErrorParamsRelease.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install the released CLI in a fresh cache, then test its generated consumer.
|
||||
|
||||
The fixture tests typed message templates, runtime parameters, HTTP 200 business failures,
|
||||
OpenAPI examples, fixed errors and regeneration protection. Only local temporary resources
|
||||
are used; the framework and CLI are fetched from the specified remote version.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE = "git.yuntuo.hk/k2safe/go-omni"
|
||||
|
||||
|
||||
def run(args, cwd, env, timeout=300):
|
||||
result = subprocess.run([str(arg) for arg in args], cwd=cwd, env=env,
|
||||
capture_output=True, text=True, timeout=timeout)
|
||||
output = re.sub(r"https://[^/@\s]+:[^/@\s]+@", "https://", result.stdout + result.stderr)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{args[0]} failed ({result.returncode}):\n{output}")
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--report", type=Path, default=ROOT / "performance/errorParamsRemote.json")
|
||||
args = parser.parse_args()
|
||||
if not re.fullmatch(r"v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?", args.version):
|
||||
parser.error("a fixed release version is required")
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith("GO_OMNI_")}
|
||||
env.update(GOWORK="off", GOFLAGS="", GOPRIVATE="git.yuntuo.hk", GONOSUMDB="git.yuntuo.hk")
|
||||
start = time.monotonic()
|
||||
with tempfile.TemporaryDirectory(prefix="goOmniErrorParamsRelease-") as directory:
|
||||
temp = Path(directory)
|
||||
(temp / "bin").mkdir()
|
||||
env.update(GOBIN=str(temp / "bin"), GOMODCACHE=str(temp / "modcache"))
|
||||
print(f"Installing CLI {args.version} in an empty module cache", flush=True)
|
||||
run(["go", "install", MODULE + "/cmd/go-omni@" + args.version], temp, env)
|
||||
cli = temp / "bin/go-omni"
|
||||
run([cli, "api", "validate", "--api", ROOT / "examples/contracts/errorParams.api"], temp, env)
|
||||
env.update(GO_OMNI_SCAFFOLD_INTEGRATION="1", GO_OMNI_RELEASE_CLI=str(cli), GO_OMNI_RELEASE_VERSION=args.version)
|
||||
print("Testing remote typed error generation, parameter failures and raw responses", flush=True)
|
||||
output = run(["go", "test", "-race", "-count=1", "-json", "-timeout=180s",
|
||||
"./cmd/go-omni/apigen", "-run", "^TestErrorParamsConsumer$"], ROOT, env)
|
||||
events = [json.loads(line) for line in output.splitlines() if line.startswith("{")]
|
||||
passed = [e["Test"] for e in events if e.get("Action") == "pass" and "Test" in e]
|
||||
assert passed == ["TestErrorParamsConsumer"], output
|
||||
evidence = "".join(e.get("Output", "") for e in events)
|
||||
checksum = re.search(r"checksum (h1:\S+); no local replace", evidence)
|
||||
assert checksum, "consumer did not verify its remote dependency"
|
||||
report = {
|
||||
"passed": True, "frameworkVersion": args.version, "frameworkSum": checksum.group(1),
|
||||
"freshModuleCache": True, "noLocalReplace": True,
|
||||
"durationSeconds": round(time.monotonic() - start, 2),
|
||||
"checks": ["remoteCLIInstall", "errorCatalogValidation", "generatedErrorConstructors",
|
||||
"http200BusinessFailure", "openAPIConcreteExampleAndParameters", "typedParameterOrder",
|
||||
"companyAndRemarkMessages", "runtimeAndGeneratedAgreement", "missingAndWrongParameterFallback",
|
||||
"concurrentIsolation", "fixedErrorCompatibility", "perErrorHTTPStatus",
|
||||
"causeNotExposed", "rawSuccessAndWrappedFailure",
|
||||
"earlyMiddlewareCatalog", "handwrittenFilesPreserved", "generatedConsumerRaceTests",
|
||||
"fixedRemoteModuleChecksum"],
|
||||
"sourceHashes": {name: hashlib.sha256((ROOT / name).read_bytes()).hexdigest() for name in
|
||||
("scripts/verifyErrorParamsRelease.py", "cmd/go-omni/apigen/errorParams_test.go", "cmd/go-omni/apigen/errorCatalog_test.go",
|
||||
"examples/contracts/errorParams.api",
|
||||
"cmd/go-omni/apigen/testdata/errorParams/consumer_test.go")},
|
||||
}
|
||||
args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n")
|
||||
print(f"PASS remote error template contract {args.version}; temporary files removed", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user