feat(preprocess): add resumable DWH jobs
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from tht.cli import app
|
||||
from tht.jobs.dwh_pipeline import DwhPreprocessPipeline
|
||||
from tht.jobs.locking import _lock_name
|
||||
|
||||
|
||||
FP = "sha256:" + hashlib.sha256(b"test").hexdigest()
|
||||
|
||||
|
||||
def test_dwh_and_evidence_jobs_have_distinct_lock_names():
|
||||
assert _lock_name("demo", "dwh") != _lock_name("demo", "evidence")
|
||||
|
||||
|
||||
def test_selected_dwh_stages_run_in_declared_order(tmp_path):
|
||||
calls = []
|
||||
pipeline = DwhPreprocessPipeline(
|
||||
workspace_id="demo",
|
||||
workspace_root=tmp_path,
|
||||
config_fingerprint=FP,
|
||||
input_fingerprint=FP,
|
||||
introspect=lambda: calls.append("introspect"),
|
||||
build_lsh=lambda: calls.append("lsh"),
|
||||
)
|
||||
|
||||
report = pipeline.run(("introspect", "lsh"))
|
||||
|
||||
assert report.status == "succeeded"
|
||||
assert calls == ["introspect", "lsh"]
|
||||
assert [stage.name for stage in report.stages] == ["introspect", "lsh"]
|
||||
|
||||
|
||||
def test_preprocess_dwh_json_is_pristine(monkeypatch, tmp_path):
|
||||
import tht.cli.preprocess_cmd as command
|
||||
|
||||
class Report:
|
||||
status = "succeeded"
|
||||
|
||||
def model_dump(self, mode=None):
|
||||
return {"status": "succeeded", "run_id": "a" * 32, "stages": []}
|
||||
|
||||
seen = {}
|
||||
|
||||
def run(config, *, steps, resume):
|
||||
seen.update(config=config, steps=steps, resume=resume)
|
||||
return Report()
|
||||
|
||||
monkeypatch.setattr(command, "run_dwh_from_config", run)
|
||||
response = CliRunner().invoke(
|
||||
app,
|
||||
[
|
||||
"preprocess", "dwh", "--steps", "introspect,lsh", "--json",
|
||||
"-c", str(tmp_path / "workspace.yaml"),
|
||||
],
|
||||
)
|
||||
|
||||
assert response.exit_code == 0, response.output
|
||||
assert json.loads(response.output)["run_id"] == "a" * 32
|
||||
assert seen["steps"] == ("introspect", "lsh")
|
||||
|
||||
|
||||
def test_preprocess_dwh_rejects_unknown_or_duplicate_steps(monkeypatch, tmp_path):
|
||||
import tht.cli.preprocess_cmd as command
|
||||
|
||||
called = False
|
||||
|
||||
def forbidden(*args, **kwargs):
|
||||
nonlocal called
|
||||
called = True
|
||||
|
||||
monkeypatch.setattr(command, "run_dwh_from_config", forbidden)
|
||||
runner = CliRunner()
|
||||
for value in ("introspect,unknown", "lsh,lsh", ""):
|
||||
response = runner.invoke(
|
||||
app,
|
||||
["preprocess", "dwh", "--steps", value, "--json", "-c", str(tmp_path / "w.yaml")],
|
||||
)
|
||||
assert response.exit_code == 2
|
||||
assert json.loads(response.output)["status"] == "failed"
|
||||
assert called is False
|
||||
@@ -0,0 +1,66 @@
|
||||
import hashlib
|
||||
|
||||
from tht.jobs.dwh_pipeline import DwhPreprocessPipeline
|
||||
|
||||
|
||||
FP = "sha256:" + hashlib.sha256(b"test").hexdigest()
|
||||
|
||||
|
||||
def test_lsh_failure_resumes_exact_run_without_repeating_introspection(tmp_path):
|
||||
calls = []
|
||||
|
||||
def introspect():
|
||||
calls.append("introspect")
|
||||
|
||||
def fail_lsh():
|
||||
calls.append("lsh-failed")
|
||||
raise RuntimeError("database detail that must not leak")
|
||||
|
||||
failed = DwhPreprocessPipeline(
|
||||
workspace_id="demo",
|
||||
workspace_root=tmp_path,
|
||||
config_fingerprint=FP,
|
||||
input_fingerprint=FP,
|
||||
introspect=introspect,
|
||||
build_lsh=fail_lsh,
|
||||
).run(("introspect", "lsh"))
|
||||
assert failed.status == "failed"
|
||||
|
||||
resumed = DwhPreprocessPipeline(
|
||||
workspace_id="demo",
|
||||
workspace_root=tmp_path,
|
||||
config_fingerprint=FP,
|
||||
input_fingerprint=FP,
|
||||
introspect=introspect,
|
||||
build_lsh=lambda: calls.append("lsh-recovered"),
|
||||
).run(("introspect", "lsh"), resume_run_id=failed.run_id)
|
||||
|
||||
assert resumed.status == "succeeded"
|
||||
assert resumed.resumed_from == failed.run_id
|
||||
assert calls == ["introspect", "lsh-failed", "lsh-recovered"]
|
||||
|
||||
|
||||
def test_resume_rejects_a_different_stage_selection(tmp_path):
|
||||
failed = DwhPreprocessPipeline(
|
||||
workspace_id="demo",
|
||||
workspace_root=tmp_path,
|
||||
config_fingerprint=FP,
|
||||
input_fingerprint=FP,
|
||||
introspect=lambda: None,
|
||||
build_lsh=lambda: (_ for _ in ()).throw(RuntimeError()),
|
||||
).run(("introspect", "lsh"))
|
||||
|
||||
pipeline = DwhPreprocessPipeline(
|
||||
workspace_id="demo",
|
||||
workspace_root=tmp_path,
|
||||
config_fingerprint=FP,
|
||||
input_fingerprint=FP,
|
||||
introspect=lambda: None,
|
||||
build_lsh=lambda: None,
|
||||
)
|
||||
try:
|
||||
pipeline.run(("lsh",), resume_run_id=failed.run_id)
|
||||
except Exception as error:
|
||||
assert "incompatible" in str(error)
|
||||
else:
|
||||
raise AssertionError("resume with different stages must fail")
|
||||
Reference in New Issue
Block a user