From 499bd8d4d6f6d8ad8d9e9c241296b6aeab0e161b Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Thu, 23 Jul 2026 17:00:06 -0400 Subject: [PATCH 01/10] Add `cuda_suffixed` check to `verify-dependencies` --- .../dependencies/__init__.py | 13 + .../dependencies/cuda_suffixed.py | 295 +++++++ .../dependencies/use_cuda_wheels.py | 5 +- .../utils/dependencies_yaml.py | 83 ++ .../dependencies/test_cuda_suffixed.py | 832 ++++++++++++++++++ .../dependencies/test_use_cuda_wheels.py | 27 +- .../utils/test_dependencies_yaml.py | 126 +++ tests/test_testing_utils.py | 59 ++ .../rapids_pre_commit_hooks_test_utils.py | 52 +- 9 files changed, 1477 insertions(+), 15 deletions(-) create mode 100644 src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py create mode 100644 tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py diff --git a/src/rapids_pre_commit_hooks/dependencies/__init__.py b/src/rapids_pre_commit_hooks/dependencies/__init__.py index 4843d0e..f7ab930 100644 --- a/src/rapids_pre_commit_hooks/dependencies/__init__.py +++ b/src/rapids_pre_commit_hooks/dependencies/__init__.py @@ -3,6 +3,7 @@ import argparse +from .cuda_suffixed import CUDASuffixedHandler from .use_cuda_wheels import UseCUDAWheelsHandler from ..lint import Linter, LintMain from ..utils.dependencies_yaml import ( @@ -13,6 +14,7 @@ def check_dependencies(linter: "Linter", args: "argparse.Namespace") -> None: handler = ChainedHandler() + handler.add_handler(CUDASuffixedHandler(linter, args)) handler.add_handler(UseCUDAWheelsHandler(linter, args)) traverse_dependencies_yaml(handler, linter.content) @@ -22,6 +24,17 @@ def main() -> None: m.argparser.description = ( "Verify that dependencies.yaml follows the correct conventions." ) + m.argparser.add_argument( + "--rapids-version", + help="Specify a RAPIDS version to use instead of reading from the " + "VERSION file", + ) + m.argparser.add_argument( + "--rapids-version-file", + help="Specify a file to read the RAPIDS version from instead of " + "VERSION", + default="VERSION", + ) with m.execute() as ctx: ctx.add_check(check_dependencies) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py new file mode 100644 index 0000000..f113bc7 --- /dev/null +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -0,0 +1,295 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import contextlib +import os +import re +from dataclasses import dataclass, field +from functools import cache +from typing import Optional, TYPE_CHECKING + +from packaging.requirements import InvalidRequirement, Requirement +from packaging.version import Version + +from rapids_pre_commit_hooks.utils.dependencies_yaml import ( + Handler, +) +from rapids_metadata.remote import fetch_latest + +if TYPE_CHECKING: + import argparse + from collections.abc import Generator + + import yaml + + from rapids_pre_commit_hooks.lint import Linter + from rapids_metadata.metadata import RAPIDSMetadata, RAPIDSVersion + + +@cache +def all_metadata() -> "RAPIDSMetadata": + return fetch_latest() + + +def get_rapids_version(args: "argparse.Namespace") -> "RAPIDSVersion": + md = all_metadata() + return ( + md.versions[args.rapids_version] + if args.rapids_version + else md.get_current_version(os.getcwd(), args.rapids_version_file) + ) + + +class CUDASuffixedHandler(Handler): + @dataclass + class CommonContext: + common_key: "yaml.Node" + + @dataclass + class CommonItemContext: + has_python_output_type: bool = False + suspicious_suffixed_packages: ( + "list[tuple[str, Optional[str], yaml.Node]]" + ) = field(default_factory=list) + suspicious_unsuffixed_packages: ( + "list[tuple[str, Optional[str], yaml.Node]]" + ) = field(default_factory=list) + + @dataclass + class SpecificItemContext: + has_python_output_type: bool = False + matrices_item_contexts: ( + "list[CUDASuffixedHandler.MatricesItemContext]" + ) = field(default_factory=list) + + @dataclass + class MatricesItemContext: + matrix_node: "Optional[yaml.Node]" = None + cuda_suffixed_node: "Optional[yaml.Node]" = None + cuda_suffixed: "Optional[bool]" = None + cuda_node: "Optional[yaml.Node]" = None + cuda_major: "Optional[int]" = None + suspicious_suffixed_packages: ( + "list[tuple[str, Optional[str], yaml.Node]]" + ) = field(default_factory=list) + suspicious_unsuffixed_packages: ( + "list[tuple[str, Optional[str], yaml.Node]]" + ) = field(default_factory=list) + + def __init__(self, linter: "Linter", args: "argparse.Namespace") -> None: + self.linter = linter + self.args = args + + def handle_output_type( + self, + output_types_context: ( + "CUDASuffixedHandler.CommonItemContext | " + "CUDASuffixedHandler.SpecificItemContext" + ), + item: "yaml.Node", + ) -> None: + if item.value in {"requirements", "pyproject"}: + output_types_context.has_python_output_type = True + + @contextlib.contextmanager + def handle_common( + self, + dependency_set_context: None, # noqa: ARG002 + key: "yaml.Node", + value: "yaml.Node", # noqa: ARG002 + ) -> "Generator[CUDASuffixedHandler.CommonContext]": + context = CUDASuffixedHandler.CommonContext(key) + yield context + + @contextlib.contextmanager + def handle_common_item( + self, + common_context: "CUDASuffixedHandler.CommonContext", + item: "yaml.Node", # noqa: ARG002 + ) -> "Generator[CUDASuffixedHandler.CommonItemContext]": + context = CUDASuffixedHandler.CommonItemContext() + yield context + + if context.has_python_output_type: + for name, anchor, node in context.suspicious_suffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in common dependency set', + ) + w.add_note( + ( + common_context.common_key.start_mark.index, + common_context.common_key.end_mark.index, + ), + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ) + for name, anchor, node in context.suspicious_unsuffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in common dependency set', + ) + w.add_note( + ( + common_context.common_key.start_mark.index, + common_context.common_key.end_mark.index, + ), + "place in a specific dependency set with " + 'cuda_suffixed: "false" instead', + ) + + @contextlib.contextmanager + def handle_specific_item( + self, + specific_context: None, # noqa: ARG002 + item: "yaml.Node", # noqa: ARG002 + ) -> "Generator[CUDASuffixedHandler.SpecificItemContext]": + context = CUDASuffixedHandler.SpecificItemContext() + yield context + + if context.has_python_output_type: + for matrices_item_context in context.matrices_item_contexts: + if matrices_item_context.cuda_suffixed is None: + for ( + name, + anchor, + node, + ) in matrices_item_context.suspicious_suffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in specific dependency set ' + "with no cuda_suffixed field", + ) + if matrices_item_context.matrix_node: + w.add_note( + ( + matrices_item_context.matrix_node.start_mark.index, + matrices_item_context.matrix_node.end_mark.index, + ), + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ) + for ( + name, + anchor, + node, + ) in matrices_item_context.suspicious_unsuffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in common dependency set', + ) + if matrices_item_context.matrix_node: + w.add_note( + ( + matrices_item_context.matrix_node.start_mark.index, + matrices_item_context.matrix_node.end_mark.index, + ), + "place in a specific dependency set with " + 'cuda_suffixed: "false" instead', + ) + elif matrices_item_context.cuda_suffixed: + for ( + name, + anchor, + node, + ) in matrices_item_context.suspicious_unsuffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in specific dependency set ' + 'with cuda_suffixed: "true"', + ) + if matrices_item_context.cuda_major: + anchor_text = f"&{anchor} " if anchor else "" + w.add_replacement( + (node.start_mark.index, node.end_mark.index), + f"{anchor_text}{name}-cu{matrices_item_context.cuda_major}", + ) + elif matrices_item_context.matrix_node: + w.add_note( + ( + matrices_item_context.matrix_node.start_mark.index, + matrices_item_context.matrix_node.end_mark.index, + ), + "add a cuda matrix field and add matching " + "-cu* suffix to package name", + ) + else: + for ( + name, + anchor, + node, + ) in matrices_item_context.suspicious_suffixed_packages: + w = self.linter.add_warning( + (node.start_mark.index, node.end_mark.index), + f'package "{name}" in specific dependency set ' + 'with cuda_suffixed: "false"', + ) + anchor_text = f"&{anchor} " if anchor else "" + w.add_replacement( + (node.start_mark.index, node.end_mark.index), + f"{anchor_text}{name}", + ) + + @contextlib.contextmanager + def handle_matrices_item( + self, + matrices_context: "CUDASuffixedHandler.SpecificItemContext", + item: "yaml.Node", # noqa: ARG002 + ) -> "Generator[CUDASuffixedHandler.MatricesItemContext]": + context = CUDASuffixedHandler.MatricesItemContext() + yield context + + matrices_context.matrices_item_contexts.append(context) + + @contextlib.contextmanager + def handle_matrix( + self, + matrices_item_context: "CUDASuffixedHandler.MatricesItemContext", + key: "yaml.Node", + value: "yaml.Node", # noqa: ARG002 + ) -> "Generator[CUDASuffixedHandler.MatricesItemContext]": + matrices_item_context.matrix_node = key + yield matrices_item_context + + def handle_matrix_item( + self, + matrix_context: "CUDASuffixedHandler.MatricesItemContext", + key: "yaml.Node", + value: "yaml.Node", + ) -> None: + if key.value == "cuda_suffixed": + matrix_context.cuda_suffixed_node = value + if value.value == "true": + matrix_context.cuda_suffixed = True + elif value.value == "false": + matrix_context.cuda_suffixed = False + elif key.value == "cuda": + matrix_context.cuda_node = value + matrix_context.cuda_major = Version(value.value).major + + def handle_package( + self, + packages_context: ( + "CUDASuffixedHandler.CommonItemContext | " + "CUDASuffixedHandler.MatricesItemContext" + ), + anchor: "Optional[str]", + item: "yaml.Node", + ) -> None: + try: + req = Requirement(item.value) + except InvalidRequirement: + return + + if req.name in get_rapids_version(self.args).cuda_suffixed_packages: + packages_context.suspicious_unsuffixed_packages.append( + (req.name, anchor, item) + ) + elif ( + match := re.search(r"^(?P.*)-cu[0-9]+$", req.name) + ) and match.group("package") in get_rapids_version( + self.args + ).cuda_suffixed_packages: + packages_context.suspicious_suffixed_packages.append( + (match.group("package"), anchor, item) + ) diff --git a/src/rapids_pre_commit_hooks/dependencies/use_cuda_wheels.py b/src/rapids_pre_commit_hooks/dependencies/use_cuda_wheels.py index 805255f..ff88059 100644 --- a/src/rapids_pre_commit_hooks/dependencies/use_cuda_wheels.py +++ b/src/rapids_pre_commit_hooks/dependencies/use_cuda_wheels.py @@ -1,18 +1,19 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -import argparse import contextlib import re from dataclasses import dataclass, field from typing import Any, Optional, TYPE_CHECKING from packaging.requirements import InvalidRequirement, Requirement + from rapids_pre_commit_hooks.utils.dependencies_yaml import ( Handler, ) if TYPE_CHECKING: + import argparse from collections.abc import Generator import yaml @@ -78,7 +79,7 @@ class Context: default_factory=list ) - def __init__(self, linter: "Linter", args: argparse.Namespace): + def __init__(self, linter: "Linter", args: "argparse.Namespace"): self.linter = linter self.args = args diff --git a/src/rapids_pre_commit_hooks/utils/dependencies_yaml.py b/src/rapids_pre_commit_hooks/utils/dependencies_yaml.py index b45682f..fd3b514 100644 --- a/src/rapids_pre_commit_hooks/utils/dependencies_yaml.py +++ b/src/rapids_pre_commit_hooks/utils/dependencies_yaml.py @@ -50,6 +50,21 @@ def handle_common_item( ) -> "contextlib.AbstractContextManager[Any]": return contextlib.nullcontext(common_context) + def handle_output_types( + self, + common_item_or_specific_item_context: "Any", + key: "yaml.Node", # noqa: ARG002 + value: "yaml.Node", # noqa: ARG002 + ) -> "contextlib.AbstractContextManager[Any]": + return contextlib.nullcontext(common_item_or_specific_item_context) + + def handle_output_type( + self, + output_types_context: "Any", + item: "yaml.Node", # noqa: ARG002 + ) -> None: + pass + def handle_specific( self, dependency_set_context: "Any", @@ -196,6 +211,26 @@ def handle_common_item( "handle_common_item", common_context, *args, **kwargs ) + def handle_output_types( + self, + common_item_or_specific_item_context: "tuple[Any, ...]", + *args, + **kwargs, + ) -> "contextlib.AbstractContextManager[tuple[Any, ...]]": + return self._handle_context( + "handle_output_types", + common_item_or_specific_item_context, + *args, + **kwargs, + ) + + def handle_output_type( + self, output_types_context: "tuple[Any, ...]", *args, **kwargs + ) -> None: + return self._handle_no_context( + "handle_output_type", output_types_context, *args, **kwargs + ) + def handle_specific( self, dependency_set_context: "tuple[Any, ...]", *args, **kwargs ) -> "contextlib.AbstractContextManager[tuple[Any, ...]]": @@ -293,6 +328,34 @@ def traverse_packages( ) +def traverse_output_type( + handler: Handler, + output_types_context: "Any", + node: "yaml.Node", +) -> None: + if node_has_type(node, "str"): + handler.handle_output_type(output_types_context, node) + + +def traverse_output_types( + handler: Handler, + common_item_or_specific_item_context: "Any", + key_node: "yaml.Node", + node: "yaml.Node", +) -> None: + if node_has_type(node, "seq"): + with handler.handle_output_types( + common_item_or_specific_item_context, key_node, node + ) as output_types_context: + for item in node.value: + traverse_output_type(handler, output_types_context, item) + elif node_has_type(node, "str"): + with handler.handle_output_types( + common_item_or_specific_item_context, key_node, node + ) as output_types_context: + traverse_output_type(handler, output_types_context, node) + + def traverse_common_item( handler: Handler, common_context: "Any", @@ -309,6 +372,16 @@ def traverse_common_item( common_item_value, ) in node.value: if ( + node_has_type(common_item_key, "str") + and common_item_key.value == "output_types" + ): + traverse_output_types( + handler, + common_item_context, + common_item_key, + common_item_value, + ) + elif ( node_has_type(common_item_key, "str") and common_item_key.value == "packages" ): @@ -444,6 +517,16 @@ def traverse_specific_item( specific_item_value, ) in node.value: if ( + node_has_type(specific_item_key, "str") + and specific_item_key.value == "output_types" + ): + traverse_output_types( + handler, + specific_item_context, + specific_item_key, + specific_item_value, + ) + elif ( node_has_type(specific_item_key, "str") and specific_item_key.value == "matrices" ): diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py new file mode 100644 index 0000000..2f5b6cb --- /dev/null +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -0,0 +1,832 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import pytest + +from rapids_pre_commit_hooks import lint +from rapids_pre_commit_hooks.dependencies.cuda_suffixed import ( + CUDASuffixedHandler, +) +from rapids_pre_commit_hooks.utils import dependencies_yaml +from rapids_pre_commit_hooks_test_utils import ( + find_yaml_node_for_span, + parse_named_spans, + zip_expected_warnings, +) + + +def _compose(content): + loader = dependencies_yaml.AnchorPreservingLoader(content) + try: + return loader.get_single_node() + finally: + loader.dispose() + + +class TestCUDASuffixedHandler: + @pytest.mark.parametrize( + ["output_type", "expected"], + [ + pytest.param("requirements", True, id="requirements"), + pytest.param("pyproject", True, id="pyproject"), + pytest.param("conda", False, id="conda"), + ], + ) + def test_handle_output_type(self, output_type, expected): + handler = CUDASuffixedHandler(Mock(), Mock()) + context = CUDASuffixedHandler.CommonItemContext() + + handler.handle_output_type(context, Mock(value=output_type)) + assert context.has_python_output_type is expected + + context.has_python_output_type = True + handler.handle_output_type(context, Mock(value=output_type)) + assert context.has_python_output_type is True + + def test_handle_common(self): + composed = _compose( + """\ + common: + packages: [] + """ + ) + common_key, common = composed.value[0] + + handler = CUDASuffixedHandler(Mock(), Mock()) + with handler.handle_common(Mock(), common_key, common) as context: + assert context.common_key == common_key + + @pytest.mark.parametrize( + [ + "content", + "has_python_output_type", + "suffixed_names", + "unsuffixed_names", + "warnings", + ], + [ + pytest.param( + """\ + + common: + : ~~~~~~warnings.0.notes.0 + : ~~~~~~warnings.1.notes.0 + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~warnings.0.warning + + - package + : ~~~~~~~unsuffixed.0 + : ~~~~~~~warnings.1.warning + """, + True, + ["package"], + ["package"], + [ + { + "warning": 'package "package" in common ' + "dependency set", + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ], + }, + { + "warning": 'package "package" in common ' + "dependency set", + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "false" instead', + ], + }, + ], + id="both-package-forms", + ), + pytest.param( + """\ + + common: + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + + - package + : ~~~~~~~unsuffixed.0 + """, + False, + ["package"], + ["package"], + [], + id="non-python-output", + ), + pytest.param( + """\ + + common: + + packages: [] + """, + True, + [], + [], + [], + id="no-suspicious-packages", + ), + ], + ) + def test_handle_common_item( + self, + content, + has_python_output_type, + suffixed_names, + unsuffixed_names, + warnings, + ): + content, spans = parse_named_spans(content, dict) + composed = _compose(content) + common_key, common = composed.value[0] + linter = lint.Linter( + "dependencies.yaml", content, "verify-dependencies" + ) + handler = CUDASuffixedHandler(linter, Mock()) + + common_context = Mock(common_key=common_key) + with handler.handle_common_item( + common_context, common + ) as item_context: + item_context.has_python_output_type = has_python_output_type + item_context.suspicious_suffixed_packages.extend( + ( + name, + None, + find_yaml_node_for_span(composed, span), + ) + for name, span in zip( + suffixed_names, + spans.get("suffixed", []), + strict=True, + ) + ) + item_context.suspicious_unsuffixed_packages.extend( + ( + name, + None, + find_yaml_node_for_span(composed, span), + ) + for name, span in zip( + unsuffixed_names, + spans.get("unsuffixed", []), + strict=True, + ) + ) + + assert linter.warnings == zip_expected_warnings( + spans.get("warnings", []), warnings + ) + + @pytest.mark.parametrize( + [ + "content", + "has_python_output_type", + "cuda_suffixed", + "cuda_major", + "suffixed_names", + "unsuffixed_names", + "warnings", + ], + [ + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + : ~~~~~~warnings.0.notes.0 + + cuda: "12.0" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~warnings.0.warning + """, + True, + None, + None, + [("package", None)], + [], + [ + { + "warning": 'package "package" in specific dependency ' + "set with no cuda_suffixed field", + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ], + }, + ], + id="no-field-suffixed-package", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + : ~~~~~~warnings.0.notes.0 + + cuda: "12.0" + + packages: + + - package + : ~~~~~~~unsuffixed.0 + : ~~~~~~~warnings.0.warning + """, + True, + None, + None, + [], + [("package", None)], + [ + { + "warning": 'package "package" in common ' + "dependency set", + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "false" instead', + ], + }, + ], + id="no-field-unsuffixed-package", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + : ~~~~~~warnings.0.notes.0 + + cuda_suffixed: "true" + + packages: + + - package + : ~~~~~~~unsuffixed.0 + : ~~~~~~~warnings.0.warning + """, + True, + True, + None, + [], + [("package", None)], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "true"', + "notes": [ + "add a cuda matrix field and add matching -cu* " + "suffix to package name", + ], + }, + ], + id="true-unsuffixed-package", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "true" + + cuda: "12.8" + + packages: + + - package + : ~~~~~~~unsuffixed.0 + : ~~~~~~~warnings.0.warning + : ~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 12, + [], + [("package", None)], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "true"', + "replacements": [ + "package-cu12", + ], + }, + ], + id="true-unsuffixed-package-cuda-major", + ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "true" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + """, + True, + True, + None, + [("package", None)], + [], + [], + id="true-suffixed-package", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "false" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + False, + None, + [("package", None)], + [], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "false"', + "replacements": [ + "package", + ], + }, + ], + id="false-suffixed-package", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "false" + + packages: + + - &package_anchor package-cu12 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + False, + None, + [("package", "package_anchor")], + [], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "false"', + "replacements": [ + "&package_anchor package", + ], + }, + ], + id="false-suffixed-package-anchor", + ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "false" + + packages: + + - package + : ~~~~~~~unsuffixed.0 + """, + True, + False, + None, + [], + [("package", None)], + [], + id="false-unsuffixed-package", + ), + pytest.param( + """\ + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + """, + False, + None, + None, + [("package", None)], + [], + [], + id="non-python-output", + ), + ], + ) + def test_handle_specific_item( + self, + content, + has_python_output_type, + cuda_suffixed, + cuda_major, + suffixed_names, + unsuffixed_names, + warnings, + ): + content, spans = parse_named_spans(content, dict) + composed = _compose(content) + linter = lint.Linter( + "dependencies.yaml", content, "verify-dependencies" + ) + handler = CUDASuffixedHandler(linter, Mock()) + matrix_node = ( + find_yaml_node_for_span(composed, span) + if (span := spans.get("matrix")) + else None + ) + + with handler.handle_specific_item( + Mock(), composed + ) as specific_context: + specific_context.has_python_output_type = has_python_output_type + matrix_context = CUDASuffixedHandler.MatricesItemContext( + matrix_node=matrix_node, + cuda_suffixed=cuda_suffixed, + cuda_major=cuda_major, + suspicious_suffixed_packages=[ + (name, anchor, find_yaml_node_for_span(composed, span)) + for (name, anchor), span in zip( + suffixed_names, + spans.get("suffixed", []), + strict=True, + ) + ], + suspicious_unsuffixed_packages=[ + (name, anchor, find_yaml_node_for_span(composed, span)) + for (name, anchor), span in zip( + unsuffixed_names, + spans.get("unsuffixed", []), + strict=True, + ) + ], + ) + specific_context.matrices_item_contexts.append(matrix_context) + + assert linter.warnings == zip_expected_warnings( + spans.get("warnings", []), warnings + ) + + def test_handle_matrices_item(self): + handler = CUDASuffixedHandler(Mock(), Mock()) + specific_context = CUDASuffixedHandler.SpecificItemContext() + + with handler.handle_matrices_item( + specific_context, Mock() + ) as matrix_context: + assert specific_context.matrices_item_contexts == [] + + assert specific_context.matrices_item_contexts == [matrix_context] + + def test_handle_matrix(self): + content, spans = parse_named_spans( + """\ + + matrix: + : ~~~~~~matrix_key + + cuda_suffixed: "true" + """ + ) + composed = _compose(content) + matrix_key, matrix = composed.value[0] + context = CUDASuffixedHandler.MatricesItemContext() + handler = CUDASuffixedHandler(Mock(), Mock()) + + with handler.handle_matrix( + context, matrix_key, matrix + ) as matrix_context: + assert matrix_context is context + assert context.matrix_node == find_yaml_node_for_span( + composed, spans["matrix_key"] + ) + + @pytest.mark.parametrize( + [ + "content", + "expected_cuda_suffixed", + "has_cuda_suffixed_node", + "expected_cuda_major", + "has_cuda_node", + ], + [ + pytest.param( + 'cuda_suffixed: "true"', + True, + True, + None, + False, + id="cuda-suffixed-true", + ), + pytest.param( + 'cuda_suffixed: "false"', + False, + True, + None, + False, + id="cuda-suffixed-false", + ), + pytest.param( + 'cuda_suffixed: "other"', + None, + True, + None, + False, + id="cuda-suffixed-other", + ), + pytest.param( + 'cuda: "12.8"', + None, + False, + 12, + True, + id="cuda-version", + ), + pytest.param( + 'other: "value"', + None, + False, + None, + False, + id="other", + ), + ], + ) + def test_handle_matrix_item( + self, + content, + expected_cuda_suffixed, + has_cuda_suffixed_node, + expected_cuda_major, + has_cuda_node, + ): + composed = _compose(content) + key, value = composed.value[0] + context = CUDASuffixedHandler.MatricesItemContext() + handler = CUDASuffixedHandler(Mock(), Mock()) + + handler.handle_matrix_item(context, key, value) + + assert context.cuda_suffixed is expected_cuda_suffixed + assert context.cuda_suffixed_node == ( + value if has_cuda_suffixed_node else None + ) + assert context.cuda_major == expected_cuda_major + assert context.cuda_node == (value if has_cuda_node else None) + + @pytest.mark.parametrize( + [ + "requirement", + "suffixed_names", + "unsuffixed_names", + ], + [ + pytest.param( + "package", + [], + ["package"], + id="unsuffixed", + ), + pytest.param( + "package[extra]>=1.0", + [], + ["package"], + id="unsuffixed-with-extras-and-version", + ), + pytest.param( + "package-cu12", + ["package"], + [], + id="suffixed", + ), + pytest.param( + "package-cu123==1.0", + ["package"], + [], + id="multi-digit-suffix", + ), + pytest.param( + "package-cu12x", + [], + [], + id="invalid-cuda-suffix", + ), + pytest.param( + "other-cu12", + [], + [], + id="unknown-package", + ), + pytest.param( + "not a requirement", + [], + [], + id="invalid-requirement", + ), + ], + ) + def test_handle_package( + self, requirement, suffixed_names, unsuffixed_names + ): + package_node = _compose(requirement) + rapids_version = SimpleNamespace(cuda_suffixed_packages={"package"}) + context = CUDASuffixedHandler.MatricesItemContext() + handler = CUDASuffixedHandler(Mock(), Mock()) + + with patch( + "rapids_pre_commit_hooks.dependencies.cuda_suffixed." + "get_rapids_version", + return_value=rapids_version, + ): + handler.handle_package(context, None, package_node) + + assert context.suspicious_suffixed_packages == [ + (name, None, package_node) for name in suffixed_names + ] + assert context.suspicious_unsuffixed_packages == [ + (name, None, package_node) for name in unsuffixed_names + ] + + +@pytest.mark.parametrize( + ["content", "warnings"], + [ + pytest.param( + """\ + + dependencies: + + file_set: + + common: + : ~~~~~~warnings.0.notes.0 + : ~~~~~~warnings.1.notes.0 + + - output_types: pyproject + + packages: + + - package-cu12 + : ~~~~~~~~~~~~warnings.0.warning + + - package + : ~~~~~~~warnings.1.warning + """, + [ + { + "warning": 'package "package" in common dependency set', + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ], + }, + { + "warning": 'package "package" in common dependency set', + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "false" instead', + ], + }, + ], + id="common-python-packages", + ), + pytest.param( + """\ + + dependencies: + + file_set: + + common: + + - output_types: conda + + packages: + + - package-cu12 + + - package + """, + [], + id="common-non-python-output", + ), + pytest.param( + """\ + + dependencies: + + file_set: + + specific: + + - output_types: requirements + + matrices: + + - matrix: + : ~~~~~~warnings.0.notes.0 + + cuda: "12.8" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~warnings.0.warning + + - matrix: + + cuda_suffixed: "true" + + cuda: "12.8" + + packages: + + - package + : ~~~~~~~warnings.1.warning + : ~~~~~~~warnings.1.replacements.0 + + - matrix: + : ~~~~~~warnings.2.notes.0 + + cuda_suffixed: "true" + + packages: + + - package + : ~~~~~~~warnings.2.warning + + - matrix: + + cuda_suffixed: "false" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~warnings.3.warning + : ~~~~~~~~~~~~warnings.3.replacements.0 + """, + [ + { + "warning": 'package "package" in specific dependency set ' + "with no cuda_suffixed field", + "notes": [ + "place in a specific dependency set with " + 'cuda_suffixed: "true" instead', + ], + }, + { + "warning": 'package "package" in specific dependency set ' + 'with cuda_suffixed: "true"', + "replacements": [ + "package-cu12", + ], + }, + { + "warning": 'package "package" in specific dependency set ' + 'with cuda_suffixed: "true"', + "notes": [ + "add a cuda matrix field and add matching -cu* " + "suffix to package name", + ], + }, + { + "warning": 'package "package" in specific dependency set ' + 'with cuda_suffixed: "false"', + "replacements": [ + "package", + ], + }, + ], + id="specific-invalid-package-forms", + ), + pytest.param( + """\ + + dependencies: + + file_set: + + specific: + + - output_types: pyproject + + matrices: + + - matrix: + + cuda_suffixed: "true" + + packages: + + - package-cu12 + + - matrix: + + cuda_suffixed: "false" + + packages: + + - package + """, + [], + id="specific-valid-package-forms", + ), + pytest.param( + """\ + + dependencies: + + file_set: + + common: + + - output_types: pyproject + + packages: + + - non-rapids-package + + - non-rapids-package-cu12 + + specific: + + - output_types: pyproject + + matrices: + + - matrix: + + packages: + + - non-rapids-package + + - non-rapids-package-cu12 + + - matrix: + + cuda_suffixed: "false" + + packages: + + - non-rapids-package + + - non-rapids-package-cu12 + + - matrix: + + cuda_suffixed: "true" + + packages: + + - non-rapids-package + + - non-rapids-package-cu12 + """, + [], + id="non-rapids-packages", + ), + ], +) +def test_check_cuda_suffixed_integration(content, warnings): + content, spans = parse_named_spans(content, dict) + + loader = dependencies_yaml.AnchorPreservingLoader(content) + try: + composed = loader.get_single_node() + finally: + loader.dispose() + + args = Mock() + linter = lint.Linter("dependencies.yaml", content, "verify-dependencies") + rapids_version = SimpleNamespace(cuda_suffixed_packages={"package"}) + + handler = CUDASuffixedHandler(linter, args) + + with patch( + "rapids_pre_commit_hooks.dependencies.cuda_suffixed." + "get_rapids_version", + return_value=rapids_version, + ): + dependencies_yaml.traverse_root(handler, {}, set(), composed) + + assert linter.warnings == zip_expected_warnings( + spans.get("warnings", []), warnings + ) diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_use_cuda_wheels.py b/tests/rapids_pre_commit_hooks/dependencies/test_use_cuda_wheels.py index 3b1a639..eae4f61 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_use_cuda_wheels.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_use_cuda_wheels.py @@ -6,7 +6,7 @@ import pytest from packaging.requirements import Requirement -from rapids_pre_commit_hooks import lint, dependencies +from rapids_pre_commit_hooks import lint from rapids_pre_commit_hooks.dependencies.use_cuda_wheels import ( UseCUDAWheelsHandler, is_cupy_ctk_package, @@ -115,7 +115,7 @@ def test_handle_common( args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -286,7 +286,7 @@ def test_handle_matrices_item( args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -350,7 +350,7 @@ def test_handle_matrix(self): args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -403,7 +403,7 @@ def test_handle_matrix_item(self, content, expected_has_use_cuda_wheels): args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -461,7 +461,7 @@ def test_handle_packages(self, content): args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -534,7 +534,7 @@ def test_handle_packages(self, content): def test_handle_package(self, content, expected_node, expected_name): args = Mock() linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" + "dependencies.yaml", content, "verify-dependencies" ) loader = dependencies_yaml.AnchorPreservingLoader(content) try: @@ -781,10 +781,15 @@ def test_handle_package(self, content, expected_node, expected_name): def test_check_use_cuda_wheels_integration(content, warnings): content, spans = parse_named_spans(content, dict) + loader = dependencies_yaml.AnchorPreservingLoader(content) + try: + composed = loader.get_single_node() + finally: + loader.dispose() + args = Mock() - linter = lint.Linter( - "dependencies.yaml", content, "verify-use-cuda-wheels" - ) + linter = lint.Linter("dependencies.yaml", content, "verify-dependencies") + handler = UseCUDAWheelsHandler(linter, args) expected_warnings = [ lint.LintWarning( @@ -804,5 +809,5 @@ def test_check_use_cuda_wheels_integration(content, warnings): ) ] - dependencies.check_dependencies(linter, args) + dependencies_yaml.traverse_root(handler, {}, set(), composed) assert linter.warnings == expected_warnings diff --git a/tests/rapids_pre_commit_hooks/utils/test_dependencies_yaml.py b/tests/rapids_pre_commit_hooks/utils/test_dependencies_yaml.py index a746c15..342b0ed 100644 --- a/tests/rapids_pre_commit_hooks/utils/test_dependencies_yaml.py +++ b/tests/rapids_pre_commit_hooks/utils/test_dependencies_yaml.py @@ -5,7 +5,12 @@ import pytest import yaml + from rapids_pre_commit_hooks.utils import dependencies_yaml +from rapids_pre_commit_hooks_test_utils import ( + find_yaml_node_for_span, + parse_named_spans, +) class TestChainedHandler: @@ -42,6 +47,12 @@ class TestChainedHandler: (Mock(),), id="handle_common_item", ), + pytest.param( + "handle_output_types", + True, + (Mock(), Mock()), + id="handle_output_types", + ), pytest.param( "handle_specific", True, @@ -139,6 +150,11 @@ def test_context(self, hook_name, use_context, hook_args): ("anchor", Mock()), id="handle_package", ), + pytest.param( + "handle_output_type", + (Mock(),), + id="handle_output_type", + ), ], ) def test_no_context(self, hook_name, hook_args): @@ -347,6 +363,96 @@ def test_traverse_packages_used_anchor(): assert manager.mock_calls == expected_calls +def test_traverse_output_type(): + output_types = yaml.SafeLoader("""\ + [requirements] + """).get_single_node() + output_type = output_types.value[0] + output_types_context = Mock() + manager = MagicMock() + + expected_calls = [ + call.handler.handle_output_type(output_types_context, output_type), + ] + manager.reset_mock() + + dependencies_yaml.traverse_output_type( + manager.handler, output_types_context, output_type + ) + + assert manager.mock_calls == expected_calls + + +@pytest.mark.parametrize( + ["content"], + [ + pytest.param( + """\ + + output_types: pyproject + : ~~~~~~~~~~~~key_node + : ~~~~~~~~~node + : ~~~~~~~~~items.0 + """, + id="string-item", + ), + pytest.param( + """\ + + output_types: [requirements, pyproject] + : ~~~~~~~~~~~~key_node + : ~~~~~~~~~~~~~~~~~~~~~~~~~node + : ~~~~~~~~~~~~items.0 + : ~~~~~~~~~items.1 + """, + id="list", + ), + pytest.param( + """\ + + output_types: [] + : ~~~~~~~~~~~~key_node + : ~~node + """, + id="empty-list", + ), + ], +) +def test_traverse_output_types(content): + content, spans = parse_named_spans(content) + item = yaml.SafeLoader(content).get_single_node() + output_types_key = find_yaml_node_for_span(item, spans["key_node"]) + output_types = find_yaml_node_for_span(item, spans["node"]) + item_context = Mock() + manager = MagicMock() + + expected_calls = [ + call.handler.handle_output_types( + item_context, output_types_key, output_types + ), + call.handler.handle_output_types().__enter__(), + *( + call.traverse_output_type( + manager.handler, + manager.handler.handle_output_types().__enter__(), + find_yaml_node_for_span(item, output_type_span), + ) + for output_type_span in spans.get("items", []) + ), + call.handler.handle_output_types().__exit__(None, None, None), + ] + manager.reset_mock() + + with ( + patch( + "rapids_pre_commit_hooks.utils.dependencies_yaml.traverse_output_type", + manager.traverse_output_type, + ), + ): + dependencies_yaml.traverse_output_types( + manager.handler, item_context, output_types_key, output_types + ) + + assert manager.mock_calls == expected_calls + + def test_traverse_common_item(): common = yaml.SafeLoader("""\ - output_types: pyproject @@ -359,6 +465,12 @@ def test_traverse_common_item(): expected_calls = [ call.handler.handle_common_item(common_context, common_item), call.handler.handle_common_item().__enter__(), + call.traverse_output_types( + manager.handler, + manager.handler.handle_common_item().__enter__(), + common_item.value[0][0], + common_item.value[0][1], + ), call.traverse_packages( manager.handler, manager.handler.handle_common_item().__enter__(), @@ -372,6 +484,10 @@ def test_traverse_common_item(): manager.reset_mock() with ( + patch( + "rapids_pre_commit_hooks.utils.dependencies_yaml.traverse_output_types", + manager.traverse_output_types, + ), patch( "rapids_pre_commit_hooks.utils.dependencies_yaml.traverse_packages", manager.traverse_packages, @@ -621,6 +737,12 @@ def test_traverse_specific_item(): expected_calls = [ call.handler.handle_specific_item(specific_context, specific_item), call.handler.handle_specific_item().__enter__(), + call.traverse_output_types( + manager.handler, + manager.handler.handle_specific_item().__enter__(), + specific_item.value[0][0], + specific_item.value[0][1], + ), call.traverse_matrices( manager.handler, manager.handler.handle_specific_item().__enter__(), @@ -634,6 +756,10 @@ def test_traverse_specific_item(): manager.reset_mock() with ( + patch( + "rapids_pre_commit_hooks.utils.dependencies_yaml.traverse_output_types", + manager.traverse_output_types, + ), patch( "rapids_pre_commit_hooks.utils.dependencies_yaml.traverse_matrices", manager.traverse_matrices, diff --git a/tests/test_testing_utils.py b/tests/test_testing_utils.py index c0581c6..1f1e8d2 100644 --- a/tests/test_testing_utils.py +++ b/tests/test_testing_utils.py @@ -5,11 +5,13 @@ import pytest +from rapids_pre_commit_hooks.lint import LintWarning, Note, Replacement from rapids_pre_commit_hooks.utils.yaml import AnchorPreservingLoader from rapids_pre_commit_hooks_test_utils import ( ParseError, find_yaml_node_for_span, parse_named_spans, + zip_expected_warnings, ) @@ -681,6 +683,63 @@ def test_parse_named_spans( assert spans == expected_spans +@pytest.mark.parametrize( + ["content", "warnings", "expected_warnings"], + [ + pytest.param( + """\ + + This is a warning + : ~~~~0.warning + : ~~0.notes.0 + : ~0.notes.1 + : ^0.replacements.0 + : ~~~~0.replacements.1 + : ^1.warning + """, + [ + { + "warning": "First warning", + "notes": [ + "First note", + "Second note", + ], + "replacements": [ + "!", + "THIS", + ], + }, + { + "warning": "Second warning", + }, + ], + [ + LintWarning( + (0, 4), + "First warning", + notes=[ + Note((5, 7), "First note"), + Note((8, 9), "Second note"), + ], + replacements=[ + Replacement((17, 17), "!"), + Replacement((0, 4), "THIS"), + ], + ), + LintWarning( + (4, 4), + "Second warning", + notes=[], + replacements=[], + ), + ], + ), + ], +) +def test_zip_expected_warnings(content, warnings, expected_warnings): + content, spans = parse_named_spans(content, list) + assert zip_expected_warnings(spans, warnings) == expected_warnings + + @pytest.mark.parametrize( ["content", "node_lambda"], [ diff --git a/tests/utils/rapids_pre_commit_hooks_test_utils.py b/tests/utils/rapids_pre_commit_hooks_test_utils.py index 5ee3841..86a9ee9 100644 --- a/tests/utils/rapids_pre_commit_hooks_test_utils.py +++ b/tests/utils/rapids_pre_commit_hooks_test_utils.py @@ -7,10 +7,10 @@ from typing import TYPE_CHECKING from rapids_pre_commit_hooks.utils.yaml import node_has_type -from rapids_pre_commit_hooks.lint import Lines +from rapids_pre_commit_hooks.lint import Lines, LintWarning, Note, Replacement if TYPE_CHECKING: - from typing import Optional, TypeGuard + from typing import Optional, TypeGuard, TypedDict import yaml @@ -261,6 +261,54 @@ def postprocess(named_spans: "_NamedSpans") -> "NamedSpans": return content, postprocessed +if TYPE_CHECKING: + + class ExpectedWarningSpan(TypedDict): + warning: "Span" + notes: "list[Span]" + replacements: "list[Span]" + + class ExpectedWarning(TypedDict): + warning: str + notes: list[str] + replacements: list[str] + + +def zip_expected_warnings( + warning_spans: "list[ExpectedWarningSpan]", + warnings: "list[ExpectedWarning]", +) -> "list[LintWarning]": + return [ + LintWarning( + warning_span["warning"], + warning["warning"], + notes=[ + Note( + note_span, + note, + ) + for note_span, note in zip( + warning_span.get("notes", []), + warning.get("notes", []), + strict=True, + ) + ], + replacements=[ + Replacement( + replacement_span, + replacement, + ) + for replacement_span, replacement in zip( + warning_span.get("replacements", []), + warning.get("replacements", []), + strict=True, + ) + ], + ) + for warning_span, warning in zip(warning_spans, warnings, strict=True) + ] + + def find_yaml_node_for_span( node: "yaml.Node", span: "Span" ) -> "Optional[yaml.Node]": From a7af70b5a5384bb29988d3741823917d0a99c4d1 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 27 Jul 2026 15:57:02 -0400 Subject: [PATCH 02/10] Fix pre-commit test --- .pre-commit-hooks.yaml | 3 +++ pyproject.toml | 4 ++++ tests/examples/verify-dependencies/fail/metadata.yaml | 1 + tests/examples/verify-dependencies/pass/metadata.yaml | 1 + 4 files changed, 9 insertions(+) create mode 100644 tests/examples/verify-dependencies/fail/metadata.yaml create mode 100644 tests/examples/verify-dependencies/pass/metadata.yaml diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index c776de0..1e8f9b9 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -47,6 +47,9 @@ language: python files: (^|/)dependencies[.]yaml$ args: [--fix] + additional_dependencies: + - --extra-index-url=https://pypi.anaconda.org/rapidsai-wheels-nightly/simple + - .[dependencies] - id: verify-hardcoded-version name: verify-hardcoded-version description: make sure RAPIDS version is not hard-coded in files diff --git a/pyproject.toml b/pyproject.toml index 685e126..b5635b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,10 +36,14 @@ test = [ "pre-commit", "pytest", "rapids-pre-commit-hooks[alpha-spec]", + "rapids-pre-commit-hooks[dependencies]", ] alpha-spec = [ "rapids-metadata>=0.4.0,<0.5.0.dev0", ] +dependencies = [ + "rapids-metadata>=0.4.0,<0.5.0.dev0", +] [project.scripts] verify-alpha-spec = "rapids_pre_commit_hooks.alpha_spec:main" diff --git a/tests/examples/verify-dependencies/fail/metadata.yaml b/tests/examples/verify-dependencies/fail/metadata.yaml new file mode 100644 index 0000000..4171270 --- /dev/null +++ b/tests/examples/verify-dependencies/fail/metadata.yaml @@ -0,0 +1 @@ +write_version_file: true diff --git a/tests/examples/verify-dependencies/pass/metadata.yaml b/tests/examples/verify-dependencies/pass/metadata.yaml new file mode 100644 index 0000000..4171270 --- /dev/null +++ b/tests/examples/verify-dependencies/pass/metadata.yaml @@ -0,0 +1 @@ +write_version_file: true From e21661ac37d3ae2fc15b153b9612a19e99095fc4 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 27 Jul 2026 16:10:45 -0400 Subject: [PATCH 03/10] Formatting --- .../dependencies/cuda_suffixed.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index f113bc7..7235824 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -48,19 +48,19 @@ class CommonContext: @dataclass class CommonItemContext: has_python_output_type: bool = False - suspicious_suffixed_packages: ( - "list[tuple[str, Optional[str], yaml.Node]]" - ) = field(default_factory=list) - suspicious_unsuffixed_packages: ( - "list[tuple[str, Optional[str], yaml.Node]]" - ) = field(default_factory=list) + suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + default_factory=list + ) + suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + default_factory=list + ) @dataclass class SpecificItemContext: has_python_output_type: bool = False - matrices_item_contexts: ( - "list[CUDASuffixedHandler.MatricesItemContext]" - ) = field(default_factory=list) + matrices_item_contexts: "list[CUDASuffixedHandler.MatricesItemContext]" = field( + default_factory=list + ) @dataclass class MatricesItemContext: @@ -69,12 +69,12 @@ class MatricesItemContext: cuda_suffixed: "Optional[bool]" = None cuda_node: "Optional[yaml.Node]" = None cuda_major: "Optional[int]" = None - suspicious_suffixed_packages: ( - "list[tuple[str, Optional[str], yaml.Node]]" - ) = field(default_factory=list) - suspicious_unsuffixed_packages: ( - "list[tuple[str, Optional[str], yaml.Node]]" - ) = field(default_factory=list) + suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + default_factory=list + ) + suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + default_factory=list + ) def __init__(self, linter: "Linter", args: "argparse.Namespace") -> None: self.linter = linter From db006ac36357e074f8d507c44feb1a051adb0b03 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 27 Jul 2026 16:21:56 -0400 Subject: [PATCH 04/10] noqa --- .../dependencies/cuda_suffixed.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index 7235824..2f54d06 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -48,17 +48,17 @@ class CommonContext: @dataclass class CommonItemContext: has_python_output_type: bool = False - suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) - suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) @dataclass class SpecificItemContext: has_python_output_type: bool = False - matrices_item_contexts: "list[CUDASuffixedHandler.MatricesItemContext]" = field( + matrices_item_contexts: "list[CUDASuffixedHandler.MatricesItemContext]" = field( # noqa: E501 default_factory=list ) @@ -69,10 +69,10 @@ class MatricesItemContext: cuda_suffixed: "Optional[bool]" = None cuda_node: "Optional[yaml.Node]" = None cuda_major: "Optional[int]" = None - suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) - suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( + suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) From acbee96f30b62aeac69915b5da7e8d952c952994 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Tue, 28 Jul 2026 15:15:49 -0400 Subject: [PATCH 05/10] Fix wildcard CUDA version --- src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py | 7 ++++--- .../dependencies/test_cuda_suffixed.py | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index 2f54d06..4328958 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -9,7 +9,6 @@ from typing import Optional, TYPE_CHECKING from packaging.requirements import InvalidRequirement, Requirement -from packaging.version import Version from rapids_pre_commit_hooks.utils.dependencies_yaml import ( Handler, @@ -263,9 +262,11 @@ def handle_matrix_item( matrix_context.cuda_suffixed = True elif value.value == "false": matrix_context.cuda_suffixed = False - elif key.value == "cuda": + elif key.value == "cuda" and ( + match := re.search(r"^(?P[0-9]+)", value.value) + ): matrix_context.cuda_node = value - matrix_context.cuda_major = Version(value.value).major + matrix_context.cuda_major = int(match.group("major")) def handle_package( self, diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py index 2f5b6cb..daabb3b 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -533,6 +533,14 @@ def test_handle_matrix(self): True, id="cuda-version", ), + pytest.param( + 'cuda: "12.*"', + None, + False, + 12, + True, + id="cuda-version-wildcard", + ), pytest.param( 'other: "value"', None, From e78d8473d63fc9fb4d58a2d4de0f2401c53574ef Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Tue, 28 Jul 2026 16:26:56 -0400 Subject: [PATCH 06/10] xgboost --- .../dependencies/cuda_suffixed.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index 4328958..6c8f91b 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -25,6 +25,13 @@ from rapids_metadata.metadata import RAPIDSMetadata, RAPIDSVersion +# Extra packages that need to have/not have the -cu* suffix that are not in +# RAPIDS +EXTRA_CUDA_SUFFIXED_PACKAGES: set[str] = { + "xgboost", +} + + @cache def all_metadata() -> "RAPIDSMetadata": return fetch_latest() @@ -282,15 +289,18 @@ def handle_package( except InvalidRequirement: return - if req.name in get_rapids_version(self.args).cuda_suffixed_packages: + cuda_suffixed_packages = ( + get_rapids_version(self.args).cuda_suffixed_packages + | EXTRA_CUDA_SUFFIXED_PACKAGES + ) + + if req.name in cuda_suffixed_packages: packages_context.suspicious_unsuffixed_packages.append( (req.name, anchor, item) ) elif ( match := re.search(r"^(?P.*)-cu[0-9]+$", req.name) - ) and match.group("package") in get_rapids_version( - self.args - ).cuda_suffixed_packages: + ) and match.group("package") in cuda_suffixed_packages: packages_context.suspicious_suffixed_packages.append( (match.group("package"), anchor, item) ) From 514517ecde2715d2294d324aecb71231ebcb166f Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Wed, 29 Jul 2026 09:45:02 -0400 Subject: [PATCH 07/10] Add test case --- .../dependencies/test_cuda_suffixed.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py index daabb3b..8485c05 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -541,6 +541,14 @@ def test_handle_matrix(self): True, id="cuda-version-wildcard", ), + pytest.param( + 'cuda: "invalid"', + None, + False, + None, + False, + id="cuda-version-invalid", + ), pytest.param( 'other: "value"', None, From 1585c440f67e0e36cc392311cd7520b2e2832079 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Wed, 29 Jul 2026 10:08:15 -0400 Subject: [PATCH 08/10] Fix suffix --- .../dependencies/cuda_suffixed.py | 47 +++++++++-- .../dependencies/test_cuda_suffixed.py | 79 ++++++++++++++++--- 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index 6c8f91b..7081960 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -54,7 +54,7 @@ class CommonContext: @dataclass class CommonItemContext: has_python_output_type: bool = False - suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 + suspicious_suffixed_packages: "list[tuple[str, str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 @@ -75,7 +75,7 @@ class MatricesItemContext: cuda_suffixed: "Optional[bool]" = None cuda_node: "Optional[yaml.Node]" = None cuda_major: "Optional[int]" = None - suspicious_suffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 + suspicious_suffixed_packages: "list[tuple[str, str, Optional[str], yaml.Node]]" = field( # noqa: E501 default_factory=list ) suspicious_unsuffixed_packages: "list[tuple[str, Optional[str], yaml.Node]]" = field( # noqa: E501 @@ -117,7 +117,12 @@ def handle_common_item( yield context if context.has_python_output_type: - for name, anchor, node in context.suspicious_suffixed_packages: + for ( + name, + suffix, + anchor, + node, + ) in context.suspicious_suffixed_packages: w = self.linter.add_warning( (node.start_mark.index, node.end_mark.index), f'package "{name}" in common dependency set', @@ -158,6 +163,7 @@ def handle_specific_item( if matrices_item_context.cuda_suffixed is None: for ( name, + suffix, anchor, node, ) in matrices_item_context.suspicious_suffixed_packages: @@ -194,6 +200,34 @@ def handle_specific_item( 'cuda_suffixed: "false" instead', ) elif matrices_item_context.cuda_suffixed: + if matrices_item_context.cuda_major: + for ( + name, + suffix, + anchor, + node, + ) in ( + matrices_item_context.suspicious_suffixed_packages + ): + if ( + suffix + != f"cu{matrices_item_context.cuda_major}" + ): + w = self.linter.add_warning( + ( + node.start_mark.index, + node.end_mark.index, + ), + f'package "{name}" has wrong -cu* suffix', + ) + anchor_text = f"&{anchor} " if anchor else "" + w.add_replacement( + ( + node.start_mark.index, + node.end_mark.index, + ), + f"{anchor_text}{name}-cu{matrices_item_context.cuda_major}", + ) for ( name, anchor, @@ -222,6 +256,7 @@ def handle_specific_item( else: for ( name, + suffix, anchor, node, ) in matrices_item_context.suspicious_suffixed_packages: @@ -299,8 +334,10 @@ def handle_package( (req.name, anchor, item) ) elif ( - match := re.search(r"^(?P.*)-cu[0-9]+$", req.name) + match := re.search( + r"^(?P.*)(?P-cu[0-9]+)$", req.name + ) ) and match.group("package") in cuda_suffixed_packages: packages_context.suspicious_suffixed_packages.append( - (match.group("package"), anchor, item) + (match.group("package"), match.group("suffix"), anchor, item) ) diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py index 8485c05..27070dd 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -157,6 +157,7 @@ def test_handle_common_item( ( name, None, + None, find_yaml_node_for_span(composed, span), ) for name, span in zip( @@ -207,7 +208,7 @@ def test_handle_common_item( True, None, None, - [("package", None)], + [("package", "-cu12", None)], [], [ { @@ -316,11 +317,63 @@ def test_handle_common_item( True, True, None, - [("package", None)], + [("package", "-cu12", None)], [], [], id="true-suffixed-package", ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "true" + + cuda: "13.*" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 13, + [("package", "-cu12", None)], + [], + [ + { + "warning": 'package "package" has wrong -cu* suffix', + "replacements": [ + "package-cu13", + ], + }, + ], + id="true-suffixed-package-wrong-cuda-version", + ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "true" + + cuda: "13.*" + + packages: + + - &package_anchor package-cu12 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 13, + [("package", "-cu12", "package_anchor")], + [], + [ + { + "warning": 'package "package" has wrong -cu* suffix', + "replacements": [ + "&package_anchor package-cu13", + ], + }, + ], + id="true-suffixed-package-wrong-cuda-version-anchor", + ), pytest.param( """\ + matrix: @@ -335,7 +388,7 @@ def test_handle_common_item( True, False, None, - [("package", None)], + [("package", "-cu12", None)], [], [ { @@ -362,7 +415,7 @@ def test_handle_common_item( True, False, None, - [("package", "package_anchor")], + [("package", "-cu12", "package_anchor")], [], [ { @@ -400,7 +453,7 @@ def test_handle_common_item( False, None, None, - [("package", None)], + [("package", "-cu12", None)], [], [], id="non-python-output", @@ -438,8 +491,13 @@ def test_handle_specific_item( cuda_suffixed=cuda_suffixed, cuda_major=cuda_major, suspicious_suffixed_packages=[ - (name, anchor, find_yaml_node_for_span(composed, span)) - for (name, anchor), span in zip( + ( + name, + suffix, + anchor, + find_yaml_node_for_span(composed, span), + ) + for (name, suffix, anchor), span in zip( suffixed_names, spans.get("suffixed", []), strict=True, @@ -602,13 +660,13 @@ def test_handle_matrix_item( ), pytest.param( "package-cu12", - ["package"], + [("package", "-cu12")], [], id="suffixed", ), pytest.param( "package-cu123==1.0", - ["package"], + [("package", "-cu123")], [], id="multi-digit-suffix", ), @@ -648,7 +706,8 @@ def test_handle_package( handler.handle_package(context, None, package_node) assert context.suspicious_suffixed_packages == [ - (name, None, package_node) for name in suffixed_names + (name, suffix, None, package_node) + for (name, suffix) in suffixed_names ] assert context.suspicious_unsuffixed_packages == [ (name, None, package_node) for name in unsuffixed_names From dc731336d4a64996ae4c81a9af6511921267ab1d Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Wed, 29 Jul 2026 10:35:02 -0400 Subject: [PATCH 09/10] Version requirements --- .../dependencies/cuda_suffixed.py | 19 ++- .../dependencies/test_cuda_suffixed.py | 113 +++++++++++++++++- 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py index 7081960..924f233 100644 --- a/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py +++ b/src/rapids_pre_commit_hooks/dependencies/cuda_suffixed.py @@ -211,7 +211,7 @@ def handle_specific_item( ): if ( suffix - != f"cu{matrices_item_context.cuda_major}" + != f"-cu{matrices_item_context.cuda_major}" ): w = self.linter.add_warning( ( @@ -221,12 +221,17 @@ def handle_specific_item( f'package "{name}" has wrong -cu* suffix', ) anchor_text = f"&{anchor} " if anchor else "" + req = Requirement(node.value) + req.name = ( + f"{name}" + f"-cu{matrices_item_context.cuda_major}" + ) w.add_replacement( ( node.start_mark.index, node.end_mark.index, ), - f"{anchor_text}{name}-cu{matrices_item_context.cuda_major}", + f"{anchor_text}{req}", ) for ( name, @@ -240,9 +245,13 @@ def handle_specific_item( ) if matrices_item_context.cuda_major: anchor_text = f"&{anchor} " if anchor else "" + req = Requirement(node.value) + req.name = ( + f"{name}-cu{matrices_item_context.cuda_major}" + ) w.add_replacement( (node.start_mark.index, node.end_mark.index), - f"{anchor_text}{name}-cu{matrices_item_context.cuda_major}", + f"{anchor_text}{req}", ) elif matrices_item_context.matrix_node: w.add_note( @@ -266,9 +275,11 @@ def handle_specific_item( 'with cuda_suffixed: "false"', ) anchor_text = f"&{anchor} " if anchor else "" + req = Requirement(node.value) + req.name = name w.add_replacement( (node.start_mark.index, node.end_mark.index), - f"{anchor_text}{name}", + f"{anchor_text}{req}", ) @contextlib.contextmanager diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py index 27070dd..643705d 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -306,6 +306,62 @@ def test_handle_common_item( ], id="true-unsuffixed-package-cuda-major", ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "true" + + cuda: "12.8" + + packages: + + - package==26.08.*,>=0.0.0a0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~unsuffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 12, + [], + [("package", None)], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "true"', + "replacements": [ + "package-cu12==26.08.*,>=0.0.0a0", + ], + }, + ], + id="true-unsuffixed-package-cuda-major-version-req", + ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "true" + + cuda: "12.8" + + packages: + + - &package_anchor package + : ~~~~~~~~~~~~~~~~~~~~~~~unsuffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 12, + [], + [("package", "package_anchor")], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "true"', + "replacements": [ + "&package_anchor package-cu12", + ], + }, + ], + id="true-unsuffixed-package-cuda-major-anchor", + ), pytest.param( """\ + matrix: @@ -346,7 +402,33 @@ def test_handle_common_item( ], }, ], - id="true-suffixed-package-wrong-cuda-version", + id="true-suffixed-package-wrong-cuda-major", + ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "true" + + cuda: "13.*" + + packages: + + - package-cu12==26.08.*,>=0.0.0a0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + True, + 13, + [("package", "-cu12", None)], + [], + [ + { + "warning": 'package "package" has wrong -cu* suffix', + "replacements": [ + "package-cu13==26.08.*,>=0.0.0a0", + ], + }, + ], + id="true-suffixed-package-wrong-cuda-major-version-req", ), pytest.param( """\ @@ -372,7 +454,7 @@ def test_handle_common_item( ], }, ], - id="true-suffixed-package-wrong-cuda-version-anchor", + id="true-suffixed-package-wrong-cuda-major-anchor", ), pytest.param( """\ @@ -401,6 +483,33 @@ def test_handle_common_item( ], id="false-suffixed-package", ), + pytest.param( + """\ + + matrix: + : ~~~~~~matrix + + cuda_suffixed: "false" + + packages: + + - package-cu12==26.08.*,>=0.0.0a0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~suffixed.0 + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.warning + : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~warnings.0.replacements.0 + """, + True, + False, + None, + [("package", "-cu12", None)], + [], + [ + { + "warning": 'package "package" in specific dependency ' + 'set with cuda_suffixed: "false"', + "replacements": [ + "package==26.08.*,>=0.0.0a0", + ], + }, + ], + id="false-suffixed-package-version-req", + ), pytest.param( """\ + matrix: From e1d8aec3bf8e2269d30d93221cf11a4975ff774e Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Wed, 29 Jul 2026 10:38:53 -0400 Subject: [PATCH 10/10] Test case --- .../dependencies/test_cuda_suffixed.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py index 643705d..6b702c2 100644 --- a/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py +++ b/tests/rapids_pre_commit_hooks/dependencies/test_cuda_suffixed.py @@ -378,6 +378,23 @@ def test_handle_common_item( [], id="true-suffixed-package", ), + pytest.param( + """\ + + matrix: + + cuda_suffixed: "true" + + cuda: "12.*" + + packages: + + - package-cu12 + : ~~~~~~~~~~~~suffixed.0 + """, + True, + True, + 12, + [("package", "-cu12", None)], + [], + [], + id="true-suffixed-package-cuda-major", + ), pytest.param( """\ + matrix: