This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 80ca9f51a86 Add Pre-commit check for airflowctl tests (#58856)
80ca9f51a86 is described below
commit 80ca9f51a867e681c591d83ab8375772ec337b49
Author: Steve Ahn <[email protected]>
AuthorDate: Sun Nov 30 15:16:56 2025 -0800
Add Pre-commit check for airflowctl tests (#58856)
* pre-commit adds airflowctl int check
* cleanup
* remove asset materialize & others
* dags update param, mege conflict, cicd error
---
.../tests/airflowctl_tests/conftest.py | 10 ++
airflow-ctl/.pre-commit-config.yaml | 10 ++
.../ci/prek/check_airflowctl_command_coverage.py | 147 +++++++++++++++++++++
3 files changed, 167 insertions(+)
diff --git a/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
index 2c57c0ca120..5186e3e794e 100644
--- a/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
+++ b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
@@ -243,6 +243,8 @@ def test_commands(login_command, date_param):
login_command,
# Assets commands
"assets list",
+ "assets get --asset-id=1",
+ "assets create-event --asset-id=1",
# Backfill commands
"backfill list",
# Config commands
@@ -263,12 +265,20 @@ def test_commands(login_command, date_param):
# DAGs commands
"dags list",
"dags get --dag-id=example_bash_operator",
+ "dags get-details --dag-id=example_bash_operator",
+ "dags get-stats --dag-ids=example_bash_operator",
+ "dags get-version --dag-id=example_bash_operator --version-number=1",
+ "dags list-import-errors",
+ "dags list-version --dag-id=example_bash_operator",
+ "dags list-warning",
# Order of trigger and pause/unpause is important for test stability
because state checked
f"dags trigger --dag-id=example_bash_operator
--logical-date={date_param} --run-after={date_param}",
"dags pause --dag-id=example_bash_operator",
"dags unpause --dag-id=example_bash_operator",
# DAG Run commands
f'dagrun get --dag-id=example_bash_operator
--dag-run-id="manual__{date_param}"',
+ "dags update --dag-id=example_bash_operator --no-is-paused",
+ # DAG Run commands
"dagrun list --dag-id example_bash_operator --state success --limit=1",
# Jobs commands
"jobs list",
diff --git a/airflow-ctl/.pre-commit-config.yaml
b/airflow-ctl/.pre-commit-config.yaml
index 00eada7f645..a68ac971c98 100644
--- a/airflow-ctl/.pre-commit-config.yaml
+++ b/airflow-ctl/.pre-commit-config.yaml
@@ -53,3 +53,13 @@ repos:
^src/airflowctl/ctl/cli_config.py$|
^src/airflowctl/api/operations.py$|
^src/airflowctl/ctl/commands/.*\.py$
+ - id: check-airflowctl-command-coverage
+ name: Check airflowctl CLI command test coverage
+ entry: ../scripts/ci/prek/check_airflowctl_command_coverage.py
+ language: python
+ pass_filenames: false
+ files:
+ (?x)
+ ^src/airflowctl/api/operations.py$|
+ ^../airflow-ctl-tests/tests/airflowctl_tests/conftest.py$|
+ ^../scripts/ci/prek/check_airflowctl_command_coverage.py$
diff --git a/scripts/ci/prek/check_airflowctl_command_coverage.py
b/scripts/ci/prek/check_airflowctl_command_coverage.py
new file mode 100755
index 00000000000..00f89c723b7
--- /dev/null
+++ b/scripts/ci/prek/check_airflowctl_command_coverage.py
@@ -0,0 +1,147 @@
+#!/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.
+# /// script
+# requires-python = ">=3.10,<3.11"
+# dependencies = [
+# "rich>=13.6.0",
+# ]
+# ///
+"""
+Check that all airflowctl CLI commands have integration test coverage by
comparing commands from operations.py against test_commands in conftest.py.
+"""
+
+from __future__ import annotations
+
+import ast
+import re
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))
+from common_prek_utils import AIRFLOW_ROOT_PATH, console
+
+OPERATIONS_FILE = AIRFLOW_ROOT_PATH / "airflow-ctl" / "src" / "airflowctl" /
"api" / "operations.py"
+CONFTEST_FILE = AIRFLOW_ROOT_PATH / "airflow-ctl-tests" / "tests" /
"airflowctl_tests" / "conftest.py"
+
+# Operations excluded from CLI (see cli_config.py)
+EXCLUDED_OPERATION_CLASSES = {"BaseOperations", "LoginOperations",
"VersionOperations"}
+EXCLUDED_METHODS = {
+ "__init__",
+ "__init_subclass__",
+ "error",
+ "_check_flag_and_exit_if_server_response_error",
+ "bulk",
+}
+
+EXCLUDED_COMMANDS = {
+ "assets delete-dag-queued-events",
+ "assets delete-queued-event",
+ "assets delete-queued-events",
+ "assets get-by-alias",
+ "assets get-dag-queued-event",
+ "assets get-dag-queued-events",
+ "assets get-queued-events",
+ "assets list-by-alias",
+ "assets materialize",
+ "backfill cancel",
+ "backfill create",
+ "backfill create-dry-run",
+ "backfill get",
+ "backfill pause",
+ "backfill unpause",
+ "connections create-defaults",
+ "connections test",
+ "dags delete",
+ "dags get-import-error",
+ "dags get-tags",
+}
+
+
+def parse_operations() -> dict[str, list[str]]:
+ commands: dict[str, list[str]] = {}
+
+ with open(OPERATIONS_FILE) as f:
+ tree = ast.parse(f.read(), filename=str(OPERATIONS_FILE))
+
+ for node in ast.walk(tree):
+ if isinstance(node, ast.ClassDef) and node.name.endswith("Operations"):
+ if node.name in EXCLUDED_OPERATION_CLASSES:
+ continue
+
+ group_name = node.name.replace("Operations", "").lower()
+ commands[group_name] = []
+
+ for child in node.body:
+ if isinstance(child, ast.FunctionDef):
+ method_name = child.name
+ if method_name in EXCLUDED_METHODS or
method_name.startswith("_"):
+ continue
+ subcommand = method_name.replace("_", "-")
+ commands[group_name].append(subcommand)
+
+ return commands
+
+
+def parse_tested_commands() -> set[str]:
+ tested: set[str] = set()
+
+ with open(CONFTEST_FILE) as f:
+ content = f.read()
+
+ # Match command patterns like "assets list", "dags list-import-errors",
etc.
+ # Also handles f-strings like f"dagrun get..." or f'dagrun get...'
+ pattern = r'f?["\']([a-z]+(?:-[a-z]+)*\s+[a-z]+(?:-[a-z]+)*)'
+ for match in re.findall(pattern, content):
+ parts = match.split()
+ if len(parts) >= 2:
+ tested.add(f"{parts[0]} {parts[1]}")
+
+ return tested
+
+
+def main():
+ available = parse_operations()
+ tested = parse_tested_commands()
+
+ missing = []
+ for group, subcommands in sorted(available.items()):
+ for subcommand in sorted(subcommands):
+ cmd = f"{group} {subcommand}"
+ if cmd not in tested and cmd not in EXCLUDED_COMMANDS:
+ missing.append(cmd)
+
+ if missing:
+ console.print("[red]ERROR: Commands not covered by integration
tests:[/]")
+ for cmd in missing:
+ console.print(f" [red]- {cmd}[/]")
+ console.print()
+ console.print("[yellow]Fix by either:[/]")
+ console.print("1. Add test to
airflow-ctl-tests/tests/airflowctl_tests/conftest.py")
+ console.print("2. Add to EXCLUDED_COMMANDS in
scripts/ci/prek/check_airflowctl_command_coverage.py")
+ sys.exit(1)
+
+ total = sum(len(cmds) for cmds in available.values())
+ console.print(
+ f"[green]All {total} CLI commands covered ({len(tested)} tested,
{len(EXCLUDED_COMMANDS)} excluded)[/]"
+ )
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()