|
7 | 7 | description: "Target branch to compare against (e.g., main)." |
8 | 8 | required: true |
9 | 9 | type: string |
| 10 | + # Python/pytest options |
10 | 11 | python-version: |
11 | 12 | description: "Python version for pytest." |
12 | 13 | required: false |
13 | 14 | type: string |
14 | 15 | default: "3.10" |
| 16 | + # C++/CMake options |
| 17 | + cmake-version: |
| 18 | + description: "CMake version for C++ tests." |
| 19 | + required: false |
| 20 | + type: string |
| 21 | + default: "3.28" |
| 22 | + cpp-compiler: |
| 23 | + description: "C++ compiler (gcc, clang). Auto-detects if empty." |
| 24 | + required: false |
| 25 | + type: string |
| 26 | + default: "" |
| 27 | + cpp-build-type: |
| 28 | + description: "CMake build type (Debug, Release, RelWithDebInfo, MinSizeRel)." |
| 29 | + required: false |
| 30 | + type: string |
| 31 | + default: "Release" |
| 32 | + cpp-build-dir: |
| 33 | + description: "Build directory for C++ projects." |
| 34 | + required: false |
| 35 | + type: string |
| 36 | + default: "build" |
| 37 | + cpp-cmake-args: |
| 38 | + description: "Additional CMake configuration arguments." |
| 39 | + required: false |
| 40 | + type: string |
| 41 | + default: "" |
| 42 | + cpp-test-args: |
| 43 | + description: "Additional CTest arguments." |
| 44 | + required: false |
| 45 | + type: string |
| 46 | + default: "" |
| 47 | + # Common options |
15 | 48 | runs_on: |
16 | 49 | description: "Runner label." |
17 | 50 | required: false |
18 | 51 | type: string |
19 | 52 | default: '["self-hosted", "multithreaded"]' |
20 | 53 | parallel_workers: |
21 | | - description: "Number of parallel pytest workers. Leave empty for auto-detect (6 for multithreaded, 1 for singlethreaded). Use 'auto' for CPU count (caution: detects host CPUs)." |
| 54 | + description: "Number of parallel workers. Leave empty for auto-detect (6 for multithreaded, 1 for singlethreaded). Use 'auto' for CPU count." |
22 | 55 | required: false |
23 | 56 | type: string |
24 | 57 | default: "" |
|
34 | 67 | required: false |
35 | 68 | outputs: |
36 | 69 | has_regressions: |
37 | | - description: "Whether regressions were detected" |
| 70 | + description: "Whether regressions were detected (pytest)" |
38 | 71 | value: ${{ jobs.compare.outputs.has_regressions }} |
39 | 72 | regression_count: |
40 | | - description: "Number of regressions" |
| 73 | + description: "Number of regressions (pytest)" |
41 | 74 | value: ${{ jobs.compare.outputs.regression_count }} |
| 75 | + cpp_has_regressions: |
| 76 | + description: "Whether regressions were detected (C++)" |
| 77 | + value: ${{ jobs.compare-cpp.outputs.has_regressions }} |
| 78 | + cpp_regression_count: |
| 79 | + description: "Number of regressions (C++)" |
| 80 | + value: ${{ jobs.compare-cpp.outputs.regression_count }} |
42 | 81 |
|
43 | 82 | jobs: |
44 | 83 | # Detect which test frameworks are present |
45 | 84 | detect-frameworks: |
46 | 85 | runs-on: ${{ fromJSON(inputs.runs_on) }} |
47 | 86 | outputs: |
48 | 87 | has_pytest: ${{ steps.detect.outputs.has_pytest }} |
49 | | - # Future: has_jest, has_xunit, etc. |
| 88 | + has_cpp: ${{ steps.detect.outputs.has_cpp }} |
50 | 89 | steps: |
51 | 90 | - uses: actions/checkout@v4.2.2 |
52 | 91 | - name: Detect test frameworks |
|
59 | 98 | else |
60 | 99 | echo "has_pytest=false" >> $GITHUB_OUTPUT |
61 | 100 | fi |
62 | | - # Future: Add jest, xunit, etc. detection |
| 101 | +
|
| 102 | + # Detect C++ with CMake and tests |
| 103 | + HAS_CPP="false" |
| 104 | + if [ -f "CMakeLists.txt" ]; then |
| 105 | + # Check for test-related CMake content |
| 106 | + if grep -rqE "(enable_testing|add_test|gtest|catch|boost.*test)" CMakeLists.txt 2>/dev/null || \ |
| 107 | + find . -name "CMakeLists.txt" -exec grep -lE "(enable_testing|add_test|gtest|catch)" {} \; 2>/dev/null | head -1 | grep -q .; then |
| 108 | + HAS_CPP="true" |
| 109 | + echo "✅ Detected: C++ (CMake with tests)" |
| 110 | + fi |
| 111 | + fi |
| 112 | + # Check for test source files |
| 113 | + if [ "$HAS_CPP" = "false" ]; then |
| 114 | + if find . \( -name "*_test.cpp" -o -name "*_test.cc" -o -name "test_*.cpp" -o -name "test_*.cc" \) 2>/dev/null | head -1 | grep -q .; then |
| 115 | + HAS_CPP="true" |
| 116 | + echo "✅ Detected: C++ test files" |
| 117 | + fi |
| 118 | + fi |
| 119 | + echo "has_cpp=$HAS_CPP" >> $GITHUB_OUTPUT |
63 | 120 |
|
64 | 121 | # Test source branch (always fresh, no caching) |
65 | 122 | test-source: |
@@ -616,3 +673,95 @@ jobs: |
616 | 673 | curl -s -H "Content-Type: application/json" \ |
617 | 674 | -d "{\"content\": \"$(echo -e "$MSG")\"}" \ |
618 | 675 | "$WEBHOOK" || true |
| 676 | +
|
| 677 | + # ============================================ |
| 678 | + # C++ Tests (GTest/CTest) |
| 679 | + # ============================================ |
| 680 | + |
| 681 | + # Test C++ source branch |
| 682 | + test-source-cpp: |
| 683 | + needs: detect-frameworks |
| 684 | + if: needs.detect-frameworks.outputs.has_cpp == 'true' |
| 685 | + uses: ./.github/workflows/test-cpp-gtest.yml |
| 686 | + with: |
| 687 | + ref: "" # Default checkout = PR branch |
| 688 | + cmake-version: ${{ inputs.cmake-version }} |
| 689 | + compiler: ${{ inputs.cpp-compiler }} |
| 690 | + build-type: ${{ inputs.cpp-build-type }} |
| 691 | + build-dir: ${{ inputs.cpp-build-dir }} |
| 692 | + cmake-args: ${{ inputs.cpp-cmake-args }} |
| 693 | + test-args: ${{ inputs.cpp-test-args }} |
| 694 | + runs_on: ${{ inputs.runs_on }} |
| 695 | + artifact_name: cpp_source_${{ github.event.pull_request.number || github.run_id }} |
| 696 | + parallel_workers: ${{ inputs.parallel_workers }} |
| 697 | + |
| 698 | + # Test C++ target branch |
| 699 | + test-target-cpp: |
| 700 | + needs: detect-frameworks |
| 701 | + if: needs.detect-frameworks.outputs.has_cpp == 'true' |
| 702 | + uses: ./.github/workflows/test-cpp-gtest.yml |
| 703 | + with: |
| 704 | + ref: ${{ inputs.target_branch }} |
| 705 | + cmake-version: ${{ inputs.cmake-version }} |
| 706 | + compiler: ${{ inputs.cpp-compiler }} |
| 707 | + build-type: ${{ inputs.cpp-build-type }} |
| 708 | + build-dir: ${{ inputs.cpp-build-dir }} |
| 709 | + cmake-args: ${{ inputs.cpp-cmake-args }} |
| 710 | + test-args: ${{ inputs.cpp-test-args }} |
| 711 | + runs_on: ${{ inputs.runs_on }} |
| 712 | + artifact_name: cpp_target_${{ github.event.pull_request.number || github.run_id }} |
| 713 | + parallel_workers: ${{ inputs.parallel_workers }} |
| 714 | + |
| 715 | + # Compare C++ results |
| 716 | + compare-cpp: |
| 717 | + needs: [test-source-cpp, test-target-cpp] |
| 718 | + if: always() && needs.test-source-cpp.result == 'success' |
| 719 | + uses: ./.github/workflows/regression-test.yml |
| 720 | + with: |
| 721 | + runs_on: ${{ inputs.runs_on }} |
| 722 | + baseline_label: ${{ inputs.target_branch }} |
| 723 | + baseline_results_artifact: cpp_target_${{ github.event.pull_request.number || github.run_id }} |
| 724 | + baseline_results_filename: test_data.json |
| 725 | + current_label: ${{ github.head_ref || github.ref_name }} |
| 726 | + current_results_artifact: cpp_source_${{ github.event.pull_request.number || github.run_id }} |
| 727 | + current_results_filename: test_data.json |
| 728 | + baseline_passed: ${{ needs.test-target-cpp.outputs.passed }} |
| 729 | + baseline_total: ${{ needs.test-target-cpp.outputs.total }} |
| 730 | + baseline_percentage: ${{ needs.test-target-cpp.outputs.percentage }} |
| 731 | + current_passed: ${{ needs.test-source-cpp.outputs.passed }} |
| 732 | + current_total: ${{ needs.test-source-cpp.outputs.total }} |
| 733 | + current_percentage: ${{ needs.test-source-cpp.outputs.percentage }} |
| 734 | + baseline_collection_errors: ${{ needs.test-target-cpp.outputs.collection_errors }} |
| 735 | + baseline_no_tests_found: ${{ needs.test-target-cpp.outputs.no_tests_found }} |
| 736 | + current_collection_errors: ${{ needs.test-source-cpp.outputs.collection_errors }} |
| 737 | + current_no_tests_found: ${{ needs.test-source-cpp.outputs.no_tests_found }} |
| 738 | + artifact_name: regression_cpp_${{ github.event.pull_request.number || github.run_id }} |
| 739 | + |
| 740 | + # Notify on C++ regressions |
| 741 | + notify-cpp: |
| 742 | + needs: [test-source-cpp, test-target-cpp, compare-cpp] |
| 743 | + if: | |
| 744 | + always() && |
| 745 | + (needs.compare-cpp.outputs.has_regressions == 'true' || needs.compare-cpp.result == 'failure') |
| 746 | + runs-on: ${{ fromJSON(inputs.runs_on) }} |
| 747 | + steps: |
| 748 | + - name: Send notification |
| 749 | + env: |
| 750 | + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} |
| 751 | + run: | |
| 752 | + if [ -z "$WEBHOOK" ]; then |
| 753 | + echo "No Discord webhook configured, skipping notification" |
| 754 | + exit 0 |
| 755 | + fi |
| 756 | +
|
| 757 | + MSG="**C++ Test Regression Alert**\n" |
| 758 | + MSG+="PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}\n" |
| 759 | + MSG+="\`${{ github.head_ref }}\` → \`${{ inputs.target_branch }}\`\n\n" |
| 760 | + MSG+="Source: ${{ needs.test-source-cpp.outputs.passed }}/${{ needs.test-source-cpp.outputs.total }}\n" |
| 761 | + MSG+="Target: ${{ needs.test-target-cpp.outputs.passed }}/${{ needs.test-target-cpp.outputs.total }}\n" |
| 762 | + MSG+="Regressions: ${{ needs.compare-cpp.outputs.regression_count || '?' }}\n\n" |
| 763 | + MSG+="[View Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" |
| 764 | +
|
| 765 | + curl -s -H "Content-Type: application/json" \ |
| 766 | + -d "{\"content\": \"$(echo -e "$MSG")\"}" \ |
| 767 | + "$WEBHOOK" || true |
0 commit comments