diff --git a/.agents/skills/testing/SKILL.md b/.agents/skills/testing/SKILL.md
new file mode 100644
index 000000000..9cc04b3e1
--- /dev/null
+++ b/.agents/skills/testing/SKILL.md
@@ -0,0 +1,61 @@
+---
+name: testing
+description: Provides guidance and instructions for writing, running and filtering tests.
+user-invocable: false
+---
+
+**IMPORTANT**: This repository uses xUnit v3 with Microsoft.Testing.Platform (MTP v2). Traditional `--filter` syntax does NOT work. Use the options below instead.
+
+* There should generally be one test project (under the `test` directory) per shipping project (under the `src` directory). Test projects are named after the project being tested with a `.Tests` suffix.
+* Tests use xUnit v3 with Microsoft.Testing.Platform (MTP v2). The threading tests use xUnit's AOT runner on .NET 10 and the reflection-based runner on older targets.
+* Run published NativeAOT tests with `tools/dotnet-test-cloud.ps1 -Configuration Release -IncludeNativeAOT` after `dotnet publish test -c Release`.
+
+## Running Tests
+
+**Run all tests**:
+```bash
+dotnet test --no-build -c Release
+```
+
+**Run tests for a specific test project**:
+```bash
+dotnet test --no-build -c Release
+```
+
+**Run a single test method**:
+```bash
+dotnet test --no-build -c Release -- --filter-method "ClassName.MethodName"
+```
+
+**Run all tests in a test class**:
+```bash
+dotnet test --no-build -c Release -- --filter-class "ClassName"
+```
+
+**Run tests with wildcard matching** (supports wildcards at beginning and/or end):
+```bash
+dotnet test --no-build -c Release -- --filter-method "*Pattern*"
+```
+
+**Run tests with a specific trait**:
+```bash
+dotnet test --no-build -c Release -- --filter-trait "TraitName=value"
+```
+
+**Run tests for a specific framework only**:
+```bash
+dotnet test --no-build -c Release --framework net8.0
+```
+
+**List all available tests without running them**:
+```bash
+dotnet run --no-build -c Release --framework net8.0 -- --list-tests
+```
+
+**Key points about test filtering with xUnit / MTP v2**:
+- Options after `--` are passed to the test runner, not to `dotnet test`
+- Use `--filter-method`, `--filter-class`, `--filter-namespace`, and `--filter-trait`
+- Exclude unstable tests with `--filter-not-trait "TestCategory=FailsInCloudTest"`
+- Traditional VSTest `--filter` expressions do NOT work
+- Wildcards `*` are supported at the beginning and/or end of filter values
+- See `--help` for query filter language for advanced scenarios
diff --git a/.agents/skills/update-library-template/SKILL.md b/.agents/skills/update-library-template/SKILL.md
index 941df2f2f..99731a42f 100644
--- a/.agents/skills/update-library-template/SKILL.md
+++ b/.agents/skills/update-library-template/SKILL.md
@@ -30,6 +30,28 @@ Conflicts in the following files should always be resolved by keeping the curren
* README.md
+### Test framework docs, scripts, and packages
+
+Library.Template's `AGENTS.md`, test docs, and `tools/dotnet-test-cloud.ps1` assume **TUnit** on Microsoft.Testing.Platform.
+Many consumers still use **xunit v3 + MTP**, classic **VSTest xunit**, or hybrid setups (including xunit *extension* libraries such as Xunit.StaFact / Xunit.SkippableFact / Xunit.Combinatorial, whose own tests must stay on xunit).
+
+When merging:
+
+* Prefer the template only where the consumer's prior content was **equivalent**. Do not overwrite repo-specific behavior with template placeholders or TUnit-only defaults.
+* Keep framework-specific **AGENTS.md** (and similar agent/contributor docs): filter syntax (`--filter-method` / `--filter-not-trait` vs `--treenode-filter`), FailsInCloudTest / FailureExpected exclusions, real test project paths, and `--framework` TFMs that match the repo's test projects.
+* Keep bespoke **`tools/dotnet-test-cloud.ps1`** logic when it is not equivalent to the template (per-project loops, NonTUnit vs TUnit splits, MultiRID/NativeAOT discovery, hang/blame timeouts, coverage naming, custom filters). Graft genuine template improvements (for example `IncludeNativeAOT` + `Get-NativeAOTTestProjects.ps1`) onto the repo script only when they fit.
+* Do not add unused template **PackageVersion** entries (`TUnit.Engine`, `xunit.v3.assert.aot`, etc.) to `Directory.Packages.props` unless a project in the repo actually references them (or Central Package Management truly requires them).
+* Preserve repo-specific Azure Pipelines / GitHub Actions build steps that the template lacks equivalents for (for example full **MSBuild@1** on Windows for `net35`, `IncompleteBuild` as `warnNotAsError`).
+
+### Traversal coverage, placeholders, and non-test projects
+
+* `GitVersionBaseDirectory` in the template's `Directory.Build.props` assumes the repo has exactly one `version.json`. If the repo has nested `version.json` files (typically for analyzer or source generator projects that need `assemblyVersion` precision `revision`), the repo has deliberately removed or must not have that property. Never re-add it during a merge; keep the repo's first-parent state.
+* `init.ps1` and CI restore and build only `tools/dirs.proj`. If the solution contains projects outside `src` and `test` (for example `samples/` or `benchmark/`), add them to `tools/dirs.proj` (with `Pack="false"` and/or `Publish="false"` as appropriate) so they are still restored and compiled. Otherwise `dotnet format --no-restore` and sample regressions go unnoticed.
+* `test/Directory.Build.props` marks every project under `test` as a test project. Set `false` on non-test executables there (for example BenchmarkDotNet projects) so they are not run by `dotnet test` or picked up by NativeAOT test discovery.
+* After the merge, search `CONTRIBUTING.md`, `AGENTS.md`, and source headers for template placeholders such as `test/Library.Tests/Library.Tests.csproj`, the company-name placeholder token that `Expand-Template.ps1` replaces with the author, and the template's `net8.0` examples, and replace them with the repo's real project paths, metadata, and NativeAOT-enabled frameworks.
+* If the repo already publishes NativeAOT tests through its own mechanism (for example `MultiRIDProjectReference` items in `test/dirs.proj`), either keep that mechanism and disable the template's discovery (`PublishNativeAOTTests=false`, and drop or don't wire up `Get-NativeAOTTestProjects.ps1`), or replace it fully. Don't leave both half-wired, and update `CONTRIBUTING.md` to describe whichever one the repo actually uses.
+
+
### Deleted files
Very typically, when the incoming change is to a file that was deleted locally, the correct resolution is to re-delete the file.
diff --git a/.agents/skills/update-library-template/template-release-notes.md b/.agents/skills/update-library-template/template-release-notes.md
index 975da315e..62fbddaf4 100644
--- a/.agents/skills/update-library-template/template-release-notes.md
+++ b/.agents/skills/update-library-template/template-release-notes.md
@@ -18,3 +18,75 @@ dotnet solution EXISTING.sln migrate
This will create an EXISTING.slnx file. `git add` that file, then `git rm` the old `.sln` file.
Sometimes a repo will reference the sln filename in a script or doc somewhere.
Search the repo for such references and update them to the slnx file.
+
+## Migrating from xUnit to TUnit
+
+The template now uses TUnit with Microsoft.Testing.Platform instead of the xUnit test runner.
+When merging this update into an existing repository, migrate all test projects and repo-specific test automation.
+Follow the [TUnit xUnit migration guide](https://tunit.dev/docs/migration/xunit), including its automated migration code fixes.
+
+* Replace xUnit runner/framework package references with `TUnit.Engine` and set `Exe` in each test project.
+ Remove obsolete runner dependencies such as `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, and `coverlet.collector`, along with `xunit.runner.json`.
+ Reconcile central package versions with the template's `Directory.Packages.props`.
+* Existing xUnit assertions can remain: the template retains `xunit.v3.assert`, using `xunit.v3.assert.aot` for .NET 9 and later.
+ Keep `Xunit` imports for assertions, but use TUnit for test attributes and lifecycle APIs.
+* Convert `[Fact]` and `[Theory]` to `[Test]`, and `[InlineData]` to `[Arguments]`.
+ Migrate member/class data sources, traits, fixtures, collection behavior, setup/teardown, and test output according to the migration guide.
+ Review tests that depend on xUnit's execution ordering or parallelization rules; do not assume those rules carry over.
+* Merge the `global.json` Microsoft.Testing.Platform runner setting, test directory build files, traversal projects, and updated PowerShell/YAML automation.
+ Keep every test project in the repository's solution for managed test runs.
+ The traversals discover `.csproj` files under `src` and `test`; adjust discovery if your projects live elsewhere or use another project language.
+* Update custom test commands and filters.
+ VSTest `--filter` expressions do not work with this runner; pass TUnit filters after `--`, for example `-- --treenode-filter "/*/*/ClassName/MethodName"`.
+ Replace any xUnit-specific CI result or coverage handling with the template's Microsoft.Testing.Platform integration.
+* Validate managed tests and coverage, then run `dotnet publish tools\dirs.proj -c Release` and `.\tools\dotnet-test-cloud.ps1 -Configuration Release -IncludeNativeAOT`.
+ Test projects targeting .NET 8 or later are eligible for NativeAOT publication by default.
+ If a project cannot support NativeAOT, set `false` in its project file; it will still run as managed tests.
+
+## Template expansion validation
+
+The `tools/Validate-TemplateExpansion.ps1` script and the template-expansion validation steps in Azure Pipelines and GitHub Actions exist only to test Library.Template itself.
+They clean the working tree, run `Expand-Template.ps1`, and build the expanded result after the normal build and test sequence has completed.
+
+Do not bring `tools/Validate-TemplateExpansion.ps1`, `azure-pipelines/expand-template.yml`, or the block marked `TEMPLATE EXPANSION VALIDATION` in `.github/workflows/build.yml` into an existing repository.
+These files and steps are removed automatically when creating a repository through `Expand-Template.ps1`, and should likewise be discarded while merging template updates into an already-expanded repository.
+
+## NativeAOT compatibility validation
+
+Shipping projects now set `IsAotCompatible` for target frameworks compatible with .NET 8.
+Apply the same conditional property to each shipping project that is intended to support NativeAOT, then resolve all trim and AOT analyzer warnings rather than suppressing them broadly.
+
+The new `test/AotCompatibilityTest` project catches NativeAOT compiler warnings that library analyzers may not report.
+Keep it in the solution, add every NativeAOT-compatible shipping assembly as a `TrimmerRootAssembly`, and retain its publishable entry in `test/dirs.proj`.
+CI validates this project through the existing traversal publish on Windows, Linux, and macOS; it is intentionally marked `IsTestProject=false` because publishing successfully is the test.
+
+## NativeAOT opt-out and AI merge guidance
+
+Not every repo wants to take on NativeAOT readiness or NativeAOT-published test validation.
+If a receiving repo does not intend to support NativeAOT, opt out explicitly instead of inheriting the template's defaults.
+
+For test projects that should remain managed-only:
+
+* Set `false` in the project file or in a repo-level `Directory.Build.props`.
+* Leave the managed test project in the solution so usual IL-based testing continues to work.
+* Keep `dotnet test` and the managed coverage flow unchanged; only the NativeAOT publish step is disabled.
+
+For shipping libraries that do not want NativeAOT compatibility analysis:
+
+* Remove `IsAotCompatible` from the project, or set it only for specific target frameworks that truly support NativeAOT.
+* Do not add new AOT compatibility warnings to the build if the library is not intended to be AOT-compatible.
+* If the repo does not target .NET 8 or later, the property should be omitted entirely rather than applied unconditionally.
+
+For the compatibility project itself:
+
+* Remove the `test/AotCompatibilityTest` project from the solution if NativeAOT validation is out of scope.
+* Remove its entry from `test/dirs.proj` so the traversal publish no longer publishes it.
+* Keep the project only when the repo intends to validate shipping assemblies under NativeAOT in CI.
+
+When merging this template into a downstream repo, the AI assistant should ask whether NativeAOT is a relevant target for that repo before applying these changes if `IsAotCompatible` is not already set by any of the projects.
+If the answer is no, it should apply the opt-out steps above instead of blindly keeping the NativeAOT defaults.
+This is especially important for repos that are not shipping a NativeAOT-ready library or that cannot support the extra publish-time validation in CI.
+If the repo owner is unsure about NativeAOT support, you might offer to test the repo for NativeAOT readiness by attempting a NativeAOT publish of the relevant projects and observing whether any compatibility warnings or errors are produced.
+
+In the event the user elects for NativeAOT targeting, make efforts to get tests to work under NativeAOT as well so the shipping code can confidently advertise its NativeAOT compatibility.
+If aspects of the library or tests make successful builds, publish or test runs problematic, discuss this with the repo owner before making significant changes or giving up on NativeAOT support.
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index ac2120a2d..97c8b80be 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -10,7 +10,7 @@
"rollForward": false
},
"dotnet-coverage": {
- "version": "18.11.2",
+ "version": "18.12.0",
"commands": [
"dotnet-coverage"
],
@@ -24,7 +24,7 @@
"rollForward": false
},
"docfx": {
- "version": "2.78.5",
+ "version": "2.81.0",
"commands": [
"docfx"
],
diff --git a/.github/actions/publish-artifacts/action.yaml b/.github/actions/publish-artifacts/action.yaml
index 87b2f9c03..880760883 100644
--- a/.github/actions/publish-artifacts/action.yaml
+++ b/.github/actions/publish-artifacts/action.yaml
@@ -14,46 +14,46 @@ runs:
- name: ๐ข Upload project.assets.json files
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: projectAssetsJson-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/projectAssetsJson
continue-on-error: true
- name: ๐ข Upload variables
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: variables-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/Variables
continue-on-error: true
- name: ๐ข Upload build_logs
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: build_logs-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/build_logs
continue-on-error: true
- name: ๐ข Upload testResults
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: testResults-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/testResults
continue-on-error: true
- name: ๐ข Upload coverageResults
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: coverageResults-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/coverageResults
continue-on-error: true
- name: ๐ข Upload symbols
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: symbols-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/symbols
continue-on-error: true
- name: ๐ข Upload deployables
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: deployables-${{ runner.os }}
path: ${{ runner.temp }}/_artifacts/deployables
diff --git a/.github/renovate.json b/.github/renovate.json
index d5154a48d..0d125c214 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -6,10 +6,37 @@
"github>microsoft/vs-renovate-presets:dotnet_packages_LTS",
"github>microsoft/vs-renovate-presets:xunitv2"
],
+ "labels": ["dependencies"],
"packageRules": [
{
"matchFileNames": ["Directory.Packages.Analyzers.props"],
"enabled": false
+ },
+ {
+ "matchPackageNames": ["nbgv", "nerdbank.gitversioning"],
+ "groupName": "nbgv and nerdbank.gitversioning updates"
+ },
+ {
+ "matchPackageNames": ["xunit*"],
+ "groupName": "xunit"
+ },
+ {
+ "matchPackageNames": ["tunit*"],
+ "groupName": "tunit",
+ "schedule": ["on the first day of the month"]
+ },
+ {
+ "matchPackageNames": ["Microsoft.Testing.Extensions.*"],
+ "groupName": "Microsoft Testing Platform"
+ },
+ {
+ "matchDatasources": ["dotnet-version", "docker"],
+ "matchDepNames": ["dotnet-sdk", "mcr.microsoft.com/dotnet/sdk"],
+ "groupName": "Dockerfile and global.json updates"
+ },
+ {
+ "matchPackageNames": ["*"],
+ "allowedVersions": "!/-g[a-f0-9]+$/"
}
],
"customManagers": [
diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
index 71adf9e73..74a625667 100644
--- a/.github/workflows/copilot-setup-steps.yml
+++ b/.github/workflows/copilot-setup-steps.yml
@@ -16,7 +16,7 @@ on:
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index c0492a90d..9616f6949 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
+ workflow_dispatch:
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
@@ -22,7 +23,7 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -37,7 +38,12 @@ jobs:
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: docfx/_site
+ # Artifacts from prior attempts remain visible on re-runs, so make the name unique per attempt
+ # to avoid deploy-pages failing due to multiple artifacts with the same name.
+ name: github-pages-${{ github.run_attempt }}
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
+ with:
+ artifact_name: github-pages-${{ github.run_attempt }}
diff --git a/.github/workflows/docs_validate.yml b/.github/workflows/docs_validate.yml
index 80e11913b..15c770da1 100644
--- a/.github/workflows/docs_validate.yml
+++ b/.github/workflows/docs_validate.yml
@@ -12,18 +12,51 @@ jobs:
build:
name: ๐ Doc validation
runs-on: ubuntu-26.04
+ permissions:
+ contents: read
+ id-token: write
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
- fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
- - name: ๐ Markup Link Checker (mlc)
- uses: becheran/mlc@7ec24825cefe0c9c8c6bac48430e1f69e3ec356e # v1.2.0
- with:
- args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/*,https://github.com/microsoft/vssdktestfx*,https://github.com/Microsoft/perfview*,https://github.com/microsoft/perfview* -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc*,https://www.npmjs.com/package/*,https://get.dot.net/
- - name: โ Install prerequisites
- run: |
- ./init.ps1 -UpgradePrerequisites
- dotnet --info
- shell: pwsh
- - name: ๐ Verify docfx build
- run: dotnet docfx docfx/docfx.json --warningsAsErrors --disableGitFeatures
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
+ - name: ๐ Markup Link Checker (mlc)
+ uses: becheran/mlc@b0d0d353e04fcbcfd1d86716103d78a61e5b25dd # v1.2.2
+ with:
+ args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/*,https://github.com/microsoft/vssdktestfx*,https://github.com/Microsoft/perfview*,https://github.com/microsoft/perfview* -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc*,https://www.npmjs.com/package/*,https://get.dot.net/
+ - name: Authenticate to Azure with GitHub OIDC
+ id: azure-login
+ # This tenant requires enterprise-issued assertions. Other owners restore anonymously.
+ # Same-repository dependency update PRs authenticate; fork PRs do not.
+ if: github.repository_owner == 'microsoft' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
+ uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
+ with:
+ client-id: 2799af29-63f3-404f-bdcf-67ff9c70abc9
+ tenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47
+ allow-no-subscriptions: true
+ audience: api://AzureADTokenExchange
+ - name: Configure Azure Artifacts credentials
+ if: steps.azure-login.outcome == 'success'
+ shell: pwsh
+ run: |
+ $accessToken = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken --output tsv
+ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($accessToken)) {
+ throw 'Failed to acquire an Azure DevOps access token.'
+ }
+ Write-Output "::add-mask::$accessToken"
+ $nugetConfig = [xml](Get-Content -LiteralPath nuget.config -Raw)
+ $sources = @($nugetConfig.configuration.packageSources.add | Where-Object {
+ $_.value -match '^https://pkgs\.dev\.azure\.com/' -or $_.value -match '^https://[\w-]+\.pkgs\.visualstudio\.com/'
+ })
+ if ($sources.Count -eq 0) {
+ throw 'No Azure Artifacts package sources were found in nuget.config.'
+ }
+ foreach ($source in $sources) {
+ Add-Content -LiteralPath $env:GITHUB_ENV -Encoding utf8 -Value "NuGetPackageSourceCredentials_$($source.key)=Username=ado;Password=$accessToken;ValidAuthenticationTypes=Basic"
+ }
+ - name: โ Install prerequisites
+ run: |
+ ./init.ps1 -UpgradePrerequisites
+ dotnet --info
+ shell: pwsh
+ - name: ๐ Verify docfx build
+ run: dotnet docfx docfx/docfx.json --warningsAsErrors --disableGitFeatures
diff --git a/.github/workflows/libtemplate-update.yml b/.github/workflows/libtemplate-update.yml
index af1a9eef8..3de947a12 100644
--- a/.github/workflows/libtemplate-update.yml
+++ b/.github/workflows/libtemplate-update.yml
@@ -12,7 +12,7 @@ on:
jobs:
merge:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
permissions:
contents: write
pull-requests: write
diff --git a/AGENTS.md b/AGENTS.md
index 387894689..abc62c5c3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -84,4 +84,4 @@ dotnet run --no-build -c Release --framework net9.0 -- --list-tests
* Honor StyleCop rules and fix any reported build warnings *after* getting tests to pass.
* In C# files, use namespace *statements* instead of namespace *blocks* for all new files that define namespaces.
* Test files are *not* expected to declare namespaces.
-* Add API doc comments to all new public and internal members.
+* Add API doc comments to all new public and internal members in shipping code under `src`. Tests and samples do not require XML API documentation; do not add XML docs to test or sample members solely to satisfy this rule.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2af8f46f5..becf7ad86 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -45,6 +45,27 @@ You can use `dotnet test` to build and/or test the repo.
There may be tests that are known to be unstable or have special requirements. These can be avoided by running tests using the [dotnet-test-cloud.ps1](tools/dotnet-test-cloud.ps1) script *after* running `dotnet build`.
+To build both managed and NativeAOT tests, run `dotnet publish test -c Release`.
+Then run `./tools/dotnet-test-cloud.ps1 -Configuration Release -IncludeNativeAOT`.
+The threading library tests retain xUnit. Their .NET 10 target uses `xunit.v3.aot.mtp-v2`
+and is the only unit-test target published as NativeAOT; older frameworks use the
+reflection-based xUnit runner. Analyzer tests remain managed-only.
+The shared tests use built-in xUnit theory data and explicit STA/UI-thread helpers
+because the reflection-based combinatorial and STA discoverers cannot run under NativeAOT.
+Keep test projects in the solution as well: managed test runs still use the solution, while NativeAOT runs use the traversal's evaluated executable paths.
+Test projects can opt out of NativeAOT publishing with `false`.
+One restore includes all test target frameworks, runtime identifiers, and NativeAOT compiler dependencies.
+Managed builds are RID-neutral by default; NativeAOT builds use the SDK's runtime-specific output directories.
+For a specified RID, managed execution and native publishing can share the same build outputs.
+Test builds keep dynamic code, startup hooks, and event tracing enabled for managed code coverage.
+The `ConfigureNativeAOTTestFeatures` target disables those features only in the native compiler's publish-time inputs, without rewriting the managed runtime configuration.
+It preserves all other runtime feature options, including invariant globalization, so native compilation and linking use consistent settings.
+Test builds use invariant globalization and retain only English satellite resources; RID-specific builds are self-contained.
+Shipping libraries targeting .NET opt into NativeAOT compatibility analysis with `IsAotCompatible`.
+The `test/NativeAOTCompatibility.Test` project complements those analyzers by rooting the shipping assembly and passing it through the NativeAOT compiler during every traversal publish.
+Add each shipping assembly that must be validated as a `TrimmerRootAssembly`, and keep this project publishable in `test/dirs.proj`.
+Root `Directory.Build.props` supplies project-reference defaults for both traversal and SDK projects that remove the `_IsPublishing` global property for managed dependencies, avoiding duplicate project instances that write to the same outputs during parallel publishing.
+
## Releases
Use `nbgv tag` to create a tag for a particular commit that you mean to release.
@@ -66,6 +87,46 @@ After making a change, you can rebuild the docs site while the localhost server
The `.github/workflows/docs.yml` GitHub Actions workflow publishes the content of these docs to github.io if the workflow itself and [GitHub Pages is enabled for your repository](https://docs.github.com/en/pages/quickstart).
+### Documentation validation feed authentication
+
+The `.github/workflows/docs_validate.yml` workflow uses GitHub OIDC to authenticate
+as the **azure-public/vside package pull** Entra application before `init.ps1` restores packages.
+It requests an Azure DevOps access token and supplies it through
+`NuGetPackageSourceCredentials_` for each Azure Artifacts source in
+`nuget.config`, including repositories that use a different name such as
+`msft_consumption_public`.
+No client secret or PAT is required, and no credentials are written to `nuget.config`.
+
+Run `tools/Configure-GitHubOidc.ps1` from the repository to configure GitHub's immutable
+OIDC subject format and create matching Entra federated credentials.
+Sign in with `gh auth login` and
+`az login --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47 --allow-no-subscriptions` first.
+The signed-in identities need repository administration and permission to manage the app's
+federated credentials. Use `-WhatIf` to preview without making changes.
+The script discovers the calling repository, trusts pull requests plus its default branch,
+and can be rerun without duplicating credentials.
+Use `-Branches microbuild,release` to add other trusted branches; the default branch
+and PR context are always included. The script reports each created or reused credential.
+Credential names identify the owner, repository, and PR or branch context, for example
+`github-AArnott-Library.Template-pull-request` and
+`github-AArnott-Library.Template-branch-main`. Names requiring punctuation replacement
+or truncation include a short hash suffix to preserve uniqueness within Entra's name limits.
+
+This changes OIDC subjects for **all workflows** in the repository. Update any other cloud
+trust policies (including environment subjects) before running it; existing Entra credentials
+are preserved. The script does not configure Azure DevOps feed permissions.
+
+Authentication is enabled only for repositories owned by the `microsoft` organization,
+because this Entra tenant requires enterprise-issued GitHub assertions.
+Same-repository dependency update PRs, including Renovate and Dependabot, authenticate
+using the job's explicit `id-token: write` permission.
+Repositories owned by non-microsoft accounts and fork PRs
+skip authentication and retain anonymous restore behavior;
+new upstream dependencies may still need to be ingested by a trusted run first.
+Do not switch this workflow to `pull_request_target` to give untrusted PR code credentials.
+Repositories based on this template must configure their own trusted subjects and, if necessary,
+update the application and tenant IDs in the workflow.
+
## Updating dependencies
This repo uses Renovate to keep dependencies current.
diff --git a/Directory.Build.props b/Directory.Build.props
index 62f74ed37..24cb72490 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -54,7 +54,6 @@
arm64$(RidOsPrefix)-$(RidOsArchitecture)
-
$(DefaultRuntimeIdentifier)
@@ -67,6 +66,11 @@
+
+
+ _IsPublishing
+ _IsPublishing
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 67f015e79..873daa0f2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,8 @@
truetrue
- 2.4.1
+ 2.5.1
+
2.0.2575.9.01.1.4
@@ -37,17 +38,18 @@
-
-
+
+
+
diff --git a/azure-pipelines/archive-sourcecode.yml b/azure-pipelines/archive-sourcecode.yml
index 13015bef5..28de232e9 100644
--- a/azure-pipelines/archive-sourcecode.yml
+++ b/azure-pipelines/archive-sourcecode.yml
@@ -36,7 +36,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml
index 646659295..e09367911 100644
--- a/azure-pipelines/build.yml
+++ b/azure-pipelines/build.yml
@@ -102,7 +102,7 @@ parameters:
- name: linuxPool
type: object
default:
- vmImage: ubuntu-24.04
+ vmImage: ubuntu-26.04
- name: macOSPool
type: object
default:
diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml
index fb6a8c3d3..98d07834c 100644
--- a/azure-pipelines/dotnet.yml
+++ b/azure-pipelines/dotnet.yml
@@ -10,13 +10,21 @@ parameters:
steps:
-- script: dotnet build tools/dirs.proj -t:build,pack,publish --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
- displayName: ๐ dotnet build
- ${{ if parameters.BuildRequiresAccessToken }}:
- env:
- SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+- ${{ if parameters.RunTests }}:
+ - script: dotnet build tools/dirs.proj -t:build,pack,publish --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
+ displayName: ๐ dotnet build
+ ${{ if parameters.BuildRequiresAccessToken }}:
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+
+- ${{ if not(parameters.RunTests) }}:
+ - script: dotnet build tools/dirs.proj -t:build,pack --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
+ displayName: ๐ dotnet build
+ ${{ if parameters.BuildRequiresAccessToken }}:
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
-- powershell: tools/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults
+- powershell: tools/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults -IncludeNativeAOT
displayName: ๐งช dotnet test
condition: and(succeeded(), ${{ parameters.RunTests }})
diff --git a/azure-pipelines/libtemplate-update.yml b/azure-pipelines/libtemplate-update.yml
index 8b342de2b..aaeb690f6 100644
--- a/azure-pipelines/libtemplate-update.yml
+++ b/azure-pipelines/libtemplate-update.yml
@@ -31,7 +31,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Unofficial.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool:
name: AzurePipelines-EO
diff --git a/azure-pipelines/official.yml b/azure-pipelines/official.yml
index edfc2e6f5..d0c99075d 100644
--- a/azure-pipelines/official.yml
+++ b/azure-pipelines/official.yml
@@ -46,7 +46,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
codeSignValidation:
diff --git a/azure-pipelines/release.yml b/azure-pipelines/release.yml
index d435dab3b..886ab8643 100644
--- a/azure-pipelines/release.yml
+++ b/azure-pipelines/release.yml
@@ -21,7 +21,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
diff --git a/azure-pipelines/unofficial.yml b/azure-pipelines/unofficial.yml
index ef52618c8..ca9b7693a 100644
--- a/azure-pipelines/unofficial.yml
+++ b/azure-pipelines/unofficial.yml
@@ -57,7 +57,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Unofficial.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
credscan:
diff --git a/azure-pipelines/vs-insertion.yml b/azure-pipelines/vs-insertion.yml
index 1851e4ea6..71499572b 100644
--- a/azure-pipelines/vs-insertion.yml
+++ b/azure-pipelines/vs-insertion.yml
@@ -23,7 +23,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
sbom:
diff --git a/azure-pipelines/vs-validation.yml b/azure-pipelines/vs-validation.yml
index 195181a8b..49786dedd 100644
--- a/azure-pipelines/vs-validation.yml
+++ b/azure-pipelines/vs-validation.yml
@@ -27,7 +27,7 @@ extends:
template: azure-pipelines/MicroBuild.1ES.Unofficial.yml@MicroBuildTemplate
parameters:
settings:
- networkIsolationPolicy: DefaultDeny,CFSClean3
+ networkIsolationPolicy: DefaultDeny,CFSClean,CFSClean2,CFSClean3
sdl:
sourceAnalysisPool: VSEng-MicroBuildVSStable
credscan:
diff --git a/global.json b/global.json
index 00a82c8e6..a5010ea45 100644
--- a/global.json
+++ b/global.json
@@ -9,6 +9,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.134",
- "Microsoft.Build.Traversal": "4.1.82"
+ "Microsoft.Build.Traversal": "4.1.82"
}
}
diff --git a/init.ps1 b/init.ps1
index 183a7156d..22f7c525c 100755
--- a/init.ps1
+++ b/init.ps1
@@ -82,7 +82,7 @@ if (!$NoPrerequisites) {
& "$PSScriptRoot\tools\Install-NuGetCredProvider.ps1" -AccessToken $AccessToken -Force:$UpgradePrerequisites
}
- & "$PSScriptRoot\tools\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality
+ & "$PSScriptRoot\tools\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality -Upgrade:$UpgradePrerequisites
if ($LASTEXITCODE -eq 3010) {
Exit 3010
}
@@ -109,10 +109,11 @@ try {
if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) {
Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor
- dotnet restore @RestoreArguments
+ dotnet restore "$PSScriptRoot/tools/dirs.proj" @RestoreArguments
if ($lastexitcode -ne 0) {
throw "Failure while restoring packages."
}
+
}
if (!$NoToolRestore -and $PSCmdlet.ShouldProcess("dotnet tool", "restore")) {
diff --git a/test/AOT.props b/test/AOT.props
index 27d5cbf21..d7b62a679 100644
--- a/test/AOT.props
+++ b/test/AOT.props
@@ -1,16 +1,43 @@
Exe
- truetrue
- true$(AvailableRuntimeIdentifiers)
- $(DefaultRuntimeIdentifier)
+ $(DefaultRuntimeIdentifier)
+
+
+
+ true
+ true
+ true
+
+
+
+ true
+ en
+
+
+ true
-
- RuntimeIdentifier
+ %(ProjectReference.GlobalPropertiesToRemove);RuntimeIdentifier;NativeAOT
+
+
+
+ false
+ false
+ false
+
+
+
+
+ false
+
+
+
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index 466be54e4..ddfaa00f2 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -3,11 +3,17 @@
+ true
+ truefalsetruetrue
+
+ $(DefaultRuntimeIdentifier)
+
+
diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets
index 3758bb8c9..1e46d9eb4 100644
--- a/test/Directory.Build.targets
+++ b/test/Directory.Build.targets
@@ -1,14 +1,54 @@
+
+
+ false
+
+
+
+
+
+ $(NoWarn);IL2104;IL3053
+
+
-
-
-
-
+
+
+
+ <_TestFramework Include="$(TargetFrameworks)" />
+
+
+
+
+
+
+
+
+
+
+
+ <_NativeExecutableExtension Condition="$([MSBuild]::IsOsPlatform('Windows'))">.exe
+
+
+ <_NativeAOTTestExecutable Include="$(MSBuildProjectFullPath)">
+ $(MSBuildProjectName)
+ $(TargetFramework)
+ $([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(PublishDir)', '$(AssemblyName)$(_NativeExecutableExtension)'))
+
+
+
diff --git a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/Microsoft.VisualStudio.Threading.Analyzers.Tests.csproj b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/Microsoft.VisualStudio.Threading.Analyzers.Tests.csproj
index a0a354341..3e911ecef 100644
--- a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/Microsoft.VisualStudio.Threading.Analyzers.Tests.csproj
+++ b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/Microsoft.VisualStudio.Threading.Analyzers.Tests.csproj
@@ -1,7 +1,9 @@
-๏ปฟ
+
+ net8.0Exe
+ falsetruetrue$(NoWarn);NU1701
@@ -66,4 +68,9 @@
+
+
+
+ true
+
diff --git a/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyInitializerTests.cs b/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyInitializerTests.cs
index 69eb2dccd..8ebcd34dc 100644
--- a/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyInitializerTests.cs
+++ b/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyInitializerTests.cs
@@ -12,7 +12,9 @@ public AsyncLazyInitializerTests(ITestOutputHelper logger)
{
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void Ctor_NullAction(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
diff --git a/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyTests.cs b/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyTests.cs
index 21254abed..32cec054f 100644
--- a/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyTests.cs
+++ b/test/Microsoft.VisualStudio.Threading.Tests/AsyncLazyTests.cs
@@ -86,7 +86,9 @@ public void CtorNullArgs()
/// Verifies that multiple sequential calls to
/// do not result in multiple invocations of the value factory.
///
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task ValueFactoryExecutedOnlyOnceSequential(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -113,7 +115,9 @@ async delegate
/// Verifies that multiple concurrent calls to
/// do not result in multiple invocations of the value factory.
///
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void ValueFactoryExecutedOnlyOnceConcurrent(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -163,7 +167,9 @@ public void ValueFactoryReleasedAfterExecution()
GC.KeepAlive(lazy);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task AsyncPumpReleasedAfterExecution(bool throwInValueFactory)
{
WeakReference collectible = await this.AsyncPumpReleasedAfterExecution_Helper(throwInValueFactory);
@@ -178,7 +184,9 @@ public async Task AsyncPumpReleasedAfterExecution(bool throwInValueFactory)
Assert.False(collectible.IsAlive);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void ValueFactoryThrowsSynchronously(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -199,7 +207,9 @@ public void ValueFactoryThrowsSynchronously(bool specifyJtf)
Assert.IsType(task1.Exception!.InnerException);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task ValueFactoryReentersValueFactorySynchronously(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -221,7 +231,9 @@ public async Task ValueFactoryReentersValueFactorySynchronously(bool specifyJtf)
await Assert.ThrowsAsync(() => lazy.GetValueAsync());
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task ValueFactoryReentersValueFactoryAsynchronously(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -293,7 +305,9 @@ public async Task GetValueAsyncAlreadyCompletedWithCancellationTokenPreCanceled(
Assert.True(lazy.IsValueFactoryCompleted);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void GetValue(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -301,7 +315,9 @@ public void GetValue(bool specifyJtf)
Assert.Equal(5, lazy.GetValue().Data);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void GetValue_Precanceled(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -309,7 +325,9 @@ public void GetValue_Precanceled(bool specifyJtf)
Assert.Throws(() => lazy.GetValue(new CancellationToken(canceled: true)));
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task GetValue_CalledAfterGetValueAsyncHasCompleted(bool specifyJtf)
{
JoinableTaskFactory? jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
@@ -318,7 +336,9 @@ public async Task GetValue_CalledAfterGetValueAsyncHasCompleted(bool specifyJtf)
Assert.Same(result, lazy.GetValue());
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task GetValue_CalledAfterGetValueAsync_InProgress(bool specifyJtf)
{
var completeValueFactory = new AsyncManualResetEvent();
@@ -339,7 +359,9 @@ async delegate
Assert.Same(results[0], results[1]);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task GetValue_ThenCanceled(bool specifyJtf)
{
var completeValueFactory = new AsyncManualResetEvent();
@@ -358,7 +380,9 @@ async delegate
await Assert.ThrowsAnyAsync(() => getValueTask);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public void GetValue_ValueFactoryThrows(bool specifyJtf)
{
var exception = new InvalidOperationException();
@@ -407,7 +431,15 @@ public void ToStringForFaultedValue()
}
[Theory]
- [CombinatorialData]
+ [InlineData(NamedSyncContext.None, NamedSyncContext.None)]
+ [InlineData(NamedSyncContext.None, NamedSyncContext.A)]
+ [InlineData(NamedSyncContext.None, NamedSyncContext.B)]
+ [InlineData(NamedSyncContext.A, NamedSyncContext.None)]
+ [InlineData(NamedSyncContext.A, NamedSyncContext.A)]
+ [InlineData(NamedSyncContext.A, NamedSyncContext.B)]
+ [InlineData(NamedSyncContext.B, NamedSyncContext.None)]
+ [InlineData(NamedSyncContext.B, NamedSyncContext.A)]
+ [InlineData(NamedSyncContext.B, NamedSyncContext.B)]
public void AsyncLazy_CompletesOnThreadWithValueFactory(NamedSyncContext invokeOn, NamedSyncContext completeOn)
{
// Set up various SynchronizationContexts that we may invoke or complete the async method with.
@@ -495,7 +527,8 @@ async delegate
/// Verifies that no deadlock occurs if the value factory synchronously blocks while switching to the UI thread.
///
[Theory]
- [CombinatorialData]
+ [InlineData(false)]
+ [InlineData(true)]
public void ValueFactoryRequiresMainThreadHeldByOtherSync(bool passJtfToLazyCtor)
{
SynchronizationContext? ctxt = SingleThreadedTestSynchronizationContext.New();
@@ -784,7 +817,9 @@ async Task FireAndForgetCodeAsync()
}
}
- [Theory, PairwiseData]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
public async Task SuppressRecursiveFactoryDetection_WithJTF(bool suppressWithJTF)
{
JoinableTaskContext? context = this.InitializeJTCAndSC();
@@ -856,7 +891,10 @@ public async Task Dispose_ValueType_Completed()
await this.AssertDisposedLazyAsync(lazy);
}
- [Theory, CombinatorialData]
+ [Theory]
+ [InlineData(DisposeStyle.IDisposable)]
+ [InlineData(DisposeStyle.SystemIAsyncDisposable)]
+ [InlineData(DisposeStyle.ThreadingIAsyncDisposable)]
public async Task Dispose_Disposable_Completed(DisposeStyle variety)
{
AsyncLazy