From 95760c05e7da621a1f2fdf81e80b76605e7355aa Mon Sep 17 00:00:00 2001 From: "LI,JHE-CHEN" <103923510+RoyLee1224@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:55:36 +0800 Subject: [PATCH] [v3-2-test] ci: sync AGENTS.md commands from contributing docs via prek hook (#68204) (cherry picked from commit 6a5cbdbc1318df8f32b1c7322c84b1076c30deb2) Co-authored-by: LI,JHE-CHEN <103923510+RoyLee1224@users.noreply.github.com> --- .pre-commit-config.yaml | 11 ++ AGENTS.md | 2 + contributing-docs/08_static_code_checks.rst | 14 +++ contributing-docs/testing/unit_tests.rst | 27 +++++ scripts/ci/prek/generate_agent_skills.py | 110 ++++++++++++++++++ .../ci/prek/test_generate_agent_skills.py | 47 ++++++++ 6 files changed, 211 insertions(+) create mode 100644 scripts/ci/prek/generate_agent_skills.py create mode 100644 scripts/tests/ci/prek/test_generate_agent_skills.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07d7ca449bc29..f6f4fb6e65207 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -830,6 +830,17 @@ repos: ^airflow-core/src/airflow/ui/public/i18n/locales/en/.*\.json$| ^\.github/skills/airflow-translations/SKILL\.md$ pass_filenames: false + - id: generate-agent-skills + name: Sync AGENTS.md commands from contributing docs + entry: ./scripts/ci/prek/generate_agent_skills.py + language: python + files: > + (?x) + ^contributing-docs/.*\.rst$| + ^AGENTS\.md$| + ^scripts/ci/prek/generate_agent_skills\.py$ + pass_filenames: false + additional_dependencies: ['pyyaml', 'rich>=13.6.0'] - id: update-pyproject-toml name: Update Airflow's meta-package pyproject.toml language: python diff --git a/AGENTS.md b/AGENTS.md index 1feab15d514fc..52fa64507c335 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ Don't spell out **Directed Acyclic Graph** except for historical context. `` is folder where pyproject.toml of the package you want to test is located. For example, `airflow-core` or `providers/amazon`. `` is the branch the PR will be merged into — usually `main`, but could be `v3-1-test` when creating a PR for the 3.1 branch. + - **Run a single test:** `uv run --project pytest path/to/test.py::TestClass::test_method -xvs` - **Run a test file:** `uv run --project pytest path/to/test.py -xvs` - **Run all tests in package:** `uv run --project pytest path/to/package -xvs` @@ -54,6 +55,7 @@ Don't spell out **Directed Acyclic Graph** except for historical context. - **Run manual (slower) checks:** `prek run --from-ref --stage manual` - **Build docs:** `breeze build-docs` - **Determine which tests to run based on changed files:** `breeze selective-checks --commit-ref ` + SQLite is the default backend. Use `--backend postgres` or `--backend mysql` for integration tests that need those databases. If Docker networking fails, run `docker network prune`. diff --git a/contributing-docs/08_static_code_checks.rst b/contributing-docs/08_static_code_checks.rst index a4a796f81bd26..11b008bd5d047 100644 --- a/contributing-docs/08_static_code_checks.rst +++ b/contributing-docs/08_static_code_checks.rst @@ -226,6 +226,20 @@ You can always skip running the tests by providing ``--no-verify`` flag to the To check other usage types of the pre-commit framework, see `Pre-commit website `__. +.. AGENT-SKILL-START + type: agents-md-commands + order: 30 + lines: + - "- **Type-check (non-providers):** run the prek hook \u2014 `prek run mypy- --all-files` (e.g. `mypy-airflow-core`, `mypy-task-sdk`, `mypy-shared-logging`; each `shared/` workspace member has its own `mypy-shared-` hook). The hook uses a dedicated virtualenv and mypy cache under `.build/mypy-venvs//` and `.build/mypy-caches//`; mypy itself is installed from `uv.lock` via the `mypy` dependency group (`uv sync --group mypy`), so it never mutates your project `.venv`. The hook prefers `uv` from the project's main `.venv/bin/uv` (installed by `uv sync` \u2014 `uv` is part of the `dev` dependency group via the `all` extras) for a project-pinned uv version; it falls back to `uv` on `$PATH` with a warning if that binary is missing. Clear with `breeze down --cleanup-mypy-cache`." + - "- **Type-check (providers):** `breeze run mypy path/to/code`" + - "- **Lint with ruff only:** `prek run ruff --from-ref `" + - "- **Format with ruff only:** `prek run ruff-format --from-ref `" + - "- **Run regular (fast) static checks:** `prek run --from-ref --stage pre-commit`" + - "- **Run manual (slower) checks:** `prek run --from-ref --stage manual`" + - "- **Build docs:** `breeze build-docs`" + - "- **Determine which tests to run based on changed files:** `breeze selective-checks --commit-ref `" +.. AGENT-SKILL-END + Disabling particular checks --------------------------- diff --git a/contributing-docs/testing/unit_tests.rst b/contributing-docs/testing/unit_tests.rst index 3032abe8664c3..9388eaef576fa 100644 --- a/contributing-docs/testing/unit_tests.rst +++ b/contributing-docs/testing/unit_tests.rst @@ -218,6 +218,17 @@ rerun in Breeze as needed (``-n auto`` will parallelize tests using the ``pytest breeze shell --backend none --python 3.10 > pytest airflow-core/tests --skip-db-tests -n auto +.. AGENT-SKILL-START + type: agents-md-commands + order: 10 + lines: + - "- **Run a single test:** `uv run --project pytest path/to/test.py::TestClass::test_method -xvs`" + - "- **Run a test file:** `uv run --project pytest path/to/test.py -xvs`" + - "- **Run all tests in package:** `uv run --project pytest path/to/package -xvs`" + - "- **If uv tests fail with missing system dependencies, run the tests with breeze**: `breeze run pytest -xvs`" + - "- **Run a Python script:** `uv run --project python dev/my_script.py`" +.. AGENT-SKILL-END + Airflow DB tests ................ @@ -278,6 +289,22 @@ As explained before, you cannot run DB tests in parallel using the ``pytest-xdis breeze testing core-tests --run-db-tests-only --backend postgres --python 3.10 --run-in-parallel +.. AGENT-SKILL-START + type: agents-md-commands + order: 20 + lines: + - "- **Run core or provider tests suite in parallel:** `breeze testing --run-in-parallel` (test groups: `core-tests`, `providers-tests`)" + - "- **Run core or provider db tests suite in parallel:** `breeze testing --run-db-tests-only --run-in-parallel` (test groups: `core-tests`, `providers-tests`)" + - "- **Run core or provider non-db tests suite in parallel:** `breeze testing --skip-db-tests --use-xdist` (test groups: `core-tests`, `providers-tests`)" + - "- **Run single provider complete test suite:** `breeze testing providers-tests --test-type \"Providers[PROVIDERS_LIST]\"` (e.g., `Providers[google]` or `Providers[amazon]` or \"Providers[amazon,google]\")" + - "- **Run Helm tests in parallel with xdist** `breeze testing helm-tests --use-xdist`" + - "- **Run Helm tests with specific K8s version:** `breeze testing helm-tests --use-xdist --kubernetes-version 1.35.0`" + - "- **Run specific Helm test type:** `breeze testing helm-tests --use-xdist --test-type ` (types: `airflow_aux`, `airflow_core`, `apiserver`, `dagprocessor`, `other`, `redis`, `security`, `statsd`, `webserver`)" + - "- **Run other suites of tests** `breeze testing ` (test groups: `airflow-ctl-tests`, `docker-compose-tests`, `task-sdk-tests`)" + - "- **Run scripts tests:** `uv run --project scripts pytest scripts/tests/ -xvs`" + - "- **Run Airflow CLI:** `breeze run airflow dags list`" +.. AGENT-SKILL-END + Examples of marking test as DB test ................................... diff --git a/scripts/ci/prek/generate_agent_skills.py b/scripts/ci/prek/generate_agent_skills.py new file mode 100644 index 0000000000000..a3c59418e13d9 --- /dev/null +++ b/scripts/ci/prek/generate_agent_skills.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Sync the AGENTS.md Commands section from contributing docs. + +Contributing docs contain ``.. AGENT-SKILL-START`` / ``.. AGENT-SKILL-END`` +RST comment blocks with pre-formatted AGENTS.md bullet lines. This script +collects those blocks, sorts them by ``order``, and updates the +generated-commands section in AGENTS.md via ``insert_documentation()``. + +The prek hook runs this automatically when contributing docs or AGENTS.md +change. +""" + +from __future__ import annotations + +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +import yaml +from common_prek_utils import AIRFLOW_ROOT_PATH, console, insert_documentation + +CONTRIBUTING_DOCS_ROOT = AIRFLOW_ROOT_PATH / "contributing-docs" +AGENTS_MD_PATH = AIRFLOW_ROOT_PATH / "AGENTS.md" +AGENTS_MD_START = "" +AGENTS_MD_END = "" +BLOCK_RE = re.compile(r"\.\. AGENT-SKILL-START\n(.*?)\.\. AGENT-SKILL-END", re.DOTALL) + + +@dataclass(frozen=True) +class CommandBlock: + order: int + lines: tuple[str, ...] + + +def _dedent_rst_comment_body(raw: str) -> str: + """Remove the 3-space RST comment indentation from each line.""" + lines = raw.splitlines() + return "\n".join(line[3:] if line.startswith(" ") else line for line in lines).strip() + + +def _parse_blocks_from_rst(rst_path: Path) -> list[CommandBlock]: + """Extract all AGENT-SKILL command blocks from a single RST file.""" + text = rst_path.read_text() + blocks: list[CommandBlock] = [] + for match in BLOCK_RE.finditer(text): + block = yaml.safe_load(_dedent_rst_comment_body(match.group(1))) + if not isinstance(block, dict) or block.get("type") != "agents-md-commands": + continue + order = int(block.get("order", 100)) + raw_lines = block.get("lines", []) + lines = tuple(str(line).rstrip() for line in raw_lines) + blocks.append(CommandBlock(order=order, lines=lines)) + return blocks + + +def collect_blocks( + contributing_docs_root: Path = CONTRIBUTING_DOCS_ROOT, +) -> list[CommandBlock]: + """Scan all RST files and return command blocks sorted by order.""" + blocks: list[CommandBlock] = [] + for rst_path in sorted(contributing_docs_root.rglob("*.rst")): + blocks.extend(_parse_blocks_from_rst(rst_path)) + blocks.sort(key=lambda b: b.order) + return blocks + + +def render_lines(blocks: list[CommandBlock]) -> list[str]: + """Flatten all blocks into a list of lines for insert_documentation().""" + result: list[str] = [] + for block in blocks: + for line in block.lines: + result.append(line + "\n") + return result + + +def main() -> int: + blocks = collect_blocks() + if not blocks: + console.print("[red]No AGENT-SKILL blocks found in contributing docs.[/red]") + return 1 + content = render_lines(blocks) + insert_documentation( + file_path=AGENTS_MD_PATH, + content=content, + header=AGENTS_MD_START, + footer=AGENTS_MD_END, + extra_information="AGENTS.md commands from contributing docs", + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/tests/ci/prek/test_generate_agent_skills.py b/scripts/tests/ci/prek/test_generate_agent_skills.py new file mode 100644 index 0000000000000..166b7e65ee81b --- /dev/null +++ b/scripts/tests/ci/prek/test_generate_agent_skills.py @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +from ci.prek.generate_agent_skills import ( + collect_blocks, + render_lines, +) + + +def test_collect_blocks_finds_all_markers(): + blocks = collect_blocks() + + assert len(blocks) >= 3 + + +def test_collect_blocks_sorted_by_order(): + blocks = collect_blocks() + + orders = [b.order for b in blocks] + assert orders == sorted(orders) + + +def test_render_lines_produces_bullet_list(): + blocks = collect_blocks() + lines = render_lines(blocks) + + assert len(lines) > 0 + assert all(line.startswith("- **") for line in lines) + assert any("uv run --project pytest" in line for line in lines) + assert any("breeze testing helm-tests" in line for line in lines) + assert any("prek run mypy-" in line for line in lines) + assert any("breeze selective-checks" in line for line in lines)