diff --git a/.claude/rules/testing-patterns.md b/.claude/rules/testing-patterns.md index d46e7c228f..990cb695fa 100644 --- a/.claude/rules/testing-patterns.md +++ b/.claude/rules/testing-patterns.md @@ -7,11 +7,8 @@ - **Never decrease code coverage** - PRs must maintain or increase coverage - Target: **70%+ coverage** for new code - Coverage collection: - - Centralized in `TestResults/` directory at repository root - - Collected only on Linux (ubuntu-latest) runners in CI for performance - - Windows and macOS runners skip coverage collection to reduce execution time - - Coverage reports uploaded to Codecov automatically from Linux runner - - CI monitors coverage on each PR + - Temporarily disabled in CI during xUnit v3 / MTP migration + - Will be re-enabled once an MTP-compatible coverage solution is integrated ## Test Patterns @@ -47,11 +44,11 @@ - ~10 min timeout - Uses `Application.Init` and static state - Cannot run in parallel -- Includes `--blame` flags for crash diagnostics +- Includes `--diagnostic` flag for logging **Command:** ```bash -dotnet test Tests/UnitTests --no-build --verbosity normal +dotnet test --project Tests/UnitTests --no-build --verbosity normal ``` ### 2. Parallel Tests (`Tests/UnitTestsParallelizable/`) - **PREFERRED** @@ -67,7 +64,7 @@ dotnet test Tests/UnitTests --no-build --verbosity normal **Command:** ```bash -dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal +dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal ``` ### 3. Integration Tests (`Tests/IntegrationTests/`) @@ -78,13 +75,13 @@ dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal **Command:** ```bash -dotnet test Tests/IntegrationTests --no-build --verbosity normal +dotnet test --project Tests/IntegrationTests --no-build --verbosity normal ``` ## Test Configuration Files - `xunit.runner.json` - xUnit configuration -- `coverlet.runsettings` - Coverage settings (OpenCover format) +- `coverlet.runsettings` - Coverage settings (currently unused, pending MTP integration) ## Example Test Pattern diff --git a/.claude/tasks/clean-code-review.md b/.claude/tasks/clean-code-review.md index 0c814ca006..30ded9e58e 100644 --- a/.claude/tasks/clean-code-review.md +++ b/.claude/tasks/clean-code-review.md @@ -50,9 +50,9 @@ Run these for each commit: ```bash dotnet build --no-restore -dotnet test Tests/IntegrationTests --no-build -dotnet test Tests/UnitTests --no-build -dotnet test Tests/UnitTestsParallelizable --no-build +dotnet test --project Tests/IntegrationTests --no-build +dotnet test --project Tests/UnitTests --no-build +dotnet test --project Tests/UnitTestsParallelizable --no-build ``` ## Terminal.Gui Specific Requirements diff --git a/.claude/workflows/build-test-workflow.md b/.claude/workflows/build-test-workflow.md index e86fa5e516..36b1a0ed2b 100644 --- a/.claude/workflows/build-test-workflow.md +++ b/.claude/workflows/build-test-workflow.md @@ -47,19 +47,19 @@ dotnet build --configuration Release --no-restore **Time:** ~10 min timeout ```bash -dotnet test Tests/UnitTests --no-build --verbosity normal +dotnet test --project Tests/UnitTests --no-build --verbosity normal ``` - Uses `Application.Init` and static state - Cannot run in parallel -- Includes `--blame` flags for crash diagnostics +- Includes `--diagnostic` flag for logging ### Run Parallel Tests (Preferred) **Time:** ~10 min timeout ```bash -dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal +dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal ``` - No dependencies on static state @@ -69,13 +69,13 @@ dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal ### Run Integration Tests ```bash -dotnet test Tests/IntegrationTests --no-build --verbosity normal +dotnet test --project Tests/IntegrationTests --no-build --verbosity normal ``` ### Run All Tests ```bash -dotnet test --no-build --verbosity normal +dotnet test --project Tests/UnitTests --no-build --verbosity normal && dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal ``` ## Common Build Issues @@ -94,7 +94,7 @@ dotnet restore ./Examples/SelfContained/SelfContained.csproj -f **For clean builds, always run in this order:** ```bash -dotnet restore && dotnet build --no-restore && dotnet test --no-build +dotnet restore && dotnet build --no-restore && dotnet test --project Tests/UnitTests --no-build && dotnet test --project Tests/UnitTestsParallelizable --no-build ``` This ensures: diff --git a/.claude/workflows/pr-workflow.md b/.claude/workflows/pr-workflow.md index 56731edfaa..26f5b9b593 100644 --- a/.claude/workflows/pr-workflow.md +++ b/.claude/workflows/pr-workflow.md @@ -27,7 +27,7 @@ Before submitting a PR, ensure: - [ ] **Build passes** locally: `dotnet build --no-restore` -- [ ] **Tests pass** locally: `dotnet test --no-build` +- [ ] **Tests pass** locally: `dotnet test --project Tests/UnitTests --no-build && dotnet test --project Tests/UnitTestsParallelizable --no-build` ## PR Description Template @@ -70,16 +70,14 @@ dotnet build --configuration Debug --no-restore ### 2. Run Tests ```bash -dotnet test --no-build --verbosity normal +dotnet test --project Tests/UnitTests --no-build --verbosity normal && dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal ``` **Expected:** All tests pass ### 3. Check Coverage -Coverage is automatically collected on Linux runners in CI. - -**Verify coverage didn't decrease** by checking Codecov report on PR. +Coverage collection is temporarily disabled in CI during the xUnit v3 / MTP migration. ### 4. Format Code diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 54a83d910c..0fe1570677 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -2,12 +2,16 @@ The repository uses multiple GitHub Actions workflows. What runs and when: -### 1) Build Solution (`.github/workflows/build.yml`) +> **Note:** Tests use xUnit v3 with Microsoft Testing Platform (MTP). The test runner is +> configured in `global.json` via `"test": { "runner": "Microsoft.Testing.Platform" }`. +> MTP runs test projects as standalone executables, so each OS must build its own binaries. -- **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`); supports `workflow_call` +### 1) Build Validation (`.github/workflows/build-validation.yml`) + +- **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`) - **Runner/timeout**: `ubuntu-latest`, 10 minutes - **Steps**: -- Checkout and setup .NET 8.x GA +- Checkout and setup .NET 10.x GA - `dotnet restore` - Build Debug: `dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612` - Build Release (library): `dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release --no-incremental --force -property:NoWarn=0618%3B0612` @@ -15,27 +19,20 @@ The repository uses multiple GitHub Actions workflows. What runs and when: - Restore NativeAot/SelfContained examples, then restore solution again - Build Release for `Examples/NativeAot` and `Examples/SelfContained` - Build Release solution -- Upload artifacts named `build-artifacts`, retention 1 day ### 2) Build & Run Unit Tests (`.github/workflows/unit-tests.yml`) - **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`) - **Matrix**: Ubuntu/Windows/macOS -- **Timeout**: 15 minutes per job +- **Timeout**: 15 minutes (non-parallel), 60 minutes (parallel) - **Process**: -1. Calls build workflow to build solution once -2. Downloads build artifacts -3. Runs `dotnet restore` (required for `--no-build` to work) -4. **Performance optimizations**: +1. Each OS checks out code, restores, and builds locally +2. **Performance optimizations**: - Disables Windows Defender on Windows runners (significant speedup) - - Collects code coverage **only on Linux** (ubuntu-latest) for performance - - Windows and macOS skip coverage collection to reduce test time - - Increased blame-hang-timeout to 120s for Windows/macOS (60s for Linux) -5. Runs two test jobs: - - **Non-parallel UnitTests**: `Tests/UnitTests` with blame/diag flags; `xunit.stopOnFail=false` - - **Parallel UnitTestsParallelizable**: `Tests/UnitTestsParallelizable` with blame/diag flags; `xunit.stopOnFail=false` -6. Uploads test logs and diagnostic data from all runners -7. **Uploads code coverage to Codecov only from Linux runner** +3. Runs two test jobs: + - **Non-parallel UnitTests**: `Tests/UnitTests` with diagnostic output + - **Parallel UnitTestsParallelizable**: `Tests/UnitTestsParallelizable` with diagnostic output +4. Uploads test logs and diagnostic data from all runners **Test results**: All tests output to unified `TestResults/` directory at repository root @@ -45,16 +42,11 @@ The repository uses multiple GitHub Actions workflows. What runs and when: - **Matrix**: Ubuntu/Windows/macOS - **Timeout**: 15 minutes - **Process**: -1. Calls build workflow -2. Downloads build artifacts -3. Runs `dotnet restore` -4. **Performance optimizations** (same as unit tests): +1. Each OS checks out code, restores, and builds locally +2. **Performance optimizations**: - Disables Windows Defender on Windows runners - - Collects code coverage **only on Linux** - - Increased blame-hang-timeout to 120s for Windows/macOS -5. Runs IntegrationTests with blame/diag flags; `xunit.stopOnFail=true` -6. Uploads logs per-OS -7. **Uploads coverage to Codecov only from Linux runner** +3. Runs IntegrationTests with diagnostic output +4. Uploads logs per-OS ### 4) Create Release (`.github/workflows/release.yml`) @@ -95,7 +87,7 @@ The repository uses multiple GitHub Actions workflows. What runs and when: # Full CI sequence: dotnet restore dotnet build --configuration Debug --no-restore -dotnet test Tests/UnitTests --no-build --verbosity normal -dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal +dotnet test --project Tests/UnitTests --no-build --verbosity normal +dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal dotnet build --configuration Release --no-restore ``` diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index c1ec99668c..d41181adfe 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,15 +10,11 @@ on: - '**.md' jobs: - build: - uses: ./.github/workflows/quick-build.yml - integration_tests: name: Integration Tests runs-on: ${{ matrix.os }} - needs: build strategy: - fail-fast: false # Let all OSes finish even if one fails + fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] timeout-minutes: 15 @@ -33,57 +29,27 @@ jobs: dotnet-version: 10.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: Build + run: dotnet build --no-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/IntegrationTests/${{ runner.os }}/" >> $GITHUB_ENV - name: Run IntegrationTests shell: bash run: | - if [ "${{ runner.os }}" == "Linux" ]; then - # Run with coverage on Linux only - dotnet test Tests/IntegrationTests \ - --no-build \ - --verbosity minimal \ - --collect:"XPlat Code Coverage" \ - --settings Tests/IntegrationTests/runsettings.coverage.xml \ - --diag:logs/IntegrationTests/${{ 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/IntegrationTests \ - --no-build \ - --verbosity minimal \ - --settings Tests/IntegrationTests/runsettings.xml \ - --diag:logs/IntegrationTests/${{ runner.os }}/logs.txt \ - --blame \ - --blame-crash \ - --blame-hang \ - --blame-hang-timeout 60s \ - --blame-crash-collect-always - fi + dotnet test \ + --project Tests/IntegrationTests \ + --no-build \ + --verbosity minimal \ + --diagnostic --diagnostic-output-directory logs/IntegrationTests/${{ runner.os }} - name: Upload Integration Test Logs if: always() @@ -93,14 +59,3 @@ jobs: path: | logs/IntegrationTests/ TestResults/ - - - name: Upload Integration Tests Coverage to Codecov - if: matrix.os == 'ubuntu-latest' && always() - uses: codecov/codecov-action@v4 - with: - files: TestResults/**/coverage.cobertura.xml - flags: integrationtests - name: IntegrationTests-${{ runner.os }} - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false - diff --git a/.github/workflows/stress-tests.yml b/.github/workflows/stress-tests.yml index 2298f60126..ffbb23e566 100644 --- a/.github/workflows/stress-tests.yml +++ b/.github/workflows/stress-tests.yml @@ -7,7 +7,7 @@ on: branches: [ v2_release, v2_develop ] paths-ignore: - '**.md' - + jobs: run_stress_tests: runs-on: ${{ matrix.os }} @@ -37,7 +37,7 @@ jobs: run: | end=$((SECONDS+900)) while [ $SECONDS -lt $end ]; do - dotnet test Tests/StressTests --no-build --verbosity normal --diag:logs/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true + dotnet test --project Tests/StressTests --no-build --verbosity normal --diagnostic --diagnostic-output-directory logs/${{ runner.os }} done - name: Upload Test Logs @@ -80,62 +80,31 @@ jobs: 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 (3 iterations with varying parallelization) + - name: Run UnitTestsParallelizable (3 iterations) shell: bash run: | - # Run tests 3 times with different parallelization settings to expose concurrency issues - # Run 1: Default parallelization (2x) - standard test execution - # Run 2: Maximum parallelization (unlimited) - stress test with high concurrency - # Run 3: Single-threaded execution (1) - deterministic execution to expose ordering issues + # Run tests 3 times 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 - if [ $RUN -eq 1 ]; then - XUNIT_MAX_PARALLEL_THREADS="2x" - echo "Run $RUN: Using default parallelization (2x)" - elif [ $RUN -eq 2 ]; then - XUNIT_MAX_PARALLEL_THREADS="unlimited" - echo "Run $RUN: Using maximum parallelization (unlimited)" - else - XUNIT_MAX_PARALLEL_THREADS="1" - echo "Run $RUN: Using single-threaded execution" - fi - - dotnet test Tests/UnitTestsParallelizable \ + + dotnet test \ + --project 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} - + --diagnostic --diagnostic-output-directory logs/UnitTestsParallelizable/${{ runner.os }}/run${RUN} + if [ $? -ne 0 ]; then echo "ERROR: Test run $RUN failed!" exit 1 fi - + echo "Test run $RUN completed successfully" echo "" done - + echo "============================================" echo "All 3 test runs completed successfully!" echo "============================================" @@ -148,4 +117,3 @@ jobs: path: | logs/UnitTestsParallelizable/ TestResults/ - diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 96b2429ac6..181333709a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -9,23 +9,17 @@ on: 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 +jobs: non_parallel_unittests: - name: Non-Parallel Unit Tests + 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 + timeout-minutes: 15 steps: - name: Checkout code @@ -37,16 +31,12 @@ jobs: dotnet-version: 10.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 + - name: Build + run: dotnet build --no-restore + # Optimize Windows performance - name: Disable Windows Defender (Windows only) if: runner.os == 'Windows' @@ -54,42 +44,15 @@ jobs: 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 + dotnet test \ + --project Tests/UnitTests \ + --no-build \ + --verbosity normal \ + --diagnostic --diagnostic-output-directory logs/UnitTests/${{ runner.os }} - name: Upload Test Logs if: always() @@ -103,23 +66,11 @@ jobs: **/*.dmp if-no-files-found: ignore retention-days: 7 - - - 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 + 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 ] @@ -136,87 +87,28 @@ jobs: dotnet-version: 10.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: Build + run: dotnet build --no-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 shell: bash run: | - # Detect CPU count and calculate optimal thread count - if [ "${{ runner.os }}" == "Linux" ]; then - CPU_COUNT=$(nproc) - elif [ "${{ runner.os }}" == "macOS" ]; then - CPU_COUNT=$(sysctl -n hw.ncpu) - else # Windows - CPU_COUNT=$NUMBER_OF_PROCESSORS - fi - - # Use 2x CPU count for I/O-bound tests, capped at reasonable max - # macOS uses lower cap (4) to prevent thread pool exhaustion - MAX_THREADS=$((CPU_COUNT * 2)) - if [ "${{ runner.os }}" == "macOS" ]; then - if [ $MAX_THREADS -gt 4 ]; then - MAX_THREADS=2 - fi - HANG_TIMEOUT="240s" - else - if [ $MAX_THREADS -gt 16 ]; then - MAX_THREADS=16 - fi - HANG_TIMEOUT="60s" - fi - - echo "Detected $CPU_COUNT CPUs, using $MAX_THREADS parallel threads" - - # Run tests with dynamic thread count - if [ "${{ runner.os }}" == "Linux" ]; then - dotnet test Tests/UnitTestsParallelizable \ - --no-build \ - --verbosity normal \ - --collect:"XPlat Code Coverage" \ - --settings Tests/UnitTests/runsettings.coverage.xml \ - --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \ - --blame \ - --blame-crash \ - --blame-hang \ - --blame-hang-timeout $HANG_TIMEOUT \ - --blame-crash-collect-always \ - -- xUnit.MaxParallelThreads=$MAX_THREADS \ - -- xUnit.StopOnFail=true - else - dotnet test Tests/UnitTestsParallelizable \ - --no-build \ - --verbosity normal \ - --settings Tests/UnitTestsParallelizable/runsettings.xml \ - --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \ - --blame \ - --blame-crash \ - --blame-hang \ - --blame-hang-timeout $HANG_TIMEOUT \ - --blame-crash-collect-always \ - -- xUnit.MaxParallelThreads=$MAX_THREADS \ - -- xUnit.StopOnFail=true - fi - + dotnet test \ + --project Tests/UnitTestsParallelizable \ + --no-build \ + --verbosity normal \ + --diagnostic --diagnostic-output-directory logs/UnitTestsParallelizable/${{ runner.os }} + echo "============================================" echo "Parallel unit tests completed successfully!" echo "============================================" @@ -233,13 +125,3 @@ jobs: **/*.dmp if-no-files-found: ignore retention-days: 7 - - - 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 diff --git a/AGENTS.md b/AGENTS.md index 0275642d2b..37238ac059 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ dotnet run **Terminal.Gui** - Cross-platform console UI toolkit for .NET (C# 12, net8.0) **Build:** `dotnet restore && dotnet build --no-restore` -**Test:** `dotnet test --no-build` +**Test:** `dotnet test --project Tests/UnitTests --no-build && dotnet test --project Tests/UnitTestsParallelizable --no-build` **Details:** [Build & Test Workflow](.claude/workflows/build-test-workflow.md) ## Quick Rules diff --git a/CLAUDE.md b/CLAUDE.md index 5973460009..ac89ea232b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,8 +81,8 @@ When in planning mode: ```bash dotnet restore dotnet build --no-restore -dotnet test Tests/UnitTestsParallelizable --no-build -dotnet test Tests/UnitTests --no-build +dotnet test --project Tests/UnitTestsParallelizable --no-build +dotnet test --project Tests/UnitTests --no-build ``` ## Key Concepts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b556c5a00..0b1abf6c57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,22 +76,22 @@ Welcome! This guide provides everything you need to know to contribute effective 1. **Non-parallel tests** (depend on static state, ~10 min timeout): ```bash - dotnet test Tests/UnitTests --no-build --verbosity normal + dotnet test --project Tests/UnitTests --no-build --verbosity normal ``` - Uses `Application.Init` and static state - Cannot run in parallel - - Includes `--blame` flags for crash diagnostics + - Includes `--diagnostic` flag for logging 2. **Parallel tests** (can run concurrently, ~10 min timeout): ```bash - dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal + dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal ``` - No dependencies on static state - **Preferred for new tests** 3. **Integration tests**: ```bash - dotnet test Tests/IntegrationTests --no-build --verbosity normal + dotnet test --project Tests/IntegrationTests --no-build --verbosity normal ``` ### Common Build Issues @@ -176,11 +176,8 @@ Welcome! This guide provides everything you need to know to contribute effective - **Never decrease code coverage** - PRs must maintain or increase coverage - Target: 70%+ coverage for new code - **Coverage collection**: -- Centralized in `TestResults/` directory at repository root -- Collected only on Linux (ubuntu-latest) runners in CI for performance -- Windows and macOS runners skip coverage collection to reduce execution time -- Coverage reports uploaded to Codecov automatically from Linux runner -- CI monitors coverage on each PR +- Temporarily disabled in CI during xUnit v3 / MTP migration +- Will be re-enabled once an MTP-compatible coverage solution is integrated ### Test Patterns @@ -195,7 +192,7 @@ Welcome! This guide provides everything you need to know to contribute effective ### Test Configuration - `xunit.runner.json` - xUnit configuration -- `coverlet.runsettings` - Coverage settings (OpenCover format) +- `coverlet.runsettings` - Coverage settings (currently unused, pending MTP integration) ## API Documentation Requirements diff --git a/Directory.Packages.props b/Directory.Packages.props index 34805646a7..b699b270a7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,43 +6,43 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/Examples/ReactiveExample/LoginView.cs b/Examples/ReactiveExample/LoginView.cs index 0eb5e7a345..4090d2694d 100644 --- a/Examples/ReactiveExample/LoginView.cs +++ b/Examples/ReactiveExample/LoginView.cs @@ -1,4 +1,5 @@ using System.Reactive.Disposables; +using System.Reactive.Disposables.Fluent; using System.Reactive.Linq; using ReactiveMarbles.ObservableEvents; using ReactiveUI; @@ -138,7 +139,7 @@ public LoginView (LoginViewModel viewModel) ViewModel .WhenAnyObservable (x => x.Login.IsExecuting) .Select (executing => executing ? ProgressMessage : IdleMessage) - .ObserveOn (RxApp.MainThreadScheduler) + .ObserveOn (Program._rxApp.MainThreadScheduler!) .BindTo (progress, x => x.Text) .DisposeWith (_disposable); }); diff --git a/Examples/ReactiveExample/Program.cs b/Examples/ReactiveExample/Program.cs index e70611afc5..fd7572b69d 100644 --- a/Examples/ReactiveExample/Program.cs +++ b/Examples/ReactiveExample/Program.cs @@ -1,5 +1,5 @@ using System.Reactive.Concurrency; -using ReactiveUI; +using ReactiveUI.Builder; using Terminal.Gui.App; using Terminal.Gui.Configuration; @@ -7,14 +7,17 @@ namespace ReactiveExample; public static class Program { - private static void Main (string [] args) + internal static ReactiveUIBuilder _rxApp; + + private static void Main (string [] _) { ConfigurationManager.Enable (ConfigLocations.All); using IApplication app = Application.Create (); app.Init (); - RxApp.MainThreadScheduler = new TerminalScheduler (app); - RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; - var loginView = new LoginView (new ()); + _rxApp = RxAppBuilder.CreateReactiveUIBuilder (); + _rxApp.WithMainThreadScheduler (new TerminalScheduler (app)); + _rxApp.WithTaskPoolScheduler (TaskPoolScheduler.Default); + var loginView = new LoginView (new LoginViewModel ()); app.Run (loginView); loginView.Dispose (); } diff --git a/Examples/ScenarioRunner/Program.cs b/Examples/ScenarioRunner/Program.cs index 4925d55128..b2d353c46d 100644 --- a/Examples/ScenarioRunner/Program.cs +++ b/Examples/ScenarioRunner/Program.cs @@ -1,8 +1,6 @@ #nullable enable using System.Collections.ObjectModel; using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.Parsing; using System.Reflection; using System.Text; using Microsoft.Extensions.Logging; @@ -34,168 +32,178 @@ public static int Main (string [] args) // Get allowed driver names string? [] allowedDrivers = DriverRegistry.GetDriverNames ().ToArray (); - Option driverOption = new Option ("--driver", "The IDriver to use.").FromAmong (allowedDrivers!); - driverOption.SetDefaultValue (string.Empty); - driverOption.AddAlias ("-d"); + Option driverOption = new Option ("--driver") { Description = "The IDriver to use.", DefaultValueFactory = _ => string.Empty }; + driverOption.AcceptOnlyFromAmong (allowedDrivers!); + driverOption.Aliases.Add ("-d"); - Option disableConfigManagement = new ("--disable-cm", "Indicates Configuration Management should not be enabled."); - disableConfigManagement.AddAlias ("-dcm"); + Option disableConfigManagement = new ("--disable-cm") { Description = "Indicates Configuration Management should not be enabled." }; + disableConfigManagement.Aliases.Add ("-dcm"); - Option force16Colors = new ("--force-16-colors", "Forces the driver to use 16-color mode instead of TrueColor."); - force16Colors.AddAlias ("-16"); + Option force16Colors = new ("--force-16-colors") { Description = "Forces the driver to use 16-color mode instead of TrueColor." }; + force16Colors.Aliases.Add ("-16"); - Option benchmarkTimeout = new ("--timeout", - () => Scenario.BenchmarkTimeout, - $"The maximum time in milliseconds to run a benchmark. Default is {Scenario.BenchmarkTimeout}ms."); - benchmarkTimeout.AddAlias ("-t"); + Option benchmarkTimeout = new ("--timeout") + { + Description = $"The maximum time in milliseconds to run a benchmark. Default is {Scenario.BenchmarkTimeout}ms.", + DefaultValueFactory = _ => Scenario.BenchmarkTimeout + }; + benchmarkTimeout.Aliases.Add ("-t"); - Option resultsFile = new ("--file", "The file to save benchmark results to."); - resultsFile.AddAlias ("-f"); + Option resultsFile = new ("--file") { Description = "The file to save benchmark results to." }; + resultsFile.Aliases.Add ("-f"); LogFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}"; - Option debugLogLevel = new Option ("--debug-log-level", "The level to use for logging.").FromAmong (Enum.GetNames ()); - debugLogLevel.SetDefaultValue ("Warning"); - debugLogLevel.AddAlias ("-dl"); + Option debugLogLevel = new Option ("--debug-log-level") + { + Description = "The level to use for logging.", DefaultValueFactory = _ => "Warning" + }; + debugLogLevel.AcceptOnlyFromAmong (Enum.GetNames ()); + debugLogLevel.Aliases.Add ("-dl"); // List command Command listCommand = new ("list", "List all available scenarios"); - listCommand.SetHandler (() => - { - ObservableCollection scenarios = Scenario.GetScenarios (); + listCommand.SetAction (_ => + { + ObservableCollection scenarios = Scenario.GetScenarios (); - Console.WriteLine (@$"Available scenarios ({scenarios.Count})"); - Console.WriteLine (); + Console.WriteLine (@$"Available scenarios ({scenarios.Count})"); + Console.WriteLine (); - foreach (Scenario s in scenarios) - { - Console.WriteLine (@$" {s.GetName (),-30} {s.GetDescription ()}"); - } - }); + foreach (Scenario s in scenarios) + { + Console.WriteLine (@$" {s.GetName (),-30} {s.GetDescription ()}"); + } + }); // Run command - Argument scenarioArgument = new ("scenario", "The name of the Scenario to run."); + Argument scenarioArgument = new ("scenario") { Description = "The name of the Scenario to run." }; Command runCommand = new ("run", "Run a specific scenario") { scenarioArgument }; - runCommand.AddOption (driverOption); - runCommand.AddOption (disableConfigManagement); - runCommand.AddOption (force16Colors); - runCommand.AddOption (debugLogLevel); + runCommand.Options.Add (driverOption); + runCommand.Options.Add (disableConfigManagement); + runCommand.Options.Add (force16Colors); + runCommand.Options.Add (debugLogLevel); - runCommand.SetHandler ((scenarioName, driver, disableCm, force16, logLevel) => - { - SetupLogging (logLevel); + runCommand.SetAction (parseResult => + { + // Extract the values ​​using parseResult. + string scenarioName = parseResult.GetRequiredValue (scenarioArgument); + string driver = parseResult.GetRequiredValue (driverOption); + bool disableCm = parseResult.GetRequiredValue (disableConfigManagement); + bool force16 = parseResult.GetRequiredValue (force16Colors); + string logLevel = parseResult.GetRequiredValue (debugLogLevel); - Runner runner = new (); + // Executing the original logic + SetupLogging (logLevel); - if (!disableCm) - { - runner.SetRuntimeConfig(driver, force16 ? true : null); - ConfigurationManager.Enable (ConfigLocations.All); - } + Runner runner = new (); - Scenario? scenario = FindScenario (scenarioName); + if (!disableCm) + { + runner.SetRuntimeConfig (driver, force16 ? true : null); + ConfigurationManager.Enable (ConfigLocations.All); + } - if (scenario is null) - { - Console.Error.WriteLine ($"Scenario '{scenarioName}' not found."); + Scenario? scenario = FindScenario (scenarioName); - return; - } + if (scenario is null) + { + Console.Error.WriteLine ($"Scenario '{scenarioName}' not found."); - // Pass force16 only if explicitly set (default false means not set) - runner.RunScenario (scenarioName, false); - }, - scenarioArgument, - driverOption, - disableConfigManagement, - force16Colors, - debugLogLevel); + return; // SetAction returns void, so the empty return value works. + } + + // Pass force16 only if explicitly set (default false means not set) + runner.RunScenario (scenarioName, false); + }); // Benchmark command - Argument benchmarkScenarioArgument = - new ("scenario", () => null, "The name of the Scenario to benchmark. If not specified, all scenarios are benchmarked."); + Argument benchmarkScenarioArgument = new ("scenario") + { + Description = "The name of the Scenario to benchmark. If not specified, all scenarios are benchmarked.", DefaultValueFactory = _ => null + }; Command benchmarkCommand = new ("benchmark", "Benchmark scenarios") { benchmarkScenarioArgument }; - benchmarkCommand.AddOption (driverOption); - benchmarkCommand.AddOption (disableConfigManagement); - benchmarkCommand.AddOption (force16Colors); - benchmarkCommand.AddOption (benchmarkTimeout); - benchmarkCommand.AddOption (resultsFile); - benchmarkCommand.AddOption (debugLogLevel); - - benchmarkCommand.SetHandler ((scenarioName, driver, disableCm, force16, timeout, file, logLevel) => - { - SetupLogging (logLevel); - Scenario.BenchmarkTimeout = timeout; - - Runner runner = new (); - - if (!disableCm) - { - // Pass force16 only if explicitly set - runner.SetRuntimeConfig (driver, force16 ? true : null); - ConfigurationManager.Enable (ConfigLocations.All); - } - - List results; - - if (string.IsNullOrEmpty (scenarioName)) - { - // Benchmark all scenarios - ObservableCollection scenarios = Scenario.GetScenarios (); - results = runner.BenchmarkAllScenarios (scenarios); - } - else - { - // Benchmark single scenario - Scenario? scenario = FindScenario (scenarioName); - - if (scenario is null) - { - Console.Error.WriteLine ($"Scenario '{scenarioName}' not found."); - - return; - } - - BenchmarkResults? result = runner.RunScenario (scenarioName, true); - results = result is { } ? [result] : []; - } - - if (results.Count == 0) - { - Console.WriteLine (@"No benchmark results collected."); - - return; - } - - if (!string.IsNullOrEmpty (file)) - { - Runner.SaveResultsToFile (results, file); - Console.WriteLine (@$"Results saved to {file}"); - } - else - { - // Display in UI - Runner.DisplayResultsUI (results); - } - }, - benchmarkScenarioArgument, - driverOption, - disableConfigManagement, - force16Colors, - benchmarkTimeout, - resultsFile, - debugLogLevel); + benchmarkCommand.Options.Add (driverOption); + benchmarkCommand.Options.Add (disableConfigManagement); + benchmarkCommand.Options.Add (force16Colors); + benchmarkCommand.Options.Add (benchmarkTimeout); + benchmarkCommand.Options.Add (resultsFile); + benchmarkCommand.Options.Add (debugLogLevel); - RootCommand rootCommand = new ("Terminal.Gui Scenario Runner - Run and benchmark Terminal.Gui scenarios") { listCommand, runCommand, benchmarkCommand }; + benchmarkCommand.SetAction (parseResult => + { + // Extract the values ​​using parseResult. + string scenarioName = parseResult.GetRequiredValue (scenarioArgument); + string driver = parseResult.GetRequiredValue (driverOption); + bool disableCm = parseResult.GetRequiredValue (disableConfigManagement); + bool force16 = parseResult.GetRequiredValue (force16Colors); + uint timeout = parseResult.GetRequiredValue (benchmarkTimeout); + string file = parseResult.GetRequiredValue (resultsFile); + string logLevel = parseResult.GetRequiredValue (debugLogLevel); + + SetupLogging (logLevel); + Scenario.BenchmarkTimeout = timeout; + + Runner runner = new (); + + if (!disableCm) + { + // Pass force16 only if explicitly set + runner.SetRuntimeConfig (driver, force16 ? true : null); + ConfigurationManager.Enable (ConfigLocations.All); + } + + List results; + + if (string.IsNullOrEmpty (scenarioName)) + { + // Benchmark all scenarios + ObservableCollection scenarios = Scenario.GetScenarios (); + results = runner.BenchmarkAllScenarios (scenarios); + } + else + { + // Benchmark single scenario + Scenario? scenario = FindScenario (scenarioName); + + if (scenario is null) + { + Console.Error.WriteLine ($"Scenario '{scenarioName}' not found."); + + return; + } + + BenchmarkResults? result = runner.RunScenario (scenarioName, true); + results = result is { } ? [result] : []; + } + + if (results.Count == 0) + { + Console.WriteLine (@"No benchmark results collected."); + + return; + } + + if (!string.IsNullOrEmpty (file)) + { + Runner.SaveResultsToFile (results, file); + Console.WriteLine (@$"Results saved to {file}"); + } + else + { + // Display in UI + Runner.DisplayResultsUI (results); + } + }); - Parser parser = new CommandLineBuilder (rootCommand).UseDefaults ().Build (); + RootCommand rootCommand = new ("Terminal.Gui Scenario Runner - Run and benchmark Terminal.Gui scenarios") { listCommand, runCommand, benchmarkCommand }; - return parser.Invoke (args); + return rootCommand.Parse (args).Invoke (); } private static Scenario? FindScenario (string name) diff --git a/Examples/UICatalog/Scenarios/Editor.cs b/Examples/UICatalog/Scenarios/Editor.cs index 602184cd2a..2569539709 100644 --- a/Examples/UICatalog/Scenarios/Editor.cs +++ b/Examples/UICatalog/Scenarios/Editor.cs @@ -107,7 +107,7 @@ public override void Main () Title = "ForceMinimumPosTo_Zero", Value = _forceMinimumPosToZero ? CheckState.Checked : CheckState.UnChecked }; - _miForceMinimumPosToZeroCheckBox.ValueChanging += (s, e) => + _miForceMinimumPosToZeroCheckBox.ValueChanged += (s, e) => { _forceMinimumPosToZero = e.NewValue == CheckState.Checked; @@ -143,8 +143,7 @@ public override void Main () { if (!e.Value) { - // BUGBUG: This should restore the original culture info - Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; } }; @@ -304,7 +303,7 @@ void CreateCultureMenuItem (string title, string cultureName, bool isChecked) allCheckBoxes.Add (checkBox); - checkBox.ValueChanging += (s, e) => + checkBox.ValueChanged += (s, e) => { if (e.NewValue == CheckState.Checked) { @@ -744,14 +743,14 @@ private View CreateFindTab () { X = 0, Y = Pos.Top (txtToFind) + 2, Value = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase" }; - ckbMatchCase.ValueChanging += (s, e) => { _matchCase = e.NewValue == CheckState.Checked; }; + ckbMatchCase.ValueChanged += (s, e) => { _matchCase = e.NewValue == CheckState.Checked; }; d.Add (ckbMatchCase); CheckBox ckbMatchWholeWord = new () { X = 0, Y = Pos.Top (ckbMatchCase) + 1, Value = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word" }; - ckbMatchWholeWord.ValueChanging += (s, e) => { _matchWholeWord = e.NewValue == CheckState.Checked; }; + ckbMatchWholeWord.ValueChanged += (s, e) => { _matchWholeWord = e.NewValue == CheckState.Checked; }; d.Add (ckbMatchWholeWord); return d; @@ -824,14 +823,14 @@ private View CreateReplaceTab () { X = 0, Y = Pos.Top (txtToFind) + 2, Value = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase" }; - ckbMatchCase.ValueChanging += (s, e) => { _matchCase = e.NewValue == CheckState.Checked; }; + ckbMatchCase.ValueChanged += (s, e) => { _matchCase = e.NewValue == CheckState.Checked; }; d.Add (ckbMatchCase); CheckBox ckbMatchWholeWord = new () { X = 0, Y = Pos.Top (ckbMatchCase) + 1, Value = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word" }; - ckbMatchWholeWord.ValueChanging += (s, e) => { _matchWholeWord = e.NewValue == CheckState.Checked; }; + ckbMatchWholeWord.ValueChanged += (s, e) => { _matchWholeWord = e.NewValue == CheckState.Checked; }; d.Add (ckbMatchWholeWord); return d; diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs index c6f32830eb..b6a5e2bbe6 100644 --- a/Examples/UICatalog/UICatalog.cs +++ b/Examples/UICatalog/UICatalog.cs @@ -10,9 +10,8 @@ global using Terminal.Gui.Text; global using Terminal.Gui.FileServices; global using Terminal.Gui.Resources; -global using Terminal.Gui.Tracing; using System.CommandLine; -using System.CommandLine.Builder; +using System.CommandLine.Help; using System.CommandLine.Parsing; using System.Diagnostics; using System.Globalization; @@ -81,60 +80,77 @@ private static int Main (string [] args) // Get allowed driver names string? [] allowedDrivers = DriverRegistry.GetDriverNames ().ToArray (); - Option driverOption = new Option ("--driver", "The IDriver to use.").FromAmong (allowedDrivers!); - driverOption.SetDefaultValue (string.Empty); - driverOption.AddAlias ("-d"); - driverOption.AddAlias ("--d"); + Option driverOption = new ("--driver") + { + Description = "The IDriver to use." + }; + driverOption.AcceptOnlyFromAmong (allowedDrivers!); + driverOption.DefaultValueFactory = _ => string.Empty; + driverOption.Aliases.Add ("-d"); + driverOption.Aliases.Add ("--d"); // Add validator separately (not chained) - driverOption.AddValidator (result => + driverOption.Validators.Add (result => { var value = result.GetValueOrDefault (); if (result.Tokens.Count > 0 && !allowedDrivers.Contains (value)) { - result.ErrorMessage = $"Invalid driver name '{value}'. Allowed values: {string.Join (", ", allowedDrivers)}"; + result.AddError ($"Invalid driver name '{value}'. Allowed values: {string.Join (", ", allowedDrivers)}"); } }); // Configuration Management - Option disableConfigManagement = new ("--disable-cm", - "Indicates Configuration Management should not be enabled. Only `ConfigLocations.HardCoded` settings will be loaded."); - disableConfigManagement.AddAlias ("-dcm"); - disableConfigManagement.AddAlias ("--dcm"); + Option disableConfigManagement = new ("--disable-cm") + { + Description = "Indicates Configuration Management should not be enabled. Only `ConfigLocations.HardCoded` settings will be loaded." + }; + disableConfigManagement.Aliases.Add ("-dcm"); + disableConfigManagement.Aliases.Add ("--dcm"); - Option benchmarkFlag = new ("--benchmark", "Enables benchmarking. If a Scenario is specified, just that Scenario will be benchmarked."); - benchmarkFlag.AddAlias ("-b"); - benchmarkFlag.AddAlias ("--b"); + Option benchmarkFlag = new ("--benchmark") + { + Description = "Enables benchmarking. If a Scenario is specified, just that Scenario will be benchmarked." + }; + benchmarkFlag.Aliases.Add ("-b"); + benchmarkFlag.Aliases.Add ("--b"); - Option force16ColorsOption = new ("--force-16-colors", "Forces the driver to use 16-color mode instead of TrueColor."); - force16ColorsOption.AddAlias ("-16"); + Option force16ColorsOption = new ("--force-16-colors") { Description = "Forces the driver to use 16-color mode instead of TrueColor." }; + force16ColorsOption.Aliases.Add ("-16"); - Option benchmarkTimeout = new ("--timeout", - () => Scenario.BenchmarkTimeout, - $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms."); - benchmarkTimeout.AddAlias ("-t"); - benchmarkTimeout.AddAlias ("--t"); + Option benchmarkTimeout = new ("--timeout") + { + Description = $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms.", + DefaultValueFactory = _ => Scenario.BenchmarkTimeout + }; + benchmarkTimeout.Aliases.Add ("-t"); + benchmarkTimeout.Aliases.Add ("--t"); - Option resultsFile = new ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView."); - resultsFile.AddAlias ("-f"); - resultsFile.AddAlias ("--f"); + Option resultsFile = new ("--file") + { + Description = "The file to save benchmark results to. If not specified, the results will be displayed in a TableView.", + DefaultValueFactory = _ => string.Empty + }; + resultsFile.Aliases.Add ("-f"); + resultsFile.Aliases.Add ("--f"); // what's the app name? LogFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}.log"; - Option debugLogLevel = - new Option ("--debug-log-level", $"The level to use for logging (debug console and {LogFilePath})").FromAmong (Enum.GetNames ()); - debugLogLevel.SetDefaultValue ("Warning"); - debugLogLevel.AddAlias ("-dl"); - debugLogLevel.AddAlias ("--dl"); + Option debugLogLevel = new ("--debug-log-level") + { + Description = $"The level to use for logging (debug console and {LogFilePath})" + }; + debugLogLevel.AcceptOnlyFromAmong (Enum.GetNames ()); + debugLogLevel.DefaultValueFactory = _ => "Warning"; + debugLogLevel.Aliases.Add ("-dl"); + debugLogLevel.Aliases.Add ("--dl"); - Argument scenarioArgument = - new Argument ("scenario", - description: "The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.", - getDefaultValue: () => "none").FromAmong (UICatalogRunnable.CachedScenarios.Select (s => s.GetName ()) - .Append ("none") - .ToArray ()); + Argument scenarioArgument = new ("scenario") + { + Description = "The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.", DefaultValueFactory = _ => "none" + }; + scenarioArgument.AcceptOnlyFromAmong (UICatalogRunnable.CachedScenarios.Select (s => s.GetName ()).Append ("none").ToArray ()); var rootCommand = new RootCommand ("A comprehensive sample library and test app for Terminal.Gui") { @@ -148,19 +164,19 @@ private static int Main (string [] args) force16ColorsOption }; - rootCommand.SetHandler (context => + rootCommand.SetAction (context => { - bool force16 = context.ParseResult.GetValueForOption (force16ColorsOption); + bool force16 = context.GetRequiredValue (force16ColorsOption); UICatalogCommandLineOptions options = new () { - Scenario = context.ParseResult.GetValueForArgument (scenarioArgument), - Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty, - DontEnableConfigurationManagement = context.ParseResult.GetValueForOption (disableConfigManagement), - Benchmark = context.ParseResult.GetValueForOption (benchmarkFlag), - BenchmarkTimeout = context.ParseResult.GetValueForOption (benchmarkTimeout), - ResultsFile = context.ParseResult.GetValueForOption (resultsFile) ?? string.Empty, - DebugLogLevel = context.ParseResult.GetValueForOption (debugLogLevel) ?? "Warning", + Scenario = context.GetRequiredValue (scenarioArgument), + Driver = context.GetRequiredValue (driverOption) ?? string.Empty, + DontEnableConfigurationManagement = context.GetRequiredValue (disableConfigManagement), + Benchmark = context.GetRequiredValue (benchmarkFlag), + BenchmarkTimeout = context.GetRequiredValue (benchmarkTimeout), + ResultsFile = context.GetRequiredValue (resultsFile) ?? string.Empty, + DebugLogLevel = context.GetRequiredValue (debugLogLevel) ?? "Warning", // Only set Force16Colors if explicitly specified on command line Force16Colors = force16 ? true : null @@ -172,17 +188,21 @@ private static int Main (string [] args) var helpShown = false; - Parser parser = new CommandLineBuilder (rootCommand).UseHelp (_ => helpShown = true).Build (); + ParseResult parseResult = rootCommand.Parse (args); - parser.Invoke (args); + // Check if the analysis results indicate that help should be displayed + if (parseResult.Errors.Count == 0 && parseResult.Action is HelpAction) + { + helpShown = true; + } + + parseResult.Invoke (); if (helpShown) { return 0; } - ParseResult parseResult = parser.Parse (args); - if (parseResult.Errors.Count > 0) { foreach (ParseError error in parseResult.Errors) diff --git a/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs b/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs index 70c471d744..07f431dfd1 100644 --- a/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs +++ b/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs @@ -31,6 +31,11 @@ public static (int exitCode, string result) Process ( { var output = string.Empty; + if (Console.IsInputRedirected || Console.IsOutputRedirected) + { + return (-1, output); + } + using var process = new Process (); process.StartInfo = new() diff --git a/Terminal.Gui/Drivers/Input/IInput.cs b/Terminal.Gui/Drivers/Input/IInput.cs index 5ad1f6b690..b5a6f96658 100644 --- a/Terminal.Gui/Drivers/Input/IInput.cs +++ b/Terminal.Gui/Drivers/Input/IInput.cs @@ -66,7 +66,7 @@ public interface IInput : IDisposable /// /// /// - /// This property allows external code (e.g., test harnesses like TestContext) to + /// This property allows external code (e.g., test harnesses like AppTestHelper) to /// provide additional cancellation signals such as timeouts or hard-stop conditions. /// /// diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs b/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs index 6562f3b2dc..acfc35b36a 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs @@ -8,7 +8,7 @@ namespace Terminal.Gui.Drivers; internal class UnixClipboard : ClipboardBase { private string _xclipPath = string.Empty; - public UnixClipboard () { IsSupported = CheckSupport (); } + public UnixClipboard () => IsSupported = CheckSupport (); public override bool IsSupported { get; } protected override string GetClipboardDataImpl () diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 765c46305d..544579f229 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -87,7 +87,7 @@ - + diff --git a/Terminal.sln b/Terminal.sln index ff01131635..74d32e37af 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -113,11 +113,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTests", "Tests\Stress EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", "Tests\UnitTestsParallelizable\UnitTests.Parallelizable.csproj", "{DE780834-190A-8277-51FD-750CC666E82D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting", "Tests\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj", "{2DBA7BDC-17AE-474B-A507-00807D087607}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppTestHelpers", "Tests\AppTestHelpers\AppTestHelpers.csproj", "{2DBA7BDC-17AE-474B-A507-00807D087607}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTestingXunit", "Tests\TerminalGuiFluentTestingXunit\TerminalGuiFluentTestingXunit.csproj", "{F56BAFFD-F227-4B0A-96F0-C800FAEF2036}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppTestHelpers.XunitHelpers", "Tests\AppTestHelpers.XunitHelpers\AppTestHelpers.XunitHelpers.csproj", "{F56BAFFD-F227-4B0A-96F0-C800FAEF2036}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTestingXunit.Generator", "Tests\TerminalGuiFluentTestingXunit.Generator\TerminalGuiFluentTestingXunit.Generator.csproj", "{199F27D8-A905-4DDC-82CA-1FE1A90B1788}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppTestHelpers.XunitHelpers.Generator", "Tests\AppTestHelpers.XunitHelpers.Generator\AppTestHelpers.XunitHelpers.Generator.csproj", "{199F27D8-A905-4DDC-82CA-1FE1A90B1788}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{1A3CBA89-EDAB-4F75-811E-FE81B13A4836}" ProjectSection(SolutionItems) = preProject diff --git a/Tests/TerminalGuiFluentTestingXunit.Generator/TerminalGuiFluentTestingXunit.Generator.csproj b/Tests/AppTestHelpers.XunitHelpers.Generator/AppTestHelpers.XunitHelpers.Generator.csproj similarity index 100% rename from Tests/TerminalGuiFluentTestingXunit.Generator/TerminalGuiFluentTestingXunit.Generator.csproj rename to Tests/AppTestHelpers.XunitHelpers.Generator/AppTestHelpers.XunitHelpers.Generator.csproj diff --git a/Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs b/Tests/AppTestHelpers.XunitHelpers.Generator/TheGenerator.cs similarity index 93% rename from Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs rename to Tests/AppTestHelpers.XunitHelpers.Generator/TheGenerator.cs index 45e74fc293..2f4859e6dc 100644 --- a/Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs +++ b/Tests/AppTestHelpers.XunitHelpers.Generator/TheGenerator.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace TerminalGuiFluentTestingXunit.Generator; +namespace AppTestHelpers.XunitHelpers.Generator; [Generator] public class TheGenerator : IIncrementalGenerator @@ -89,10 +89,10 @@ private void GenerateMethods (INamedTypeSymbol assertType, SourceProductionConte var header = """" #nullable enable - using TerminalGuiFluentTesting; + using AppTestHelpers; using Xunit; - namespace TerminalGuiFluentTestingXunit; + namespace AppTestHelpers.XunitHelpers; public static partial class XunitContextExtensions { @@ -117,26 +117,26 @@ public static partial class XunitContextExtensions } var method = $$""" - {{signature}} - { - try + {{signature}} { - Assert.{{methodName}}{{typeParams}} ({{string.Join (",", paramNames)}}); - } - catch(Exception ex) - { - context.HardStop (ex); + try + { + Xunit.Assert.{{methodName}}{{typeParams}} ({{string.Join (",", paramNames)}}); + } + catch(Exception ex) + { + context.HardStop (ex); + + + throw; + + } - - throw; - + return context; } - - return context; - } """; - sb.AppendLine (method); + sb.AppendLine (method.Replace ("*", "")); } sb.AppendLine (tail); @@ -154,9 +154,9 @@ out string typeParams { typeParams = string.Empty; - // Create the "this TestContext context" parameter + // Create the "this AppTestHelper context" parameter ParameterSyntax contextParam = SyntaxFactory.Parameter (SyntaxFactory.Identifier ("context")) - .WithType (SyntaxFactory.ParseTypeName ("TestContext")) + .WithType (SyntaxFactory.ParseTypeName ("AppTestHelper")) .AddModifiers (SyntaxFactory.Token (SyntaxKind.ThisKeyword)); // Add the "this" keyword // Extract the parameter names (expected and actual) @@ -182,8 +182,8 @@ out string typeParams parameters.Insert (0, contextParam); // Insert 'context' as the first parameter - // Change the return type to TestContext - TypeSyntax returnType = SyntaxFactory.ParseTypeName ("TestContext"); + // Change the return type to AppTestHelper + TypeSyntax returnType = SyntaxFactory.ParseTypeName ("AppTestHelper"); // Change the method name to AssertEqual SyntaxToken newMethodName = SyntaxFactory.Identifier ($"Assert{methodName}"); diff --git a/Tests/TerminalGuiFluentTestingXunit/TerminalGuiFluentTestingXunit.csproj b/Tests/AppTestHelpers.XunitHelpers/AppTestHelpers.XunitHelpers.csproj similarity index 50% rename from Tests/TerminalGuiFluentTestingXunit/TerminalGuiFluentTestingXunit.csproj rename to Tests/AppTestHelpers.XunitHelpers/AppTestHelpers.XunitHelpers.csproj index 31e008633a..6e783b82c5 100644 --- a/Tests/TerminalGuiFluentTestingXunit/TerminalGuiFluentTestingXunit.csproj +++ b/Tests/AppTestHelpers.XunitHelpers/AppTestHelpers.XunitHelpers.csproj @@ -1,6 +1,8 @@  + Exe + true net10.0 enable enable @@ -9,11 +11,9 @@ - - - - - + + + diff --git a/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs b/Tests/AppTestHelpers.XunitHelpers/XunitContextExtensions.cs similarity index 77% rename from Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs rename to Tests/AppTestHelpers.XunitHelpers/XunitContextExtensions.cs index 71755ce035..3318240847 100644 --- a/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs +++ b/Tests/AppTestHelpers.XunitHelpers/XunitContextExtensions.cs @@ -1,8 +1,7 @@ using System.Drawing; -using TerminalGuiFluentTesting; using Xunit; -namespace TerminalGuiFluentTestingXunit; +namespace AppTestHelpers.XunitHelpers; public static partial class XunitContextExtensions { @@ -15,7 +14,7 @@ public static partial class XunitContextExtensions /// /// /// - public static TestContext AssertCursorPosition (this TestContext context, Point expected) + public static AppTestHelper AssertCursorPosition (this AppTestHelper context, Point expected) { try { diff --git a/Tests/TerminalGuiFluentTesting/TestContext.ContextMenu.cs b/Tests/AppTestHelpers/AppTestHelper.ContextMenu.cs similarity index 90% rename from Tests/TerminalGuiFluentTesting/TestContext.ContextMenu.cs rename to Tests/AppTestHelpers/AppTestHelper.ContextMenu.cs index a20cf5ce92..f440f4bb16 100644 --- a/Tests/TerminalGuiFluentTesting/TestContext.ContextMenu.cs +++ b/Tests/AppTestHelpers/AppTestHelper.ContextMenu.cs @@ -2,9 +2,9 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; -public partial class TestContext +public partial class AppTestHelper { /// /// Registers a right click handler on the added view (or root view) that @@ -12,7 +12,7 @@ public partial class TestContext /// /// /// - public TestContext WithContextMenu (PopoverMenu? contextMenu) + public AppTestHelper WithContextMenu (PopoverMenu? contextMenu) { if (contextMenu?.App is null) { diff --git a/Tests/TerminalGuiFluentTesting/TestContext.Input.cs b/Tests/AppTestHelpers/AppTestHelper.Input.cs similarity index 89% rename from Tests/TerminalGuiFluentTesting/TestContext.Input.cs rename to Tests/AppTestHelpers/AppTestHelper.Input.cs index 6a151c98f1..5f54506f2e 100644 --- a/Tests/TerminalGuiFluentTesting/TestContext.Input.cs +++ b/Tests/AppTestHelpers/AppTestHelper.Input.cs @@ -4,9 +4,9 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; -public partial class TestContext +public partial class AppTestHelper { /// /// Simulates a right click at the given screen coordinates on the current driver. @@ -16,7 +16,7 @@ public partial class TestContext /// 0 indexed screen coordinates /// 0 indexed screen coordinates /// - public TestContext RightClick (int screenX, int screenY) + public AppTestHelper RightClick (int screenX, int screenY) { InjectMouseEvent (new Mouse { @@ -40,7 +40,7 @@ public TestContext RightClick (int screenX, int screenY) /// 0 indexed screen coordinates /// 0 indexed screen coordinates /// - public TestContext LeftClick (int screenX, int screenY) + public AppTestHelper LeftClick (int screenX, int screenY) { InjectMouseEvent (new Mouse { @@ -63,7 +63,7 @@ public TestContext LeftClick (int screenX, int screenY) /// /// /// - public TestContext LeftClick (Func evaluator) where TView : View => + public AppTestHelper LeftClick (Func evaluator) where TView : View => InjectMouseEvent (new Mouse { Flags = MouseFlags.LeftButtonClicked }, evaluator); /// @@ -75,8 +75,8 @@ public TestContext LeftClick (Func evaluator) where TView : /// Whether to advance time after this event to space clicks apart (prevents multi-click /// detection). /// - /// This TestContext for fluent chaining. - private TestContext InjectMouseEvent (Mouse mouse, bool advanceTimeAfter = false) + /// This AppTestHelper for fluent chaining. + private AppTestHelper InjectMouseEvent (Mouse mouse, bool advanceTimeAfter = false) { // Use the new injection infrastructure WaitIteration (app => @@ -116,12 +116,12 @@ private TestContext InjectMouseEvent (Mouse mouse, bool advanceTimeAfter = false /// /// The mouse event to inject. /// Function to find the target view. - /// This TestContext for fluent chaining. - private TestContext InjectMouseEvent (Mouse mouse, Func evaluator) where TView : View + /// This AppTestHelper for fluent chaining. + private AppTestHelper InjectMouseEvent (Mouse mouse, Func evaluator) where TView : View { var screen = Point.Empty; - TestContext ctx = WaitIteration (_ => + AppTestHelper ctx = WaitIteration (_ => { TView v = Find (evaluator); screen = v.ViewportToScreen (new Point (0, 0)); @@ -139,8 +139,8 @@ private TestContext InjectMouseEvent (Mouse mouse, Func eval /// Uses the new simplified injection API with virtual time support. /// /// The key to inject. - /// This TestContext for fluent chaining. - public TestContext KeyDown (Key key) + /// This AppTestHelper for fluent chaining. + public AppTestHelper KeyDown (Key key) { //Logging.Trace ($"Injecting key: {key}"); diff --git a/Tests/TerminalGuiFluentTesting/TestContext.Navigation.cs b/Tests/AppTestHelpers/AppTestHelper.Navigation.cs similarity index 93% rename from Tests/TerminalGuiFluentTesting/TestContext.Navigation.cs rename to Tests/AppTestHelpers/AppTestHelper.Navigation.cs index 2e3a82bfc2..27ce8d5c6f 100644 --- a/Tests/TerminalGuiFluentTesting/TestContext.Navigation.cs +++ b/Tests/AppTestHelpers/AppTestHelper.Navigation.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; -public partial class TestContext +public partial class AppTestHelper { /// /// Sets the input focus to the given . @@ -13,7 +13,7 @@ public partial class TestContext /// /// /// - public TestContext Focus (View toFocus) + public AppTestHelper Focus (View toFocus) { toFocus.FocusDeepest (NavigationDirection.Forward, TabBehavior.TabStop); @@ -37,7 +37,7 @@ public TestContext Focus (View toFocus) /// /// /// - public TestContext Focus (Func? evaluator = null) where T : View + public AppTestHelper Focus (Func? evaluator = null) where T : View { evaluator ??= _ => true; View? t = App?.TopRunnableView; diff --git a/Tests/TerminalGuiFluentTesting/TestContext.ViewBase.cs b/Tests/AppTestHelpers/AppTestHelper.ViewBase.cs similarity index 95% rename from Tests/TerminalGuiFluentTesting/TestContext.ViewBase.cs rename to Tests/AppTestHelpers/AppTestHelper.ViewBase.cs index 549737ea12..f9d2deeb81 100644 --- a/Tests/TerminalGuiFluentTesting/TestContext.ViewBase.cs +++ b/Tests/AppTestHelpers/AppTestHelper.ViewBase.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; -public partial class TestContext +public partial class AppTestHelper { /// /// Adds the given to the current top level view @@ -10,7 +10,7 @@ public partial class TestContext /// /// /// - public TestContext Add (View v) + public AppTestHelper Add (View v) { WaitIteration ((app) => { diff --git a/Tests/TerminalGuiFluentTesting/TestContext.cs b/Tests/AppTestHelpers/AppTestHelper.cs similarity index 94% rename from Tests/TerminalGuiFluentTesting/TestContext.cs rename to Tests/AppTestHelpers/AppTestHelper.cs index 4814958d62..a7d25954e2 100644 --- a/Tests/TerminalGuiFluentTesting/TestContext.cs +++ b/Tests/AppTestHelpers/AppTestHelper.cs @@ -6,13 +6,13 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; /// -/// Fluent API context for testing a Terminal.Gui application. Create +/// Helper for building integration tests for Terminal.Gui applications. Create /// an instance using static class. /// -public partial class TestContext : IDisposable +public partial class AppTestHelper : IDisposable { // ===== Threading & Synchronization ===== private readonly CancellationTokenSource _runCancellationTokenSource = new (); @@ -60,7 +60,7 @@ public partial class TestContext : IDisposable /// Constructor for tests that only need Application.Init without running the main loop. /// Uses the driver's default screen size instead of forcing a specific size. /// - public TestContext (string driverName, TextWriter? logWriter = null, TimeSpan? timeout = null) + public AppTestHelper (string driverName, TextWriter? logWriter = null, TimeSpan? timeout = null) { _driverName = driverName; _logWriter = logWriter; @@ -110,7 +110,7 @@ public TestContext (string driverName, TextWriter? logWriter = null, TimeSpan? t /// /// Constructor for tests that need to run the application with Application.Run. /// - internal TestContext (Func runnableBuilder, int width, int height, string driverName, TextWriter? logWriter = null, TimeSpan? timeout = null) + internal AppTestHelper (Func runnableBuilder, int width, int height, string driverName, TextWriter? logWriter = null, TimeSpan? timeout = null) { _driverName = driverName; _logWriter = logWriter; @@ -283,7 +283,7 @@ private void CommonInit (int width, int height, TimeSpan? timeout) /// /// /// - public TestContext Then (Action doAction) + public AppTestHelper Then (Action doAction) { try { @@ -307,7 +307,7 @@ public TestContext Then (Action doAction) /// /// /// - public TestContext WaitIteration (Action? action = null) + public AppTestHelper WaitIteration (Action? action = null) { // If application has already exited don't wait! if (Finished || _runCancellationTokenSource.Token.IsCancellationRequested || _ansiInput.ExternalCancellationTokenSource!.Token.IsCancellationRequested) @@ -355,9 +355,9 @@ public TestContext WaitIteration (Action? action = null) return this; } - public TestContext WaitUntil (Func condition) + public AppTestHelper WaitUntil (Func condition) { - TestContext? c = null; + AppTestHelper? c = null; var sw = Stopwatch.StartNew (); //Logging.Trace ($"WaitUntil started with timeout {_timeout}"); @@ -397,9 +397,9 @@ public TestContext WaitUntil (Func condition) /// new Width for the console. /// new Height for the console. /// - public TestContext ResizeConsole (int width, int height) => WaitIteration (app => { app.Driver!.SetScreenSize (width, height); }); + public AppTestHelper ResizeConsole (int width, int height) => WaitIteration (app => { app.Driver!.SetScreenSize (width, height); }); - public TestContext ScreenShot (string title, TextWriter? writer) => + public AppTestHelper ScreenShot (string title, TextWriter? writer) => //Logging.Trace ($"{this.ToIdentifyingString ()}"); WaitIteration (app => @@ -410,7 +410,7 @@ public TestContext ScreenShot (string title, TextWriter? writer) => writer?.WriteLine (text); }); - public TestContext AnsiScreenShot (string title, TextWriter? writer) => + public AppTestHelper AnsiScreenShot (string title, TextWriter? writer) => //Logging.Trace ($"{this.ToIdentifyingString ()}"); WaitIteration (app => @@ -424,7 +424,7 @@ public TestContext AnsiScreenShot (string title, TextWriter? writer) => /// /// Stops the application and waits for the background thread to exit. /// - public TestContext Stop () + public AppTestHelper Stop () { Logging.Trace ($"Stopping application for driver: {_driverName}"); @@ -513,7 +513,7 @@ public void HardStop (Exception? ex = null) /// /// /// - public TestContext WriteOutLogs (TextWriter? writer) + public AppTestHelper WriteOutLogs (TextWriter? writer) { if (writer is null) { @@ -554,7 +554,7 @@ private void CleanupApplication () /// public void Dispose () { - //Logging.Trace ($"Disposing TestContext"); + //Logging.Trace ($"Disposing AppTestHelper"); Stop (); var shouldThrow = false; diff --git a/Tests/TerminalGuiFluentTesting/TerminalGuiFluentTesting.csproj b/Tests/AppTestHelpers/AppTestHelpers.csproj similarity index 100% rename from Tests/TerminalGuiFluentTesting/TerminalGuiFluentTesting.csproj rename to Tests/AppTestHelpers/AppTestHelpers.csproj diff --git a/Tests/TerminalGuiFluentTesting/AssemblyInfo.cs b/Tests/AppTestHelpers/AssemblyInfo.cs similarity index 100% rename from Tests/TerminalGuiFluentTesting/AssemblyInfo.cs rename to Tests/AppTestHelpers/AssemblyInfo.cs diff --git a/Tests/TerminalGuiFluentTesting/TestDriver.cs b/Tests/AppTestHelpers/TestDriver.cs similarity index 93% rename from Tests/TerminalGuiFluentTesting/TestDriver.cs rename to Tests/AppTestHelpers/TestDriver.cs index f67b9e66a1..ac1e25189d 100644 --- a/Tests/TerminalGuiFluentTesting/TestDriver.cs +++ b/Tests/AppTestHelpers/TestDriver.cs @@ -1,4 +1,4 @@ -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; /// /// Which driver simulation should be used for testing diff --git a/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs b/Tests/AppTestHelpers/TextWriterLogger.cs similarity index 93% rename from Tests/TerminalGuiFluentTesting/TextWriterLogger.cs rename to Tests/AppTestHelpers/TextWriterLogger.cs index 39c39266bb..60dda5f01e 100644 --- a/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs +++ b/Tests/AppTestHelpers/TextWriterLogger.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; internal class TextWriterLogger (TextWriter writer) : ILogger { diff --git a/Tests/TerminalGuiFluentTesting/TextWriterLoggerProvider.cs b/Tests/AppTestHelpers/TextWriterLoggerProvider.cs similarity index 88% rename from Tests/TerminalGuiFluentTesting/TextWriterLoggerProvider.cs rename to Tests/AppTestHelpers/TextWriterLoggerProvider.cs index 2a6628329c..d6ea91a0b8 100644 --- a/Tests/TerminalGuiFluentTesting/TextWriterLoggerProvider.cs +++ b/Tests/AppTestHelpers/TextWriterLoggerProvider.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; internal class TextWriterLoggerProvider (TextWriter writer) : ILoggerProvider { diff --git a/Tests/TerminalGuiFluentTesting/ThreadSafeStringWriter.cs b/Tests/AppTestHelpers/ThreadSafeStringWriter.cs similarity index 93% rename from Tests/TerminalGuiFluentTesting/ThreadSafeStringWriter.cs rename to Tests/AppTestHelpers/ThreadSafeStringWriter.cs index f9d06743f2..2edebb7282 100644 --- a/Tests/TerminalGuiFluentTesting/ThreadSafeStringWriter.cs +++ b/Tests/AppTestHelpers/ThreadSafeStringWriter.cs @@ -1,6 +1,6 @@ using System.Text; -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; class ThreadSafeStringWriter : StringWriter { diff --git a/Tests/TerminalGuiFluentTesting/With.cs b/Tests/AppTestHelpers/With.cs similarity index 80% rename from Tests/TerminalGuiFluentTesting/With.cs rename to Tests/AppTestHelpers/With.cs index c55384f287..acfeac4274 100644 --- a/Tests/TerminalGuiFluentTesting/With.cs +++ b/Tests/AppTestHelpers/With.cs @@ -1,5 +1,5 @@  -namespace TerminalGuiFluentTesting; +namespace AppTestHelpers; /// /// Entry point to fluent assertions. @@ -14,7 +14,7 @@ public static class With /// /// /// - public static TestContext A (int width, int height, string driverName, TextWriter? logWriter = null) where T : IRunnable, new() + public static AppTestHelper A (int width, int height, string driverName, TextWriter? logWriter = null) where T : IRunnable, new() { return new (() => new T () { @@ -32,7 +32,7 @@ public static class With /// /// /// - public static TestContext A (Func runnableFactory, int width, int height, string driverName, TextWriter? logWriter = null) + public static AppTestHelper A (Func runnableFactory, int width, int height, string driverName, TextWriter? logWriter = null) { return new (runnableFactory, width, height, driverName, logWriter, Timeout); } diff --git a/Tests/IntegrationTests/DialogTests.cs b/Tests/IntegrationTests/DialogTests.cs index e8767f58bc..67ebf4b2bb 100644 --- a/Tests/IntegrationTests/DialogTests.cs +++ b/Tests/IntegrationTests/DialogTests.cs @@ -1,7 +1,4 @@ #nullable enable -using UnitTests; -using Xunit.Abstractions; - namespace IntegrationTests; public class DialogTests diff --git a/Tests/IntegrationTests/FluentTests/FileDialogTests.cs b/Tests/IntegrationTests/FluentTests/FileDialogTests.cs index e456d90bed..9580a79af4 100644 --- a/Tests/IntegrationTests/FluentTests/FileDialogTests.cs +++ b/Tests/IntegrationTests/FluentTests/FileDialogTests.cs @@ -3,9 +3,8 @@ using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using System.Runtime.InteropServices; -using TerminalGuiFluentTesting; -using TerminalGuiFluentTestingXunit; -using Xunit.Abstractions; +using AppTestHelpers; +using AppTestHelpers.XunitHelpers; namespace IntegrationTests; @@ -58,7 +57,7 @@ public void CancelFileDialog_QuitKey_Quits (string d) { SaveDialog? sd = null; - using TestContext c = With.A (() => NewSaveDialog (out sd), 100, 20, d, _out) + using AppTestHelper c = With.A (() => NewSaveDialog (out sd), 100, 20, d, _out) .ScreenShot ("Save dialog", _out) .KeyDown (Application.QuitKey) .AssertTrue (sd!.Canceled); @@ -70,7 +69,7 @@ public void CancelFileDialog_UsingCancelButton_TabThenEnter (string d) { SaveDialog? sd = null; - using TestContext c = With.A (() => NewSaveDialog (out sd), 100, 20, d) + using AppTestHelper c = With.A (() => NewSaveDialog (out sd), 100, 20, d) .ScreenShot ("Save dialog", _out) .Focus