name: Build & Run Unit Tests on: push: branches: [ v2_release, v2_develop ] paths-ignore: - '**.md' pull_request: branches: [ v2_release, v2_develop ] paths-ignore: - '**.md' jobs: # Call the quick-build workflow to build Debug configuration only build: uses: ./.github/workflows/quick-build.yml non_parallel_unittests: name: Non-Parallel Unit Tests runs-on: ${{ matrix.os }} needs: build strategy: # Turn off fail-fast to let all runners run even if there are errors fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] timeout-minutes: 15 # Increased from 10 for Windows steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: dotnet-version: 8.x dotnet-quality: 'ga' - name: Download build artifacts uses: actions/download-artifact@v4 with: name: test-build-artifacts path: . # KEEP THIS - It's needed for --no-build to work - name: Restore NuGet packages run: dotnet restore # Optimize Windows performance - name: Disable Windows Defender (Windows only) if: runner.os == 'Windows' shell: powershell run: | Add-MpPreference -ExclusionPath "${{ github.workspace }}" Add-MpPreference -ExclusionProcess "dotnet.exe" Add-MpPreference -ExclusionProcess "testhost.exe" Add-MpPreference -ExclusionProcess "VSTest.Console.exe" - name: Set VSTEST_DUMP_PATH shell: bash run: echo "VSTEST_DUMP_PATH=logs/UnitTests/${{ runner.os }}/" >> $GITHUB_ENV - name: Run UnitTests shell: bash run: | if [ "${{ runner.os }}" == "Linux" ]; then # Run with coverage on Linux only dotnet test Tests/UnitTests \ --no-build \ --verbosity normal \ --collect:"XPlat Code Coverage" \ --settings Tests/UnitTests/runsettings.xml \ --diag:logs/UnitTests/${{ runner.os }}/logs.txt \ --blame \ --blame-crash \ --blame-hang \ --blame-hang-timeout 60s \ --blame-crash-collect-always else # Run without coverage on Windows/macOS for speed dotnet test Tests/UnitTests \ --no-build \ --verbosity normal \ --settings Tests/UnitTests/runsettings.xml \ --diag:logs/UnitTests/${{ runner.os }}/logs.txt \ --blame \ --blame-crash \ --blame-hang \ --blame-hang-timeout 120s \ --blame-crash-collect-always fi - name: Upload Test Logs if: always() uses: actions/upload-artifact@v4 with: name: non_parallel_unittests-logs-${{ runner.os }} path: | logs/UnitTests TestResults/ - name: Upload Non-Parallel UnitTests Coverage to Codecov if: matrix.os == 'ubuntu-latest' && always() uses: codecov/codecov-action@v4 with: files: TestResults/**/coverage.cobertura.xml flags: unittests-nonparallel name: UnitTests-${{ runner.os }} token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false parallel_unittests: name: Parallel Unit Tests runs-on: ${{ matrix.os }} needs: build strategy: # Turn off fail-fast to let all runners run even if there are errors fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] timeout-minutes: 60 steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: dotnet-version: 8.x dotnet-quality: 'ga' - name: Download build artifacts uses: actions/download-artifact@v4 with: name: test-build-artifacts path: . - name: Restore NuGet packages run: dotnet restore - name: Disable Windows Defender (Windows only) if: runner.os == 'Windows' shell: powershell run: | Add-MpPreference -ExclusionPath "${{ github.workspace }}" Add-MpPreference -ExclusionProcess "dotnet.exe" Add-MpPreference -ExclusionProcess "testhost.exe" Add-MpPreference -ExclusionProcess "VSTest.Console.exe" - name: Set VSTEST_DUMP_PATH shell: bash run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV - name: Run UnitTestsParallelizable (10 iterations with varying parallelization) shell: bash run: | # Run tests 3 times with different parallelization settings to expose concurrency issues for RUN in {1..3}; do echo "============================================" echo "Starting test run $RUN of 3" echo "============================================" # Use a combination of run number and timestamp to create different execution patterns SEED=$((1000 + $RUN + $(date +%s) % 1000)) echo "Using randomization seed: $SEED" # Vary the xUnit parallelization based on run number to expose race conditions # Runs 1-3: Default parallelization (2x CPU cores) # Runs 4-6: Max parallelization (unlimited) # Runs 7-9: Single threaded (1) # Run 10: Random (1-4 threads) if [ $RUN -le 3 ]; then XUNIT_MAX_PARALLEL_THREADS="2x" echo "Run $RUN: Using default parallelization (2x)" elif [ $RUN -le 6 ]; then XUNIT_MAX_PARALLEL_THREADS="unlimited" echo "Run $RUN: Using maximum parallelization (unlimited)" elif [ $RUN -le 9 ]; then XUNIT_MAX_PARALLEL_THREADS="1" echo "Run $RUN: Using single-threaded execution" else # Random parallelization based on seed PROC_COUNT=$(( ($SEED % 4) + 1 )) XUNIT_MAX_PARALLEL_THREADS="$PROC_COUNT" echo "Run $RUN: Using random parallelization with $PROC_COUNT threads" fi # Run tests with or without coverage based on OS and run number if [ "${{ runner.os }}" == "Linux" ] && [ $RUN -eq 1 ]; then echo "Run $RUN: Running with coverage collection" dotnet test Tests/UnitTestsParallelizable \ --no-build \ --verbosity normal \ --collect:"XPlat Code Coverage" \ --settings Tests/UnitTests/runsettings.coverage.xml \ --diag:logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN}-logs.txt \ --blame \ --blame-crash \ --blame-hang \ --blame-hang-timeout 60s \ --blame-crash-collect-always \ -- xUnit.MaxParallelThreads=${XUNIT_MAX_PARALLEL_THREADS} else dotnet test Tests/UnitTestsParallelizable \ --no-build \ --verbosity normal \ --settings Tests/UnitTestsParallelizable/runsettings.xml \ --diag:logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN}-logs.txt \ --blame \ --blame-crash \ --blame-hang \ --blame-hang-timeout 60s \ --blame-crash-collect-always \ -- xUnit.MaxParallelThreads=${XUNIT_MAX_PARALLEL_THREADS} fi if [ $? -ne 0 ]; then echo "ERROR: Test run $RUN failed!" exit 1 fi echo "Test run $RUN completed successfully" echo "" done echo "============================================" echo "All 10 test runs completed successfully!" echo "============================================" - name: Upload UnitTestsParallelizable Logs if: always() uses: actions/upload-artifact@v4 with: name: parallel_unittests-logs-${{ runner.os }} path: | logs/UnitTestsParallelizable/ TestResults/ - name: Upload Parallelizable UnitTests Coverage to Codecov if: matrix.os == 'ubuntu-latest' && always() uses: codecov/codecov-action@v4 with: files: TestResults/**/coverage.cobertura.xml flags: unittests-parallel name: UnitTestsParallelizable-${{ runner.os }} token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false