Skip to content

Commit 86c1201

Browse files
authored
Merge pull request #52 from astrofrog/fixes
2 parents 7257254 + e97b438 commit 86c1201

6 files changed

Lines changed: 126 additions & 14 deletions

File tree

astropy_integration/dashboard.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def _variant_meta(variant, python, data):
6868
"python_version": data.get("python_version") or python,
6969
"started_at": data["started_at"],
7070
"finished_at": data["finished_at"],
71+
"installed_deps": data.get("installed_deps") or {},
7172
}
7273

7374

astropy_integration/run.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
Creates a single shared venv, installs astropy first then each package
44
from packages.yaml in order, recording per-package install outcome
5-
(installed / skipped / install-fail / no-spec). Then runs
5+
(installed / skipped / install-fail / no-spec). Each install is
6+
constrained so it can never downgrade a package already in the venv;
7+
a package needing an older version is reported as skipped. Then runs
68
`pytest --pyargs <module>` for each successfully-installed package.
79
Writes results/<variant>__<python>.json with the full venv freeze and
810
per-package data.
@@ -223,6 +225,29 @@ def _freeze(python):
223225
return out
224226

225227

228+
def _write_no_downgrade_constraints(python, path):
229+
"""Pin every installed package to '>=' its current version.
230+
231+
Passed as a uv `--constraint` file to later installs so a new package
232+
can pull its deps forward but never downgrade what the shared venv
233+
already has; a package that genuinely needs an older version then
234+
shows up as a resolver conflict (skipped) instead of silently
235+
poisoning the venv for everything tested afterwards.
236+
"""
237+
frozen = _freeze(python)
238+
lines = []
239+
for name, ver in sorted(frozen.items()):
240+
try:
241+
# Drop any PEP 440 local-version segment (e.g. the '+g1a2b3c4'
242+
# on astropy/pyerfa nightly wheels): uv rejects a '>=' specifier
243+
# with a local segment and would fail to parse the whole file.
244+
public = Version(ver).public
245+
except InvalidVersion:
246+
continue
247+
lines.append(f"{name}>={public}")
248+
Path(path).write_text("\n".join(lines) + ("\n" if lines else ""))
249+
250+
226251
def _load_packages(path):
227252
raw = yaml.safe_load(Path(path).read_text()) or {}
228253
return list(raw.get("packages", []))
@@ -313,6 +338,7 @@ def run_variant(variant, python_version, packages, repo_root, results_dir, timeo
313338
return result, out_path
314339
python = os.path.join(venv, "bin", "python")
315340
result["python_version"] = _venv_python_version(python)
341+
constraints_path = os.path.join(tmpdir, "no-downgrade-constraints.txt")
316342

317343
common = ["uv", "pip", "install", "--python", python, "-q"]
318344
for url in astropy["extra_index_urls"]:
@@ -339,6 +365,7 @@ def run_variant(variant, python_version, packages, repo_root, results_dir, timeo
339365
result["astropy"]["version"] = (
340366
_pkg_version(python, "astropy") or result["astropy"]["version"]
341367
)
368+
_write_no_downgrade_constraints(python, constraints_path)
342369

343370
installed_pkgs = []
344371
for pkg, install_spec, target_version in pkg_specs:
@@ -364,13 +391,18 @@ def run_variant(variant, python_version, packages, repo_root, results_dir, timeo
364391
continue
365392

366393
print(f"\nInstalling {pkg['pypi_name']}...")
367-
install_cmd = common + [install_spec] + (pkg.get("extra_deps") or [])
394+
install_cmd = (
395+
common
396+
+ ["--constraint", constraints_path, install_spec]
397+
+ (pkg.get("extra_deps") or [])
398+
)
368399
rc, err = _run_install(install_cmd, timeouts["install"])
369400
if rc == 0:
370401
entry["install_status"] = status.INSTALLED
371402
entry["resolved_version"] = _pkg_version(python, pkg["pypi_name"])
372403
print(f" installed at {entry['resolved_version']}")
373404
installed_pkgs.append((pkg, entry))
405+
_write_no_downgrade_constraints(python, constraints_path)
374406
else:
375407
entry["install_error"] = err
376408
if _resolver_conflict(err):
@@ -467,12 +499,20 @@ def run(args):
467499
# instead of buffering until the script exits.
468500
sys.stdout.reconfigure(line_buffering=True)
469501

470-
packages = _load_packages(args.config)
502+
all_packages = _load_packages(args.config)
503+
packages = all_packages
471504
if args.tiers:
472505
wanted_tiers = {t.strip() for t in args.tiers.split(",") if t.strip()}
473506
packages = [p for p in packages if p.get("tier", "coordinated") in wanted_tiers]
474507
if args.packages:
475508
wanted = {n.strip() for n in args.packages.split(",") if n.strip()}
509+
known = {p["pypi_name"] for p in all_packages}
510+
unknown = wanted - known
511+
if unknown:
512+
sys.exit(
513+
f"Unknown package name(s): {', '.join(sorted(unknown))}. "
514+
f"Valid names are the pypi_name entries in {args.config}."
515+
)
476516
packages = [p for p in packages if p["pypi_name"] in wanted]
477517
packages = _install_order(packages)
478518

astropy_integration/templates/_style.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,22 @@ details summary { cursor: pointer; padding: 4px 0; font-weight: 600; }
3232
nav { margin-bottom: 1.5em; font-size: 0.9em; }
3333
.legend { font-size: 0.85em; color: #6a737d; margin-top: 1em; }
3434
.legend .pill { margin: 0 0.3em; }
35+
36+
@media (prefers-color-scheme: dark) {
37+
body { background: #0d1117; color: #c9d1d9; }
38+
a { color: #58a6ff; }
39+
th, td { border-color: #30363d; }
40+
th { background: #161b22; }
41+
th.python-group { border-bottom-color: #484f58; }
42+
.banner { background: #2b1d00; border-color: #6e5500; }
43+
tr.tier-header th { background: #161b22; color: #8b949e; }
44+
.meta dl { color: #8b949e; }
45+
.matrix tr:hover td { background: #161b22; }
46+
.pill.pass { background: #0a3a17; color: #3fb950; }
47+
.pill.fail { background: #4a1216; color: #f85149; }
48+
.pill.no-tests { background: #21262d; color: #8b949e; }
49+
.pill.skipped { background: #21262d; color: #8b949e; }
50+
.pill.install-fail{ background: #3d2900; color: #e3b341; }
51+
.pill.timeout { background: #261c4a; color: #a371f7; }
52+
.pill.missing { background: #161b22; color: #6e7681; }
53+
}

astropy_integration/templates/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ <h3>Test output</h3>
9696
<p class="legend">No failures &mdash; every package passed in every variant that ran.</p>
9797
{% endif %}
9898

99+
{% if variants_meta|selectattr("installed_deps")|list %}
100+
<h2>Installed packages</h2>
101+
<p class="legend">Full <code>uv pip freeze</code> of the shared venv for each variant that ran.</p>
102+
{% for v in variants_meta %}
103+
{% if v.installed_deps %}
104+
<details>
105+
<summary>
106+
<code>{{ v.variant }}</code> / Python <code>{{ v.python }}</code>
107+
<small style="color:#6a737d">({{ v.installed_deps|length }} packages)</small>
108+
</summary>
109+
<pre>{% for name, ver in v.installed_deps|dictsort %}{{ name }}=={{ ver }}
110+
{% endfor %}</pre>
111+
</details>
112+
{% endif %}
113+
{% endfor %}
114+
{% endif %}
115+
99116
<script>
100117
function openTarget() {
101118
const id = location.hash.slice(1);

conftest.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
"""Repo-level pytest plugin used during PR-preview runs.
1+
"""Repo-level pytest plugin, auto-discovered because the runner invokes
2+
every `pytest --pyargs <module>` with cwd set to the repo root.
23
3-
When the env var `PYTEST_LIMIT_N` is set to a positive integer, pytest
4-
collects normally and then truncates the test list to the first N
5-
items. This lets the PR matrix surface a fast smoke signal per
6-
package (typically 10 tests each) without per-package configuration.
4+
Two jobs:
75
8-
Discovered automatically by pytest because cwd is the repo root for
9-
every `pytest --pyargs <module>` invocation in the runner.
6+
1. PR-preview smoke runs: when the env var `PYTEST_LIMIT_N` is set to a
7+
positive integer, pytest collects normally and then truncates the
8+
test list to the first N items. This lets the PR matrix surface a
9+
fast smoke signal per package (typically 10 tests each) without
10+
per-package configuration.
11+
12+
2. Supply shared test fixtures that a package defines in its own
13+
repo-root conftest.py but does not ship in the installed wheel.
14+
`pytest --pyargs` collects from site-packages, so such fixtures are
15+
otherwise missing and their tests error at setup. Currently:
16+
`tmp_cwd` (astroquery).
1017
"""
1118

1219
import os
20+
from pathlib import Path
21+
22+
import pytest
1323

1424

1525
def pytest_collection_modifyitems(config, items):
@@ -22,3 +32,22 @@ def pytest_collection_modifyitems(config, items):
2232
return
2333
if n > 0 and len(items) > n:
2434
del items[n:]
35+
36+
37+
@pytest.fixture(scope="function")
38+
def tmp_cwd(tmp_path):
39+
"""astroquery shim: run the test in a pristine temp working directory.
40+
41+
Exists solely for astroquery, which defines this fixture in its
42+
repo-root conftest.py. That file is not part of the installed
43+
package, so the fixture is missing under `pytest --pyargs
44+
astroquery` and the esa/utils, esa/iso and esa/xmm_newton download
45+
tests error out at setup. Remove this if astroquery ever ships the
46+
fixture inside the package.
47+
"""
48+
old_dir = Path.cwd()
49+
os.chdir(tmp_path)
50+
try:
51+
yield tmp_path
52+
finally:
53+
os.chdir(old_dir)

packages.yaml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ packages:
3838
module: astroquery
3939
repo_url: https://github.com/astropy/astroquery.git
4040
install_extras: [test, all]
41+
# test_raises_deprecation_warning uses pytest.raises() on a warning,
42+
# which only works under astroquery's own filterwarnings=error config;
43+
# that setup.cfg config is not picked up when running --pyargs from
44+
# the harness rootdir, so the test cannot pass here.
45+
pytest_args: ["-k", "not test_raises_deprecation_warning"]
4146

4247
- pypi_name: ccdproc
4348
tier: coordinated
@@ -104,10 +109,11 @@ packages:
104109
module: astroalign
105110
repo_url: https://github.com/quatrope/astroalign.git
106111

107-
- pypi_name: astroML
108-
tier: affiliated
109-
module: astroML
110-
repo_url: https://github.com/astroML/astroML.git
112+
# astroML is no longer maintained, so it is excluded from the matrix.
113+
# - pypi_name: astroML
114+
# tier: affiliated
115+
# module: astroML
116+
# repo_url: https://github.com/astroML/astroML.git
111117

112118
- pypi_name: astroplan
113119
tier: affiliated

0 commit comments

Comments
 (0)