Skip to content

fix: skip IResourceWithoutLifetime resources in Aspire fixture wait logic#5268

Merged
thomhurst merged 1 commit intomainfrom
fix/aspire-skip-resources-without-lifetime
Mar 27, 2026
Merged

fix: skip IResourceWithoutLifetime resources in Aspire fixture wait logic#5268
thomhurst merged 1 commit intomainfrom
fix/aspire-skip-resources-without-lifetime

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

Summary

  • Aspire 13.2.0 introduces internal {name}-rebuilder resources for .NET Project resources that appear in DistributedApplicationModel.Resources but never transition to Running/Healthy
  • AllHealthy/AllRunning wait modes now filter out IResourceWithoutLifetime resources (data-holder/reference resources with no lifecycle) before waiting, preventing indefinite hangs
  • Skipped resources are logged via LogProgress for visibility

Fixes #5260

Test plan

  • Verify TUnit.Aspire builds on all target frameworks (net8.0, net9.0, net10.0)
  • Verify Aspire integration tests pass with Aspire 13.2.0 AppHost that includes project resources (which now have companion rebuilder resources)
  • Verify AllHealthy mode no longer hangs on rebuilder resources
  • Verify AllRunning mode no longer hangs on rebuilder resources
  • Verify Named mode is unaffected (does not use the new filter)
  • Verify skipped resources appear in log output

…ogic

Aspire 13.2.0 introduces internal rebuilder resources for .NET Project
resources that appear in DistributedApplicationModel.Resources but never
transition to Running/Healthy. This caused AllHealthy/AllRunning wait
modes to hang until timeout.

Filter out IResourceWithoutLifetime resources (data-holder/reference
resources with no lifecycle) when collecting resources to wait for.

Fixes #5260
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 27, 2026

Code Review

This is a clean, well-scoped fix for the Aspire 13.2.0 regression. Here's my analysis:

What's good

Correct interface check. Filtering on IResourceWithoutLifetime is the right seam — these are data-holder/reference resources that Aspire explicitly marks as having no lifecycle, so waiting for them to become Running/Healthy is inherently meaningless.

Lazy allocation is consistent with TUnit's performance-first principles. Using skipped ??= [] avoids allocating the skipped list in the common case where all resources have a lifecycle, which is a nice touch.

Named mode is correctly excluded from the filter. If a user explicitly opts in to waiting for a specific resource by name, they've made that decision — silently filtering it would violate the principle of least surprise. The PR description correctly calls this out.

Early return in WaitForResourcesWithFailFastAsync is already there (resourceNames.Count == 0), so the edge case where every resource is IResourceWithoutLifetime is handled gracefully without any additional changes.

Logging skipped resources provides the right amount of visibility — future readers won't wonder why rebuilder resources disappeared from the wait log.


One minor observation

In InitializeAsync at line 266, the fallback timeout error message still enumerates all resources from model.Resources (including IResourceWithoutLifetime ones):

var resourceNames = string.Join(", ", model.Resources.Select(r => $"'{r.Name}'"));

This is the catch block for custom WaitForResourcesAsync overrides, so it's rarely reached with the default implementation. But if a user is debugging an AllHealthy/AllRunning hang, seeing {name}-rebuilder in the timeout error could be confusing since the default path skips them. Consider using GetWaitableResourceNames(model) here too for consistency:

var waitableNames = GetWaitableResourceNames(model);
var resourceNames = string.Join(", ", waitableNames.Select(n => $"'{n}'"));

This is a nice-to-have, not a blocker — the fallback path is only reached when overriding WaitForResourcesAsync without the fail-fast helper.


Overall

The root cause fix is correct, the implementation is clean, and the change is minimal. The lazy-init pattern and correct exclusion of Named mode show good attention to detail. The observation above is minor. Approved.

@thomhurst thomhurst enabled auto-merge (squash) March 27, 2026 18:13
@thomhurst thomhurst merged commit 67166dc into main Mar 27, 2026
12 of 14 checks passed
@thomhurst thomhurst deleted the fix/aspire-skip-resources-without-lifetime branch March 27, 2026 18:45
This was referenced Mar 27, 2026
github-actions bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Mar 30, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.21.6 to
1.23.7.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.23.7

<!-- Release notes generated using configuration in .github/release.yml
at v1.23.7 -->

## What's Changed
### Other Changes
* feat: use results directory provided by Microsoft Testing Platform in
HtmlReporter by @​DavidZidar in
thomhurst/TUnit#5294
* feat: add benchmarks for Imposter and Mockolate mocking frameworks by
@​vbreuss in thomhurst/TUnit#5295
* feat: add TUnit0080 analyzer for missing polyfill types by @​thomhurst
in thomhurst/TUnit#5292
* fix: respect user-set TUnitImplicitUsings from Directory.Build.props
by @​thomhurst in thomhurst/TUnit#5280
* perf: optimize TUnit.Mocks hot paths by @​thomhurst in
thomhurst/TUnit#5300
### Dependencies
* chore(deps): update tunit to 1.22.19 by @​thomhurst in
thomhurst/TUnit#5296

## New Contributors
* @​DavidZidar made their first contribution in
thomhurst/TUnit#5294

**Full Changelog**:
thomhurst/TUnit@v1.22.19...v1.23.7

## 1.22.19

<!-- Release notes generated using configuration in .github/release.yml
at v1.22.19 -->

## What's Changed
### Other Changes
* Add mock library benchmarks: TUnit.Mocks vs Moq, NSubstitute,
FakeItEasy by @​Copilot in thomhurst/TUnit#5284
* perf: lazily initialize optional MockEngine collections by @​thomhurst
in thomhurst/TUnit#5289
* Always emit TUnit.Mocks.Generated namespace from source generator by
@​Copilot in thomhurst/TUnit#5282
### Dependencies
* chore(deps): update tunit to 1.22.6 by @​thomhurst in
thomhurst/TUnit#5285


**Full Changelog**:
thomhurst/TUnit@v1.22.6...v1.22.19

## 1.22.6

<!-- Release notes generated using configuration in .github/release.yml
at v1.22.6 -->

## What's Changed
### Other Changes
* fix: use IComputeResource to filter waitable Aspire resources by
@​thomhurst in thomhurst/TUnit#5278
* fix: preserve StateBag when creating per-test TestBuilderContext by
@​thomhurst in thomhurst/TUnit#5279
### Dependencies
* chore(deps): update tunit to 1.22.3 by @​thomhurst in
thomhurst/TUnit#5275


**Full Changelog**:
thomhurst/TUnit@v1.22.3...v1.22.6

## 1.22.3

<!-- Release notes generated using configuration in .github/release.yml
at v1.22.3 -->

## What's Changed
### Other Changes
* fix: pass assembly version properties to dotnet pack by @​thomhurst in
thomhurst/TUnit#5274
### Dependencies
* chore(deps): update tunit to 1.22.0 by @​thomhurst in
thomhurst/TUnit#5272


**Full Changelog**:
thomhurst/TUnit@v1.22.0...v1.22.3

## 1.22.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.22.0 -->

## What's Changed
### Other Changes
* perf: run GitVersion once in CI instead of per-project by @​slang25 in
thomhurst/TUnit#5259
* perf: disable GitVersion MSBuild task globally by @​thomhurst in
thomhurst/TUnit#5266
* fix: skip IResourceWithoutLifetime resources in Aspire fixture wait
logic by @​thomhurst in thomhurst/TUnit#5268
* fix: relax docs site Node.js engine constraint to >=24 by @​thomhurst
in thomhurst/TUnit#5269
* fix: catch unhandled exceptions in ExecuteRequestAsync to prevent IDE
RPC crashes by @​thomhurst in
thomhurst/TUnit#5271
* feat: register HTML report as MTP session artifact by @​thomhurst in
thomhurst/TUnit#5270
### Dependencies
* chore(deps): update tunit to 1.21.30 by @​thomhurst in
thomhurst/TUnit#5254
* chore(deps): update opentelemetry to 1.15.1 by @​thomhurst in
thomhurst/TUnit#5258
* chore(deps): bump node-forge from 1.3.1 to 1.4.0 in /docs by
@​dependabot[bot] in thomhurst/TUnit#5255
* chore(deps): bump picomatch from 2.3.1 to 2.3.2 in /docs by
@​dependabot[bot] in thomhurst/TUnit#5256
* chore(deps): update react by @​thomhurst in
thomhurst/TUnit#5261
* chore(deps): update node.js to >=18.20.8 by @​thomhurst in
thomhurst/TUnit#5262
* chore(deps): update node.js to v24 by @​thomhurst in
thomhurst/TUnit#5264


**Full Changelog**:
thomhurst/TUnit@v1.21.30...v1.22.0

## 1.21.30

<!-- Release notes generated using configuration in .github/release.yml
at v1.21.30 -->

## What's Changed
### Other Changes
* feat: add test discovery Activity span for tracing by @​thomhurst in
thomhurst/TUnit#5246
* Fix mock generator not preserving nullable annotations on reference
types by @​Copilot in thomhurst/TUnit#5251
* Fix ITestSkippedEventReceiver not firing for [Skip]-attributed tests
by @​thomhurst in thomhurst/TUnit#5253
* Use CallerArgumentExpression for TestDataRow by default. by @​m-gasser
in thomhurst/TUnit#5135
### Dependencies
* chore(deps): update tunit to 1.21.24 by @​thomhurst in
thomhurst/TUnit#5247


**Full Changelog**:
thomhurst/TUnit@v1.21.24...v1.21.30

## 1.21.24

<!-- Release notes generated using configuration in .github/release.yml
at v1.21.24 -->

## What's Changed
### Other Changes
* Fix OpenTelemetry missing root span by reordering session activity
lifecycle by @​Copilot in thomhurst/TUnit#5245
### Dependencies
* chore(deps): update tunit to 1.21.20 by @​thomhurst in
thomhurst/TUnit#5241
* chore(deps): update dependency stackexchange.redis to 2.12.8 by
@​thomhurst in thomhurst/TUnit#5243


**Full Changelog**:
thomhurst/TUnit@v1.21.20...v1.21.24

## 1.21.20

<!-- Release notes generated using configuration in .github/release.yml
at v1.21.20 -->

## What's Changed
### Other Changes
* fix: respect TUnitImplicitUsings set in Directory.Build.props by
@​thomhurst in thomhurst/TUnit#5225
* feat: covariant assertions for interfaces and non-sealed classes by
@​thomhurst in thomhurst/TUnit#5226
* feat: support string-to-parseable type conversions in [Arguments] by
@​thomhurst in thomhurst/TUnit#5227
* feat: add string length range assertions by @​thomhurst in
thomhurst/TUnit#4935
* Fix BeforeEvery/AfterEvery hooks for Class and Assembly not being
executed by @​Copilot in thomhurst/TUnit#5239
### Dependencies
* chore(deps): update tunit to 1.21.6 by @​thomhurst in
thomhurst/TUnit#5228
* chore(deps): update dependency gitversion.msbuild to 6.7.0 by
@​thomhurst in thomhurst/TUnit#5229
* chore(deps): update dependency gitversion.tool to v6.7.0 by
@​thomhurst in thomhurst/TUnit#5230
* chore(deps): update aspire to 13.2.0 - autoclosed by @​thomhurst in
thomhurst/TUnit#5232
* chore(deps): update dependency typescript to v6 by @​thomhurst in
thomhurst/TUnit#5233
* chore(deps): update dependency polyfill to 9.23.0 by @​thomhurst in
thomhurst/TUnit#5235
* chore(deps): update dependency polyfill to 9.23.0 by @​thomhurst in
thomhurst/TUnit#5236


**Full Changelog**:
thomhurst/TUnit@v1.21.6...v1.21.20

Commits viewable in [compare
view](thomhurst/TUnit@v1.21.6...v1.23.7).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.21.6&new-version=1.23.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Timeout — Resources not ready

1 participant