Skip to content

Conversation

@rnetser
Copy link
Collaborator

@rnetser rnetser commented Dec 4, 2025

Short description:

Create a tool to scan the tests/ directory for Python test files, and identify quarantined tests, and generates an HTML dashboard with stats on tactive/quaratnined tests

Generated using Claude cli

More details:
What this PR does / why we need it:
Which issue(s) this PR fixes:
Special notes for reviewer:
jira-ticket:

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a comprehensive test dashboard that summarizes all test metrics
    • Displays total, active, and quarantined test counts with health indicators
    • Shows test breakdown by team/category with color-coded status
    • Lists quarantined tests with locations, line numbers, reasons, and linked Jira tickets
    • Generates a self-contained HTML dashboard with live timestamps

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Walkthrough

Introduces a new Python module that builds an HTML dashboard summarizing test statistics. Implements test scanning via AST parsing to detect test functions and quarantine status through decorator analysis, aggregates statistics by category, and generates a self-contained HTML dashboard with summary cards and detailed quarantined tests list.

Changes

Cohort / File(s) Change Summary
New dashboard generation module
stats/generate_dashboard.py
Added new module with TestInfo and DashboardStats data models, TestScanner class for scanning test files and detecting quarantine decorators, DashboardGenerator class for HTML dashboard rendering, and main() entry point for orchestration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • AST parsing logic: Test function detection and class-level quarantine propagation requires careful validation
  • Quarantine detection: Regex patterns and decorator parsing for various formatting styles (including Jira ticket extraction) need thorough review
  • Category mapping: Folder-to-category mapping configuration should be verified against test structure
  • HTML generation: Self-contained dashboard markup and styling should be checked for correctness and completeness
  • Data flow: Aggregation logic across scanner output to statistics calculation to dashboard rendering

Suggested labels

size/S, branch-main

Suggested reviewers

  • dshchedr
  • vsibirsk
  • RoniKishner

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete with only a short description filled in; most required template sections are empty or contain placeholder instructions. Complete the PR description by filling in: More details, What this PR does/why we need it, Which issue(s) this PR fixes, Special notes for reviewer, and jira-ticket fields.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a stats tool to count active and quarantined tests, which aligns with the new stats/generate_dashboard.py module that scans and aggregates test statistics.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-virtualization-qe-bot

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: A tracking issue is created for this PR and will be closed when the PR is merged or closed
  • Pre-commit Checks: pre-commit runs automatically if .pre-commit-config.yaml exists
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest build-container - Rebuild and test container image
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 2 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No WIP, hold, or conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • dshchedr
  • myakove
  • rnetser
  • vsibirsk

Reviewers:

  • RoniKishner
  • dshchedr
  • rnetser
  • vsibirsk
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
stats/generate_dashboard.py (5)

80-84: Add ClassVar annotations to mutable class attributes.

Per static analysis (RUF012), mutable class attributes should be annotated with typing.ClassVar to clarify they are shared across instances and not per-instance state.

+from typing import ClassVar, Dict, List, NamedTuple, Set
+
 class TestScanner:
     ...
     # Folders to exclude from the report
-    EXCLUDED_FOLDERS = {"after_cluster_deploy_sanity", "deprecated_api"}
+    EXCLUDED_FOLDERS: ClassVar[Set[str]] = {"after_cluster_deploy_sanity", "deprecated_api"}
 
     # Folder mappings (source -> target) for combining stats
-    FOLDER_MAPPINGS = {"data_protection": "storage", "cross_cluster_live_migration": "storage"}
+    FOLDER_MAPPINGS: ClassVar[Dict[str, str]] = {"data_protection": "storage", "cross_cluster_live_migration": "storage"}

130-136: Consider narrowing the exception scope or re-raising after logging.

The broad except Exception catches all errors including unexpected ones (e.g., KeyboardInterrupt is not caught, but SystemExit edge cases could be). Based on your preference for fail-fast design, consider either:

  1. Narrowing to specific expected exceptions (SyntaxError, OSError, UnicodeDecodeError)
  2. Re-raising after logging if unexpected errors occur
         for test_file in test_files:
             try:
                 tests = self._scan_file(file_path=test_file)
                 all_tests.extend(tests)
-            except Exception as e:
+            except (SyntaxError, OSError, UnicodeDecodeError) as e:
                 print(f"Warning: Error scanning {test_file}: {e}")

Based on learnings, user prefers fail-fast design.


240-250: Remove unused noqa directive and consider edge case for root-level test files.

  1. Per static analysis (RUF100), FCN001 is not a valid Ruff code - remove the directive.

  2. Edge case: if a test file exists directly in tests/ (e.g., tests/test_sanity.py), parts[0] would be the filename itself (test_sanity.py), resulting in an unexpected category name.

-        parts = file_path.relative_to(self.tests_dir).parts  # noqa: FCN001
-        if len(parts) > 0:
+        parts = file_path.relative_to(self.tests_dir).parts
+        if len(parts) > 1:  # Require at least one subdirectory
             category = parts[0]
 
             if category in self.EXCLUDED_FOLDERS:
                 return None
 
             category = self.FOLDER_MAPPINGS.get(category, category)
 
             return category
         return "uncategorized"

659-659: Remove unused noqa directive and consider path fragility.

  1. Per static analysis (RUF100), remove the # noqa: FCN001 directive.

  2. Path.cwd() makes the output dependent on the working directory. If someone runs the script from a different directory, the relative paths in the HTML will be incorrect. Consider passing the base path as a parameter or using the project root from main().

-                rel_path = test.file_path.relative_to(Path.cwd())  # noqa: FCN001
+                rel_path = test.file_path.relative_to(Path.cwd())

For the path fragility, consider making DashboardGenerator aware of the project root:

def __init__(self, stats: DashboardStats, project_root: Path):
    self.stats = stats
    self.project_root = project_root

688-688: Remove unused noqa directive.

Per static analysis (RUF100), FCN001 is not a valid Ruff code.

-    script_dir = Path(__file__).parent  # noqa: FCN001
+    script_dir = Path(__file__).parent
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e39f8e and 78f6aea.

📒 Files selected for processing (1)
  • stats/generate_dashboard.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: rnetser
Repo: RedHatQE/openshift-virtualization-tests PR: 2469
File: utilities/sanity.py:139-142
Timestamp: 2025-11-08T07:36:57.616Z
Learning: In the openshift-virtualization-tests repository, user rnetser prefers to keep refactoring PRs (like PR #2469) strictly focused on moving/organizing code into more granular modules without adding new functionality, error handling, or behavioral changes. Such improvements should be handled in separate PRs.
Learnt from: rnetser
Repo: RedHatQE/openshift-virtualization-tests PR: 1244
File: utilities/os_utils.py:248-250
Timestamp: 2025-07-08T05:51:06.314Z
Learning: User rnetser prefers fail-fast code design where functions should raise exceptions immediately when encountering unexpected or malformed input, rather than implementing defensive error handling with fallbacks. They value making problems visible immediately during development over graceful degradation.
🪛 Ruff (0.14.7)
stats/generate_dashboard.py

1-1: The file is executable but no shebang is present

(EXE002)


81-81: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


84-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


134-134: Do not catch blind exception: Exception

(BLE001)


240-240: Unused noqa directive (unknown: FCN001)

Remove unused noqa directive

(RUF100)


659-659: Unused noqa directive (unknown: FCN001)

Remove unused noqa directive

(RUF100)


688-688: Unused noqa directive (unknown: FCN001)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: build-container
  • GitHub Check: tox
  • GitHub Check: can-be-merged
🔇 Additional comments (9)
stats/generate_dashboard.py (9)

1-18: LGTM - Well-documented module header.

The module docstring provides clear usage instructions and configuration documentation.


28-48: LGTM - Clean data model.

Appropriate use of NamedTuple with well-documented fields and sensible defaults.


50-67: LGTM - Appropriate aggregation structure.


139-205: LGTM - Solid AST-based test detection with class-level quarantine propagation.

The two-pass approach (classes first, then functions) correctly handles inheritance of quarantine status from parent classes.


207-222: LGTM - Correct parent class detection.

The identity comparison child is func_node correctly identifies the exact node instance.


252-344: LGTM - Thorough decorator detection with careful scoping.

The backward-walk heuristic correctly handles multi-line decorators, and the Jira extraction is properly scoped to the xfail section to avoid false positives from @polarion markers.


346-376: LGTM - Efficient statistics aggregation.

Single-pass iteration with defaultdict is the right approach.


589-627: LGTM - Clean table row generation with sensible health thresholds.


730-731: LGTM - Standard CLI entry point.

quarantined_pct = (
(self.stats.quarantined_tests / self.stats.total_tests * 100) if self.stats.total_tests > 0 else 0
)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Timestamp label says "UTC" but uses local time.

datetime.now() returns local time, not UTC. The label will be misleading.

-        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
+        from datetime import timezone
+        timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")

Or import timezone at the top of the file with the other imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
from datetime import timezone
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
🤖 Prompt for AI Agents
In stats/generate_dashboard.py around line 408, the timestamp string currently
uses datetime.now() but is labeled "UTC" which is incorrect; change to a true
UTC timestamp by either using datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S
UTC") or, preferably, import timezone and use
datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") (add "from datetime
import timezone" to the imports if using timezone).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants