From 5236e8e1e66a32f4af31214ff085457847bb662c Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 15:17:47 +0100 Subject: [PATCH 1/6] Repair test-report tooling The Test Report workflow has been broken since the CI migration to pixi (#9276): - Run the report generation through a new, self-contained `test-report` pixi environment: `pixi run test-report dask/distributed`. Delete the unused conda environment file. - Adapt job/artifact name parsing in test_report.py to the names produced by the post-pixi tests.yaml, in both dask/distributed and dask/dask. Runs older than 2026-06-04 cannot be parsed and are skipped. - Replace the unmaintained altair_saver with altair>=6 native HTML output. - Fix regression introduced by the f-string conversion in #9245 which collapsed all tests into a single unreadable chart. - Name the local artifact caches after the repo (e.g. test_report_dask__distributed) so that reports for multiple repos can be generated from the same working directory. - Fall back to `gh auth token` when GITHUB_TOKEN is not set. - On PRs, run the workflow only when the `test-report` label is set; upload the reports and databases as workflow artifacts instead of deploying to GitHub Pages. - Fix actions/cache usage so that the database cache is actually updated on every run (cache keys are immutable). - Skip in-progress and expired runs/artifacts to avoid poisoning the cache. Co-Authored-By: Claude Fable 5 --- .github/workflows/test-report.yaml | 62 +-- .gitignore | 2 +- .../scripts/test-report-environment.yml | 12 - continuous_integration/scripts/test_report.py | 152 ++++--- pixi.lock | 384 ++++++++++++++++++ pixi.toml | 17 + 6 files changed, 511 insertions(+), 118 deletions(-) delete mode 100644 continuous_integration/scripts/test-report-environment.yml diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 7b62363210c..24c93414593 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -5,53 +5,65 @@ on: # Run 2h after the daily tests.yaml - cron: "0 8,20 * * *" workflow_dispatch: + pull_request: + types: [labeled, opened, reopened, synchronize] + +permissions: + contents: write jobs: test-report: name: Test Report - # Do not run the report job on forks - if: github.repository == 'dask/distributed' || github.event_name == 'workflow_dispatch' + # Do not run the report job on forks. + # On PRs, run only if the `test-report` label is set. + if: > + (github.event_name == 'pull_request' + && contains(github.event.pull_request.labels.*.name, 'test-report')) + || (github.event_name != 'pull_request' + && (github.repository == 'dask/distributed' || github.event_name == 'workflow_dispatch')) runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ github.token }} - defaults: - run: - shell: bash -l {0} steps: - uses: actions/checkout@v7.0.0 - - name: Setup Conda Environment - uses: conda-incubator/setup-miniconda@v4.0.1 + - uses: prefix-dev/setup-pixi@v0.10.0 with: - miniforge-version: latest - condarc-file: continuous_integration/condarc - environment-file: continuous_integration/scripts/test-report-environment.yml - activate-environment: dask-distributed - - - name: Show conda options - run: conda config --show - - - name: conda list - run: conda list + pixi-version: v0.72.0 + environments: test-report + cache: true + locked: true - uses: actions/cache@v6 id: cache with: - # Suffix is depending on the backend / OS. Let's be agnostic here - # See https://docs.python.org/3/library/shelve.html#shelve.open - path: | - test_report* - !test_report.html - key: ${{ hashFiles('continuous_integration/scripts/test_report*') }} + # Local databases of downloaded test results, created by test_report.py. + # Note that this pattern must not match test_report.html. + path: test_report_* + # Caches are immutable; use a fresh key to upload an updated database at + # the end of every run, and restore from the most recent one. + key: test-report-${{ hashFiles('continuous_integration/scripts/test_report.py') }}-${{ github.run_id }} + restore-keys: test-report-${{ hashFiles('continuous_integration/scripts/test_report.py') }}- - name: Generate report run: | - python continuous_integration/scripts/test_report.py --max-days 90 --max-runs 30 --nfails 1 -o test_report.html - python continuous_integration/scripts/test_report.py --max-days 7 --max-runs 30 --nfails 2 -o test_short_report.html --title "Test Short Report" + pixi run test-report dask/distributed mkdir deploy mv test_report.html test_short_report.html deploy/ + - name: Upload report as workflow artifact + # For debugging purposes on PRs; scheduled runs deploy to GitHub Pages instead + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v7 + with: + name: test-report + path: | + deploy + test_report_* + - name: Deploy 🚀 + # Never update GitHub Pages from PRs + if: github.event_name != 'pull_request' uses: JamesIves/github-pages-deploy-action@v4.8.0 with: branch: gh-pages diff --git a/.gitignore b/.gitignore index 4eca8708bc7..4ac7956293a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ distributed/_version.py # Test reports test_report*.html -test_report*.db +test_report_* test_short_report.html # Test failures will dump the cluster state in here diff --git a/continuous_integration/scripts/test-report-environment.yml b/continuous_integration/scripts/test-report-environment.yml deleted file mode 100644 index 1906cc3087f..00000000000 --- a/continuous_integration/scripts/test-report-environment.yml +++ /dev/null @@ -1,12 +0,0 @@ -channels: - - conda-forge -dependencies: - - python=3.9 - - altair - - altair_saver - - pandas - - pip - - requests - - urllib3 - - pip: - - junitparser diff --git a/continuous_integration/scripts/test_report.py b/continuous_integration/scripts/test_report.py index 662311c05ec..eb30601a9bf 100644 --- a/continuous_integration/scripts/test_report.py +++ b/continuous_integration/scripts/test_report.py @@ -7,6 +7,7 @@ import os import re import shelve +import subprocess import sys import zipfile from collections.abc import Generator, Iterable, Iterator @@ -14,14 +15,18 @@ from typing import Any, cast import altair -import altair_saver import junitparser import pandas import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry -TOKEN = os.environ.get("GITHUB_TOKEN") +TOKEN: str | None = None + +# The output format of tests.yaml changed substantially when CI was migrated to +# pixi (dask/distributed#9276, merged 2026-06-03; dask/dask#12389, merged +# 2026-05-21). Workflow runs older than this date cannot be parsed. +CUTOFF = pandas.Timestamp("2026-06-04", tz="UTC") # Mapping between a symbol (pass, fail, skip) and a color COLORS = { @@ -31,6 +36,29 @@ } +def get_token() -> str: + """Read the GitHub API token from the GITHUB_TOKEN environment variable, + falling back to the gh CLI if the variable is not set. + """ + if token := os.environ.get("GITHUB_TOKEN"): + return token + try: + proc = subprocess.run( + ["gh", "auth", "token"], capture_output=True, text=True, check=True + ) + except (FileNotFoundError, subprocess.CalledProcessError): + raise RuntimeError( + "Failed to find a GitHub Token. Either set the GITHUB_TOKEN " + "environment variable or log into the gh CLI (`gh auth login`)." + ) from None + return proc.stdout.strip() + + +def cache_name(prefix: str, repo: str) -> str: + """Name of the local cache database, e.g. test_report_dask__distributed""" + return f"{prefix}_{repo.replace('/', '__')}" + + @contextlib.contextmanager def get_session() -> Generator[requests.Session]: retry_strategy = Retry( @@ -123,7 +151,7 @@ def maybe_get_next_page_path(response: requests.Response) -> str | None: def get_jobs(run, session, repo): - with shelve.open("test_report_jobs") as cache: + with shelve.open(cache_name("test_report_jobs", repo)) as cache: url = run["jobs_url"] try: jobs = cache[url] @@ -137,50 +165,19 @@ def get_jobs(run, session, repo): cache[url] = jobs df_jobs = pandas.DataFrame.from_records(jobs) - # Interpolate the `$TEST_ID` variable from the job name. - # Somehow the job ID is not part of the workflow schema and we have no other way to later join - # this to the JXML results. - - if repo.endswith("/dask"): - # example name: "test (windows-latest, 3.9)" or "test (ubuntu-latest, 3.12)" - df_jobs = df_jobs[~df_jobs.name.str.contains("Event File")] - - def format(row): - return ",".join((row[1].strip(), row[2].strip(), (row[3] or "").strip())) - - name_components = ( - df_jobs[df_jobs.name.str.startswith("test")] - .name.str.replace("(", ",") - .str.replace(")", ",") - .str.split(",", expand=True) - .apply(format, axis=1) - .str.split(",", expand=True) - ) - - # See `Set $TEST_ID` step in `tests.yaml` - name_components.columns = ["OS version", "environment", "option"] - df_jobs["suite_name"] = name_components.iloc[:, 0].str.cat( - name_components.iloc[:, 1:], sep="-" - ) - df_jobs["suite_name"] = df_jobs.suite_name.str.replace("--", "-") - else: - name_components = df_jobs.name.str.split("-", expand=True).dropna() - if len(name_components.columns) != 5: - raise ValueError(f"Job names must have 5 components:\n{name_components!r}") - name_components.columns = [ - "OS", - "OS version", - "environment", - "label", - "partition", - ] - - # See `Set $TEST_ID` step in `tests.yaml` - name_components["partition"] = name_components.partition.str.replace(" ", "") - - df_jobs["suite_name"] = name_components.iloc[:, 0].str.cat( - name_components.iloc[:, 1:], sep="-" - ) + df_jobs = df_jobs[df_jobs.name != "Event File"] + + # Reconstruct the artifact name from the job name, so that it can later be + # joined with the JXML results. Job names are space-separated, e.g. + # ubuntu-latest py310 test-ci not ci1 + # whereas the matching artifact is named after $TEST_ID (see the + # `Set $TEST_ID` step in tests.yaml), which is dash-separated and with the + # space removed from the partition name: + # ubuntu-latest-py310-test-ci-notci1 + # dask/dask job names are the same, minus the trailing partition. + df_jobs["suite_name"] = df_jobs.name.str.replace(" not ci1", " notci1").str.replace( + " ", "-" + ) return df_jobs @@ -191,7 +188,13 @@ def get_workflow_run_listing( Get a list of workflow runs from GitHub actions. """ since = (pandas.Timestamp.now(tz="UTC") - pandas.Timedelta(days=days)).date() - params = {"per_page": 100, "branch": branch, "event": event, "created": f">{since}"} + since = max(since, CUTOFF.date()) + params = { + "per_page": 100, + "branch": branch, + "event": event, + "created": f">={since}", + } r = get_from_github( f"https://api.github.com/repos/{repo}/actions/runs", params=params, @@ -229,25 +232,22 @@ def get_artifacts_for_workflow_run( return artifacts -def suite_from_name(name: str, n: int | None = None) -> str: +def suite_from_name(name: str) -> str: """ - Get a test suite name from an artifact name. The artifact - can have matrix partitions, pytest marks, etc. Basically, - just lop off the front of the name to get the suite. + Get a test suite name from an artifact name. This is the artifact name + minus the trailing pytest partition (dask/distributed only), so that the + ci1 and notci1 partitions of the same test suite are shown on one row. """ - if n is not None: - parts = name.split("-") - return "-".join(parts[:n]) - return name + return re.sub(r"-(not)?ci1$", "", name) def download_and_parse_artifact( - url: str, session: requests.Session + url: str, repo: str, session: requests.Session ) -> junitparser.JUnitXml | None: """ Download the artifact at the url parse it. """ - with shelve.open("test_report") as cache: + with shelve.open(cache_name("test_report", repo)) as cache: try: xml_raw = cache[url] except KeyError: @@ -257,7 +257,7 @@ def download_and_parse_artifact( try: return junitparser.JUnitXml.fromstring(xml_raw) except Exception: - # XMLs also include things like schedule which is a simple json + # e.g. truncated XML from a job that hit the timeout return None @@ -340,6 +340,8 @@ def download_and_parse_artifacts( if ( pandas.to_datetime(r["created_at"]) > pandas.Timestamp.now(tz="UTC") - pandas.Timedelta(days=max_days) + and pandas.to_datetime(r["created_at"]) >= CUTOFF + and r["status"] == "completed" and r["conclusion"] != "cancelled" and r["name"].lower() == "tests" ) @@ -357,11 +359,13 @@ def download_and_parse_artifacts( artifacts = get_artifacts_for_workflow_run( r["id"], repo=repo, session=session ) - # We also upload timeout reports as artifacts, but we don't want them here. + # Skip artifacts that don't contain a JUnit XML report r["artifacts"] = [ a for a in artifacts - if "timeouts" not in a["name"] and "cluster_dumps" not in a["name"] + if not a["expired"] + and a["name"] != "Event File" + and "cluster_dumps" not in a["name"] ] nartifacts = sum(len(r["artifacts"]) for r in runs) @@ -374,21 +378,11 @@ def download_and_parse_artifacts( for a in r["artifacts"]: url = a["archive_download_url"] df: pandas.DataFrame | None - xml = download_and_parse_artifact(url, session=session) + xml = download_and_parse_artifact(url, repo=repo, session=session) if xml is None: continue df = dataframe_from_jxml(cast(Iterable, xml)) - # Needed until *-*-mindeps-numpy shows up in TEST_ID - a["name"] = a["name"].replace("--", "-numpy-") - - # TODO: Some artifacts created w/ wrong name in dask - # This can be removed after ~90 days. - # Between time https://github.com/dask/dask/pull/10769 was merged and - # then https://github.com/dask/dask/pull/10781 which changed the name - if a["name"].startswith("test-results") and repo.endswith("/dask"): - continue - # Note: we assign a column with the workflow run timestamp rather # than the artifact timestamp so that artifacts triggered under # the same workflow run can be aligned according to the same trigger @@ -401,9 +395,7 @@ def download_and_parse_artifacts( assert html_url is not None df2 = df.assign( name=a["name"], - suite=suite_from_name( - a["name"], None if repo.endswith("/dask") else 4 - ), + suite=suite_from_name(a["name"]), date=r["created_at"], html_url=html_url, ) @@ -455,9 +447,10 @@ def make_chart(name, df, times): def main(argv: list[str] | None = None) -> None: + global TOKEN + args = parse_args(argv) - if not TOKEN: - raise RuntimeError("Failed to find a GitHub Token") + TOKEN = get_token() # Note: we drop **all** tests which did not have at least failures. # This is because, as nice as a block of green tests can be, there are @@ -489,7 +482,7 @@ def main(argv: list[str] | None = None) -> None: total.groupby([total.file, total.test]) .filter(lambda g: (g.status == "x").sum() >= args.nfails) .reset_index() - .assign(test=lambda df: f"{df.file}.{df.test}") + .assign(test=lambda df: df.file + "." + df.test) .groupby("test") ) overall = {name: grouped.get_group(name) for name in grouped.groups} @@ -529,8 +522,7 @@ def main(argv: list[str] | None = None) -> None: .resolve_scale(x="shared") # enforce aligned x axes ) - altair_saver.save( - chart, + chart.save( args.output, embed_options={ "renderer": "svg", # Makes the text searchable diff --git a/pixi.lock b/pixi.lock index 148d51cd6ce..4f7c2920d5d 100644 --- a/pixi.lock +++ b/pixi.lock @@ -11444,6 +11444,253 @@ environments: - conda_source: brotli-python[17d67cd3] @ git+https://github.com/dask/dask?subdirectory=continuous_integration%2Fpixi-recipes%2Fbrotli#e237122a298a00b6091712d83d77c00e94bf30fc - conda_source: dask-core[c325d5eb] @ git+https://github.com/dask/dask#e237122a298a00b6091712d83d77c00e94bf30fc - conda_source: distributed[61151bab] @ . + test-report: + channels: + - url: https://prefix.dev/conda-forge/ + packages: + p1: + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://prefix.dev/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://prefix.dev/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.53.3-h0c1763c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py313hf6604e3_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/pandas-3.0.3-py313hbfd7664_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.14-h6add32d_100_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/rpds-py-2026.6.3-py313hafbe609_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/junitparser-5.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/narwhals-2.23.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + p2: + - conda: https://prefix.dev/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/backports.zstd-1.6.0-py313h5c42d0f_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/brotli-python-1.2.0-py313hb260801_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libblas-3.11.0-8_haddc8a3_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libcblas-3.11.0-8_hd72aa62_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_19.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_19.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_19.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_19.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/liblapack-3.11.0-8_h88aeb00_openblas.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libopenblas-0.3.33-pthreads_h9d3fd7e_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.53.3-h10b116e_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_19.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libuuid-2.42.2-h1022ec0_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/markupsafe-3.0.3-py313hfa222a2_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.6-hf8d1292_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py313h839dfd1_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.6.3-h546c87b_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/pandas-3.0.3-py313h59403f9_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/python-3.13.14-h6185564_100_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rpds-py-2026.6.3-py313hc72d3b0_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/junitparser-5.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/narwhals-2.23.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + p3: + - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/junitparser-5.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/narwhals-2.23.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/backports.zstd-1.6.0-py313h7208f8c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.3-h1b79a29_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc7d1edf_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py313hce9b930_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/pandas-3.0.3-py313h1188861_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.14-h448ec07_100_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rpds-py-2026.6.3-py313hb9d2816_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + p4: + - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/junitparser-5.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/narwhals-2.23.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/backports.zstd-1.6.0-py313h2a31948_0.conda + - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://prefix.dev/conda-forge/win-64/libblas-3.11.0-8_h8455456_mkl.conda + - conda: https://prefix.dev/conda-forge/win-64/libcblas-3.11.0-8_h2a3cdd5_mkl.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda + - conda: https://prefix.dev/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://prefix.dev/conda-forge/win-64/liblapack-3.11.0-8_hf9ab0e9_mkl.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.3-hf5d6505_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://prefix.dev/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://prefix.dev/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_0.conda + - conda: https://prefix.dev/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda + - conda: https://prefix.dev/conda-forge/win-64/mkl-2026.0.0-hac47afa_908.conda + - conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py313ha8dc839_0.conda + - conda: https://prefix.dev/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/pandas-3.0.3-py313h26f5e95_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.13.14-h09917c8_100_cp313.conda + - conda: https://prefix.dev/conda-forge/win-64/rpds-py-2026.6.3-py313ha9ea572_0.conda + - conda: https://prefix.dev/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-h1b7c187_39.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_39.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_39.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda packages: - conda: https://conda.anaconda.org/rapidsai/linux-64/cudf-26.06.01-cuda12_cp311_abi3_260603_77ced62c.conda noarch: python @@ -17462,6 +17709,27 @@ packages: - numpy >=1.23,<3 size: 8978806 timestamp: 1779169197952 +- conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py313hf6604e3_0.conda + sha256: 7caba80d1515ef3a697a96fa23a1f14fceffd76f3261f0daea510d5a2209b064 + md5: 508c839b99562e2354917a6c83ab195b + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - python_abi 3.13.* *_cp313 + - libblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - numpy >=1.25,<3 + size: 9005587 + timestamp: 1782112542305 - conda: https://prefix.dev/conda-forge/linux-64/nvtx-0.2.15-py314h5bd0f2a_0.conda sha256: 02e18855f0c7373c0604e511e76bdc23a93c9e859c5aa351a2efd514b82554e4 md5: f845b14e1b68ec2f66f59153b2bedf00 @@ -26163,6 +26431,26 @@ packages: - numpy >=1.23,<3 size: 8002900 timestamp: 1779169206742 +- conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py313h839dfd1_0.conda + sha256: 99a3dc95ca8dbb3219eeb4939667d9b0d2a4ac93ef2ee2b0fda7f0d640e9f710 + md5: 9e84680acaeef60bf2e0a402eea96a14 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - numpy >=1.25,<3 + size: 8084328 + timestamp: 1782112570799 - conda: https://prefix.dev/conda-forge/linux-aarch64/nvtx-0.2.15-py314hafb4487_0.conda sha256: 69cd24d90e006a446b2f17ff9e5e0cb8fb3493e80abcdabb0cc164fd651b912b md5: 91bdc05e2a62ec1b883cd581888d4b2a @@ -29331,6 +29619,22 @@ packages: run_exports: {} size: 13688 timestamp: 1751626573984 +- conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda + sha256: 7198ddcd5f46fdd56fd6848a336cf805744a43c2b71dee8f698151cd7947fb10 + md5: b355b113d568e5590bbb1627ed221271 + depends: + - importlib-metadata + - jinja2 + - jsonschema >=3.0 + - narwhals >=2.4.0 + - packaging + - python >=3.10 + - typing-extensions >=4.12.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 567614 + timestamp: 1782378427857 - conda: https://prefix.dev/conda-forge/noarch/anaconda-auth-0.15.1-pyhd8ed1ab_0.conda sha256: 373cb02a71ff4a27411fbd93cb6383c0d83c663e73830669706d7e97cbfff4fc md5: 26d0b333c79c9cb9fdb7f4cfc23a5d1f @@ -30507,6 +30811,15 @@ packages: run_exports: {} size: 149709 timestamp: 1781615868173 +- conda: https://prefix.dev/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda + sha256: 45dfd037889b7075c5eb46394f93172de0be0b1624c7f802dd3ecc94b814d8e0 + md5: 1054c53c95d85e35b88143a3eda66373 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 364561 + timestamp: 1738926525117 - conda: https://prefix.dev/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb md5: b8993c19b0c32a2f7b66cbb58ca27069 @@ -31098,6 +31411,17 @@ packages: run_exports: {} size: 4740 timestamp: 1767839954258 +- conda: https://prefix.dev/conda-forge/noarch/junitparser-5.0.0-pyhd8ed1ab_0.conda + sha256: 03ad9b46c5fecbbf5487279d7e0ca8bc3954919800f0ef03e4f80ebe81b30a03 + md5: 895f4a65116690d31bb06595cc569c72 + depends: + - future + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 19757 + timestamp: 1774762449122 - conda: https://prefix.dev/conda-forge/noarch/jupyter-builder-1.0.2-pyhcf101f3_0.conda sha256: a845bffdbcdd1749dd4a46e47be5d4e0e1c87cdda547e3a87d297a6963ab0821 md5: 12b0a9513f6abaa944c313c4a01d5500 @@ -33121,6 +33445,15 @@ packages: run_exports: {} size: 91383 timestamp: 1756220668932 +- conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + run_exports: {} + size: 94080 + timestamp: 1783002732887 - conda: https://prefix.dev/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a md5: 53f5409c5cfd6c5a66417d68e3f0a864 @@ -33146,6 +33479,16 @@ packages: run_exports: {} size: 51692 timestamp: 1756220668932 +- conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + run_exports: {} + size: 52631 + timestamp: 1783002732887 - conda: https://prefix.dev/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c md5: f6d7aa696c67756a650e91e15e88223c @@ -38072,6 +38415,26 @@ packages: - numpy >=1.23,<3 size: 6995531 timestamp: 1779169217034 +- conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py313hce9b930_0.conda + sha256: 9192f87dfc1d57249ed8d7367353de6ab6caa8525d6fd6efa03d83758f46adad + md5: cb5c510ee24c7966001e122abe8d85b8 + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - numpy >=1.25,<3 + size: 7055163 + timestamp: 1782112549179 - conda: https://prefix.dev/conda-forge/osx-arm64/onednn-3.12-omp_h95bb4b2_0.conda sha256: dc6d6eeea55ccf0c5b34b73f5fa966ae8f8fbeb27632225bb4836d14185b397d md5: ab54feaf0b7ff7f981615a8e012b191c @@ -45574,6 +45937,27 @@ packages: - numpy >=1.23,<3 size: 7482657 timestamp: 1779169231603 +- conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py313ha8dc839_0.conda + sha256: ed9de3ebceb4558470b18c39406c185347032cd78a6e63bbaa688bf98d9dd29d + md5: 1c32bba8adb3cd2bfabaca7bf506b8cc + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - numpy >=1.25,<3 + size: 7375799 + timestamp: 1782112574716 - conda: https://prefix.dev/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda sha256: 42ad15cbb3bf31830efa04d4b86dd2d5c0dd590c86f98adcd3c8c1f75acf5dd5 md5: 9c9303e08b50e09f5c23e1dac99d0936 diff --git a/pixi.toml b/pixi.toml index 183b04f4e8f..34daaff597a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -239,6 +239,22 @@ pre-commit = "*" [feature.lint.tasks] lint = "pre-commit run --all-files" +[feature.test-report.dependencies] +python = "=3.13" +altair = ">=6" +junitparser = "*" +pandas = "*" +requests = "*" +urllib3 = "*" + +[feature.test-report.tasks.test-report] +args = [{ arg = "repo" }] +cmd = """ +python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 90 --max-runs 30 --nfails 1 -o test_report.html && +python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 7 --max-runs 30 --nfails 2 -o test_short_report.html --title 'Test Short Report' +""" +description = "Generate HTML reports of the flaky tests in the CI of the given repo, e.g. `pixi run test-report dask/distributed`" + [feature.build.dependencies] python = "=3.14" conda = "*" @@ -268,3 +284,4 @@ nightly = ["cpu", "nightly", "test"] cuda = ["cuda", "test"] lint = { features = ["cpu", "lint"], no-default-feature = true } build = { features = ["cpu", "build"], no-default-feature = true } +test-report = { features = ["cpu", "test-report"], no-default-feature = true } From 2be2f8ea77dd70d843077b1c566d03c8306e1939 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 15:28:50 +0100 Subject: [PATCH 2/6] Grant actions:read for scheduled runs The explicit permissions block removes the default actions:read scope, which is needed to list workflow runs and download artifacts. The PR run passed regardless because fork PR tokens are read-only on all scopes. Co-Authored-By: Claude Fable 5 --- .github/workflows/test-report.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 24c93414593..8c43bdba0ad 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -9,6 +9,9 @@ on: types: [labeled, opened, reopened, synchronize] permissions: + # Read the artifacts of the tests.yaml runs + actions: read + # Deploy to GitHub Pages contents: write jobs: From ba4ff4e069d05de864fa745000d34279d1e4459a Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 16:23:12 +0100 Subject: [PATCH 3/6] Split test-report workflow into generate and deploy jobs Each job gets strictly the permissions it needs: generate gets actions:read (plus contents:read for checkout), deploy gets contents:write. The report is handed over between the jobs as a workflow artifact, which also becomes always available for debugging; the databases are only uploaded on PRs. Co-Authored-By: Claude Fable 5 --- .github/workflows/test-report.yaml | 48 +++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 8c43bdba0ad..2680c2a3241 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -8,15 +8,9 @@ on: pull_request: types: [labeled, opened, reopened, synchronize] -permissions: - # Read the artifacts of the tests.yaml runs - actions: read - # Deploy to GitHub Pages - contents: write - jobs: - test-report: - name: Test Report + generate: + name: Generate report # Do not run the report job on forks. # On PRs, run only if the `test-report` label is set. if: > @@ -25,6 +19,11 @@ jobs: || (github.event_name != 'pull_request' && (github.repository == 'dask/distributed' || github.event_name == 'workflow_dispatch')) runs-on: ubuntu-latest + permissions: + # Read the artifacts of the tests.yaml runs + actions: read + # actions/checkout + contents: read env: GITHUB_TOKEN: ${{ github.token }} steps: @@ -55,18 +54,39 @@ jobs: mv test_report.html test_short_report.html deploy/ - name: Upload report as workflow artifact - # For debugging purposes on PRs; scheduled runs deploy to GitHub Pages instead + # Downloaded by the deploy job; also for debugging purposes on PRs + uses: actions/upload-artifact@v7 + with: + name: test-report + path: deploy + + - name: Upload databases as workflow artifact + # For debugging purposes on PRs; scheduled runs persist them through + # actions/cache above if: github.event_name == 'pull_request' uses: actions/upload-artifact@v7 + with: + name: test-report-db + path: test_report_* + + deploy: + name: Deploy report + needs: generate + # Never update GitHub Pages from PRs + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + # Push to the gh-pages branch + contents: write + steps: + - uses: actions/checkout@v7.0.0 + + - uses: actions/download-artifact@v8 with: name: test-report - path: | - deploy - test_report_* + path: deploy - name: Deploy 🚀 - # Never update GitHub Pages from PRs - if: github.event_name != 'pull_request' uses: JamesIves/github-pages-deploy-action@v4.8.0 with: branch: gh-pages From ad5fbf39702dcc55818560b44e33cd2f7085983e Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 16:38:04 +0100 Subject: [PATCH 4/6] Drop redundant contents:read Public repositories are implicitly readable by the workflow token even when the permissions block sets contents to none. Co-Authored-By: Claude Fable 5 --- .github/workflows/test-report.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 2680c2a3241..124dcf2a061 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -22,8 +22,6 @@ jobs: permissions: # Read the artifacts of the tests.yaml runs actions: read - # actions/checkout - contents: read env: GITHUB_TOKEN: ${{ github.token }} steps: From e189713bb9557a1227d796a72e143c51e649885e Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 17:34:08 +0100 Subject: [PATCH 5/6] review --- .github/workflows/test-report.yaml | 6 +- pixi.lock | 128 ++++++++++++++--------------- pixi.toml | 8 +- 3 files changed, 70 insertions(+), 72 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 124dcf2a061..00e4e2edce7 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -52,16 +52,14 @@ jobs: mv test_report.html test_short_report.html deploy/ - name: Upload report as workflow artifact - # Downloaded by the deploy job; also for debugging purposes on PRs + # Downloaded by the deploy job; also for debugging purposes uses: actions/upload-artifact@v7 with: name: test-report path: deploy - name: Upload databases as workflow artifact - # For debugging purposes on PRs; scheduled runs persist them through - # actions/cache above - if: github.event_name == 'pull_request' + # For debugging purposes uses: actions/upload-artifact@v7 with: name: test-report-db diff --git a/pixi.lock b/pixi.lock index 4f7c2920d5d..8474384d943 100644 --- a/pixi.lock +++ b/pixi.lock @@ -11450,8 +11450,7 @@ environments: packages: p1: - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - - conda: https://prefix.dev/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://prefix.dev/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda @@ -11470,18 +11469,19 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py313hf6604e3_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py314h2b28147_0.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/pandas-3.0.3-py313hbfd7664_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.14-h6add32d_100_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/pandas-3.0.3-py314hb4ffadd_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/rpds-py-2026.6.3-py313hafbe609_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/rpds-py-2026.6.3-py314h1bee95f_0.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.6.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda @@ -11499,7 +11499,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -11510,8 +11510,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda p2: - conda: https://prefix.dev/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/backports.zstd-1.6.0-py313h5c42d0f_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/brotli-python-1.2.0-py313hb260801_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda @@ -11531,18 +11530,19 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_19.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libuuid-2.42.2-h1022ec0_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/markupsafe-3.0.3-py313hfa222a2_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.6-hf8d1292_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py313h839dfd1_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py314he1698a1_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/openssl-3.6.3-h546c87b_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/pandas-3.0.3-py313h59403f9_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/python-3.13.14-h6185564_100_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/pandas-3.0.3-py314he6363bd_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/python-3.14.6-hc679e19_100_cp314.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/rpds-py-2026.6.3-py313hc72d3b0_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rpds-py-2026.6.3-py314h721bf50_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.6.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda @@ -11560,7 +11560,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -11572,6 +11572,7 @@ environments: p3: - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.6.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda @@ -11589,7 +11590,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -11599,8 +11600,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/backports.zstd-1.6.0-py313h7208f8c_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda @@ -11617,19 +11617,20 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.3-h1b79a29_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc7d1edf_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py313hce9b930_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py314hb79c6fa_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/pandas-3.0.3-py313h1188861_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.14-h448ec07_100_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/pandas-3.0.3-py314he609de1_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.6-h156bc91_100_cp314.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/rpds-py-2026.6.3-py313hb9d2816_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rpds-py-2026.6.3-py314he1d1ac0_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda p4: - conda: https://prefix.dev/conda-forge/noarch/altair-6.2.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.6.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.6.17-h4c7d964_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda @@ -11648,7 +11649,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://prefix.dev/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -11658,8 +11659,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - - conda: https://prefix.dev/conda-forge/win-64/backports.zstd-1.6.0-py313h2a31948_0.conda - - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda + - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://prefix.dev/conda-forge/win-64/libblas-3.11.0-8_h8455456_mkl.conda - conda: https://prefix.dev/conda-forge/win-64/libcblas-3.11.0-8_h2a3cdd5_mkl.conda @@ -11676,14 +11676,14 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://prefix.dev/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_0.conda - - conda: https://prefix.dev/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda + - conda: https://prefix.dev/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2026.0.0-hac47afa_908.conda - - conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py313ha8dc839_0.conda + - conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py314h02f10f6_0.conda - conda: https://prefix.dev/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda - - conda: https://prefix.dev/conda-forge/win-64/pandas-3.0.3-py313h26f5e95_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.13.14-h09917c8_100_cp313.conda - - conda: https://prefix.dev/conda-forge/win-64/rpds-py-2026.6.3-py313ha9ea572_0.conda + - conda: https://prefix.dev/conda-forge/win-64/pandas-3.0.3-py314hf700ef7_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.14.6-h4b44e0e_100_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/rpds-py-2026.6.3-py314hc980628_0.conda - conda: https://prefix.dev/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda @@ -17709,18 +17709,18 @@ packages: - numpy >=1.23,<3 size: 8978806 timestamp: 1779169197952 -- conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py313hf6604e3_0.conda - sha256: 7caba80d1515ef3a697a96fa23a1f14fceffd76f3261f0daea510d5a2209b064 - md5: 508c839b99562e2354917a6c83ab195b +- conda: https://prefix.dev/conda-forge/linux-64/numpy-2.5.0-py314h2b28147_0.conda + sha256: bbc665584886c90daf3f33cfbf665f279cf91d4bd5323f0432c16d2bf4d525e7 + md5: bdb21d2b990f9d3aee10fd43aca851fe depends: - python - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - - python_abi 3.13.* *_cp313 + - __glibc >=2.17,<3.0.a0 + - libcblas >=3.9.0,<4.0a0 - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - liblapack >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause @@ -17728,8 +17728,8 @@ packages: run_exports: weak: - numpy >=1.25,<3 - size: 9005587 - timestamp: 1782112542305 + size: 9075918 + timestamp: 1782112541752 - conda: https://prefix.dev/conda-forge/linux-64/nvtx-0.2.15-py314h5bd0f2a_0.conda sha256: 02e18855f0c7373c0604e511e76bdc23a93c9e859c5aa351a2efd514b82554e4 md5: f845b14e1b68ec2f66f59153b2bedf00 @@ -26431,16 +26431,16 @@ packages: - numpy >=1.23,<3 size: 8002900 timestamp: 1779169206742 -- conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py313h839dfd1_0.conda - sha256: 99a3dc95ca8dbb3219eeb4939667d9b0d2a4ac93ef2ee2b0fda7f0d640e9f710 - md5: 9e84680acaeef60bf2e0a402eea96a14 +- conda: https://prefix.dev/conda-forge/linux-aarch64/numpy-2.5.0-py314he1698a1_0.conda + sha256: 8677e6bd3a1a95f8ecf2b0f1ca39f30f55b1aa0865b217bb3cb55b29d3e092fa + md5: 10165160938f6498096bda3e0ff051ac depends: - python - libstdcxx >=14 - libgcc >=14 - - libblas >=3.9.0,<4.0a0 - - python_abi 3.13.* *_cp313 - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - liblapack >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 @@ -26449,8 +26449,8 @@ packages: run_exports: weak: - numpy >=1.25,<3 - size: 8084328 - timestamp: 1782112570799 + size: 8158865 + timestamp: 1782112546539 - conda: https://prefix.dev/conda-forge/linux-aarch64/nvtx-0.2.15-py314hafb4487_0.conda sha256: 69cd24d90e006a446b2f17ff9e5e0cb8fb3493e80abcdabb0cc164fd651b912b md5: 91bdc05e2a62ec1b883cd581888d4b2a @@ -38415,17 +38415,17 @@ packages: - numpy >=1.23,<3 size: 6995531 timestamp: 1779169217034 -- conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py313hce9b930_0.conda - sha256: 9192f87dfc1d57249ed8d7367353de6ab6caa8525d6fd6efa03d83758f46adad - md5: cb5c510ee24c7966001e122abe8d85b8 +- conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.5.0-py314hb79c6fa_0.conda + sha256: dfd260fce05ff833ac121a1b1e4aa22d9c346a781c1e883dd871338cc1d9f8b0 + md5: 5283d56d01517fa1780c72f2544e8603 depends: - python - - libcxx >=19 - __osx >=11.0 - - python_abi 3.13.* *_cp313 - - libcblas >=3.9.0,<4.0a0 - - liblapack >=3.9.0,<4.0a0 + - libcxx >=19 - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause @@ -38433,8 +38433,8 @@ packages: run_exports: weak: - numpy >=1.25,<3 - size: 7055163 - timestamp: 1782112549179 + size: 7123654 + timestamp: 1782112549976 - conda: https://prefix.dev/conda-forge/osx-arm64/onednn-3.12-omp_h95bb4b2_0.conda sha256: dc6d6eeea55ccf0c5b34b73f5fa966ae8f8fbeb27632225bb4836d14185b397d md5: ab54feaf0b7ff7f981615a8e012b191c @@ -45937,18 +45937,18 @@ packages: - numpy >=1.23,<3 size: 7482657 timestamp: 1779169231603 -- conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py313ha8dc839_0.conda - sha256: ed9de3ebceb4558470b18c39406c185347032cd78a6e63bbaa688bf98d9dd29d - md5: 1c32bba8adb3cd2bfabaca7bf506b8cc +- conda: https://prefix.dev/conda-forge/win-64/numpy-2.5.0-py314h02f10f6_0.conda + sha256: 86c3e926fa1d6f27ebe6b9db11ff12e9a3b6e4b0343bf4a9b489dafd9614da3f + md5: f92585b1624ecdd117b6d13fd4d691ed depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 constrains: - numpy-base <0a0 license: BSD-3-Clause @@ -45956,8 +45956,8 @@ packages: run_exports: weak: - numpy >=1.25,<3 - size: 7375799 - timestamp: 1782112574716 + size: 7436159 + timestamp: 1782112573833 - conda: https://prefix.dev/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda sha256: 42ad15cbb3bf31830efa04d4b86dd2d5c0dd590c86f98adcd3c8c1f75acf5dd5 md5: 9c9303e08b50e09f5c23e1dac99d0936 diff --git a/pixi.toml b/pixi.toml index 34daaff597a..75b3e0eb94f 100644 --- a/pixi.toml +++ b/pixi.toml @@ -240,8 +240,8 @@ pre-commit = "*" lint = "pre-commit run --all-files" [feature.test-report.dependencies] -python = "=3.13" -altair = ">=6" +python = "=3.14" +altair = "*" junitparser = "*" pandas = "*" requests = "*" @@ -250,8 +250,8 @@ urllib3 = "*" [feature.test-report.tasks.test-report] args = [{ arg = "repo" }] cmd = """ -python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 90 --max-runs 30 --nfails 1 -o test_report.html && -python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 7 --max-runs 30 --nfails 2 -o test_short_report.html --title 'Test Short Report' +python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 90 --max-runs 60 --nfails 1 -o test_report.html && +python continuous_integration/scripts/test_report.py --repo '{{ repo }}' --max-days 7 --max-runs 15 --nfails 2 -o test_short_report.html --title 'Test Short Report' """ description = "Generate HTML reports of the flaky tests in the CI of the given repo, e.g. `pixi run test-report dask/distributed`" From db38c6456e1d6a4e32216b503cc19e716dcfd77c Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 3 Jul 2026 17:39:14 +0100 Subject: [PATCH 6/6] Add .db extension to database file names e.g. test_report.dask__distributed.db. This simplifies .gitignore and the workflow glob patterns, which previously had to avoid matching test_report.html. Co-Authored-By: Claude Fable 5 --- .github/workflows/test-report.yaml | 7 +++---- .gitignore | 2 +- continuous_integration/scripts/test_report.py | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 00e4e2edce7..01be5455e3f 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -37,9 +37,8 @@ jobs: - uses: actions/cache@v6 id: cache with: - # Local databases of downloaded test results, created by test_report.py. - # Note that this pattern must not match test_report.html. - path: test_report_* + # Local databases of downloaded test results, created by test_report.py + path: test_report*.db # Caches are immutable; use a fresh key to upload an updated database at # the end of every run, and restore from the most recent one. key: test-report-${{ hashFiles('continuous_integration/scripts/test_report.py') }}-${{ github.run_id }} @@ -63,7 +62,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: test-report-db - path: test_report_* + path: test_report*.db deploy: name: Deploy report diff --git a/.gitignore b/.gitignore index 4ac7956293a..4eca8708bc7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ distributed/_version.py # Test reports test_report*.html -test_report_* +test_report*.db test_short_report.html # Test failures will dump the cluster state in here diff --git a/continuous_integration/scripts/test_report.py b/continuous_integration/scripts/test_report.py index eb30601a9bf..5f7f79adfa2 100644 --- a/continuous_integration/scripts/test_report.py +++ b/continuous_integration/scripts/test_report.py @@ -55,8 +55,8 @@ def get_token() -> str: def cache_name(prefix: str, repo: str) -> str: - """Name of the local cache database, e.g. test_report_dask__distributed""" - return f"{prefix}_{repo.replace('/', '__')}" + """Name of the local cache database, e.g. test_report.dask__distributed.db""" + return f"{prefix}.{repo.replace('/', '__')}.db" @contextlib.contextmanager