diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
new file mode 100644
index 000000000..586c7cb15
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,34 @@
+name: Ensure that Release builds are not broken
+
+on:
+ push:
+ branches: [ v2_release, v2_develop ]
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches: [ v2_release, v2_develop ]
+ paths-ignore:
+ - '**.md'
+
+jobs:
+ build_release:
+ # Ensure that RELEASE builds are not broken
+ runs-on: ubuntu-latest
+ 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: Build Release Terminal.Gui
+ run: dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release
+
+ - name: Pack Release Terminal.Gui
+ run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages
+
+ - name: Build Release Solution
+ run: dotnet build --configuration Release
diff --git a/.github/workflows/check-duplicates.yml b/.github/workflows/check-duplicates.yml
new file mode 100644
index 000000000..f325d3f95
--- /dev/null
+++ b/.github/workflows/check-duplicates.yml
@@ -0,0 +1,14 @@
+name: Check for Duplicate UnitTests
+on:
+ push:
+ branches: [ v2_release, v2_develop ]
+ pull_request:
+ branches: [ v2_release, v2_develop ]
+ workflow_dispatch:
+jobs:
+ check-duplicates:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Run Duplicate Test Check
+ run: pwsh -File ./Scripts/FindDuplicateTestMethodsInSameFileName.ps1 -solutionPath "$PWD"
\ No newline at end of file
diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
deleted file mode 100644
index d22eefcfb..000000000
--- a/.github/workflows/dotnet-core.yml
+++ /dev/null
@@ -1,119 +0,0 @@
-name: Build & Test Terminal.Gui with .NET Core
-
-on:
- push:
- branches: [ v2_release, v2_develop ]
- paths-ignore:
- - '**.md'
- pull_request:
- branches: [ v2_release, v2_develop ]
- paths-ignore:
- - '**.md'
-
-jobs:
- build_and_test_debug:
-
- runs-on: ${{ matrix.os }}
- strategy:
- # Turn off fail-fast to let all runners run even if there are errors
- fail-fast: true
- matrix:
- os: [ ubuntu-latest, windows-latest, macos-latest ]
-
- timeout-minutes: 10
- steps:
-
-# Build (Debug)
-
- - 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: Install dependencies
- run: |
- dotnet restore
-
- - name: Build Debug
- run: dotnet build --configuration Debug --no-restore
-
-# Test
- # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
- # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
- # Without it, the test runner crashes on ubuntu (but not Windows or mac)
-
- - name: MacOS - Patch test runner settings to stop on fail
- if: runner.os == 'macOS'
- run: |
- brew install gnu-sed
- gsed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
-
- - name: Windows/Linux - Patch test runner settings to stop on fail
- if: runner.os != 'macOS'
- run: |
- sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
-
- - name: Set VSTEST_DUMP_PATH
- shell: bash
- run: echo "{VSTEST_DUMP_PATH}={logs/${{ runner.os }}/}" >> $GITHUB_ENV
-
- - name: Test
- run: |
- dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --diag:logs/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always
-
- # mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
-
- - name: Upload Test Logs
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: test-logs-${{ runner.os }}
- path: |
- logs/
- UnitTests/TestResults/
-
-
- build_release:
- # Ensure that RELEASE builds are not broken
- runs-on: ubuntu-latest
- 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: Build Release Terminal.Gui
- run: dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release
-
- - name: Pack Release Terminal.Gui
- run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages
-
- - name: Build Release Solution
- run: dotnet build --configuration Release
-
-
- # Note: this step is currently not writing to the gist for some reason
- # - name: Create Test Coverage Badge
- # uses: simon-k/dotnet-code-coverage-badge@v1.0.0
- # id: create_coverage_badge
- # with:
- # label: Unit Test Coverage
- # color: brightgreen
- # path: UnitTests/TestResults/coverage.opencover.xml
- # gist-filename: code-coverage.json
- # # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27
- # gist-id: 90ef67a684cb71db1817921a970f8d27
- # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }}
-
- # - name: Print Code Coverage
- # run: |
- # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%"
- # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}"
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
new file mode 100644
index 000000000..e0cb43025
--- /dev/null
+++ b/.github/workflows/integration-tests.yml
@@ -0,0 +1,60 @@
+name: Build & Run Integration Tests
+
+on:
+ push:
+ branches: [ v2_release, v2_develop ]
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches: [ v2_release, v2_develop ]
+ paths-ignore:
+ - '**.md'
+
+jobs:
+ build_and_test_debug:
+
+ runs-on: ${{ matrix.os }}
+ strategy:
+ # Turn off fail-fast to let all runners run even if there are errors
+ fail-fast: true
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+
+ timeout-minutes: 10
+ 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: Install dependencies
+ run: |
+ dotnet restore
+
+ - name: Build IntegrationTests
+ run: dotnet build Tests/IntegrationTests --configuration Debug --no-restore
+
+ - name: Set VSTEST_DUMP_PATH
+ shell: bash
+ run: echo "{VSTEST_DUMP_PATH}={logs/${{ runner.os }}/}" >> $GITHUB_ENV
+
+ - name: Run IntegrationTests
+ run: |
+ dotnet test Tests/IntegrationTests --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
+
+ # mv -v Tests/IntegrationTests/TestResults/*/*.* TestResults/IntegrationTests/
+
+ - name: Upload Test Logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: integration-test-logs-${{ runner.os }}
+ path: |
+ logs/
+ TestResults/IntegrationTests/
+
diff --git a/.github/workflows/stress-tests.yml b/.github/workflows/stress-tests.yml
new file mode 100644
index 000000000..6592f2510
--- /dev/null
+++ b/.github/workflows/stress-tests.yml
@@ -0,0 +1,51 @@
+name: Run StressTests (for 15 minutes)
+
+on:
+ schedule:
+ - cron: '0 0 * * *' # Runs every day at midnight UTC
+ push:
+ branches: [ v2_release, v2_develop ]
+ paths-ignore:
+ - '**.md'
+
+jobs:
+ run_stress_tests:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest ]
+
+ timeout-minutes: 70 # Allow some buffer time beyond the 1-hour test duration
+ 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: Install dependencies
+ run: dotnet restore
+
+ - name: Build StressTests
+ run: dotnet build Tests/StressTests --configuration Debug --no-restore
+
+ - name: Run StressTests for 15 minutes
+ 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
+ done
+
+ - name: Upload Test Logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: stress-test-logs-${{ runner.os }}
+ path: |
+ logs/
+ TestResults/StressTests
+
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
new file mode 100644
index 000000000..6fb66961c
--- /dev/null
+++ b/.github/workflows/unit-tests.yml
@@ -0,0 +1,116 @@
+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:
+ non_parallel_unittests:
+ name: Non-Parallel Unit Tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ # Turn off fail-fast to let all runners run even if there are errors
+ fail-fast: true
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+
+ timeout-minutes: 10
+ 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: Install dependencies
+ run: |
+ dotnet restore
+
+ - name: Build Solution Debug
+ run: dotnet build --configuration Debug --no-restore
+
+# Test
+ # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
+ # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
+ # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+
+ - name: Set VSTEST_DUMP_PATH
+ shell: bash
+ run: echo "{VSTEST_DUMP_PATH}={logs/UnitTests/${{ runner.os }}/}" >> $GITHUB_ENV
+
+ - name: Run UnitTests
+ run: |
+ dotnet test Tests/UnitTests --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTests/coverlet.runsettings --diag:logs/UnitTests/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
+
+ # mv -v Tests/UnitTests/TestResults/*/*.* TestResults/UnitTests/
+
+ - name: Upload Test Logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: non_parallel_unittests-logs-${{ runner.os }}
+ path: |
+ logs/UnitTests
+ TestResults/UnitTests/
+
+ parallel_unittests:
+ name: Parallel Unit Tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ # Turn off fail-fast to let all runners run even if there are errors
+ fail-fast: true
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+
+ timeout-minutes: 10
+ 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: Install dependencies
+ run: |
+ dotnet restore
+
+ - name: Build Solution Debug
+ run: dotnet build --configuration Debug --no-restore
+
+# Test
+ # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
+ # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
+ # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+
+ - name: Set VSTEST_DUMP_PATH
+ shell: bash
+ run: echo "{VSTEST_DUMP_PATH}={logs/UnitTestsParallelizable/${{ runner.os }}/}" >> $GITHUB_ENV
+
+ - name: Run UnitTestsParallelizable
+ run: |
+ dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTestsParallelizable/coverlet.runsettings --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
+
+ # mv -v Tests/UnitTestsParallelizable/TestResults/*/*.* TestResults/UnitTestsParallelizable/
+
+ - name: Upload UnitTestsParallelizable Logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: parallel_unittests-logs-${{ runner.os }}
+ path: |
+ logs/UnitTestsParallelizable/
+ TestResults/UnitTestsParallelizable/
diff --git a/.gitignore b/.gitignore
index 14e25a86c..99f085cf5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,3 +60,5 @@ demo.*
*.dotCover
logs/
+
+log.*
\ No newline at end of file
diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj
index 62f1be76c..369ee89ec 100644
--- a/Benchmarks/Benchmarks.csproj
+++ b/Benchmarks/Benchmarks.csproj
@@ -2,15 +2,13 @@
Exe
- net8.0
false
- enable
enable
Terminal.Gui.$(MSBuildProjectName.Replace(" ", "_"))
-
+
diff --git a/CommunityToolkitExample/CommunityToolkitExample.csproj b/CommunityToolkitExample/CommunityToolkitExample.csproj
index 8a25e7c31..13ed59966 100644
--- a/CommunityToolkitExample/CommunityToolkitExample.csproj
+++ b/CommunityToolkitExample/CommunityToolkitExample.csproj
@@ -2,14 +2,12 @@
Exe
- net8.0
- enable
enable
-
-
+
+
diff --git a/example_config.json b/DemoFiles/example_config.json
similarity index 100%
rename from example_config.json
rename to DemoFiles/example_config.json
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 000000000..dfd0c8b78
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+ net8.0
+ enable
+
+ 12
+
+
+ false
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 000000000..8ad412965
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,52 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Example/Example.csproj b/Example/Example.csproj
index 4bb8cc0bc..be1d5c035 100644
--- a/Example/Example.csproj
+++ b/Example/Example.csproj
@@ -1,7 +1,6 @@
Exe
- net8.0
diff --git a/GitVersion.yml b/GitVersion.yml
index 78d807140..9b976f1c5 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -1,10 +1,9 @@
-mode: ContinuousDeployment
+workflow: GitFlow/v1
tag-prefix: '[vV]'
-continuous-delivery-fallback-tag: dev
branches:
develop:
mode: ContinuousDeployment
- tag: develop
+ label: develop
regex: v2_develop
tracks-release-branches: true
is-source-branch-for: ['main']
@@ -12,14 +11,14 @@ branches:
main:
mode: ContinuousDeployment
- tag: prealpha
+ label: prealpha
regex: v2_release
is-release-branch: true
source-branches: ['develop']
v1_develop:
mode: ContinuousDeployment
- tag: v1_develop
+ label: v1_develop
regex: v1_develop
source-branches:
- v1_release
@@ -33,9 +32,9 @@ branches:
pull-request:
mode: ContinuousDeployment
- tag: PullRequest.{BranchName}
+ label: PullRequest.{BranchName}
increment: Inherit
- tag-number-pattern: '[/-](?\d+)'
+ label-number-pattern: '[/-](?\d+)'
regex: ^(pull|pull\-requests|pr)[/-]
source-branches:
- develop
@@ -56,13 +55,13 @@ ignore:
# branches:
# # v1_develop:
# # mode: ContinuousDeployment
-# # tag: pre
+# # label: pre
# # regex: ^v1_develop?[/-]
# # is-release-branch: false
# # source-branches:
# # - v1
# # v1:
-# # tag: rc
+# # label: rc
# # increment: Patch
# # regex: ^v2?[/-]
# # is-release-branch: false
@@ -71,7 +70,7 @@ ignore:
# v2_develop:
# mode: ContinuousDeployment
-# tag: pre
+# label: pre
# regex: ^v2_develop?[/-]
# is-release-branch: true
# tracks-release-branches: true
@@ -80,13 +79,13 @@ ignore:
# v2:
# mode: ContinuousDeployment
# is-release-branch: false
-# tag: alpha
+# label: alpha
# increment: Patch
# regex: ^v2?[/-]
# source-branches: ['v2_develop']
# # feature:
-# # tag: useBranchName
+# # label: useBranchName
# # regex: ^features?[/-]
# # source-branches:
# # - v1
@@ -95,7 +94,7 @@ ignore:
# # - v2_develop
# pull-request:
-# tag: PullRequest.{BranchName}
+# label: PullRequest.{BranchName}
# increment: Inherit
# ignore:
# sha: []
diff --git a/NativeAot/NativeAot.csproj b/NativeAot/NativeAot.csproj
index 4f5238a9d..2dffeb5af 100644
--- a/NativeAot/NativeAot.csproj
+++ b/NativeAot/NativeAot.csproj
@@ -2,8 +2,6 @@
Exe
- net8.0
- enable
enable
true
false
@@ -15,7 +13,7 @@
-
+
diff --git a/NoSamples.slnf b/NoSamples.slnf
index 8d2a38dad..60dfeeede 100644
--- a/NoSamples.slnf
+++ b/NoSamples.slnf
@@ -4,7 +4,10 @@
"projects": [
"Terminal.Gui\\Terminal.Gui.csproj",
"UICatalog\\UICatalog.csproj",
- "UnitTests\\UnitTests.csproj"
- ]
+ "Tests\\UnitTests\\UnitTests.csproj",
+ "Tests\\UnitTestsParallelizable\\UnitTests.Parallelizable.csproj",
+ "Tests\\IntegrationTests\\IntegrationTests.csproj",
+ "Tests\\StressTests\\StressTests.csproj"
+ ]
}
}
\ No newline at end of file
diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj
index 178b14b66..0e61fcdbb 100644
--- a/ReactiveExample/ReactiveExample.csproj
+++ b/ReactiveExample/ReactiveExample.csproj
@@ -1,7 +1,6 @@
Exe
- net8.0
@@ -11,9 +10,9 @@
2.0
-
-
-
+
+
+
diff --git a/Release.slnf b/Release.slnf
index 8d2a38dad..c87196ae5 100644
--- a/Release.slnf
+++ b/Release.slnf
@@ -4,7 +4,10 @@
"projects": [
"Terminal.Gui\\Terminal.Gui.csproj",
"UICatalog\\UICatalog.csproj",
- "UnitTests\\UnitTests.csproj"
+ "Tests\\UnitTests\\UnitTests.csproj",
+ "Tests\\UnitTestsParallelizable\\UnitTests.Parallelizable.csproj",
+ "Tests\\IntegrationTests\\IntegrationTests.csproj",
+ "Tests\\StressTests\\StressTests.csproj"
]
}
}
\ No newline at end of file
diff --git a/Scripts/.testloop.sh.swp b/Scripts/.testloop.sh.swp
new file mode 100644
index 000000000..d219228cc
Binary files /dev/null and b/Scripts/.testloop.sh.swp differ
diff --git a/Scripts/FindDuplicateTestMethodsInSameFileName.ps1 b/Scripts/FindDuplicateTestMethodsInSameFileName.ps1
new file mode 100644
index 000000000..b5807b760
--- /dev/null
+++ b/Scripts/FindDuplicateTestMethodsInSameFileName.ps1
@@ -0,0 +1,92 @@
+# FindDuplicateTestMethodsInSameFileName.ps1
+param (
+ [string]$solutionPath = ".\Tests"
+)
+
+# Set the base path for relative paths (current directory when script is run)
+$basePath = Get-Location
+
+# Define projects to ignore (add your project names or path patterns here)
+$ignoreProjects = @(
+ "StressTests"
+ # Add more as needed, e.g., "Tests/SubFolder/OldProject"
+)
+
+# Function to extract method names from a C# file
+function Get-TestMethodNames {
+ param ($filePath)
+ $content = Get-Content -Path $filePath -Raw
+ $testMethods = @()
+
+ # Match test attributes and capture method names with flexible spacing/comments
+ $methodPattern = '(?s)(\[TestMethod\]|\[Test\]|\[Fact\]|\[Theory\])\s*[\s\S]*?public\s+(?:void|Task)\s+(\w+)\s*\('
+ $methods = [regex]::Matches($content, $methodPattern)
+
+ foreach ($match in $methods) {
+ $methodName = $match.Groups[2].Value # Group 2 is the method name
+ if ($methodName) { # Ensure we only add non-empty method names
+ $testMethods += $methodName
+ }
+ }
+ return $testMethods
+}
+
+# Collect all test files
+$testFiles = Get-ChildItem -Path $solutionPath -Recurse -Include *.cs |
+ Where-Object { $_.FullName -match "Tests" -or $_.FullName -match "Test" }
+
+# Group files by filename
+$fileGroups = $testFiles | Group-Object -Property Name
+
+# Dictionary to track method names and their locations, scoped to same filenames
+$duplicates = @{}
+
+foreach ($group in $fileGroups) {
+ if ($group.Count -gt 1) { # Only process files that exist in multiple locations
+ $fileName = $group.Name
+ $methodMap = @{} # Track methods for this specific filename
+
+ foreach ($file in $group.Group) {
+ # Skip files in ignored projects
+ $skipFile = $false
+ foreach ($ignore in $ignoreProjects) {
+ if ($file.FullName -like "*$ignore*") {
+ $skipFile = $true
+ break
+ }
+ }
+ if ($skipFile) { continue }
+
+ $methods = Get-TestMethodNames -filePath $file.FullName
+ foreach ($method in $methods) {
+ if ($methodMap.ContainsKey($method)) {
+ # Duplicate found for this method in the same filename
+ if (-not $duplicates.ContainsKey($method)) {
+ $duplicates[$method] = @($methodMap[$method])
+ }
+ $duplicates[$method] += $file.FullName
+ } else {
+ $methodMap[$method] = $file.FullName
+ }
+ }
+ }
+ }
+}
+
+# Output results with relative paths
+if ($duplicates.Count -eq 0) {
+ Write-Host "No duplicate test method names found in files with the same name across projects." -ForegroundColor Green
+} else {
+ Write-Host "Duplicate test method names found in files with the same name across projects:" -ForegroundColor Yellow
+ foreach ($dup in $duplicates.Keys) {
+ Write-Host "Method: $dup" -ForegroundColor Cyan
+ foreach ($fullPath in $duplicates[$dup]) {
+ $relativePath = Resolve-Path -Path $fullPath -Relative -RelativeBasePath $basePath
+ Write-Host " - $relativePath" -ForegroundColor White
+ }
+ }
+ # Display total number of duplicate methods
+ Write-Host "Total number of duplicate methods: $($duplicates.Count)" -ForegroundColor Magenta
+ # Fail the pipeline by setting a non-zero exit code
+ exit 1
+}
\ No newline at end of file
diff --git a/Scripts/FindDuplicateTests.ps1 b/Scripts/FindDuplicateTests.ps1
new file mode 100644
index 000000000..2e7335a5f
--- /dev/null
+++ b/Scripts/FindDuplicateTests.ps1
@@ -0,0 +1,50 @@
+# Define the root directory containing test projects
+$testsDir = "./Tests"
+
+# Get all subfolders in the ./Tests directory
+$subfolders = Get-ChildItem -Directory $testsDir
+
+# Initialize a hashtable to track method names and their associated subfolders
+$methodMap = @{}
+
+# Iterate through each subfolder
+foreach ($subfolder in $subfolders) {
+ $subfolderName = $subfolder.Name
+
+ # Run dotnet test --list-tests to get the list of tests in the subfolder
+ $output = dotnet test $subfolder.FullName --list-tests | Out-String
+
+ # Split the output into lines and filter for lines containing a dot (indicative of test names)
+ $testLines = $output -split "`n" | Where-Object { $_ -match "\." }
+
+ # Process each test line to extract the method name
+ foreach ($testLine in $testLines) {
+ $trimmed = $testLine.Trim()
+ $parts = $trimmed -split "\."
+ $lastPart = $parts[-1]
+
+ # Handle parameterized tests by extracting the method name before any parentheses
+ if ($lastPart -match "\(") {
+ $methodName = $lastPart.Substring(0, $lastPart.IndexOf("("))
+ } else {
+ $methodName = $lastPart
+ }
+
+ # Update the hashtable with the method name and subfolder
+ if ($methodMap.ContainsKey($methodName)) {
+ # Add the subfolder only if it’s not already listed for this method name
+ if (-not ($methodMap[$methodName] -contains $subfolderName)) {
+ $methodMap[$methodName] += $subfolderName
+ }
+ } else {
+ $methodMap[$methodName] = @($subfolderName)
+ }
+ }
+}
+
+# Identify and display duplicated test method names
+foreach ($entry in $methodMap.GetEnumerator()) {
+ if ($entry.Value.Count -gt 1) {
+ Write-Output "Duplicated test: $($entry.Key) in folders: $($entry.Value -join ', ')"
+ }
+}
diff --git a/Scripts/testloop.sh b/Scripts/testloop.sh
new file mode 100755
index 000000000..936764ab3
--- /dev/null
+++ b/Scripts/testloop.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# This script runs the tests in a loop until they all pass.
+# It will exit if any test run fails.
+
+dotnet build -c Debug
+
+iterationCount=1
+
+while true; do
+ echo "Starting iteration $iterationCount..."
+
+ dotnet test Tests/UnitTests --no-build --diag:TestResults/UnitTests.log -- xunit.stopOnFail=true
+ if [ $? -ne 0 ]; then
+ echo "UnitTests run failed on iteration $iterationCount. Exiting."
+ exit 1
+ fi
+
+ dotnet test Tests/UnitTestsParallelizable --no-build --diag:TestResults/UnitTestsParallelizable.log -- xunit.stopOnFail=true
+ if [ $? -ne 0 ]; then
+ echo "UnitTestsParallelizable run failed on iteration $iterationCount. Exiting."
+ exit 1
+ fi
+
+ # Clean up the log files
+ rm log*
+
+ # Increment the iteration counter
+ ((iterationCount++))
+done
\ No newline at end of file
diff --git a/SelfContained/SelfContained.csproj b/SelfContained/SelfContained.csproj
index 46800e09c..3aec661bc 100644
--- a/SelfContained/SelfContained.csproj
+++ b/SelfContained/SelfContained.csproj
@@ -2,8 +2,6 @@
Exe
- net8.0
- enable
enable
true
Link
@@ -18,7 +16,7 @@
-
+
diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs
index 176e85d35..01bdcc1a3 100644
--- a/Terminal.Gui/Application/Application.Mouse.cs
+++ b/Terminal.Gui/Application/Application.Mouse.cs
@@ -62,7 +62,10 @@ public static partial class Application // Mouse handling
}
#if DEBUG_IDISPOSABLE
- ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
+ if (View.DebugIDisposable)
+ {
+ ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
+ }
#endif
if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
@@ -150,7 +153,7 @@ public static partial class Application // Mouse handling
if (deepestViewUnderMouse is { })
{
#if DEBUG_IDISPOSABLE
- if (deepestViewUnderMouse.WasDisposed)
+ if (View.DebugIDisposable && deepestViewUnderMouse.WasDisposed)
{
throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
}
@@ -278,7 +281,7 @@ public static partial class Application // Mouse handling
if (MouseGrabView is { })
{
#if DEBUG_IDISPOSABLE
- if (MouseGrabView.WasDisposed)
+ if (View.DebugIDisposable && MouseGrabView.WasDisposed)
{
throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
}
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 6906efd90..5bfa5f2cc 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -1,8 +1,6 @@
#nullable enable
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Serialization;
-using Microsoft.CodeAnalysis.Diagnostics;
namespace Terminal.Gui;
@@ -27,7 +25,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
-
/// Gets or sets the key to activate arranging views using the keyboard.
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key ArrangeKey
@@ -97,7 +94,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
var rs = new RunState (toplevel);
#if DEBUG_IDISPOSABLE
- if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+ if (View.DebugIDisposable && Top is { } && toplevel != Top && !TopLevels.Contains (Top))
{
// This assertion confirm if the Top was already disposed
Debug.Assert (Top.WasDisposed);
@@ -174,7 +171,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
// Force leave events for any entered views in the old Top
if (GetLastMousePosition () is { })
{
- RaiseMouseEnterLeaveEvents (GetLastMousePosition ()!.Value, new List ());
+ RaiseMouseEnterLeaveEvents (GetLastMousePosition ()!.Value, new ());
}
Top?.OnDeactivate (toplevel);
@@ -208,7 +205,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
NotifyNewRunState?.Invoke (toplevel, new (rs));
// Force an Idle event so that an Iteration (and Refresh) happen.
- Application.Invoke (() => { });
+ Invoke (() => { });
return rs;
}
@@ -231,7 +228,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
// If the view is not visible or enabled, don't position the cursor
if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
{
- CursorVisibility current = CursorVisibility.Invisible;
+ var current = CursorVisibility.Invisible;
Driver?.GetCursorVisibility (out current);
if (current != CursorVisibility.Invisible)
@@ -244,7 +241,9 @@ public static partial class Application // Run (Begin, Run, End, Stop)
// If the view is not visible within it's superview, don't position the cursor
Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
- Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
+
+ Rectangle superViewViewport =
+ mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
if (!superViewViewport.IntersectsWith (mostFocusedViewport))
{
@@ -305,8 +304,10 @@ public static partial class Application // Run (Begin, Run, End, Stop)
/// The created object. The caller is responsible for disposing this object.
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
- public static Toplevel Run (Func? errorHandler = null, IConsoleDriver? driver = null) =>
- ApplicationImpl.Instance.Run (errorHandler, driver);
+ public static Toplevel Run (Func? errorHandler = null, IConsoleDriver? driver = null)
+ {
+ return ApplicationImpl.Instance.Run (errorHandler, driver);
+ }
///
/// Runs the application by creating a -derived object of type T and calling
@@ -332,7 +333,10 @@ public static partial class Application // Run (Begin, Run, End, Stop)
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public static T Run (Func? errorHandler = null, IConsoleDriver? driver = null)
- where T : Toplevel, new() => ApplicationImpl.Instance.Run (errorHandler, driver);
+ where T : Toplevel, new ()
+ {
+ return ApplicationImpl.Instance.Run (errorHandler, driver);
+ }
/// Runs the Application using the provided view.
///
@@ -356,7 +360,8 @@ public static partial class Application // Run (Begin, Run, End, Stop)
/// method will only process any pending events, timers, idle handlers and then
/// return control immediately.
///
- /// When using or
+ ///
+ /// When using or
///
/// will be called automatically.
///
@@ -372,8 +377,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
/// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
/// rethrows when null).
///
- public static void Run (Toplevel view, Func? errorHandler = null)
- => ApplicationImpl.Instance.Run (view, errorHandler);
+ public static void Run (Toplevel view, Func? errorHandler = null) { ApplicationImpl.Instance.Run (view, errorHandler); }
/// Adds a timeout to the application.
///
@@ -381,7 +385,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
/// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
/// token that can be used to stop the timeout by calling .
///
- public static object? AddTimeout (TimeSpan time, Func callback) => ApplicationImpl.Instance.AddTimeout (time, callback);
+ public static object? AddTimeout (TimeSpan time, Func callback) { return ApplicationImpl.Instance.AddTimeout (time, callback); }
/// Removes a previously scheduled timeout
/// The token parameter is the value returned by .
@@ -393,11 +397,11 @@ public static partial class Application // Run (Begin, Run, End, Stop)
/// This method also returns
///
/// if the timeout is not found.
- public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
+ public static bool RemoveTimeout (object token) { return ApplicationImpl.Instance.RemoveTimeout (token); }
/// Runs on the thread that is processing events
/// the action to be invoked on the main processing thread.
- public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action);
+ public static void Invoke (Action action) { ApplicationImpl.Instance.Invoke (action); }
// TODO: Determine if this is really needed. The only code that calls WakeUp I can find
// is ProgressBarStyles, and it's not clear it needs to.
@@ -406,14 +410,15 @@ public static partial class Application // Run (Begin, Run, End, Stop)
public static void Wakeup () { MainLoop?.Wakeup (); }
///
- /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that need to be laid out (see ) will be laid out.
- /// Only Views that need to be drawn (see ) will be drawn.
+ /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
+ /// need to be laid out (see ) will be laid out.
+ /// Only Views that need to be drawn (see ) will be drawn.
///
- /// If the entire View hierarchy will be redrawn. The default is and should only be overriden for testing.
- public static void LayoutAndDraw (bool forceDraw = false)
- {
- ApplicationImpl.Instance.LayoutAndDraw (forceDraw);
- }
+ ///
+ /// If the entire View hierarchy will be redrawn. The default is and
+ /// should only be overriden for testing.
+ ///
+ public static void LayoutAndDraw (bool forceDraw = false) { ApplicationImpl.Instance.LayoutAndDraw (forceDraw); }
internal static void LayoutAndDrawImpl (bool forceDraw = false)
{
@@ -424,6 +429,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
forceDraw = true;
ClearScreenNextIteration = false;
}
+
if (forceDraw)
{
Driver?.ClearContents ();
@@ -431,7 +437,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
View.SetClipToScreen ();
View.Draw (TopLevels, neededLayout || forceDraw);
- View.SetClipToScreen ();
+ View.SetClipToScreen ();
Driver?.Refresh ();
}
@@ -521,11 +527,13 @@ public static partial class Application // Run (Begin, Run, End, Stop)
///
/// This will cause to return.
///
- /// Calling is equivalent to setting the
+ /// Calling is equivalent to setting the
+ ///
/// property on the currently running to false.
///
///
- public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top);
+ public static void RequestStop (Toplevel? top = null) { ApplicationImpl.Instance.RequestStop (top); }
+
internal static void OnNotifyStopRunState (Toplevel top)
{
if (EndAfterFirstIteration)
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index a493f3056..9ea1b4504 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -63,7 +63,7 @@ public static partial class Application
{
Rune rune = contents [r, c].Rune;
- if (rune.DecodeSurrogatePair (out char [] sp))
+ if (rune.DecodeSurrogatePair (out char []? sp))
{
sb.Append (sp);
}
@@ -152,7 +152,7 @@ public static partial class Application
#if DEBUG_IDISPOSABLE
// Don't dispose the Top. It's up to caller dispose it
- if (!ignoreDisposed && Top is { })
+ if (View.DebugIDisposable && !ignoreDisposed && Top is { })
{
Debug.Assert (Top.WasDisposed);
@@ -173,6 +173,7 @@ public static partial class Application
MainThreadId = -1;
Iteration = null;
EndAfterFirstIteration = false;
+ ClearScreenNextIteration = false;
// Driver stuff
if (Driver is { })
@@ -212,7 +213,6 @@ public static partial class Application
Navigation = null;
- ClearScreenNextIteration = false;
KeyBindings.Clear ();
AddKeyBindings ();
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 49e1d4b52..8bbc17ccb 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -176,7 +176,10 @@ public class ApplicationImpl : IApplication
if (runState.Toplevel is null)
{
#if DEBUG_IDISPOSABLE
- Debug.Assert (Application.TopLevels.Count == 0);
+ if (View.DebugIDisposable)
+ {
+ Debug.Assert (Application.TopLevels.Count == 0);
+ }
#endif
runState.Dispose ();
diff --git a/Terminal.Gui/Application/RunState.cs b/Terminal.Gui/Application/RunState.cs
index 0c1387ff8..e0b6fdc30 100644
--- a/Terminal.Gui/Application/RunState.cs
+++ b/Terminal.Gui/Application/RunState.cs
@@ -22,7 +22,10 @@ public class RunState : IDisposable
Dispose (true);
GC.SuppressFinalize (this);
#if DEBUG_IDISPOSABLE
- WasDisposed = true;
+ if (View.DebugIDisposable)
+ {
+ WasDisposed = true;
+ }
#endif
}
@@ -52,6 +55,12 @@ public class RunState : IDisposable
public static List Instances = new ();
/// Creates a new RunState object.
- public RunState () { Instances.Add (this); }
+ public RunState ()
+ {
+ if (View.DebugIDisposable)
+ {
+ Instances.Add (this);
+ }
+ }
#endif
}
diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs
index 676f68251..2326c028e 100644
--- a/Terminal.Gui/Configuration/ConfigurationManager.cs
+++ b/Terminal.Gui/Configuration/ConfigurationManager.cs
@@ -326,6 +326,9 @@ public static class ConfigurationManager
}
+ ///
+ /// Logs any Json deserialization errors that occurred during deserialization to the logging system.
+ ///
public static void LogJsonErrors ()
{
if (_jsonErrors.Length > 0)
diff --git a/Terminal.Gui/Configuration/ScopeJsonConverter.cs b/Terminal.Gui/Configuration/ScopeJsonConverter.cs
index 11e83c6f7..d1d6e475e 100644
--- a/Terminal.Gui/Configuration/ScopeJsonConverter.cs
+++ b/Terminal.Gui/Configuration/ScopeJsonConverter.cs
@@ -91,7 +91,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
scope! [propertyName].PropertyValue =
JsonSerializer.Deserialize (ref reader, propertyType!, SerializerContext);
}
- catch (Exception ex)
+ catch (Exception)
{
// Logging.Trace ($"scopeT Read: {ex}");
}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
index 9c442e5e2..cdcd10a59 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
@@ -52,6 +52,11 @@ public class CsiKeyPattern : AnsiKeyboardParserPattern
_pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}]|\d+~)$");
}
+ ///
+ ///
+ ///
+ ///
+ ///
protected override Key? GetKeyImpl (string input)
{
Match match = _pattern.Match (input);
@@ -66,7 +71,7 @@ public class CsiKeyPattern : AnsiKeyboardParserPattern
Key? key = _terminators.GetValueOrDefault (terminator);
- if (key != null && int.TryParse (modifierGroup, out int modifier))
+ if (key is {} && int.TryParse (modifierGroup, out int modifier))
{
key = modifier switch
{
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index aaadfee75..e19e3521f 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -1,175 +1,184 @@
-
-
-
-
-
-
-
- 2.0.0
-
-
-
-
-
-
- Terminal.Gui
-
+
+
+
+
+
+
+
+ 2.0.0
+
+
+
+
+
+
+ Terminal.Gui
+
-
-
-
-
- net8.0
- 12
- $(AssemblyName)
- true
- true
- portable
- $(DefineConstants);CONTRACTS_FULL;CODE_ANALYSIS
- enable
- true
- true
- false
- true
- true
-
-
- true
- $(DefineConstants);DEBUG_IDISPOSABLE
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- \
-
-
- true
- \
-
-
-
-
-
-
-
- true
- true
- Strings.resx
-
-
-
-
- ResXFileCodeGenerator
- Strings.Designer.cs
-
-
-
-
-
-
- $(AssemblyName)
- MIT
- https://github.com/gui-cs/$(AssemblyName)
- logo.png
- README.md
- csharp, terminal, c#, f#, gui, toolkit, console, tui
- Cross platform Terminal UI toolkit for .NET
- Miguel de Icaza, Tig Kindel
- A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix.
- $(AssemblyName) - Cross-platform Terminal User Interface (TUI) toolkit for .NET
-
- See: $(PackageProjectUrl)/releases
-
- bin\$(Configuration)\$(AssemblyName).xml
- true
- true
- https://github.com/gui-cs/$(AssemblyName).git
- git
- true
- snupkg
-
- true
- upstream
-
- true
- true
- Miguel de Icaza, Tig Kindel (@tig), @BDisp
-
-
+
+
+
+
+
+ net8.0
+ 12
+ enable
+
+ $(AssemblyName)
+ true
+ true
+ portable
+ $(DefineConstants);CONTRACTS_FULL;CODE_ANALYSIS
+ true
+ true
+ false
+ true
+ true
+
+
+ true
+ $(DefineConstants);DEBUG_IDISPOSABLE
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ \
+
+
+ true
+ \
+
+
+
+
+
+
+
+ true
+ true
+ Strings.resx
+
+
+
+
+ ResXFileCodeGenerator
+ Strings.Designer.cs
+
+
+
+
+
+
+ $(AssemblyName)
+ MIT
+ https://github.com/gui-cs/$(AssemblyName)
+ logo.png
+ README.md
+ csharp, terminal, c#, f#, gui, toolkit, console, tui
+ Cross platform Terminal UI toolkit for .NET
+ Miguel de Icaza, Tig Kindel
+ A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix.
+ $(AssemblyName) - Cross-platform Terminal User Interface (TUI) toolkit for .NET
+
+ See: $(PackageProjectUrl)/releases
+
+ bin\$(Configuration)\$(AssemblyName).xml
+ true
+ true
+ https://github.com/gui-cs/$(AssemblyName).git
+ git
+ true
+ snupkg
+
+ true
+ upstream
+
+ true
+ true
+ Miguel de Icaza, Tig Kindel (@tig), @BDisp
+
+
+
+
+
+
-
-
-
- $(MSBuildThisFileDirectory)..\local_packages\
-
- $(MSBuildThisFileDirectory)bin\$(Configuration)\
-
+
+
+
+ $(MSBuildThisFileDirectory)..\local_packages\
+
+ $(MSBuildThisFileDirectory)bin\$(Configuration)\
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
diff --git a/Terminal.Gui/View/DrawContext.cs b/Terminal.Gui/View/DrawContext.cs
index 36b60c4c4..2648caefe 100644
--- a/Terminal.Gui/View/DrawContext.cs
+++ b/Terminal.Gui/View/DrawContext.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
///
-/// Tracks the region that has been drawn during . This is primarily
+/// Tracks the region that has been drawn during . This is primarily
/// in support of .
///
public class DrawContext
diff --git a/Terminal.Gui/View/DrawEventArgs.cs b/Terminal.Gui/View/DrawEventArgs.cs
index 5aabd2e66..b172d85ab 100644
--- a/Terminal.Gui/View/DrawEventArgs.cs
+++ b/Terminal.Gui/View/DrawEventArgs.cs
@@ -16,7 +16,7 @@ public class DrawEventArgs : CancelEventArgs
/// .
///
///
- /// Add any regions that have been drawn to during operations to this context. This is
+ /// Add any regions that have been drawn to during operations to this context. This is
/// primarily
/// in support of .
///
@@ -34,7 +34,7 @@ public class DrawEventArgs : CancelEventArgs
public Rectangle NewViewport { get; }
///
- /// Add any regions that have been drawn to during operations to this context. This is
+ /// Add any regions that have been drawn to during operations to this context. This is
/// primarily
/// in support of .
///
diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs
index 691c10d8a..3c5b0d01a 100644
--- a/Terminal.Gui/View/View.Mouse.cs
+++ b/Terminal.Gui/View/View.Mouse.cs
@@ -762,6 +762,7 @@ public partial class View // Mouse APIs
/// INTERNAL: Gets the Views that are under the mouse at , including Adornments.
///
///
+ ///
///
internal static List GetViewsUnderMouse (in Point location, bool ignoreTransparent = false)
{
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 8ffff38c8..bf4fc0d8c 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -24,6 +24,83 @@ namespace Terminal.Gui;
public partial class View : IDisposable, ISupportInitializeNotification
{
+ private bool _disposedValue;
+
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
+ public void Dispose ()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Disposing?.Invoke (this, EventArgs.Empty);
+ Dispose (true);
+ GC.SuppressFinalize (this);
+#if DEBUG_IDISPOSABLE
+ if (DebugIDisposable)
+ {
+ WasDisposed = true;
+
+ foreach (View? instance in Instances.Where (
+ x =>
+ {
+ if (x is { })
+ {
+ return x.WasDisposed;
+ }
+
+ return false;
+ })
+ .ToList ())
+ {
+ Instances.Remove (instance);
+ }
+ }
+#endif
+ }
+
+ ///
+ /// Riased when the is being disposed.
+ ///
+ public event EventHandler? Disposing;
+
+ /// Pretty prints the View
+ ///
+ public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
+
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
+ /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
+ /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
+ ///
+ ///
+ protected virtual void Dispose (bool disposing)
+ {
+ LineCanvas.Dispose ();
+
+ DisposeMouse ();
+ DisposeKeyboard ();
+ DisposeAdornments ();
+ DisposeScrollBars ();
+
+ for (int i = InternalSubviews.Count - 1; i >= 0; i--)
+ {
+ View subview = InternalSubviews [i];
+ Remove (subview);
+ subview.Dispose ();
+ }
+
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ }
+
+ _disposedValue = true;
+ }
+
+ Debug.Assert (InternalSubviews.Count == 0);
+ }
+
#region Constructors and Initialization
/// Gets or sets arbitrary data for the view.
@@ -51,7 +128,10 @@ public partial class View : IDisposable, ISupportInitializeNotification
public View ()
{
#if DEBUG_IDISPOSABLE
- Instances.Add (this);
+ if (DebugIDisposable)
+ {
+ Instances.Add (this);
+ }
#endif
SetupAdornments ();
@@ -168,6 +248,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
// TODO: Figure out how to move this out of here and just depend on LayoutNeeded in Mainloop
Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)).
}
+
SetNeedsLayout ();
Initialized?.Invoke (this, EventArgs.Empty);
@@ -371,7 +452,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
get
{
#if DEBUG_IDISPOSABLE
- if (WasDisposed)
+ if (DebugIDisposable && WasDisposed)
{
throw new ObjectDisposedException (GetType ().FullName);
}
@@ -381,7 +462,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
set
{
#if DEBUG_IDISPOSABLE
- if (WasDisposed)
+ if (DebugIDisposable && WasDisposed)
{
throw new ObjectDisposedException (GetType ().FullName);
}
@@ -450,71 +531,12 @@ public partial class View : IDisposable, ISupportInitializeNotification
#endregion
- /// Pretty prints the View
- ///
- public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
-
- private bool _disposedValue;
-
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
- /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
- /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
- ///
- ///
- protected virtual void Dispose (bool disposing)
- {
- LineCanvas.Dispose ();
-
- DisposeMouse ();
- DisposeKeyboard ();
- DisposeAdornments ();
- DisposeScrollBars ();
-
- for (int i = InternalSubviews.Count - 1; i >= 0; i--)
- {
- View subview = InternalSubviews [i];
- Remove (subview);
- subview.Dispose ();
- }
-
- if (!_disposedValue)
- {
- if (disposing)
- {
- // TODO: dispose managed state (managed objects)
- }
-
- _disposedValue = true;
- }
-
- Debug.Assert (InternalSubviews.Count == 0);
- }
-
+#if DEBUG_IDISPOSABLE
///
- /// Riased when the is being disposed.
+ /// Set to false to disable the debug IDisposable feature.
///
- public event EventHandler? Disposing;
+ public static bool DebugIDisposable { get; set; } = false;
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
- public void Dispose ()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Disposing?.Invoke (this, EventArgs.Empty);
- Dispose (true);
- GC.SuppressFinalize (this);
-#if DEBUG_IDISPOSABLE
- WasDisposed = true;
-
- foreach (View instance in Instances.Where (x => x.WasDisposed).ToList ())
- {
- Instances.Remove (instance);
- }
-#endif
- }
-
-#if DEBUG_IDISPOSABLE
/// For debug purposes to verify objects are being disposed properly
public bool WasDisposed { get; set; }
diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs
index 05bcc1101..d5b72c185 100644
--- a/Terminal.Gui/Views/Bar.cs
+++ b/Terminal.Gui/Views/Bar.cs
@@ -109,11 +109,13 @@ public class Bar : View, IOrientation, IDesignable
set => _orientationHelper.Orientation = value;
}
+#pragma warning disable CS0067 // The event is never used
///
public event EventHandler>? OrientationChanging;
///
public event EventHandler>? OrientationChanged;
+#pragma warning restore CS0067 // The event is never used
/// Called when has changed.
///
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index 259638baf..a70b597fd 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -107,7 +107,7 @@ public class Dialog : Window
get
{
#if DEBUG_IDISPOSABLE
- if (WasDisposed)
+ if (View.DebugIDisposable && WasDisposed)
{
throw new ObjectDisposedException (GetType ().FullName);
}
@@ -117,7 +117,7 @@ public class Dialog : Window
set
{
#if DEBUG_IDISPOSABLE
- if (WasDisposed)
+ if (View.DebugIDisposable && WasDisposed)
{
throw new ObjectDisposedException (GetType ().FullName);
}
diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs
index 9d2889373..45d2fa3cf 100644
--- a/Terminal.Gui/Views/Line.cs
+++ b/Terminal.Gui/Views/Line.cs
@@ -33,11 +33,13 @@ public class Line : View, IOrientation
set => _orientationHelper.Orientation = value;
}
+#pragma warning disable CS0067 // The event is never used
///
public event EventHandler> OrientationChanging;
///
public event EventHandler> OrientationChanged;
+#pragma warning restore CS0067 // The event is never used
/// Called when has changed.
///
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index d72bebad7..3b9d9aeac 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -403,11 +403,13 @@ public class RadioGroup : View, IDesignable, IOrientation
private readonly OrientationHelper _orientationHelper;
+#pragma warning disable CS0067 // The event is never used
///
public event EventHandler>? OrientationChanging;
///
public event EventHandler>? OrientationChanged;
+#pragma warning restore CS0067 // The event is never used
/// Called when has changed.
///
diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs
index 10df243a1..62d60320d 100644
--- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs
+++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs
@@ -164,11 +164,13 @@ public class ScrollBar : View, IOrientation, IDesignable
set => _orientationHelper.Orientation = value;
}
+#pragma warning disable CS0067 // The event is never used
///
public event EventHandler>? OrientationChanging;
///
public event EventHandler>? OrientationChanged;
+#pragma warning restore CS0067 // The event is never used
///
public void OnOrientationChanged (Orientation newOrientation)
diff --git a/Terminal.Gui/Views/Wizard/WizardStep.cs b/Terminal.Gui/Views/Wizard/WizardStep.cs
index 8d9be18b0..afb750ffb 100644
--- a/Terminal.Gui/Views/Wizard/WizardStep.cs
+++ b/Terminal.Gui/Views/Wizard/WizardStep.cs
@@ -125,7 +125,7 @@ public class WizardStep : View
{
_contentView.Add (view);
- if (view.CanFocus)
+ if (view!.CanFocus)
{
CanFocus = true;
}
diff --git a/Terminal.sln b/Terminal.sln
index 7cb9d0367..0399a634d 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -6,8 +6,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui", "Terminal.Gu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{8B901EDE-8974-4820-B100-5226917E2990}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveExample", "ReactiveExample\ReactiveExample.csproj", "{44E15B48-0DB2-4560-82BD-D3B7989811C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}"
@@ -21,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{B8
.editorconfig = .editorconfig
.filenesting.json = .filenesting.json
.gitignore = .gitignore
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
GitVersion.yml = GitVersion.yml
global.json = global.json
nuget.config = nuget.config
@@ -31,9 +31,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{13BB2C46-B324-4B9C-92EB-CE6184D4736E}"
ProjectSection(SolutionItems) = preProject
.github\workflows\api-docs.yml = .github\workflows\api-docs.yml
- .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml
+ .github\workflows\build-release.yml = .github\workflows\build-release.yml
+ .github\workflows\check-duplicates.yml = .github\workflows\check-duplicates.yml
GitVersion.yml = GitVersion.yml
+ .github\workflows\integration-tests.yml = .github\workflows\integration-tests.yml
.github\workflows\publish.yml = .github\workflows\publish.yml
+ .github\workflows\stress-tests.yml = .github\workflows\stress-tests.yml
+ .github\workflows\unit-tests.yml = .github\workflows\unit-tests.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C7A51224-5E0F-4986-AB37-A6BF89966C12}"
@@ -50,6 +54,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "NativeAot\Nati
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{242FBD3E-2EC6-4274-BD40-8E62AF9327B2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{038B09F5-EF3A-F21E-7C10-A6551866ECE2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "Tests\IntegrationTests\IntegrationTests.csproj", "{F74EC349-B988-FCFA-A1E5-967F70FB75B5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTests", "Tests\StressTests\StressTests.csproj", "{96ACE8BA-2E07-7537-FBF2-E8176CCB8080}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", "Tests\UnitTestsParallelizable\UnitTests.Parallelizable.csproj", "{DE780834-190A-8277-51FD-750CC666E82D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -64,10 +76,6 @@ Global
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
- {8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8B901EDE-8974-4820-B100-5226917E2990}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8B901EDE-8974-4820-B100-5226917E2990}.Release|Any CPU.Build.0 = Release|Any CPU
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -92,6 +100,22 @@ Global
{242FBD3E-2EC6-4274-BD40-8E62AF9327B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{242FBD3E-2EC6-4274-BD40-8E62AF9327B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{242FBD3E-2EC6-4274-BD40-8E62AF9327B2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {96ACE8BA-2E07-7537-FBF2-E8176CCB8080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {96ACE8BA-2E07-7537-FBF2-E8176CCB8080}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {96ACE8BA-2E07-7537-FBF2-E8176CCB8080}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {96ACE8BA-2E07-7537-FBF2-E8176CCB8080}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE780834-190A-8277-51FD-750CC666E82D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE780834-190A-8277-51FD-750CC666E82D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE780834-190A-8277-51FD-750CC666E82D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE780834-190A-8277-51FD-750CC666E82D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings
index 07d849e84..7b9a335a8 100644
--- a/Terminal.sln.DotSettings
+++ b/Terminal.sln.DotSettings
@@ -409,10 +409,12 @@
True
True
True
+ 5
True
True
True
True
+ True
True
True
True
diff --git a/Tests/IntegrationTests/IntegrationTests.csproj b/Tests/IntegrationTests/IntegrationTests.csproj
new file mode 100644
index 000000000..94b97b6a0
--- /dev/null
+++ b/Tests/IntegrationTests/IntegrationTests.csproj
@@ -0,0 +1,40 @@
+
+
+
+ enable
+ false
+ true
+ $(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL
+ portable
+ enable
+ true
+ true
+
+
+ true
+ $(DefineConstants);DEBUG_IDISPOSABLE
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs
similarity index 78%
rename from UnitTests/UICatalog/ScenarioTests.cs
rename to Tests/IntegrationTests/UICatalog/ScenarioTests.cs
index 09ba8b754..3de2735f4 100644
--- a/UnitTests/UICatalog/ScenarioTests.cs
+++ b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs
@@ -1,15 +1,19 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
+using Terminal.Gui;
+using UnitTests;
+using UICatalog;
using Xunit.Abstractions;
-namespace UICatalog.Tests;
+namespace IntegrationTests.UICatalog;
public class ScenarioTests : TestsAllViews
{
public ScenarioTests (ITestOutputHelper output)
{
#if DEBUG_IDISPOSABLE
+ View.DebugIDisposable = true;
View.Instances.Clear ();
#endif
_output = output;
@@ -137,169 +141,7 @@ public class ScenarioTests : TestsAllViews
}
}
- ///
- /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run and measuring the perf of each.
- ///
- [Theory]
- [MemberData (nameof (AllScenarioTypes))]
- public void All_Scenarios_Benchmark (Type scenarioType)
- {
- Assert.Null (_timeoutLock);
- _timeoutLock = new ();
-
- // Disable any UIConfig settings
- ConfigLocations savedConfigLocations = ConfigurationManager.Locations;
- ConfigurationManager.Locations = ConfigLocations.Default;
-
- // If a previous test failed, this will ensure that the Application is in a clean state
- Application.ResetState (true);
-
- uint maxIterations = 1000;
- uint abortTime = 2000;
- object timeout = null;
- var initialized = false;
- var shutdown = false;
-
- int iterationCount = 0;
- int clearedContentCount = 0;
- int refreshedCount = 0;
- int updatedCount = 0;
- int drawCompleteCount = 0;
-
- int addedCount = 0;
- int laidOutCount = 0;
-
- _output.WriteLine ($"Running Scenario '{scenarioType}'");
- var scenario = (Scenario)Activator.CreateInstance (scenarioType);
-
- Stopwatch stopwatch = null;
-
- Application.InitializedChanged += OnApplicationOnInitializedChanged;
- Application.ForceDriver = "FakeDriver";
- scenario!.Main ();
- scenario.Dispose ();
- scenario = null;
- Application.ForceDriver = string.Empty;
- Application.InitializedChanged -= OnApplicationOnInitializedChanged;
-
- lock (_timeoutLock)
- {
- if (timeout is { })
- {
- timeout = null;
- }
- }
-
- lock (_timeoutLock)
- {
- _timeoutLock = null;
- }
-
- _output.WriteLine ($"Scenario {scenarioType}");
- _output.WriteLine ($" took {stopwatch.ElapsedMilliseconds} ms to run.");
- _output.WriteLine ($" called Driver.ClearContents {clearedContentCount} times.");
- _output.WriteLine ($" called Driver.Refresh {refreshedCount} times.");
- _output.WriteLine ($" which updated the screen {updatedCount} times.");
- _output.WriteLine ($" called View.Draw {drawCompleteCount} times.");
- _output.WriteLine ($" added {addedCount} views.");
- _output.WriteLine ($" called View.LayoutComplete {laidOutCount} times.");
-
- // Restore the configuration locations
- ConfigurationManager.Locations = savedConfigLocations;
- ConfigurationManager.Reset ();
-
- return;
-
- void OnApplicationOnInitializedChanged (object s, EventArgs a)
- {
- if (a.CurrentValue)
- {
- lock (_timeoutLock)
- {
- timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
- }
-
- initialized = true;
- Application.Iteration += OnApplicationOnIteration;
- Application.Driver!.ClearedContents += (sender, args) => clearedContentCount++;
-
- if (Application.Driver is ConsoleDriver cd)
- {
- cd!.Refreshed += (sender, args) =>
- {
- refreshedCount++;
-
- if (args.CurrentValue)
- {
- updatedCount++;
- }
- };
- }
-
- Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
-
- stopwatch = Stopwatch.StartNew ();
- }
- else
- {
- shutdown = true;
- Application.NotifyNewRunState -= OnApplicationNotifyNewRunState;
- Application.Iteration -= OnApplicationOnIteration;
- stopwatch!.Stop ();
- }
- _output.WriteLine ($"Initialized == {a.CurrentValue}");
- }
-
- void OnApplicationOnIteration (object s, IterationEventArgs a)
- {
- iterationCount++;
- if (iterationCount > maxIterations)
- {
- // Press QuitKey
- _output.WriteLine ($"Attempting to quit scenario with RequestStop");
- Application.RequestStop ();
- }
- }
-
-
- void OnApplicationNotifyNewRunState (object sender, RunStateEventArgs e)
- {
- // Get a list of all subviews under Application.Top (and their subviews, etc.)
- // and subscribe to their DrawComplete event
- void SubscribeAllSubviews (View view)
- {
- view.DrawComplete += (s, a) => drawCompleteCount++;
- view.SubviewsLaidOut += (s, a) => laidOutCount++;
- view.Added += (s, a) => addedCount++;
- foreach (View subview in view.Subviews)
- {
- SubscribeAllSubviews (subview);
- }
- }
-
- SubscribeAllSubviews (Application.Top);
- }
-
- // If the scenario doesn't close within the abort time, this will force it to quit
- bool ForceCloseCallback ()
- {
- lock (_timeoutLock)
- {
- if (timeout is { })
- {
- timeout = null;
- }
- }
-
- _output.WriteLine(
- $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms and {iterationCount} iterations. Force quit.");
-
- Application.RequestStop ();
-
- return false;
- }
- }
-
+
public static IEnumerable