feat(pyats): add tag-based filtering for pyATS tests#657
Draft
feat(pyats): add tag-based filtering for pyATS tests#657
Conversation
58b8ea2 to
c944b81
Compare
Extend --include and --exclude CLI options to filter pyATS tests based on their `groups` class attribute using Robot Framework tag pattern semantics. Changes: - Add TagMatcher class wrapping Robot's TagPatterns API for consistent tag matching between Robot and pyATS tests - Add TestMetadataResolver for AST-based extraction of test type and groups from pyATS test files without importing them - Add TestFileMetadata and TestExecutionPlan dataclasses to types.py for structured test discovery results - Integrate tag filtering into test discovery pipeline Refactor test discovery architecture: - Remove caching from TestMetadataResolver (previously TestTypeResolver) - Combine discovery and categorization into single pass returning TestExecutionPlan with pre-computed test_type_by_path mapping - Orchestrator now uses execution plan for O(1) type lookups during post-execution result splitting instead of re-instantiating resolver Tag pattern syntax (from Robot Framework): - Simple tags: 'health', 'bgp', 'ospf' - Wildcards: 'bgp*', '?est' - Boolean: 'healthANDbgp', 'healthORbgp', 'healthNOTnrfu' - Case-insensitive, underscores ignored Example usage: nac-test -d data/ -t tests/ -o out/ --include health --exclude nrfu --- feat(pyats): improve "no tests found" message when filtered by tags When all pyATS tests are filtered out by tag patterns, the previous message "No PyATS test files (*.py) found" was misleading — files existed but were excluded. Now displays a descriptive message like: No pyATS tests matching tag filter (exclude: 'bgp OR ospf') --- test(e2e): add tag filtering e2e scenarios for Robot and pyATS Add 4 e2e scenarios verifying --include/--exclude tag filtering works correctly for both Robot and pyATS tests. - TAG_FILTER_INCLUDE: --include bgp → 1 Robot + 1 PyATS pass - TAG_FILTER_EXCLUDE: --exclude ospf → 1 Robot + 1 PyATS pass - TAG_FILTER_COMBINED: --include api-only → 0 Robot (exit 252) + 1 PyATS pass - TAG_FILTER_NO_MATCH: --exclude bgpORospf → 0 Robot + 0 PyATS (exit 252) --- docs: update --include/--exclude documentation for pyATS tag filtering --- fix(pyats): use absolute() instead of resolve() for symlink-safe paths Replace Path.resolve() with Path.absolute() to preserve symlinks and prevent ValueError when using relative_to() on paths that resolve differently than their absolute form.
c944b81 to
7ee38fb
Compare
Refactor discovery tests for better maintainability: - Move test_test_type_resolver.py to tests/unit/pyats_core/discovery/ - Extract helpers.py with shared FIXTURES_DIR and create_mock_path - Split groups extraction tests into separate test_groups_extraction.py - Parametrize tests to reduce duplication (475→301 lines in main test file) - Use _UNUSED_TEST_ROOT for tests using mock paths vs FIXTURES_DIR for real fixtures Rename TestExecutionPlan to PyatsDiscoveryResult for clarity: - Better reflects its purpose as discovery output, not execution context - Update all references in discovery module and tests
…ports - Add frozen=True and slots=True to PyatsDiscoveryResult for immutability - Convert properties to cached_property for better performance - Clarify skipped_files vs filtered_by_tags semantics in docstring - Remove stale re-exports from discovery __init__.py and test_type_resolver - Update test imports to use canonical locations (common.types) - Rename execution_plan → discovery_result in orchestrator for consistency
…ization Refactor the PyATS discovery test suite to eliminate redundant coverage, consolidate overlapping tests, and apply pytest parametrization for improved maintainability. ## Changes ### Phase 1: Consolidate integration tests into unit tests - Move non-integration tests from test_discovery_integration.py to new tests/unit/pyats_core/discovery/test_test_discovery.py: - TestDiscoveryFiltering (5 parametrized tests) - TestRelaxedPathRequirements (4 parametrized tests) - TestExcludePaths (1 parametrized test) - TestErrorHandling (2 tests for unreadable file handling) - Retain only TestDiscoveryPerformance in test_discovery_integration.py as it is the only true integration test (validates discovery timing with 100+ files) ### Phase 2: Parametrize test_tag_matcher.py Consolidate 39 individual test methods into 15 parametrized tests: - TestBasicMatching: Consolidate has_filters tests (4 → 1) - TestIncludePatterns: Consolidate include tests (4 → 1, 13 cases) - TestExcludePatterns: Consolidate exclude tests (4 → 1, 14 cases) - TestCombinedPatterns: Consolidate combined tests (2 → 1, 8 cases) - TestRobotPatternSemantics: Split into boolean + wildcard (7 → 2) - TestEdgeCases: Consolidate edge case tests (9 → 4) - TestFormatFilterDescription: Consolidate formatting tests (9 → 3) ### Code quality - Add proper type annotations for all test parameters - Fix mypy errors for generic types (list → list[str]) - Add MockerFixture type hints for pytest-mock fixtures ## Test Coverage Metrics ### Before refactor | File | Tests | Coverage | |--------------------------------|-------|----------| | test_discovery_integration.py | 43 | - | | test_tag_matcher.py | 39 | 100% | | test_discovery.py (source) | - | 90% | ### After refactor | File | Tests | Coverage | |--------------------------------|-------|----------| | test_discovery_integration.py | 1 | - | | test_test_discovery.py (new) | 12 | - | | test_tag_matcher.py | 15 | 100% | | test_discovery.py (source) | - | 95% | | tag_matcher.py (source) | - | 100% | ### Summary - Integration tests: 43 → 1 (moved 42 to unit tests) - test_tag_matcher.py methods: 39 → 15 (86 parametrized cases) - Total discovery test cases: 145 passed - Overall discovery module coverage: 85% ## Validation - All pre-commit hooks pass (ruff, mypy, license headers) - All 145 tests pass - No coverage regression
Add pytest configuration to eliminate collection warnings for source classes in nac_test/ that start with "Test" prefix (e.g., TestDiscovery, TestResults, TestFileMetadata). Changes: - Add nac_test to norecursedirs to prevent pytest from recursing into source directory during test collection - Add filterwarnings with explicit regex matching the 10 Test* classes to suppress warnings when they are imported by test files The regex explicitly names each class rather than using a wildcard to avoid accidentally suppressing warnings for actual test issues. Ref: #650
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR adds tag-based filtering support for PyATS tests using the
--includeand--excludeCLI options, bringing PyATS test selection in line with Robot Framework's tag filtering capabilities. PyATS tests are filtered based on theirgroupsclass attribute using Robot Framework'sTagPatternsAPI for consistent pattern matching across both frameworks.Closes
Related Issue(s)
Type of Change
Test Framework Affected
Network as Code (NaC) Architecture Affected
Platform Tested
Key Changes
Core Tag Filtering Implementation
New
TagMatcherclass (tag_matcher.py): Wraps Robot Framework'sTagPatternsAPI to provide consistent tag matching behavior for both Robot and PyATS testsbgp*), boolean operators (healthANDbgp,bgpORospf,bgpNOTnrfu)Refactored test discovery architecture (
test_discovery.py,test_type_resolver.py):TestTypeResolver→TestMetadataResolverto reflect expanded responsibilitiesgroupsattribute from PyATS test classes without importingPyatsDiscoveryResultNew dataclasses (
types.py):TestFileMetadata: Structured representation of test file metadata (path, type, groups)PyatsDiscoveryResult: Discovery results with pre-computedtest_type_by_pathmapping for O(1) lookupsOrchestrator integration (
orchestrator.py):PyatsDiscoveryResultfor efficient type lookups during result splitting (replaces re-instantiation of resolver)Documentation (
README.md,PRD_AND_ARCHITECTURE.md):--include/--excludeCLI option descriptionsgroupsattribute usage examplesMinor Cleanups
ALL_SCENARIOSandget_scenario_by_namefrom e2e config__future__import fromtag_matcher.pyTesting
New Tests for Tag Filtering
E2E tests (
tests/e2e/):Unit tests (
tests/unit/pyats_core/discovery/):test_tag_matcher.py: Tag pattern matching, boolean logic, wildcards, edge casestest_metadata_resolver.py: AST-based test type detection, directory fallback, error handlingtest_groups_extraction.py: Groups attribute extraction for tag filteringPyatsDiscoveryResultstructureTest Suite Refactoring (Independent of Feature)
This PR includes test refactoring that improves the PyATS discovery test suite independently of the tag filtering feature:
Consolidation: Integration → Unit Tests
test_discovery_integration.pyto newtests/unit/pyats_core/discovery/test_test_discovery.pyTestDiscoveryPerformanceas the true integration test (validates timing with 100+ files)Parametrization
test_tag_matcher.pyinto 15 parametrized tests (86 total cases)Code Quality
list→list[str])MockerFixturetype hints for pytest-mock fixturesTesting Done
tests/unit/pyats_core/discovery/:test_tag_matcher.py: Tag pattern matching with Robot Framework semanticstest_metadata_resolver.py: AST-based type detection and fallback logictest_groups_extraction.py: Groups attribute extraction for filteringPyatsDiscoveryResultstructurepytest/pre-commit run -a)Test Commands Used
Checklist
pre-commit run -apasses)Screenshots (if applicable)
N/A
Additional Notes
Implementation Approach
The tag filtering implementation leverages Robot Framework's
TagPatternsAPI to ensure consistent behavior between Robot and PyATS test selection. Key design decisions:PyATS
groupsattribute: Uses the native pyATS mechanism (simple list of strings on test classes) for taggingDiscovery-phase filtering: Tests are filtered during the discovery phase via AST parsing, avoiding subprocess overhead and enabling early validation
Single-pass architecture: Discovery and categorization combined into one pass that returns a
PyatsDiscoveryResultwith pre-computed mappings for efficient O(1) lookupsConsistent error messaging: Uses Robot Framework's
TagPatternsstring formatting for user-facing messages ('bgpORospf'→'bgp OR ospf')Breaking Changes
None. The changes are backward-compatible:
groupsattribute remain unaffected and run as before--include/--excludebehavior for Robot Framework unchangedMigration Notes
groupsclass attribute to enable tag-based filteringgroupsattribute should be a list of strings (native pyATS format)groupsare treated as having no tags and are included by default (unless an--includepattern is specified)