mirror of
https://github.com/marcredhat/kql
synced 2026-06-08 21:27:09 +00:00
93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent post-export verification.
|
|
|
|
Reads every file in `pq/` AS WRITTEN ON DISK (no template substitution,
|
|
no scope prefix, no harness magic) and POSTs it to /api/powerQuery on
|
|
the configured tenant. The script asserts each file:
|
|
|
|
* parses cleanly (no 'error/client/badParam' status),
|
|
* returns a syntactically valid response (status='success').
|
|
|
|
It does NOT assert that the query returns any rows — empty results are
|
|
fine. The purpose is to catch syntax / field / function errors so the
|
|
published .pq files are guaranteed runnable by anyone who copies them.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
from harness.sdl_client import power_query # noqa: E402
|
|
|
|
PQ_DIR = ROOT / "pq"
|
|
files = sorted(PQ_DIR.glob("*.pq"))
|
|
|
|
|
|
def strip_comments(text: str) -> str:
|
|
return "\n".join(l for l in text.splitlines()
|
|
if not l.lstrip().startswith("//")).strip()
|
|
|
|
|
|
def collapse_whitespace(body: str) -> str:
|
|
"""Single-line form: same query, all whitespace collapsed to one space.
|
|
|
|
This simulates what happens when a user pastes the query into a web
|
|
textbox that strips newlines. A correctly-formatted PQ must survive
|
|
this transformation — every `|` between stages must be present.
|
|
"""
|
|
return re.sub(r"\s+", " ", body).strip()
|
|
|
|
|
|
print(f"Verifying {len(files)} .pq files run cleanly on SDL ...")
|
|
print("(Each file tested in TWO forms: as-written and whitespace-collapsed.)")
|
|
print()
|
|
|
|
passed: list[str] = []
|
|
failed: list[tuple[str, str, str]] = [] # (file, variant, reason)
|
|
|
|
|
|
def run(name: str, variant: str, body: str) -> bool:
|
|
t0 = time.time()
|
|
try:
|
|
r = power_query(body, start_time="2h")
|
|
except Exception as e:
|
|
failed.append((name, variant, f"exception: {e}"))
|
|
return False
|
|
elapsed = time.time() - t0
|
|
status = r.get("status", "")
|
|
if status == "success":
|
|
matching = r.get("matchingEvents", 0)
|
|
print(f" ✓ {name:<48} [{variant:<9}] "
|
|
f"matching={matching} ({elapsed:.1f}s)")
|
|
return True
|
|
msg = r.get("message", "")[:200]
|
|
print(f" ✗ {name:<48} [{variant:<9}] {status} :: {msg}")
|
|
failed.append((name, variant, f"{status}: {msg}"))
|
|
return False
|
|
|
|
|
|
for f in files:
|
|
text = f.read_text()
|
|
body = strip_comments(text)
|
|
if not body:
|
|
failed.append((f.name, "as-written", "empty after stripping comments"))
|
|
continue
|
|
|
|
ok1 = run(f.name, "as-written", body)
|
|
ok2 = run(f.name, "collapsed", collapse_whitespace(body))
|
|
if ok1 and ok2:
|
|
passed.append(f.name)
|
|
|
|
print()
|
|
print(f"PASS: {len(passed)} FAIL: {len(failed)}")
|
|
if failed:
|
|
print()
|
|
print("Failed queries:")
|
|
for name, variant, why in failed:
|
|
print(f" {name} [{variant}]: {why}")
|
|
sys.exit(1)
|