mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
feat(pre-push-safety-net): reuse compatible artifacts throughout replay
This commit is contained in:
@@ -88,3 +88,21 @@ def test_generic_driver_accepts_backend_validated_lease(tmp_path, monkeypatch):
|
||||
assert result.run("census", {})["file_names"] == ["a.js"]
|
||||
_dispose(result)
|
||||
assert graph.read_bytes() == b"graph original"
|
||||
|
||||
|
||||
def test_installed_frontend_adaptation_changes_graph_identity(tmp_path, monkeypatch):
|
||||
import openultrasast.cpg.backend as module
|
||||
|
||||
backend, root, _, identity, _ = prepared(tmp_path, monkeypatch)
|
||||
home = tmp_path / "engine"
|
||||
home.mkdir()
|
||||
launcher = home / "joern"
|
||||
launcher.write_text("launcher")
|
||||
jar = home / "frontends/jssrc2cpg/lib/io.joern.jssrc2cpg-4.0.625.jar"
|
||||
jar.parent.mkdir(parents=True)
|
||||
jar.write_bytes(b"frontend-one")
|
||||
monkeypatch.setattr(module.shutil, "which", lambda name: str(launcher) if name == "joern" else None)
|
||||
first = backend.graph_identity(root, language="javascript", declarations_digest="d", exclusions_digest="e")
|
||||
jar.write_bytes(b"frontend-two")
|
||||
second = backend.graph_identity(root, language="javascript", declarations_digest="d", exclusions_digest="e")
|
||||
assert first != second
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Live integration of validated graph and query reuse under one deadline."""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from openultrasast.cpg.backend import CpgResult, JoernBackend
|
||||
from openultrasast.model.contracts import ExecutionBudget
|
||||
from openultrasast.push.cache import ArtifactCache, SemanticKeys
|
||||
from openultrasast.push.reuse import ReusingBackend
|
||||
|
||||
|
||||
def test_backend_reuses_graph_and_answers_then_invalidates_source_and_dependencies(tmp_path, monkeypatch):
|
||||
import openultrasast.cpg.backend as module
|
||||
|
||||
monkeypatch.setattr(module, "joern_version", lambda: (4, 0, 625))
|
||||
builds = []
|
||||
queries = []
|
||||
|
||||
class Engine(JoernBackend):
|
||||
def build(self, root, *, language, exclude, execution_budget):
|
||||
builds.append(root)
|
||||
path = tmp_path / f"graph-{len(builds)}.json"
|
||||
paths = [str(p) for p in root.glob("*.js")]
|
||||
path.write_text(json.dumps(paths))
|
||||
return CpgResult(path, lambda kind, params: self.query(path, kind, params), lambda kind, req: self.query_batch(path, kind, req))
|
||||
|
||||
def query(self, path, kind, params):
|
||||
names = json.loads(path.read_text())
|
||||
return {"files": len(names), "methods": 2, "calls": 1, "file_names": names, "overlays": "dataflowOss"}
|
||||
|
||||
def query_batch(self, path, kind, requests):
|
||||
queries.append((kind, requests))
|
||||
return {**{rid: [] for rid in requests}, "__census__": [self.query(path, "census", {})]}
|
||||
|
||||
root = tmp_path / "source"
|
||||
root.mkdir()
|
||||
(root / "a.js").write_text("function f() {}")
|
||||
cache = ArtifactCache(tmp_path / "cache", max_bytes=100000)
|
||||
semantics = SemanticKeys("facts", "queries", "config", "ranking", "admission", "disabled", "empty", "advisory")
|
||||
deadline = ExecutionBudget(time.monotonic() + 10, 0.2)
|
||||
backend = ReusingBackend(Engine(), cache, semantics, declarations="dependencies", exclusions="exclusions")
|
||||
|
||||
def run():
|
||||
result = backend.build(root, language="javascript", exclude=(), execution_budget=deadline)
|
||||
assert result is not None
|
||||
answer = result.run_batch("taint", {"q": {"file": "a.js", "callDepth": 3}})
|
||||
if result.cleanup:
|
||||
result.cleanup()
|
||||
return answer
|
||||
|
||||
expected = run()
|
||||
assert run() == expected
|
||||
assert len(builds) == 1 and len(queries) == 1
|
||||
(root / "a.js").write_text("function g() {}")
|
||||
run()
|
||||
assert len(builds) == 2 and len(queries) == 2
|
||||
backend.declarations = "changed-dependency"
|
||||
run()
|
||||
assert len(builds) == 3 and len(queries) == 3
|
||||
backend.semantics = SemanticKeys("changed-facts", "queries", "config", "ranking", "admission", "disabled", "empty", "advisory")
|
||||
run()
|
||||
assert len(builds) == 3 and len(queries) == 4
|
||||
|
||||
|
||||
def test_fresh_missing_census_cannot_borrow_a_cached_query_census(tmp_path):
|
||||
from openultrasast.cpg.artifact import GraphArtifact, GraphCensus, GraphIdentity
|
||||
|
||||
identity = GraphIdentity(
|
||||
"source", "declarations", "exclude", "repository", "javascript", "engine", "frontend", "version", ("dataflowOss",), ()
|
||||
)
|
||||
artifact = GraphArtifact(identity, "graph-bytes", 10, GraphCensus(1, 1, 1), "complete", (), "/old", ("a.js",))
|
||||
cache = ArtifactCache(tmp_path / "cache", max_bytes=100000)
|
||||
semantics = SemanticKeys("facts", "queries", "config", "ranking", "admission", "disabled", "empty", "advisory")
|
||||
wrapper = ReusingBackend(JoernBackend(), cache, semantics, declarations="d", exclusions="e")
|
||||
census = [{"files": 1, "methods": 1, "file_names": ["a.js"]}]
|
||||
calls = []
|
||||
|
||||
def batch(kind, requests):
|
||||
calls.append(requests)
|
||||
result = {rid: [] for rid in requests}
|
||||
if len(calls) == 1:
|
||||
result["__census__"] = census
|
||||
return result
|
||||
|
||||
graph = CpgResult(tmp_path / "graph", lambda *_: None, batch)
|
||||
result = wrapper._answers(graph, artifact, ExecutionBudget(time.monotonic() + 5, 0.2))
|
||||
assert "q" in result.run_batch("taint", {"q": {"file": "a.js"}})
|
||||
answer = result.run_batch("taint", {"q": {"file": "a.js"}, "fresh": {"file": "a.js", "callDepth": 3}})
|
||||
assert "q" in answer and "fresh" not in answer
|
||||
@@ -218,3 +218,84 @@ def test_default_replay_does_not_launch_unbudgeted_availability_probe(tmp_path,
|
||||
monkeypatch.setattr(runner, "JoernBackend", Backend, raising=False)
|
||||
result = replay(root, base=base, head=head, artifact=tmp_path / "probe.json")
|
||||
assert builds and result.result.coverage_status == "incomplete"
|
||||
|
||||
|
||||
def test_replay_persists_discovery_without_skipping_readable_snapshots(tmp_path, monkeypatch):
|
||||
from openultrasast.push import runner
|
||||
|
||||
root, base, head, _ = history(tmp_path)
|
||||
cache = tmp_path / "cache"
|
||||
cold = tmp_path / "cold.json"
|
||||
warm = tmp_path / "warm.json"
|
||||
replay(root, base=base, head=head, artifact=cold, backend=NullBackend(), cache_dir=cache)
|
||||
|
||||
def should_reuse(*args):
|
||||
raise AssertionError("discovery should be cached")
|
||||
|
||||
monkeypatch.setattr(runner, "_discover", should_reuse)
|
||||
replay(root, base=base, head=head, artifact=warm, backend=NullBackend(), cache_dir=cache)
|
||||
a, b = json.loads(cold.read_text()), json.loads(warm.read_text())
|
||||
assert a["result"] == b["result"]
|
||||
assert a["snapshots"] == b["snapshots"]
|
||||
assert b["timings"]["discovery_hits"] == 2
|
||||
assert b["snapshots"][0]["bytes_read"] > 0
|
||||
|
||||
|
||||
def test_cache_cannot_write_inside_repository(tmp_path):
|
||||
root, base, head, git = history(tmp_path)
|
||||
before = git("status", "--porcelain")
|
||||
with pytest.raises(ValueError, match="cache.*outside"):
|
||||
replay(root, base=base, head=head, artifact=tmp_path / "out.json", cache_dir=root / "cache")
|
||||
assert git("status", "--porcelain") == before
|
||||
|
||||
|
||||
def test_cached_discovery_invalidates_source_and_dependency_edits(tmp_path):
|
||||
root, base, head, git = history(tmp_path)
|
||||
cache = tmp_path / "cache"
|
||||
replay(root, base=base, head=head, artifact=tmp_path / "initial.json", backend=NullBackend(), cache_dir=cache)
|
||||
(root / "api.js").write_text("function replacement(req) { eval(req.body.other); }")
|
||||
git("add", "api.js")
|
||||
git("commit", "-m", "change source")
|
||||
edited = git("rev-parse", "HEAD")
|
||||
replay(root, base=base, head=edited, artifact=tmp_path / "edited.json", backend=NullBackend(), cache_dir=cache)
|
||||
data = json.loads((tmp_path / "edited.json").read_text())
|
||||
assert data["timings"]["discovery_hits"] == 1
|
||||
(root / "package.json").write_text('{"dependencies":{"express":"5"}}')
|
||||
git("add", "package.json")
|
||||
git("commit", "-m", "change dependencies")
|
||||
dependency = git("rev-parse", "HEAD")
|
||||
replay(root, base=base, head=dependency, artifact=tmp_path / "dependency.json", backend=NullBackend(), cache_dir=cache)
|
||||
data = json.loads((tmp_path / "dependency.json").read_text())
|
||||
assert data["timings"]["discovery_hits"] == 1
|
||||
assert data["change_context"]["head_revision"] == dependency
|
||||
|
||||
|
||||
def test_identical_revision_materializes_once_and_reuses_discovery(tmp_path, monkeypatch):
|
||||
from openultrasast.push.snapshot import SnapshotAdapter
|
||||
|
||||
root, base, _, _ = history(tmp_path)
|
||||
original = SnapshotAdapter.materialize
|
||||
calls = []
|
||||
|
||||
def counted(self, oid, **kwargs):
|
||||
calls.append(oid)
|
||||
return original(self, oid, **kwargs)
|
||||
|
||||
monkeypatch.setattr(SnapshotAdapter, "materialize", counted)
|
||||
replay(root, base=base, head=base, artifact=tmp_path / "same.json", backend=NullBackend(), cache_dir=tmp_path / "cache")
|
||||
data = json.loads((tmp_path / "same.json").read_text())
|
||||
assert calls == [base]
|
||||
assert data["timings"]["discovery_hits"] == 1
|
||||
assert len(data["snapshots"]) == 2
|
||||
|
||||
|
||||
def test_admission_provenance_invalidates_when_installed_runtime_changes(tmp_path, monkeypatch):
|
||||
from openultrasast.model.scan import ScanBudget
|
||||
from openultrasast.push import runner
|
||||
|
||||
monkeypatch.setattr(runner, "engine_runtime_identity", lambda: "first-installation")
|
||||
first = runner._provenance(tmp_path, ScanBudget())
|
||||
monkeypatch.setattr(runner, "engine_runtime_identity", lambda: "second-installation")
|
||||
second = runner._provenance(tmp_path, ScanBudget())
|
||||
assert first["semantics"] != second["semantics"]
|
||||
assert first["facts"] == second["facts"] and first["graph_layout"] == second["graph_layout"]
|
||||
|
||||
Reference in New Issue
Block a user