diff --git a/.github/actions/check-warnings/README.md b/.github/actions/check-warnings/README.md new file mode 100644 index 0000000..1b94ad9 --- /dev/null +++ b/.github/actions/check-warnings/README.md @@ -0,0 +1,339 @@ +# Check for Python Warnings Action + +This GitHub Action scans HTML files for Python warnings and optionally fails the workflow if any are found. It's designed to be used after building documentation or running code that generates HTML output, to ensure that no warnings are present in the final output. + +**Important:** This action specifically targets warnings found within code cell outputs (elements with `cell_output` class) to avoid false positives from warnings mentioned in text content. + +## Features + +- Scans HTML files for configurable Python warnings **within code cell outputs only** +- Prevents false positives by only checking warnings in `cell_output` HTML elements +- Supports multiple warning types (SyntaxWarning, DeprecationWarning, FutureWarning) +- Provides detailed output about warnings found +- Optionally fails the workflow when warnings are detected +- **Creates GitHub issues** with detailed warning reports +- **Generates workflow artifacts** containing warning reports +- **Posts PR comments** with warning reports when failing on warnings +- Configurable search path and warning types + +## Usage + +### Basic Usage + +```yaml +- name: Check for Python warnings + uses: QuantEcon/meta/.github/actions/check-warnings@main +``` + +### Advanced Usage with PR Comments + +```yaml +- name: Check for Python warnings with PR feedback + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'true' # This will post a comment to the PR if warnings are found +``` + +### Advanced Usage with Issue Creation + +```yaml +- name: Check for Python warnings with issue creation + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning,UserWarning' + fail-on-warning: 'false' + create-issue: 'true' + issue-title: 'Python Warnings Found in Documentation Build' +``` + +### Advanced Usage with Issue Creation and User Assignment + +```yaml +- name: Check for Python warnings with assigned issue + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'false' + create-issue: 'true' + issue-title: 'Python Warnings Found in Documentation Build' + notify: 'username1,username2' # Assign issue to multiple users +``` + +### Advanced Usage with Artifact Creation + +```yaml +- name: Check for Python warnings with artifact + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'true' + create-artifact: 'true' + artifact-name: 'python-warning-report' +``` + +### Complete Advanced Usage + +```yaml +- name: Check for Python warnings in build output + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning,UserWarning' + fail-on-warning: 'false' + create-issue: 'true' + issue-title: 'Python Warnings Detected in Build' + notify: 'maintainer1,reviewer2' # Assign to specific team members + create-artifact: 'true' + artifact-name: 'detailed-warning-report' +``` + +### Using Outputs + +```yaml +- name: Check for Python warnings + id: warning-check + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + fail-on-warning: 'false' + +- name: Report warnings + if: steps.warning-check.outputs.warnings-found == 'true' + run: | + echo "Found ${{ steps.warning-check.outputs.warning-count }} warnings:" + echo "${{ steps.warning-check.outputs.warning-details }}" +``` + +## New Features + +### GitHub Issue Creation + +When `create-issue` is set to `true`, the action will automatically create a GitHub issue when warnings are detected. The issue includes: + +- Detailed warning information with file paths and line numbers +- Repository and workflow context +- Direct links to the failing workflow run +- Suggested next steps for resolution +- Automatic labeling (`bug`, `documentation`, `python-warnings`) + +#### Automatic User Assignment + +When the `notify` parameter is provided, the created issue will be automatically assigned to the specified GitHub users. This feature supports: + +- Single user assignment: `notify: 'username'` +- Multiple user assignment: `notify: 'user1,user2,user3'` +- Robust error handling: If assignment fails, the issue is still created successfully + +This ensures that the right team members are immediately notified about warnings and can take action to resolve them. + +Additionally, when issues are created in pull request contexts, a simple notification comment is posted to the PR thread containing: + +- List of files with warnings +- Direct link to the created issue for detailed information + +This provides immediate awareness to PR authors without cluttering the conversation with full warning details. + +### Workflow Artifacts + +When `create-artifact` is set to `true`, the action generates a detailed Markdown report as a workflow artifact. This report includes: + +- Complete warning details in a readable format +- Repository and workflow metadata +- Timestamp and commit information +- Downloadable for offline review + +### Pull Request Comments + +When `fail-on-warning` is set to `true` and warnings are found in a pull request, the action automatically posts a detailed comment to the PR containing: + +- Complete warning information formatted for easy reading +- Direct links to the failing workflow run +- Suggested next steps for fixing the warnings +- Repository and commit context + +This feature helps developers quickly identify and fix warnings without digging through workflow logs. + +### Using Both Features Together + +You can enable both issue creation and artifact generation simultaneously: + +```yaml +- name: Comprehensive warning check + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + fail-on-warning: 'false' # Don't fail, just report + create-issue: 'true' # Create issue for tracking + create-artifact: 'true' # Create artifact for detailed review +``` + +## How It Works + +This action specifically searches for Python warnings within HTML elements that have `cell_output` in their class attribute. This approach prevents false positives that would occur if warnings like "FutureWarning" or "DeprecationWarning" are mentioned in the text content of documentation pages. + +### Example HTML Structure + +The action will detect warnings in this structure: +```html +
+
+    /path/to/file.py:10: FutureWarning: This feature will be deprecated
+      result = old_function()
+    
+
+``` + +But will **ignore** warnings mentioned in regular content: +```html +
+

In this tutorial, we'll discuss FutureWarning messages.

+
+``` + +This ensures that educational content about warnings doesn't trigger false positives in the check. + +## Permissions + +For the action to work correctly with all features, ensure your workflow has the appropriate permissions: + +```yaml +permissions: + contents: read # For checking out the repository + issues: write # For creating GitHub issues (if create-issue is enabled) + actions: read # For creating workflow artifacts (if create-artifact is enabled) + pull-requests: write # For posting PR comments (when fail-on-warning is true OR create-issue is true in PRs) +``` + +If you're only using the basic warning check functionality, only `contents: read` is required. Add `pull-requests: write` when you want PR comments on warnings or when using issue creation in PR contexts. + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `html-path` | Path to directory containing HTML files to scan | No | `.` | +| `warnings` | Comma-separated list of warnings to check for | No | `SyntaxWarning,DeprecationWarning,FutureWarning` | +| `fail-on-warning` | Whether to fail the workflow if warnings are found | No | `true` | +| `create-issue` | Whether to create a GitHub issue when warnings are found | No | `false` | +| `issue-title` | Title for the GitHub issue when warnings are found | No | `Python Warnings Found in Documentation Build` | +| `create-artifact` | Whether to create a workflow artifact with the warning report | No | `false` | +| `artifact-name` | Name for the workflow artifact containing the warning report | No | `warning-report` | +| `notify` | GitHub username(s) to assign to the created issue (comma-separated for multiple users) | No | `` | + +## Outputs + +| Output | Description | +|--------|-------------| +| `warnings-found` | Whether warnings were found (`true`/`false`) | +| `warning-count` | Number of warnings found | +| `warning-details` | Details of warnings found | +| `issue-url` | URL of the created GitHub issue (if `create-issue` is enabled) | +| `artifact-path` | Path to the created artifact file (if `create-artifact` is enabled) | + +## Example Workflow + +Here's a complete example of how to use this action in a workflow: + +```yaml +name: Build and Check Documentation + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + issues: write + actions: read + pull-requests: write + +jobs: + build-and-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Build documentation + run: | + jupyter-book build . + + - name: Check for Python warnings + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: ${{ github.event_name == 'push' }} # Fail on push, warn on PR + create-issue: ${{ github.event_name == 'push' }} # Create issues for main branch + notify: 'maintainer1,reviewer2' # Assign issues to team members + create-artifact: 'true' # Always create artifacts + artifact-name: 'warning-report' +``` + +## Use Case + +This action is particularly useful for: + +1. **Documentation builds**: After building Jupyter Books or Sphinx documentation, check that no Python warnings appear in the generated HTML +2. **Code execution**: When running notebooks or Python scripts that generate HTML output, ensure no warnings are present +3. **Continuous Integration**: Maintain code quality by preventing warnings from being introduced + +## How It Works + +1. The action searches for all `.html` files in the specified directory +2. For each HTML file, it searches for the specified warning strings +3. If warnings are found, it reports the details and optionally fails the workflow +4. The action provides outputs that can be used by subsequent steps + +## Error Handling + +- If the specified HTML path doesn't exist, the action will fail with an error +- The action will report the exact location (file and line number) where warnings are found +- When `fail-on-warning` is `true`, the workflow will fail if any warnings are detected + +## Tips for Usage + +1. **Place the warning check after your build step**: The action needs the final HTML output to scan. + +2. **Use `fail-on-warning: 'false'` for reporting**: If you want to report warnings without failing the workflow. + +3. **Customize warning types**: Adjust the `warnings` input to match your project's needs. + +4. **Path considerations**: Make sure the `html-path` points to where your build process outputs HTML files. + +5. **Integration with existing workflows**: This action can be easily added to existing CI/CD pipelines. + +6. **Issue management**: When using `create-issue: 'true'`, consider: + - Setting up issue templates for consistency + - Using branch-specific conditions to avoid duplicate issues + - Implementing automatic issue closing when warnings are resolved + +7. **Artifact usage**: Artifacts are perfect for: + - Detailed offline review of warnings + - Sharing warning reports with team members + - Historical tracking of warning trends + +8. **Performance considerations**: For large HTML output directories, consider: + - Using specific paths rather than scanning entire directories + - Limiting warning types to only those relevant to your project + - Setting appropriate artifact retention periods + +9. **Pull Request feedback**: + - When `fail-on-warning` is `true`: The action posts detailed warning reports as PR comments + - When `create-issue` is `true`: The action posts simple notification comments linking to created issues + - Both features provide immediate feedback to developers without requiring log diving + - Requires `pull-requests: write` permission in your workflow \ No newline at end of file diff --git a/.github/actions/check-warnings/action.yml b/.github/actions/check-warnings/action.yml new file mode 100644 index 0000000..4b14af7 --- /dev/null +++ b/.github/actions/check-warnings/action.yml @@ -0,0 +1,427 @@ +name: 'Check for Python Warnings' +description: 'Scan HTML files for Python warnings within code cell outputs (avoiding false positives from text content)' +author: 'QuantEcon' + +inputs: + html-path: + description: 'Path to directory containing HTML files to scan' + required: false + default: '.' + warnings: + description: 'Comma-separated list of warnings to check for' + required: false + default: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: + description: 'Whether to fail the workflow if warnings are found' + required: false + default: 'true' + create-issue: + description: 'Whether to create a GitHub issue when warnings are found' + required: false + default: 'false' + issue-title: + description: 'Title for the GitHub issue when warnings are found' + required: false + default: 'Python Warnings Found in Documentation Build' + create-artifact: + description: 'Whether to create a workflow artifact with the warning report' + required: false + default: 'false' + artifact-name: + description: 'Name for the workflow artifact containing the warning report' + required: false + default: 'warning-report' + notify: + description: 'GitHub username(s) to assign to the created issue (comma-separated for multiple users)' + required: false + default: '' + +outputs: + warnings-found: + description: 'Whether warnings were found (true/false)' + value: ${{ steps.check.outputs.warnings-found }} + warning-count: + description: 'Number of warnings found' + value: ${{ steps.check.outputs.warning-count }} + warning-details: + description: 'Details of warnings found' + value: ${{ steps.check.outputs.warning-details }} + issue-url: + description: 'URL of the created GitHub issue (if create-issue is enabled)' + value: ${{ steps.create-issue.outputs.issue-url }} + artifact-path: + description: 'Path to the created artifact file (if create-artifact is enabled)' + value: ${{ steps.create-artifact.outputs.artifact-path }} + +runs: + using: 'composite' + steps: + - name: Check for warnings + id: check + shell: bash + run: | + # Parse inputs + HTML_PATH="${{ inputs.html-path }}" + WARNINGS="${{ inputs.warnings }}" + FAIL_ON_WARNING="${{ inputs.fail-on-warning }}" + + echo "Scanning HTML files in: $HTML_PATH" + echo "Looking for warnings: $WARNINGS" + + # Convert comma-separated warnings to array + IFS=',' read -ra WARNING_ARRAY <<< "$WARNINGS" + + # Initialize counters + TOTAL_WARNINGS=0 + WARNING_DETAILS="" + WARNINGS_FOUND="false" + DETAILED_REPORT="" + + # Find all HTML files + if [ ! -e "$HTML_PATH" ]; then + echo "Error: HTML path '$HTML_PATH' does not exist" + exit 1 + fi + + # Determine if we're dealing with a file or directory + if [ -f "$HTML_PATH" ]; then + # Single file + if [[ "$HTML_PATH" == *.html ]]; then + echo "Checking single HTML file: $HTML_PATH" + FILES=("$HTML_PATH") + else + echo "Error: '$HTML_PATH' is not an HTML file" + exit 1 + fi + else + # Directory - find all HTML files + mapfile -d '' FILES < <(find "$HTML_PATH" -name "*.html" -type f -print0) + fi + + # Create temporary Python script for parsing HTML + echo 'import re' > /tmp/check_warnings.py + echo 'import sys' >> /tmp/check_warnings.py + echo 'import os' >> /tmp/check_warnings.py + echo '' >> /tmp/check_warnings.py + echo 'def find_warnings_in_cell_outputs(file_path, warning_text):' >> /tmp/check_warnings.py + echo ' try:' >> /tmp/check_warnings.py + echo ' with open(file_path, "r", encoding="utf-8") as f:' >> /tmp/check_warnings.py + echo ' content = f.read()' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' # Find all HTML elements with cell_output in the class attribute' >> /tmp/check_warnings.py + echo ' pattern = r"<([^>]+)\s+class=\"[^\"]*cell_output[^\"]*\"[^>]*>(.*?)"' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' matches = []' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' # Search for cell_output blocks' >> /tmp/check_warnings.py + echo ' for match in re.finditer(pattern, content, re.DOTALL | re.IGNORECASE):' >> /tmp/check_warnings.py + echo ' block_content = match.group(2)' >> /tmp/check_warnings.py + echo ' block_start = match.start()' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' # Count line number where this block starts' >> /tmp/check_warnings.py + echo ' block_line = content[:block_start].count("\\n") + 1' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' # Search for warning within this block' >> /tmp/check_warnings.py + echo ' if warning_text in block_content:' >> /tmp/check_warnings.py + echo ' # Find specific lines within the block that contain the warning' >> /tmp/check_warnings.py + echo ' block_lines = block_content.split("\\n")' >> /tmp/check_warnings.py + echo ' for i, line in enumerate(block_lines):' >> /tmp/check_warnings.py + echo ' if warning_text in line:' >> /tmp/check_warnings.py + echo ' actual_line_num = block_line + i' >> /tmp/check_warnings.py + echo ' # Clean up the line for display (remove extra whitespace, HTML tags)' >> /tmp/check_warnings.py + echo ' clean_line = re.sub(r"<[^>]+>", "", line).strip()' >> /tmp/check_warnings.py + echo ' if clean_line: # Only add non-empty lines' >> /tmp/check_warnings.py + echo ' matches.append(f"{actual_line_num}:{clean_line}")' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' # Output results' >> /tmp/check_warnings.py + echo ' for match in matches:' >> /tmp/check_warnings.py + echo ' print(match)' >> /tmp/check_warnings.py + echo ' ' >> /tmp/check_warnings.py + echo ' except Exception as e:' >> /tmp/check_warnings.py + echo ' print(f"Error processing file: {e}", file=sys.stderr)' >> /tmp/check_warnings.py + echo ' sys.exit(1)' >> /tmp/check_warnings.py + echo '' >> /tmp/check_warnings.py + echo 'if __name__ == "__main__":' >> /tmp/check_warnings.py + echo ' file_path = sys.argv[1]' >> /tmp/check_warnings.py + echo ' warning_text = sys.argv[2]' >> /tmp/check_warnings.py + echo ' find_warnings_in_cell_outputs(file_path, warning_text)' >> /tmp/check_warnings.py + + # Search for warnings in HTML files within cell_output elements + for file in "${FILES[@]}"; do + echo "Checking file: $file" + + for warning in "${WARNING_ARRAY[@]}"; do + # Remove leading/trailing whitespace from warning + warning=$(echo "$warning" | xargs) + + # Run the Python script and capture results + matches=$(python3 /tmp/check_warnings.py "$file" "$warning" 2>/dev/null || true) + + if [ -n "$matches" ]; then + WARNINGS_FOUND="true" + count=$(echo "$matches" | wc -l) + TOTAL_WARNINGS=$((TOTAL_WARNINGS + count)) + + echo "⚠️ Found $count instance(s) of '$warning' in $file:" + echo "$matches" + + # Add to basic details + if [ -n "$WARNING_DETAILS" ]; then + WARNING_DETAILS="$WARNING_DETAILS\n" + fi + WARNING_DETAILS="$WARNING_DETAILS$file: $count instance(s) of '$warning'" + + # Add to detailed report + DETAILED_REPORT="$DETAILED_REPORT## $warning in $file\n\n" + DETAILED_REPORT="$DETAILED_REPORT**Found $count instance(s):**\n\n" + DETAILED_REPORT="$DETAILED_REPORT\`\`\`\n" + DETAILED_REPORT="$DETAILED_REPORT$matches\n" + DETAILED_REPORT="$DETAILED_REPORT\`\`\`\n\n" + fi + done + done + + # Set outputs + echo "warnings-found=$WARNINGS_FOUND" >> $GITHUB_OUTPUT + echo "warning-count=$TOTAL_WARNINGS" >> $GITHUB_OUTPUT + echo "warning-details<> $GITHUB_OUTPUT + echo -e "$WARNING_DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "detailed-report<> $GITHUB_OUTPUT + echo -e "$DETAILED_REPORT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Summary + if [ "$WARNINGS_FOUND" = "true" ]; then + echo "❌ Found $TOTAL_WARNINGS warning(s) in HTML files" + echo "::error::Found $TOTAL_WARNINGS Python warning(s) in HTML output" + else + echo "✅ No warnings found in HTML files" + fi + + - name: Post PR comment with warning report + if: inputs.fail-on-warning == 'true' && steps.check.outputs.warnings-found == 'true' && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const warningCount = '${{ steps.check.outputs.warning-count }}'; + const detailedReport = ${{ toJSON(steps.check.outputs.detailed-report) }}; + + const body = [ + '## ⚠️ Python Warnings Detected', + '', + '🚨 **' + warningCount + ' Python warning(s)** were found in the HTML output during the documentation build.', + '', + '**Build Details:**', + '- **Workflow Run:** [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})', + '- **Commit:** ${{ github.sha }}', + '- **Date:** ' + new Date().toISOString(), + '', + '---', + '', + detailedReport, + '', + '---', + '', + '**Next Steps:**', + '1. Review the warnings listed above', + '2. Fix the underlying code that\'s generating these warnings', + '3. Push the changes to update this PR', + '', + '📝 *This comment was automatically generated by the [Check for Python Warnings Action](https://github.com/QuantEcon/meta/.github/actions/check-warnings).*' + ].join('\n'); + + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + console.log('Posted PR comment with warning details'); + } catch (error) { + console.error('Failed to create PR comment:', error); + core.setFailed('Failed to create PR comment: ' + error.message); + } + + - name: Fail workflow on warnings + if: inputs.fail-on-warning == 'true' && steps.check.outputs.warnings-found == 'true' + shell: bash + run: | + echo "Failing workflow due to warnings found" + exit 1 + + - name: Create artifact with warning report + id: create-artifact + if: inputs.create-artifact == 'true' && steps.check.outputs.warnings-found == 'true' + shell: bash + run: | + ARTIFACT_NAME="${{ inputs.artifact-name }}" + ARTIFACT_FILE="$ARTIFACT_NAME.md" + CURRENT_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC') + + # Create the report file + { + echo "# Python Warning Report" + echo "" + echo "**Date:** $CURRENT_DATE" + echo "**Repository:** ${{ github.repository }}" + echo "**Workflow:** ${{ github.workflow }}" + echo "**Run ID:** ${{ github.run_id }}" + echo "**Total Warnings Found:** ${{ steps.check.outputs.warning-count }}" + echo "" + echo "---" + echo "" + echo "${{ steps.check.outputs.detailed-report }}" + echo "" + echo "---" + echo "" + echo "Generated by [Check for Python Warnings Action](https://github.com/QuantEcon/meta/.github/actions/check-warnings)" + } > "$ARTIFACT_FILE" + + echo "artifact-path=$ARTIFACT_FILE" >> $GITHUB_OUTPUT + echo "Created warning report artifact: $ARTIFACT_FILE" + + - name: Upload warning report artifact + if: inputs.create-artifact == 'true' && steps.check.outputs.warnings-found == 'true' + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ steps.create-artifact.outputs.artifact-path }} + retention-days: 30 + + - name: Create GitHub issue + id: create-issue + if: inputs.create-issue == 'true' && steps.check.outputs.warnings-found == 'true' + uses: actions/github-script@v7 + with: + script: | + const warningCount = '${{ steps.check.outputs.warning-count }}'; + const detailedReport = ${{ toJSON(steps.check.outputs.detailed-report) }}; + const title = '${{ inputs.issue-title }}'; + const notify = '${{ inputs.notify }}'; + + const body = [ + '# Python Warnings Detected', + '', + '🚨 **' + warningCount + ' Python warning(s)** were found in the HTML output during the documentation build.', + '', + '**Details:**', + '- **Repository:** ${{ github.repository }}', + '- **Workflow:** ${{ github.workflow }}', + '- **Run ID:** [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})', + '- **Commit:** ${{ github.sha }}', + '- **Branch:** ${{ github.ref_name }}', + '- **Date:** ' + new Date().toISOString(), + '', + '---', + '', + detailedReport, + '', + '---', + '', + '**Next Steps:**', + '1. Review the warnings listed above', + '2. Fix the underlying code that\'s generating these warnings', + '3. Re-run the build to verify the warnings are resolved', + '', + '**Note:** This issue was automatically created by the [Check for Python Warnings Action](https://github.com/QuantEcon/meta/.github/actions/check-warnings).', + '', + 'Please close this issue once all warnings have been addressed.' + ].join('\n'); + + try { + const response = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: ['bug', 'execution', 'python-warnings'] + }); + + const issueUrl = response.data.html_url; + const issueNumber = response.data.number; + console.log('Created issue: ' + issueUrl); + core.setOutput('issue-url', issueUrl); + + // Assign users to the issue if notify parameter is provided + if (notify && notify.trim()) { + try { + // Parse comma-separated usernames and clean them + const assignees = notify.split(',') + .map(username => username.trim()) + .filter(username => username.length > 0); + + if (assignees.length > 0) { + console.log('Assigning issue to users: ' + assignees.join(', ')); + + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + assignees: assignees + }); + + console.log('Successfully assigned issue to: ' + assignees.join(', ')); + } + } catch (assignError) { + console.error('Failed to assign users to issue:', assignError); + // Don't fail the entire workflow for assignment errors + console.log('Issue was created successfully, but assignment failed. Users may need to be assigned manually.'); + } + } + + return issueUrl; + } catch (error) { + console.error('Failed to create issue:', error); + core.setFailed('Failed to create issue: ' + error.message); + } + + - name: Post simple PR comment linking to issue + if: inputs.create-issue == 'true' && steps.check.outputs.warnings-found == 'true' && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const warningDetails = ${{ toJSON(steps.check.outputs.warning-details) }}; + const issueUrl = '${{ steps.create-issue.outputs.issue-url }}'; + + // Extract file names from warning details + const files = []; + const lines = warningDetails.split('\n'); + for (const line of lines) { + if (line.includes(': ') && line.includes('instance(s) of')) { + const fileName = line.split(':')[0].trim(); + if (fileName && !files.includes(fileName)) { + files.push(fileName); + } + } + } + + const fileList = files.map(file => `- ${file}`).join('\n'); + + const body = [ + 'There were code execution warnings found in the following files:', + '', + fileList, + '', + `For further details please check ${issueUrl}`, + '', + 'Note: This issue was automatically created by the [Check for Python Warnings Action](https://github.com/QuantEcon/meta/.github/actions/check-warnings).' + ].join('\n'); + + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + console.log('Posted simple PR comment linking to issue'); + } catch (error) { + console.error('Failed to create PR comment:', error); + core.setFailed('Failed to create PR comment: ' + error.message); + } + +branding: + icon: 'alert-triangle' + color: 'orange' \ No newline at end of file diff --git a/.github/actions/check-warnings/examples.md b/.github/actions/check-warnings/examples.md new file mode 100644 index 0000000..d655cec --- /dev/null +++ b/.github/actions/check-warnings/examples.md @@ -0,0 +1,332 @@ +# Example: Using the Warning Check Action + +This directory contains examples of how to use the `check-warnings` action in different scenarios. + +## Example 1: Basic Jupyter Book Build + +```yaml +name: Build and Check Jupyter Book + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install jupyter-book + pip install -r requirements.txt + + - name: Build Jupyter Book + run: | + jupyter-book build . + + - name: Check for Python warnings in built HTML + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'true' +``` + +## Example 2: Non-failing Check with GitHub Issue Creation + +```yaml +name: Build with Issue Creation +permissions: + contents: read + issues: write + +on: + push: + branches: [ main ] + +jobs: + build-with-issue-creation: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build documentation + run: | + # Your build process here + make html + + - name: Check for warnings with issue creation + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './docs/_build/html' + fail-on-warning: 'false' + create-issue: 'true' + issue-title: 'Python Warnings Found in Documentation' + notify: 'maintainer1,reviewer2' # Assign to team members +``` + +## Example 2b: Issue Creation with Single User Assignment + +```yaml +- name: Check for warnings with single user assignment + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + create-issue: 'true' + issue-title: 'Critical Python Warnings Detected' + notify: 'team-lead' # Assign to single responsible person +``` + +## Example 3: Check with Artifact Generation + +```yaml +name: Build with Artifact Report + +on: + pull_request: + branches: [ main ] + +jobs: + build-with-artifact: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build documentation + run: | + jupyter-book build . + + - name: Check for warnings with artifact + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + fail-on-warning: 'true' + create-artifact: 'true' + artifact-name: 'pr-warning-report' +``` + +## Example 4: Comprehensive Warning Management + +```yaml +name: Complete Warning Management + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + comprehensive-warning-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install jupyter-book + pip install -r requirements.txt + + - name: Build Jupyter Book + run: | + jupyter-book build . + + - name: Comprehensive warning check + id: warning-check + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning,UserWarning' + fail-on-warning: 'false' # Don't fail on warnings + create-issue: ${{ github.event_name == 'push' }} # Create issues only on push to main + issue-title: 'Python Warnings in Documentation Build - ${{ github.sha }}' + notify: 'team-lead,maintainer' # Assign to responsible team members + create-artifact: 'true' # Always create artifact for review + artifact-name: 'warning-report-${{ github.run_id }}' + + - name: Comment on PR with warning info + if: github.event_name == 'pull_request' && steps.warning-check.outputs.warnings-found == 'true' + uses: actions/github-script@v7 + with: + script: | + const warningCount = '${{ steps.warning-check.outputs.warning-count }}'; + const artifactName = 'warning-report-${{ github.run_id }}'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `⚠️ **Warning:** Found ${warningCount} Python warning(s) in the documentation build. + + Please review the [warning report artifact](${context.payload.repository.html_url}/actions/runs/${{ github.run_id }}) for details. + + Consider fixing these warnings before merging to maintain code quality.` + }) +``` + +## Example 5: Non-failing Check with Reporting + +```yaml +name: Build with Warning Report + +on: + push: + branches: [ main ] + +jobs: + build-with-warning-report: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build documentation + run: | + # Your build process here + make html + + - name: Check for warnings (non-failing) + id: warning-check + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './docs/_build/html' + fail-on-warning: 'false' + + - name: Create warning report + if: steps.warning-check.outputs.warnings-found == 'true' + run: | + echo "## Warning Report" >> $GITHUB_STEP_SUMMARY + echo "Found ${{ steps.warning-check.outputs.warning-count }} warnings:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "${{ steps.warning-check.outputs.warning-details }}" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Post warning comment (for PRs) + if: github.event_name == 'pull_request' && steps.warning-check.outputs.warnings-found == 'true' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '⚠️ **Python Warnings Found**\n\nFound ${{ steps.warning-check.outputs.warning-count }} warnings in the built documentation:\n\n```\n${{ steps.warning-check.outputs.warning-details }}\n```' + }) +``` + +## Example 6: Custom Warning Types + +```yaml +name: Check for Custom Warnings + +on: + workflow_dispatch: + inputs: + custom_warnings: + description: 'Custom warnings to check for' + required: false + default: 'UserWarning,RuntimeWarning,ResourceWarning' + +jobs: + custom-warning-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build project + run: | + # Your build process + make build + + - name: Check for custom warnings + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './output' + warnings: ${{ github.event.inputs.custom_warnings || 'SyntaxWarning,DeprecationWarning,FutureWarning' }} + fail-on-warning: 'true' +``` + +## Example 7: Matrix Strategy + +```yaml +name: Multi-version Warning Check + +on: + schedule: + - cron: '0 2 * * 1' # Weekly on Monday + +jobs: + warning-check-matrix: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Build documentation + run: | + jupyter-book build . + + - name: Check for warnings + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'false' + + - name: Upload HTML artifacts if warnings found + if: steps.warning-check.outputs.warnings-found == 'true' + uses: actions/upload-artifact@v4 + with: + name: html-with-warnings-py${{ matrix.python-version }} + path: ./_build/html +``` + +## Tips for Usage + +1. **Place the warning check after your build step**: The action needs the final HTML output to scan. + +2. **Use `fail-on-warning: 'false'` for reporting**: If you want to report warnings without failing the workflow. + +3. **Customize warning types**: Adjust the `warnings` input to match your project's needs. + +4. **Path considerations**: Make sure the `html-path` points to where your build process outputs HTML files. + +5. **Integration with existing workflows**: This action can be easily added to existing CI/CD pipelines. \ No newline at end of file diff --git a/.github/workflows/test-warning-check.yml b/.github/workflows/test-warning-check.yml new file mode 100644 index 0000000..21f7e68 --- /dev/null +++ b/.github/workflows/test-warning-check.yml @@ -0,0 +1,85 @@ +name: Test Warning Check Action + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + test-clean-files: + runs-on: ubuntu-latest + name: Test with clean HTML files + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Test action with clean files + id: clean-test + uses: .//.github/actions/check-warnings + with: + html-path: './test/html-samples/clean.html' + fail-on-warning: 'false' + + - name: Verify clean results + run: | + echo "Warnings found: ${{ steps.clean-test.outputs.warnings-found }}" + echo "Warning count: ${{ steps.clean-test.outputs.warning-count }}" + if [ "${{ steps.clean-test.outputs.warnings-found }}" != "false" ]; then + echo "❌ Expected no warnings but found some" + exit 1 + fi + echo "✅ Clean test passed" + + test-files-with-warnings: + runs-on: ubuntu-latest + name: Test with HTML files containing warnings + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Test action with files containing warnings + id: warning-test + uses: .//.github/actions/check-warnings + with: + html-path: './test/html-samples' + fail-on-warning: 'false' + + - name: Verify warning results + run: | + echo "Warnings found: ${{ steps.warning-test.outputs.warnings-found }}" + echo "Warning count: ${{ steps.warning-test.outputs.warning-count }}" + echo "Warning details: ${{ steps.warning-test.outputs.warning-details }}" + if [ "${{ steps.warning-test.outputs.warnings-found }}" != "true" ]; then + echo "❌ Expected warnings but found none" + exit 1 + fi + if [ "${{ steps.warning-test.outputs.warning-count }}" -lt "3" ]; then + echo "❌ Expected at least 3 warnings but found ${{ steps.warning-test.outputs.warning-count }}" + exit 1 + fi + echo "✅ Warning test passed" + + test-fail-on-warning: + runs-on: ubuntu-latest + name: Test fail-on-warning functionality + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Test that action fails when warnings found and fail-on-warning is true + id: fail-test + continue-on-error: true + uses: .//.github/actions/check-warnings + with: + html-path: './test/html-samples/with-warnings.html' + fail-on-warning: 'true' + + - name: Verify action failed + run: | + if [ "${{ steps.fail-test.outcome }}" != "failure" ]; then + echo "❌ Expected action to fail but it succeeded" + exit 1 + fi + echo "✅ Fail-on-warning test passed" \ No newline at end of file diff --git a/README.md b/README.md index bf878f7..217b91d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ # meta For issues and discussion covering more than one repository + +## GitHub Actions + +This repository contains reusable GitHub Actions for QuantEcon projects: + +### Check Warnings Action + +A GitHub Action that scans HTML files for Python warnings and optionally fails the workflow if any are found. + +**Location**: `.github/actions/check-warnings` + +**Usage**: +```yaml +- name: Check for Python warnings + uses: QuantEcon/meta/.github/actions/check-warnings@main + with: + html-path: './_build/html' + warnings: 'SyntaxWarning,DeprecationWarning,FutureWarning' + fail-on-warning: 'true' +``` + +**Use case**: Ideal for checking Jupyter Book builds or any HTML output from Python code execution to ensure no warnings are present in the final documentation. + +See the [action documentation](./.github/actions/check-warnings/README.md) for detailed usage instructions and examples. diff --git a/test/html-samples/clean.html b/test/html-samples/clean.html new file mode 100644 index 0000000..52d0c9e --- /dev/null +++ b/test/html-samples/clean.html @@ -0,0 +1,16 @@ + + + + Clean Code Output + + +

Test Output

+
+
+Running code...
+Result: 42
+Success!
+        
+
+ + \ No newline at end of file diff --git a/test/html-samples/with-warnings.html b/test/html-samples/with-warnings.html new file mode 100644 index 0000000..dabd823 --- /dev/null +++ b/test/html-samples/with-warnings.html @@ -0,0 +1,27 @@ + + + + Code Output with Warnings + + +

Test Output with Warnings

+
+
+Running code...
+/path/to/file.py:15: DeprecationWarning: This function is deprecated
+  result = old_function()
+/path/to/file.py:25: SyntaxWarning: invalid escape sequence '\d'
+  pattern = '\d+'
+Result: 42
+        
+
+
+
+Another execution...
+/path/to/another.py:10: FutureWarning: This will change in future versions
+  new_behavior = True
+Done!
+        
+
+ + \ No newline at end of file