blob: 9bac05503b4c925e870dcad5a9c2606f47bbbd62 [file] [log] [blame]
# 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
import sys
from pathlib import Path
# Add the SDK src directory to sys.path so that importlib loads our airflow.sdk module
sys.path.insert(0, str(Path(__file__).parents[3] / "src"))
import importlib
import shutil
import subprocess
import zlib
from pathlib import Path
import pytest
def read_inventory(inv_path: Path):
"""
Read a Sphinx objects.inv inventory file and return a mapping of documented full names to their entries.
"""
inv: dict[str, tuple[str, str, str]] = {}
with inv_path.open("rb") as f:
f.readline()
f.readline()
f.readline()
f.readline()
data = zlib.decompress(f.read()).decode("utf-8").splitlines()
for line in data:
if not line.strip():
continue
parts = line.split(None, 4)
if len(parts) != 5:
continue
name, domain_role, prio, location, dispname = parts
inv[name] = (domain_role, location, dispname)
return inv
@pytest.mark.skipif(
shutil.which("sphinx-build") is None, reason="sphinx-build not available, skipping docs inventory test"
)
def test_docs_inventory_matches_public_api(tmp_path):
"""
Build the HTML docs and compare the generated Sphinx inventory with the public API re-exports.
"""
docs_dir = Path(__file__).parents[3] / "docs"
build_dir = tmp_path / "build"
sphinx = shutil.which("sphinx-build")
subprocess.run([sphinx, "-b", "html", "-q", str(docs_dir), str(build_dir)], check=True)
inv_path = build_dir / "objects.inv"
assert inv_path.exists(), "objects.inv not found after docs build"
inv = read_inventory(inv_path)
documented = {
name.rsplit(".", 1)[-1]
for name in inv.keys()
if name.startswith("airflow.sdk.") and name.count(".") == 2
}
sdk = importlib.import_module("airflow.sdk")
public = set(getattr(sdk, "__all__", [])) - {"__version__"}
extras = {"AirflowParsingContext"}
# we do not want to document the class for `conf` but a description is present in the docs
excluded_from_docs = {"conf", "macros"}
missing = (public - documented) - excluded_from_docs
assert not missing, f"Public API items missing in docs: {missing}"
unexpected = (documented - public) - extras
assert not unexpected, f"Unexpected documented items: {unexpected}"