#!/usr/bin/env python3 """Run the real ScadaBridge consumer in separate processes against both application versions.""" import argparse import hashlib import json import os from pathlib import Path import secrets import subprocess import time def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--baseline-host", type=Path, required=True) parser.add_argument("--candidate-host", type=Path, required=True) parser.add_argument("--probe", type=Path, required=True) parser.add_argument("--results", type=Path, required=True) args = parser.parse_args() root = args.results.resolve() root.mkdir(parents=True, exist_ok=False) artifacts = {name: path.resolve() for name, path in vars(args).items() if name != "results"} for path in artifacts.values(): if not path.is_file(): raise FileNotFoundError(path) receipt = { "artifacts": {name: {"path": str(path), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()} for name, path in artifacts.items()}, "legs": [], } for version in ("baseline", "candidate"): for mode in ("None", "SignAndEncrypt"): leg = root / f"{version}-{mode}" leg.mkdir() server_root = leg / "server" env = os.environ.copy() env["OPCUA_MATRIX_PASSWORD"] = "otua_matrix_" + secrets.token_urlsafe(32) if mode == "None": env["OPCUA_MATRIX_SERVER_SECURITY"] = "none" else: env.pop("OPCUA_MATRIX_SERVER_SECURITY", None) server_command = ["dotnet", str(artifacts[f"{version}_host"]), "server", str(server_root)] result = {"server": version, "mode": mode, "serverCommand": server_command} with (leg / "server.log").open("w") as server_log: server = subprocess.Popen(server_command, env=env, stdout=server_log, stderr=subprocess.STDOUT) try: until = time.monotonic() + 40 while not (server_root / "endpoint.txt").is_file(): if server.poll() is not None or time.monotonic() >= until: raise RuntimeError(f"Server failed to become ready; inspect {leg / 'server.log'}") time.sleep(0.1) endpoint = (server_root / "endpoint.txt").read_text().strip() env["SCADABRIDGE_PROBE_SERVER_CERTS"] = str(server_root / "server-pki" / "own" / "certs") command = ["dotnet", str(artifacts["probe"]), endpoint, mode, str(leg / "consumer")] with (leg / "consumer.log").open("w") as log: probe = subprocess.run(command, env=env, stdout=log, stderr=subprocess.STDOUT, timeout=150) result.update(command=command, exit=probe.returncode, result=str(leg / "consumer" / "result.json")) finally: server_root.mkdir(exist_ok=True) (server_root / "stop").touch() try: server.wait(timeout=15) except subprocess.TimeoutExpired: server.terminate() server.wait(timeout=10) result["serverExit"] = server.returncode receipt["legs"].append(result) (root / "receipt.json").write_text(json.dumps(receipt, indent=2)) # A pre-existing consumer limitation is evidence to investigate, never converted into a passing leg. return int(any(leg["exit"] != 0 or leg["serverExit"] != 0 for leg in receipt["legs"])) if __name__ == "__main__": raise SystemExit(main())