mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
unseen: parallelize extraction + batch per-repo blob fetch
Serial extraction at ~147s/repo made a 300-repo pool take days, and ~626 lazy per-file blob faults per clone pushed slow repos past the 240s deadline (a draw reached 21 entries from 347 attempts, 160 of them extraction_timeout). Both fixed. Parallel: a bounded ThreadPoolExecutor (--extract-workers, default 6) runs clones concurrently, each into its own clone with a private counter. The main thread alone owns the journal, REST (license via finish_license), counters, and acceptance. Entries are accepted in STRICT SEED ORDER with the fork-network dedup applied at acceptance, so the pool is byte- identical to the serial draw (tested: workers=6 == workers=1, including fork collisions, out-of-order completion, and resume). The systemic guard and resilient skips are unchanged; a graceful stop drains started work to the journal for resume. Batched blobs: repository_changes fetches the fixes together and Git.materialize fetches all missing blobs for the read revisions in one `fetch --no-filter --stdin` (rev-list --missing=print), instead of per- file promisor faults. Content is byte-identical (tested; >=12 fetches collapsed to 1). 202 unseen tests pass, ruff clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4f7eedddf1
commit
65714405eb
@@ -372,3 +372,75 @@ def test_disk_bytes_skips_files_that_vanish_mid_walk(tmp_path):
|
||||
|
||||
git.path = type("P", (), {"rglob": lambda self, pattern: iter([*real, Vanished()])})()
|
||||
assert git.disk_bytes() == 30
|
||||
|
||||
|
||||
def test_partial_clone_batch_matches_lazy_bytes_without_per_file_fetches(history, tmp_path):
|
||||
"""Real promisor packs over file:// only: count lazy fetches via Git's trace."""
|
||||
import json
|
||||
import os
|
||||
|
||||
git, commit, candidate, _, _, _, _ = history
|
||||
for revision in range(12):
|
||||
commit(
|
||||
f"def handler(value):\n return int(value) + {revision}\n",
|
||||
extra={f"file{revision * 5 + i}.py": f"x = {revision * 5 + i}\n" for i in range(5)},
|
||||
)
|
||||
git.run("config", "uploadpack.allowFilter", "true")
|
||||
git.run("config", "uploadpack.allowAnySHA1InWant", "true")
|
||||
baseline = [e.introducing(git, candidate), e.ordinary_history(git, "HEAD")]
|
||||
results, fetches = [], []
|
||||
for batch in (False, True):
|
||||
path = tmp_path / ("batch" if batch else "lazy")
|
||||
trace = tmp_path / (path.name + ".trace")
|
||||
# Local file transport only, no network. No checkout: all blobs start missing.
|
||||
subprocess.run(
|
||||
["git", "clone", "--quiet", "--filter=blob:none", "--no-checkout", tmp_path.as_uri(), str(path)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
commands = []
|
||||
|
||||
def runner(argv, commands=commands, trace=trace, **kwargs):
|
||||
commands.append(argv)
|
||||
return subprocess.run(argv, **kwargs, env={**os.environ, "GIT_TRACE": str(trace), "GIT_ALLOW_PROTOCOL": "file"})
|
||||
|
||||
partial = e.Git(path, runner=runner)
|
||||
missing = partial.run("rev-list", "--objects", "--missing=print", "HEAD")
|
||||
assert sum(line.startswith("?") for line in missing.splitlines()) >= 60
|
||||
if batch:
|
||||
partial.materialize(["HEAD", candidate.fix])
|
||||
results.append([e.introducing(partial, candidate), e.ordinary_history(partial, "HEAD")])
|
||||
assert partial.files_read > 0 and partial.bytes_read > 0
|
||||
fetches.append(sum("built-in: git fetch " in line for line in trace.read_text().splitlines()))
|
||||
if batch:
|
||||
assert sum("fetch" in argv for argv in commands) == 1
|
||||
partial.materialize(["HEAD", candidate.fix]) # Already hydrated: no second fetch.
|
||||
assert sum("fetch" in argv for argv in commands) == 1
|
||||
assert json.dumps(results[0], sort_keys=True).encode() == json.dumps(results[1], sort_keys=True).encode()
|
||||
assert results[1] == baseline
|
||||
assert fetches[0] >= 12 and fetches[1] == 1
|
||||
|
||||
|
||||
def test_materialize_timeout_uses_repository_deadline_and_checks_completeness(tmp_path):
|
||||
oid = "a" * 40
|
||||
calls = []
|
||||
now = [0.0]
|
||||
|
||||
def runner(argv, **kwargs):
|
||||
calls.append((argv, kwargs))
|
||||
if "fetch" in argv:
|
||||
assert kwargs["timeout"] == 3
|
||||
assert kwargs["input"] == (oid + "\n").encode()
|
||||
raise subprocess.TimeoutExpired(argv, kwargs["timeout"])
|
||||
now[0] += 7
|
||||
return subprocess.CompletedProcess(argv, 0, ("?" + oid + "\n").encode(), b"")
|
||||
|
||||
with pytest.raises(e.ExtractionTimeout, match="extraction_timeout"):
|
||||
e.Git(tmp_path, runner=runner, deadline_seconds=10, _now=lambda: now[0]).materialize(["HEAD"])
|
||||
assert len(calls) == 2
|
||||
|
||||
def incomplete(argv, **kwargs):
|
||||
return subprocess.CompletedProcess(argv, 0, ("?" + oid + "\n").encode(), b"")
|
||||
|
||||
with pytest.raises(e.InstrumentFailure, match="missing_blobs_after_fetch"):
|
||||
e.Git(tmp_path, runner=incomplete).materialize(["HEAD"])
|
||||
|
||||
Reference in New Issue
Block a user