Skip to content

Updates to Aspire.Hosting.Testing#4444

Merged
DamianEdwards merged 41 commits intomainfrom
damianedwards/test-resource-watcher
Jun 21, 2024
Merged

Updates to Aspire.Hosting.Testing#4444
DamianEdwards merged 41 commits intomainfrom
damianedwards/test-resource-watcher

Conversation

@DamianEdwards
Copy link
Member

@DamianEdwards DamianEdwards commented Jun 11, 2024

This change enables capturing resource logs and forwarding them to ILogger during testing when using Aspire.Hosting.Testing, along with an update to ResourceNotificationService to enable waiting for a resource to transition to a given state text.

  • Forward resource logs to ILogger in Aspire.Hosting.Testing
  • Add ResourceNotificationService.WaitForResourceAsync
  • Added more states to KnownResourceStates
  • Updates test templates to use ResourceNotificationService.WaitForResourceAsync
  • Updates xUnit templates with a comment about packages that map ILogger to ITestOutputHelper

Some open questions:

  • Should we have an option/method to control whether resource logs are forwarded to ILogger or not in Aspire.Hosting.Testing?
  • Should we have an option to control the log level the resource logs are logged at (stderr vs. stdout, etc.)?
  • Should we update ResourceNotificationService to cancel WatchAsync streams when IHostApplicationLifetime.ApplicationStopping is triggered?

Fixes #4445
Fixes #2790

File -> New -> Aspire Starter App with xUnit.net test project with these changes:
image

File -> New -> Aspire Starter app with MSTest test project:
image

File -> New -> Aspire Starter app with NUnit test project:
image

@ghost ghost added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Jun 11, 2024
{
foreach (var line in logEvent)
{
var logLevel = line.IsErrorMessage ? LogLevel.Error : LogLevel.Information;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the log level the resource LogLines are logged at an option?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was on the fence, but then I thought we might want to tune things so we only get errors/warnings to help with memory consumption in the test case (for longer running tests).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be done already by filtering the logs to error or higher in the test setup I think. But it might be desirable to be able to change the level the non-errors are logged at? Not sure, might not be worth the complexity.

@DamianEdwards DamianEdwards force-pushed the damianedwards/test-resource-watcher branch from 6caf78c to 75a6baf Compare June 12, 2024 20:38

Assert.True(res.ExitCode != 0, $"Expected the tests project build to fail");
Assert.Matches("System.ArgumentException.*Resource 'webfrontend' not found.", res.Output);
Assert.Matches("System.InvalidOperationException.*Sequence contains no matching element", res.Output);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. (In the existing code) @radical - Why are we expecting the tests to fail?
  2. For the updated code, isn't the original exception better? Seems like we are making this worse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. (In the existing code) @radical - Why are we expecting the tests to fail?

The test creates an aspire project, then a aspire-xunit project, uncomments the code in the generated test file, and runs that. And that causes the test to fail because the test is expecting a webfrontend to exist. So, this what the user would see.

We could add a webfrontend to the solution, and patch that up so it gets used in this test. Do we want to do that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move the CreateHttpClient call to before the WaitForResourceAsync call to retain the original exception message and not bother with finding the resource in the DistributedApplicationModel.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and adjusted the order of lines in the test templates so that the original exception is thrown. @radical I do think it would be better to update these tests to add a resource though so the tests actually pass. Perhaps we could just base it on the starter template instead (without the tests project option) in order to get the web project by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the understanding we were validating the scenario @radical describes above. What is the experience when a dev adds the test project, and just runs it. Do they get an understandable error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you just add the test project and run it you don't get an error. The test here actually uncomments the sample code and then runs the test, which fails because the sample code assumes there's a resource to test against (which the comments say).

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Might want to wait to see if @davidfowl doesn't approve of the ResourceLoggerService change.

/// <param name="builder">The resource builder instance.</param>
/// <param name="options">Options for configuring the Dapr sidecar, if any.</param>
/// <returns>The resource builder instance.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0027:API with optional parameter(s) should have the most parameters amongst its public overloads", Justification = "<Pending>")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unrelated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm not sure. Possibly I got a "suppression not required" diagnostic and acted on it? Will have to check

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep:
image

@afscrome
Copy link
Contributor

afscrome commented Jun 20, 2024

@DamianEdwards Wonder if the logger race is similar to #4606

Although on second thoughts, if it's the same issue I'd expect logs to be lost rather than duplicated.

Copy link
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking forward to this going in. I think I've got an immediate use case for it in some tests I'm writing!

@radical
Copy link
Member

radical commented Jun 20, 2024

You can merge main to get #4614 which disables the failing WithDockerfileTests.

@DamianEdwards
Copy link
Member Author

DamianEdwards commented Jun 20, 2024

@davidfowl @ReubenBond @eerhardt @mitchdenny I've reimplemented the ResourceLoggerService.WatchAsync logic based on a discussion I had with @davidfowl but it changes the behavior in a fundamental way, i.e. new subscribers won't get the backlog until after the next log is sent since they subscribed, as flushing the backlog was moved to the OnLog subscription method itself. I've updated the tests to account for this but I'd appreciate folks' thoughts on whether this is actually what we want. It definitely makes testing a bit harder (need to coordinate waiting for subs to change before publishing logs and need to publish extra logs to get the backlog) and could lead to situations where no historical logs are shown until the next log is sent, or never shown if another log is never sent (e.g. if the resource has stopped).

UPDATE: OK I updated this again so that the backlog is always returned immediately and is synchronized with the OnNewLog method so that dupes are properly handled there. This should return us to the original behavior and handle the de-duping required due to the race, hopefully in a way that everyone is happier with 😄

@radical
Copy link
Member

radical commented Jun 20, 2024

Green!!

@radical
Copy link
Member

radical commented Jun 21, 2024

Since #4500 has merged, this PR should merge main to check if everything still works.

@mitchdenny
Copy link
Member

kicking build again because it failed because of a code coverage upload issue.

@mitchdenny
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@DamianEdwards DamianEdwards merged commit 72edbe0 into main Jun 21, 2024
@DamianEdwards DamianEdwards deleted the damianedwards/test-resource-watcher branch June 21, 2024 14:57

flushBacklogSync.Wait(cancellationToken);
// We need to ensure we don't write this log to the channel if it was already in the backlog
if (backlogSnapshot?.Contains(log) == false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can backlogSnapshot ever be null here? flushBacklogSync.Set(); is called after backlogSnapshot is set. So if we are waiting for it to be set, backlogSnapshot should always be non-null.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I had this throwing when null previously but that seemed extreme. Are you suggesting this is a case we should just bang the nullability warning away?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I personally would just do:

Debug.Assert(backlogSnapshot is not null, "backlogSnapshot is always set before flushBacklogSync is set");
if (!backlogSnapshot.Contains(log))

(Note that this is really just a nit on the readability of the method. Not critical to change.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I personally like the Debug.Assert approach too but this seems like another example of where we're not super consistent in the repo.

@github-actions github-actions bot locked and limited conversation to collaborators Jul 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ResourceNotificationService.WaitForResourceAsync Testing: allow logs to flow into test output

8 participants