Files
Joseph Doherty 04d5513af2
v2-ci / unit-tests (pull_request) Has been cancelled
test(opcua): bind saturation artifacts to gateway 1.0 and module identities
2026-11-10 15:03:21 -05:00

84 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Prepare a fresh immutable candidate bundle. This command never launches the experiment."""
import argparse
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
from build_saturation_observer import build
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--libraries", type=Path, required=True)
parser.add_argument("--image", required=True)
options = parser.parse_args()
source = Path(__file__).resolve().parent
repo = source.parents[2]
revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
if subprocess.check_output(["git", "status", "--porcelain"], cwd=repo, text=True).strip():
raise RuntimeError("Commit and review the exact source before preparing immutable artifacts")
if not options.image.startswith("sha256:"):
raise ValueError("Use an immutable Linux .NET SDK image digest")
root = options.output.resolve()
root.mkdir(parents=True, exist_ok=False)
project = repo / "tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj"
command = ["dotnet", "publish", str(project), "-c", "Release", "-r", "linux-arm64", "--self-contained", "true",
"-p:RuntimeFrameworkVersion=10.0.14", "-p:PublishSingleFile=false", "-p:PublishTrimmed=false", "-p:UseAppHost=true",
"--artifacts-path", str(root / "build"), "-o", str(root / "app"), "-m:2", "--nologo"]
with (root / "publish.log").open("w") as log:
subprocess.run(command, cwd=repo, stdout=log, stderr=subprocess.STDOUT, check=True)
observer = build(root / "observer-build", options.libraries, True)
shutil.copytree(observer, root / "observer")
tools = root / "tools"
tools.mkdir()
for name in ["launch_saturation.py", "container_saturation.sh", "SATURATION.md"]:
shutil.copy2(source / name, tools / name)
manifest = {str(path.relative_to(root)): sha(path) for directory in ["app", "observer", "tools"]
for path in sorted((root / directory).rglob("*")) if path.is_file()}
for config in ["app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.runtimeconfig.json", "observer/observer.runtimeconfig.json"]:
runtime = json.loads((root / config).read_text())
included = runtime["runtimeOptions"]["includedFrameworks"]
if not included or any(value["version"] != "10.0.14" for value in included):
raise RuntimeError("Published participant is not the prospectively fixed self-contained runtime")
dependencies = json.loads((root / "app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.deps.json").read_text())["libraries"]
gateway_libraries = [name for name in dependencies if name.startswith("ZB.MOM.WW.HistorianGateway.")]
if not gateway_libraries or any(name.rsplit("/", 1)[-1] != "1.0.0" for name in gateway_libraries):
raise RuntimeError("The reviewed candidate requires the landed HistorianGateway 1.0.0 graph")
metadata = root / "module-info-build"
metadata.mkdir()
(metadata / "module-info.csproj").write_text(
'<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType>'
'<TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings>'
'<EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup>'
'<ItemGroup><Compile Include="' + str(source / "ArtifactModuleInfo.cs") + '" /></ItemGroup></Project>')
with (metadata / "build.log").open("w") as log:
subprocess.run(["dotnet", "build", str(metadata / "module-info.csproj"), "-c", "Release", "-m:2"],
stdout=log, stderr=subprocess.STDOUT, check=True)
modules = json.loads(subprocess.check_output(["dotnet", str(metadata / "bin/Release/net10.0/module-info.dll"),
str(root), str(root / "app"), str(root / "observer")], text=True))
receipt = {"protocol": "sdk2-saturation-v1", "sourceRevision": revision, "image": options.image,
"rid": "linux-arm64", "runtimeVersion": "10.0.14", "selfContained": True,
"canonicalCycles": 384, "completedRestarts": 96, "settlingCycles": 1, "maximumPhaseSeconds": 1800,
"windows": [[321, 352], [353, 384]], "observerBufferMiB": 16, "runtimeKeywords": "0x18", "rundown": False,
"acceptance": False, "preparedUtc": datetime.now(timezone.utc).isoformat(), "publishCommand": command,
"sdkLibraries": [name for name in dependencies if name.startswith("OPCFoundation.")],
"gatewayLibraries": gateway_libraries, "managedModules": modules,
"testSourceSha256": sha(project.parent / "Sdk2LifecycleSoakTests.cs"),
"observerSourceSha256": sha(source / "SaturationObserver.cs"), "files": manifest}
receipt_path = root / "receipt.json"
receipt_path.write_text(json.dumps(receipt, indent=2) + "\n")
print(json.dumps({"bundle": str(root), "receiptSha256": sha(receipt_path), "sourceRevision": revision,
"launched": False}, indent=2))
if __name__ == "__main__":
main()