Skip to content

Commit 861f649

Browse files
codexByron
authored andcommitted
test(performance): generate history-independent benchmark inputs
The backend benchmark fetched an old GitPython commit from the source checkout and used its current Git revision to identify the code under test. Both dependencies prevented running from a source archive and required a full-history CI checkout. Generate a fixed 30-commit repository with an annotated tag, nested tree, and submodule through `gix.testtools`. Identify tested sources by a digest of `VERSION` and package Python files, so local code edits are included without requiring Git metadata. Reject source-digest mismatches during comparison and cover changed and relocated source trees in tests. Update the fixture revision in the budget while retaining every existing CLI-call ceiling. Remove source `.git` before generating the benchmark fixture, drop the full-depth benchmark checkout, and document that old-history timing results cannot be compared to the new workload. Validation: minimal-sample CLI and Gix benchmark runs from a source copy without `.git` pass result parity and all CLI-call ceilings. The benchmark comparison tests, Ruff lint/format, ShellCheck, and YAML parsing pass. These runs validate the harness; they are not rigorous timing measurements. Agent-Model: gpt-6-astra
1 parent f3312bd commit 861f649

9 files changed

Lines changed: 62 additions & 18 deletions

File tree

‎.github/workflows/backend-benchmark.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ jobs:
1616
steps:
1717
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1818
with:
19-
fetch-depth: 0
2019
persist-credentials: false
2120
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
2221
with:
@@ -50,7 +49,8 @@ jobs:
5049
.bench-gix/bin/python -m pip install '.[gix]' -r test/performance/benchmark-requirements.txt
5150
- name: Prepare the shared read-only benchmark fixture
5251
run: |
53-
.bench-cli/bin/python -c 'from pathlib import Path; from gix import testtools; print("BENCH_REPO=" + str((testtools.scripted_fixture_read_only_with_args("benchmark.sh", [str(Path.cwd())]) / "repo").resolve()))' >> "$GITHUB_ENV"
52+
rm -rf .git
53+
.bench-cli/bin/python -c 'from gix import testtools; print("BENCH_REPO=" + str((testtools.scripted_fixture_read_only_with_args("benchmark.sh", []) / "repo").resolve()))' >> "$GITHUB_ENV"
5454
mkdir benchmark-results
5555
- name: Measure GixPython operations, opening and discovery first
5656
env:

‎test/performance/README.md‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ uv pip install --python .tox/gix/bin/python -r test/performance/benchmark-requir
2323
uv pip install --python .venv/bin/python -r test/performance/benchmark-requirements.txt
2424
```
2525

26-
Prepare the same fixed repository used by CI (GitPython 3.1.45) through
27-
`gix.testtools`, outside the measured operations. Its Bash script lives in
28-
`tests/fixtures/benchmark.sh`; no remote access is needed if that revision
29-
already exists locally:
26+
Prepare the same generated repository used by CI through `gix.testtools`,
27+
outside the measured operations. `tests/fixtures/benchmark.sh` creates 30
28+
commits, an annotated tag, a nested directory, and a submodule from scratch.
29+
It requires neither network access nor source Git metadata:
3030

3131
```sh
32-
BENCH_REPO=$(.venv/bin/python -c 'from pathlib import Path; from gix import testtools; print((testtools.scripted_fixture_read_only_with_args("benchmark.sh", [str(Path.cwd())]) / "repo").resolve())')
32+
BENCH_REPO=$(.venv/bin/python -c 'from gix import testtools; print((testtools.scripted_fixture_read_only_with_args("benchmark.sh", []) / "repo").resolve())')
3333
```
3434

3535
Run from the source root, sequentially to avoid competing for CPU and disk:
@@ -48,7 +48,7 @@ The default uses six worker processes, three measured values per process and
4848
one warmup, with calibrated loop counts and a minimum value duration of 100 ms.
4949
Pass standard `pyperf` options such as `--rigorous` for more samples or
5050
`--debug-single-value` for a smoke check. Raw JSON retains sample distributions,
51-
environment details, fixture/source revisions, result digests, and one untimed
51+
environment details, fixture revisions, source content digests, result digests, and one untimed
5252
invocation's native/fallback decisions. The comparison rejects differing
5353
results, revisions, Python/Git versions and storage formats. The harness also
5454
rejects the wrong backend rather than silently comparing CLI against itself.
@@ -64,7 +64,7 @@ mutates that worktree, and ignores ambient Git environment/config overrides.
6464
The journey also enumerates submodules, resolves tree/index revisions with
6565
their paths and modes, and reads raw commit/tree bytes through the object
6666
database. The last operation covers serialization readback without rewriting
67-
objects; the pinned fixture's signed commit remains intact. These three
67+
objects. The generated fixture includes a submodule for the inventory check. These three
6868
measurements have zero-CLI ceilings and join the existing CI job automatically.
6969

7070
Add a function returning JSON-compatible results to `MEASUREMENTS` in
@@ -98,4 +98,7 @@ Timing is observational: shared CI runners and local activity introduce noise.
9898
The CI job publishes all means, standard deviations and ratios, retains raw
9999
artifacts, and uses `pyperf compare_to` for significance reporting. It fails on
100100
execution/parity errors, without a noisy speed threshold. Compare like-for-like
101-
fixture and source revisions when tracking performance across backend changes.
101+
fixture revisions and source content digests when tracking performance across backend changes.
102+
The source digest covers `VERSION` and the Python package files, including local
103+
edits. Measurements from the old GitPython-history fixture are not comparable
104+
with this generated workload.

‎test/performance/bench_repository.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ def worker_arguments(cmd, args):
124124
cmd.extend(["--repo", str(args.repo), "--expect-backend", args.expect_backend])
125125

126126

127+
def source_digest(root):
128+
digest = sha256()
129+
for path in sorted([root / "VERSION", *(root / "git").rglob("*.py")]):
130+
digest.update(path.relative_to(root).as_posix().encode() + b"\0" + path.read_bytes() + b"\0")
131+
return digest.hexdigest()
132+
133+
127134
def main():
128135
runner = pyperf.Runner(
129136
processes=6,
@@ -156,7 +163,7 @@ def git(*arguments, cwd=args.repo):
156163
backend=_backend.name,
157164
gixpython=version("GixPython"),
158165
git_version=git("version"),
159-
gitpython_revision=git("rev-parse", "HEAD", cwd=Path.cwd()),
166+
gitpython_source=source_digest(Path(__file__).resolve().parents[2]),
160167
fixture_revision=git("rev-parse", "HEAD"),
161168
fixture_tree=git("rev-parse", "HEAD^{tree}"),
162169
fixture_status=git("status", "--porcelain") or "clean",

‎test/performance/cli-budget.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"fixture_revision": "6ba2c0a2f9ee7feffd7e079621c4845820180c9a",
2+
"fixture_revision": "bd49b71d9f760b7e1a0753c42a6c0ad0a6c48175",
33
"max_gix_cli_processes": {
44
"journey": 1,
55
"inventory": 0,

‎test/performance/compare_backends.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def compare(cli_path, gix_path, cli_budget=None):
1919
if (lm["backend"], rm["backend"]) != ("cli", "gix"):
2020
raise ValueError("Expected CLI results followed by GixPython results")
2121
for key in (
22-
"gitpython_revision",
22+
"gitpython_source",
2323
"fixture_revision",
2424
"fixture_tree",
2525
"fixture_status",

‎test/performance/test_benchmark_comparison.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
pytest.importorskip("pyperf")
88

99

10-
@pytest.mark.parametrize("changed", [None, "result_digest", "fixture_revision", "backend", "cli_processes"])
10+
@pytest.mark.parametrize(
11+
"changed", [None, "result_digest", "fixture_revision", "gitpython_source", "backend", "cli_processes"]
12+
)
1113
def test_comparison_requires_parity(capsys, changed):
1214
from test.performance.compare_backends import compare
1315

@@ -20,3 +22,11 @@ def test_comparison_requires_parity(capsys, changed):
2022
compare(*paths, cli_budget=fixture / "budget.json")
2123
assert "journey | 150.00" in capsys.readouterr().out
2224
compare(*paths, cli_budget=fixture / "relaxed-budget.json")
25+
26+
27+
def test_source_digest_detects_code_changes_without_git_metadata():
28+
from test.performance.bench_repository import source_digest
29+
30+
fixture = read_only_fixture("benchmark-source.sh")
31+
assert source_digest(fixture / "source") != source_digest(fixture / "copy")
32+
assert source_digest(fixture / "source") == source_digest(fixture / "moved")

‎tests/fixtures/benchmark-comparison.sh‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ set -eu
33
for name in cli gix; do
44
backend=$name
55
fixture_revision=fixture
6+
gitpython_source=source
67
result_digest=equal
78
cli_processes=0
89
if test "$name" = gix; then
910
case "$1" in
1011
backend) backend=different ;;
1112
fixture_revision) fixture_revision=different ;;
13+
gitpython_source) gitpython_source=different ;;
1214
result_digest) result_digest=different ;;
1315
cli_processes) cli_processes=1 ;;
1416
equal) ;;
@@ -20,7 +22,7 @@ for name in cli gix; do
2022
"version": "1.0",
2123
"benchmarks": [{"runs": [{"values": [0.1, 0.2]}]}],
2224
"metadata": {
23-
"gitpython_revision": "source", "fixture_revision": "$fixture_revision",
25+
"gitpython_source": "$gitpython_source", "fixture_revision": "$fixture_revision",
2426
"fixture_tree": "tree", "fixture_status": "clean", "fixture_object_format": "sha1",
2527
"fixture_ref_format": "files", "git_version": "git version 2.52.0",
2628
"python_version": "3.12.14", "result_digest": "$result_digest", "backend_decisions": "[]",

‎tests/fixtures/benchmark-source.sh‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
mkdir -p source/git
4+
printf '1.0\n' > source/VERSION
5+
printf 'value = 1\n' > source/git/module.py
6+
cp -R source copy
7+
cp -R source moved
8+
printf 'value = 2\n' > copy/git/module.py

‎tests/fixtures/benchmark.sh‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
#!/usr/bin/env bash
22
set -eu
33

4-
git init --object-format=sha1 --ref-format=files repo
5-
git -C repo fetch --no-tags "$1" 6ba2c0a2f9ee7feffd7e079621c4845820180c9a
6-
git -C repo checkout -b benchmark FETCH_HEAD
4+
export GIT_AUTHOR_NAME='Benchmark' GIT_AUTHOR_EMAIL='benchmark@example.invalid'
5+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
6+
export GIT_AUTHOR_DATE='2001-01-01T00:00:00 +0000'
7+
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
8+
git init --object-format=sha1 --ref-format=files -b main dependency
9+
git -C dependency commit --allow-empty -m 'Dependency'
10+
git init --object-format=sha1 --ref-format=files -b benchmark repo
11+
mkdir -p repo/git/objects repo/dependency
12+
printf 'Sample objects\n' > repo/git/objects/readme
13+
printf '[submodule "dependency"]\n\tpath = dependency\n\turl = ../dependency\n' > repo/.gitmodules
14+
git -C repo update-index --add --cacheinfo "160000,$(git -C dependency rev-parse HEAD),dependency"
15+
for n in $(seq 1 30); do
16+
printf 'Revision %s\n' "$n" >> repo/README.md
17+
git -C repo add .
18+
git -C repo commit -m "Revision $n"
19+
done
20+
git -C repo tag -a v1.0 HEAD~10 -m 'Sample release'
721
git -C repo config core.excludesFile /dev/null
822
git -C repo config core.attributesFile /dev/null
923
git -C repo config core.fsmonitor false

0 commit comments

Comments
 (0)