Skip to content

Commit d25f0de

Browse files
committed
ci: generate release baseline benchmarks with the baseline environment
The release comparison ran the previous release's benchmark generator with the head interpreter and only the baseline source on PYTHONPATH, so the generator imported the head sqlbuild package and failed once head removed a symbol the baseline still imports. Put the baseline environment's package directories ahead of the head packages so sqlbuild and its native dependencies resolve to the baseline install, and require the baseline environment to use the same Python minor version.
1 parent 79a5b45 commit d25f0de

8 files changed

Lines changed: 126 additions & 11 deletions

File tree

‎.github/workflows/release-performance.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ on:
2222
- scripts/compare_release_performance.py
2323
- scripts/release_performance/**
2424
- scripts/compile_performance_ratio/_helpers/edit.py
25+
- scripts/compile_performance_ratio/_helpers/measure.py
26+
- scripts/compile_performance_ratio/_helpers/report.py
2527
- scripts/compile_performance_ratio/constants.py
2628
- scripts/compile_performance_ratio/exceptions.py
29+
- scripts/compile_performance_ratio/models.py
2730
- scripts/cold_compile_performance/_helpers/dense_project.py
31+
- scripts/cold_compile_performance/constants.py
2832

2933
permissions:
3034
contents: read

‎scripts/compile_performance_ratio/_helpers/measure.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,19 @@ def run_checkout_generator(
6969
entry: str,
7070
arguments: tuple[str, ...],
7171
environment: dict[str, str],
72+
library_paths: tuple[Path, ...],
7273
) -> subprocess.CompletedProcess[str]:
73-
"""Run generator code from another checkout's source tree with this `python`."""
74+
"""Run another checkout's generator with `python`, importing `library_paths` before its own."""
7475

7576
return subprocess.run(
7677
[str(python), "-c", entry, *arguments],
7778
cwd=checkout,
7879
capture_output=True,
7980
text=True,
80-
env={**environment, PYTHONPATH_KEY: str(checkout)},
81+
env={
82+
**environment,
83+
PYTHONPATH_KEY: os.pathsep.join(str(path) for path in (checkout, *library_paths)),
84+
},
8185
check=False,
8286
)
8387

@@ -93,6 +97,7 @@ def write_base_benchmark_project(
9397
entry=BASE_GENERATOR_ENTRY,
9498
arguments=(kind, str(project_dir), str(models)),
9599
environment=_compile_environment(),
100+
library_paths=(),
96101
)
97102
if completed.returncode != 0:
98103
raise CompileComparisonError(

‎scripts/release_performance/_helpers/benchmark.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from scripts.cold_compile_performance._helpers.dense_project import write_dense_compile_project
1515
from scripts.compile_performance_ratio._helpers.edit import apply_one_model_edit
1616
from scripts.compile_performance_ratio._helpers.measure import run_checkout_generator
17+
from scripts.release_performance._helpers.versions import library_paths
1718
from scripts.release_performance.constants import (
1819
BASELINE_GENERATOR_ENTRY,
1920
BUILD_PROJECT,
@@ -60,12 +61,13 @@ def write_pristine_projects(*, root: Path, inspection_models: int, build_models:
6061

6162

6263
def write_baseline_pristine_projects(
63-
*, source: Path, root: Path, inspection_models: int, build_models: int
64+
*, source: Path, python: Path, root: Path, inspection_models: int, build_models: int
6465
) -> Path:
65-
"""Generate the baseline's benchmarks with the baseline source's own generator."""
66+
"""Generate the baseline's benchmarks with its own generator and installed `sqlbuild`."""
6667

6768
_run_baseline_generator(
6869
source=source,
70+
python=python,
6971
entry=BASELINE_GENERATOR_ENTRY,
7072
arguments=(str(root), str(inspection_models), str(build_models)),
7173
description="benchmark",
@@ -79,11 +81,14 @@ def write_dense_project(*, pristine: Path, models: int) -> None:
7981
write_dense_compile_project(project_dir=pristine / DENSE_PROJECT, model_count=models)
8082

8183

82-
def write_baseline_dense_project(*, source: Path, pristine: Path, models: int) -> None:
83-
"""Generate the baseline's dense benchmark with the baseline source's own generator."""
84+
def write_baseline_dense_project(
85+
*, source: Path, python: Path, pristine: Path, models: int
86+
) -> None:
87+
"""Generate the baseline's dense benchmark with its own generator and installed `sqlbuild`."""
8488

8589
_run_baseline_generator(
8690
source=source,
91+
python=python,
8792
entry=DENSE_GENERATOR_ENTRY,
8893
arguments=(str(pristine / DENSE_PROJECT), str(models)),
8994
description="dense benchmark",
@@ -166,14 +171,15 @@ def compare_command(
166171

167172

168173
def _run_baseline_generator(
169-
*, source: Path, entry: str, arguments: tuple[str, ...], description: str
174+
*, source: Path, python: Path, entry: str, arguments: tuple[str, ...], description: str
170175
) -> None:
171176
completed: subprocess.CompletedProcess[str] = run_checkout_generator(
172177
checkout=source,
173178
python=Path(sys.executable).absolute(),
174179
entry=entry,
175180
arguments=arguments,
176181
environment=_environment(),
182+
library_paths=library_paths(python=python),
177183
)
178184
if completed.returncode != 0:
179185
raise ReleasePerformanceError(

‎scripts/release_performance/_helpers/versions.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
_DISTRIBUTION_VERSION: re.Pattern[str] = re.compile(
3737
rf"^{PACKAGE_NAME}-(\d+\.\d+\.\d+)(?:-.+\.whl|\.tar\.gz)$"
3838
)
39+
_LIBRARY_PATHS_PROBE: str = (
40+
"import json, sys, sysconfig; print(json.dumps({'version': list(sys.version_info[:2]), "
41+
"'paths': [sysconfig.get_path('purelib'), sysconfig.get_path('platlib')]}))"
42+
)
3943

4044

4145
def release_versions(*, index: dict[str, object]) -> tuple[str, ...]:
@@ -211,6 +215,27 @@ def installed_version(*, sqb: Path) -> str:
211215
return match.group(0)
212216

213217

218+
def library_paths(*, python: Path) -> tuple[Path, ...]:
219+
"""Return the package directories of `python`'s environment, which must match this Python."""
220+
221+
completed: subprocess.CompletedProcess[str] = subprocess.run(
222+
[str(python), "-c", _LIBRARY_PATHS_PROBE], capture_output=True, text=True, check=False
223+
)
224+
if completed.returncode != 0:
225+
raise ReleasePerformanceError(
226+
f"Reading the package directories of {python} failed: {_tail(completed.stderr)}"
227+
)
228+
probe: dict[str, list[object]] = cast(dict[str, list[object]], json.loads(completed.stdout))
229+
version: tuple[object, ...] = tuple(probe["version"])
230+
if version != tuple(sys.version_info[:2]):
231+
raise ReleasePerformanceError(
232+
f"{python} runs Python {'.'.join(map(str, version))}, but its packages are imported "
233+
f"by Python {sys.version_info.major}.{sys.version_info.minor}; pass --python "
234+
f"{sys.version_info.major}.{sys.version_info.minor}"
235+
)
236+
return tuple(dict.fromkeys(Path(str(path)) for path in probe["paths"]))
237+
238+
214239
def existing_sqb(*, venv_dir: Path) -> Path:
215240
"""Return the sqb entry point of an existing virtual environment."""
216241

‎scripts/release_performance/_helpers/workflow.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,16 @@ def _compare(*, options: ComparisonOptions, root: Path) -> ReleaseComparison:
138138
)
139139
pristine[BASELINE_LABEL] = write_baseline_pristine_projects(
140140
source=baseline_source,
141+
python=baseline.python,
141142
root=root / BASELINE_GENERATED_DIRECTORY,
142143
inspection_models=options.inspection_models,
143144
build_models=options.build_models,
144145
)
145146
write_baseline_dense_project(
146-
source=baseline_source, pristine=pristine[BASELINE_LABEL], models=options.dense_models
147+
source=baseline_source,
148+
python=baseline.python,
149+
pristine=pristine[BASELINE_LABEL],
150+
models=options.dense_models,
147151
)
148152
versions: tuple[InstalledVersion, InstalledVersion] = (baseline, candidate)
149153
projects: dict[str, dict[str, Path]] = {}
@@ -196,7 +200,9 @@ def _install(
196200
sqb = install_published(
197201
version=spec, venv_dir=venv_dir, python=python, wait_seconds=wait_seconds
198202
)
199-
return InstalledVersion(label=label, version=installed_version(sqb=sqb), sqb=sqb)
203+
return InstalledVersion(
204+
label=label, version=installed_version(sqb=sqb), sqb=sqb, python=sqb.with_name("python")
205+
)
200206

201207

202208
def _write_evidence(*, path: Path, comparison: ReleaseComparison) -> None:

‎scripts/release_performance/models.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class InstalledVersion:
2222
label: str
2323
version: str
2424
sqb: Path
25+
python: Path
2526

2627

2728
@dataclass(frozen=True)

‎tests/unit/scripts/release_performance/_helpers/helpers.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Builders for release performance comparison test data."""
22

33
import subprocess
4+
import sys
45
from pathlib import Path
56

67
from scripts.release_performance.models import CommandComparison, CommandSample
@@ -15,6 +16,18 @@ def write_pristine_projects(*, root: Path, inspection_models: int, build_models:
1516
(root / "pristine" / name / "BASELINE_MARKER").write_text(str(models))
1617
return root / "pristine"
1718
"""
19+
BASELINE_ONLY_SYMBOL: str = "BASELINE_ONLY_RELEASE_SYMBOL"
20+
BASELINE_SQLBUILD_GENERATOR: str = f"""from pathlib import Path
21+
22+
from sqlbuild import {BASELINE_ONLY_SYMBOL}
23+
24+
25+
def write_pristine_projects(*, root: Path, inspection_models: int, build_models: int) -> Path:
26+
for name in ("inspection", "build"):
27+
(root / "pristine" / name).mkdir(parents=True)
28+
(root / "pristine" / name / "BASELINE_MARKER").write_text({BASELINE_ONLY_SYMBOL})
29+
return root / "pristine"
30+
"""
1831
FAILING_GENERATOR: str = """def write_pristine_projects(*, root, inspection_models, build_models):
1932
raise RuntimeError("baseline generator exploded")
2033
"""
@@ -25,6 +38,15 @@ def write_dense_compile_project(*, project_dir: Path, model_count: int) -> None:
2538
project_dir.mkdir(parents=True)
2639
(project_dir / "BASELINE_MARKER").write_text(str(model_count))
2740
"""
41+
BASELINE_SQLBUILD_DENSE_GENERATOR: str = f"""from pathlib import Path
42+
43+
from sqlbuild import {BASELINE_ONLY_SYMBOL}
44+
45+
46+
def write_dense_compile_project(*, project_dir: Path, model_count: int) -> None:
47+
project_dir.mkdir(parents=True)
48+
(project_dir / "BASELINE_MARKER").write_text({BASELINE_ONLY_SYMBOL})
49+
"""
2850
FAILING_DENSE_GENERATOR: str = """def write_dense_compile_project(*, project_dir, model_count):
2951
raise RuntimeError("baseline dense generator exploded")
3052
"""
@@ -80,6 +102,27 @@ def write_baseline_dense_source(*, root: Path, generator: str) -> Path:
80102
return root
81103

82104

105+
def write_baseline_environment(*, root: Path) -> Path:
106+
"""Create a virtual environment whose sqlbuild defines a symbol this checkout lacks."""
107+
108+
_ = subprocess.run(
109+
[sys.executable, "-m", "venv", "--without-pip", str(root)], check=True, capture_output=True
110+
)
111+
python: Path = root / "bin" / "python"
112+
purelib: str = subprocess.run(
113+
[str(python), "-c", "import sysconfig; print(sysconfig.get_path('purelib'))"],
114+
check=True,
115+
capture_output=True,
116+
text=True,
117+
).stdout.strip()
118+
package: Path = Path(purelib) / "sqlbuild"
119+
package.mkdir(parents=True)
120+
_ = (package / "__init__.py").write_text(
121+
f"{BASELINE_ONLY_SYMBOL} = {BASELINE_ONLY_SYMBOL!r}\n", encoding="utf-8"
122+
)
123+
return python
124+
125+
83126
def tagged_repository(*, root: Path, tag: str) -> Path:
84127
"""Create a one-commit git repository with a tracked file and the given tag."""
85128

‎tests/unit/scripts/release_performance/_helpers/test_benchmark.py‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
ReleaseSourceTestCase,
1919
)
2020
from tests.unit.scripts.release_performance._helpers.helpers import (
21+
BASELINE_ONLY_SYMBOL,
22+
BASELINE_SQLBUILD_DENSE_GENERATOR,
23+
BASELINE_SQLBUILD_GENERATOR,
2124
FAILING_DENSE_GENERATOR,
2225
FAILING_GENERATOR,
2326
MARKER_DENSE_GENERATOR,
2427
MARKER_GENERATOR,
2528
tagged_repository,
2629
write_baseline_dense_source,
30+
write_baseline_environment,
2731
write_baseline_source,
2832
)
2933

@@ -37,6 +41,12 @@
3741
expected_projects=("build", "inspection"),
3842
expected_error_fragments=(),
3943
),
44+
BaselineGeneratorTestCase(
45+
description="the generator imports the baseline environment's sqlbuild, not this one",
46+
generator=BASELINE_SQLBUILD_GENERATOR,
47+
expected_projects=("build", "inspection"),
48+
expected_error_fragments=(),
49+
),
4050
BaselineGeneratorTestCase(
4151
description="a failing baseline generator is reported with its stderr",
4252
generator=FAILING_GENERATOR,
@@ -58,7 +68,11 @@ def test_given_baseline_source_when_generating_then_uses_its_own_generator(
5868

5969
try:
6070
_ = write_baseline_pristine_projects(
61-
source=source, root=root, inspection_models=30, build_models=10
71+
source=source,
72+
python=write_baseline_environment(root=tmp_path / "venv"),
73+
root=root,
74+
inspection_models=30,
75+
build_models=10,
6276
)
6377
except ReleasePerformanceError as raised:
6478
error = str(raised)
@@ -79,6 +93,12 @@ def test_given_baseline_source_when_generating_then_uses_its_own_generator(
7993
expected_markers=("40",),
8094
expected_error_fragments=(),
8195
),
96+
BaselineDenseGeneratorTestCase(
97+
description="the dense generator imports the baseline environment's sqlbuild",
98+
generator=BASELINE_SQLBUILD_DENSE_GENERATOR,
99+
expected_markers=(BASELINE_ONLY_SYMBOL,),
100+
expected_error_fragments=(),
101+
),
82102
BaselineDenseGeneratorTestCase(
83103
description="a failing baseline dense generator is reported with its stderr",
84104
generator=FAILING_DENSE_GENERATOR,
@@ -101,7 +121,12 @@ def test_given_baseline_source_when_generating_dense_project_then_uses_its_own_g
101121
error: str = ""
102122

103123
try:
104-
write_baseline_dense_project(source=source, pristine=pristine, models=40)
124+
write_baseline_dense_project(
125+
source=source,
126+
python=write_baseline_environment(root=tmp_path / "venv"),
127+
pristine=pristine,
128+
models=40,
129+
)
105130
except ReleasePerformanceError as raised:
106131
error = str(raised)
107132

0 commit comments

Comments
 (0)