test(acceptance): reproducible Stage 8 acceptance package
Add test/acceptance/ so the customer's reviewers can re-run what the Stage 8 documents claim on an isolated stand built from this branch: - compose.acceptance.yml + stand.sh: compose project gis-acceptance (app 18080, postgres 15432, minio 19000/19001, rabbitmq mgmt 15672), POSTGIS_PLATFORM / MINIO_IMAGE knobs for arm64 and amd64 hosts. - functional.sh: the 89 test-stand checks of Приложение 7 section 5 (F01-F38, N01-N38, L02-L05, L07-L15), own prefixed test data, PostGIS/MinIO confirmation via the stand containers, full cleanup. - lp_gen.py / lp_run.sh / lp_verify.py: synthetic ЛП inputs, a run from stage 0, and an independent numpy re-implementation of every node that compares all 15 outputs pixel by pixel; read-only (GET) mode works against any stand, in row/column windows for 22219x14977 rasters. - load.sh + seed.sh: scenarios S1-S6 of Приложение 3 with hey 0.1.5 running inside the stand network; CSV/JSON summaries, criteria K1-K4. - scan.sh: govulncheck (source and binary), gosec, trivy fs/image, golangci-lint; fails on any High/Critical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
78
test/acceptance/lib/helpers.py
Normal file
78
test/acceptance/lib/helpers.py
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small helpers for functional.sh (numpy + rasterio).
|
||||
|
||||
helpers.py ndvi OUT.tif write the 600x400 EPSG:4326 Float32 NDVI test raster
|
||||
helpers.py rinfo FILE.tif print raster facts as JSON (driver, size, crs, dtype,
|
||||
nodata, layout, compression, overviews)
|
||||
helpers.py durations RUN.json print "total=<s> nodes=<min>..<max>ms" for a run
|
||||
helpers.py elapsed DATASET.json print updated_at - created_at of a dataset, seconds
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def ndvi(out):
|
||||
import numpy as np
|
||||
import rasterio
|
||||
from rasterio.transform import from_origin
|
||||
|
||||
w, h = 600, 400 # 0.01 deg pixels, bbox [68.5, 51.5, 74.5, 55.5]
|
||||
y, x = np.mgrid[0:h, 0:w].astype(np.float32)
|
||||
v = 0.45 + 0.35 * np.sin(x / 47.0) * np.cos(y / 31.0) + 0.1 * np.sin((x + y) / 13.0)
|
||||
v = np.clip(v, -0.2, 1.0).astype(np.float32)
|
||||
v[:5, :] = -9999
|
||||
profile = dict(driver="GTiff", width=w, height=h, count=1, dtype="float32", crs="EPSG:4326",
|
||||
transform=from_origin(68.5, 55.5, 0.01, 0.01), nodata=-9999)
|
||||
with rasterio.open(out, "w", **profile) as dst:
|
||||
dst.write(v, 1)
|
||||
|
||||
|
||||
def rinfo(path):
|
||||
import rasterio
|
||||
|
||||
with rasterio.open(path) as d:
|
||||
st = d.tags(ns="IMAGE_STRUCTURE")
|
||||
print(json.dumps({
|
||||
"driver": d.driver, "width": d.width, "height": d.height,
|
||||
"epsg": d.crs.to_epsg() if d.crs else None, "dtype": d.dtypes[0], "nodata": d.nodata,
|
||||
"layout": st.get("LAYOUT"), "compression": st.get("COMPRESSION"),
|
||||
"overviews": len(d.overviews(1)), "bounds": [round(b, 6) for b in d.bounds],
|
||||
}))
|
||||
|
||||
|
||||
def ts(s):
|
||||
if not s:
|
||||
return None
|
||||
s = s.replace("Z", "+00:00")
|
||||
if "." in s: # trim nanoseconds to microseconds for fromisoformat
|
||||
head, rest = s.split(".", 1)
|
||||
frac = "".join(ch for ch in rest if ch.isdigit())
|
||||
tz = rest[len(frac):]
|
||||
s = "%s.%s%s" % (head, frac[:6].ljust(6, "0"), tz)
|
||||
return datetime.fromisoformat(s)
|
||||
|
||||
|
||||
def durations(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
run = json.load(f)
|
||||
total = None
|
||||
if run.get("started_at") and run.get("finished_at"):
|
||||
total = (ts(run["finished_at"]) - ts(run["started_at"])).total_seconds()
|
||||
nd = [(ts(n["finished_at"]) - ts(n["started_at"])).total_seconds() * 1000
|
||||
for n in run.get("nodes", []) if n.get("started_at") and n.get("finished_at")]
|
||||
out = "total=%.2fs" % total if total is not None else "total=?"
|
||||
if nd:
|
||||
out += " nodes=%d..%dms" % (min(nd), max(nd))
|
||||
print(out)
|
||||
|
||||
|
||||
def elapsed(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
print("%.2f" % (ts(d["updated_at"]) - ts(d["created_at"])).total_seconds())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd, arg = sys.argv[1], sys.argv[2]
|
||||
{"ndvi": ndvi, "rinfo": rinfo, "durations": durations, "elapsed": elapsed}[cmd](arg)
|
||||
Reference in New Issue
Block a user