Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Build.UnitTests/TerminalLogger_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,5 +1070,35 @@ public async Task DisplayNodesRestoresStatusAfterMSBuildTaskYields_TestProject(b

await Verify(_outputWriter.ToString(), _settings).UniqueForOSPlatform().UseParameters(runOnCentralNode);
}

/// <summary>
/// Tests that TerminalLogger handles ProjectStarted events without a prior ProjectEvaluationFinished event.
/// This scenario can occur in multithreaded builds with taskhosts where the evaluation event may not be received
/// before the project started event.
/// </summary>
[Fact]
public void ProjectStartedWithoutEvaluation_DoesNotCrash()
{
// Start the build
_centralNodeEventSource.InvokeBuildStarted(MakeBuildStartedEventArgs());

// Intentionally skip the evaluation finished event to simulate a multithreaded build scenario
// where ProjectStarted arrives before (or without) ProjectEvaluationFinished
var buildContext = MakeBuildEventContext(evalId: -1, projectContextId: 1); // Use invalid eval ID

// This should not throw or crash
_centralNodeEventSource.InvokeProjectStarted(MakeProjectStartedEventArgs(_projectFile, buildEventContext: buildContext));

// Verify we can continue with the build
_centralNodeEventSource.InvokeTargetStarted(MakeTargetStartedEventArgs(_projectFile, "Build", buildEventContext: buildContext));
_centralNodeEventSource.InvokeTaskStarted(MakeTaskStartedEventArgs(_projectFile, "Task", buildEventContext: buildContext));
_centralNodeEventSource.InvokeTaskFinished(MakeTaskFinishedEventArgs(_projectFile, "Task", true, buildEventContext: buildContext));
_centralNodeEventSource.InvokeTargetFinished(MakeTargetFinishedEventArgs(_projectFile, "Build", true, buildEventContext: buildContext));
_centralNodeEventSource.InvokeProjectFinished(MakeProjectFinishedEventArgs(_projectFile, true, buildEventContext: buildContext));
_centralNodeEventSource.InvokeBuildFinished(MakeBuildFinishedEventArgs(true));

// If we got here without crashing, the test passed
_outputWriter.ToString().ShouldNotBeNull();
}
}
}
7 changes: 6 additions & 1 deletion src/Build/Logging/TerminalLogger/TerminalLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,12 @@ private void ProjectStarted(object sender, ProjectStartedEventArgs e)
targetFramework = evalInfo.TargetFramework;
runtimeIdentifier = evalInfo.RuntimeIdentifier;
}
System.Diagnostics.Debug.Assert(evalInfo != default, "EvalProjectInfo should have been captured before ProjectStarted");
else
{
// In some scenarios (e.g., multithreaded builds with taskhosts), the evaluation event
// may not have been received before the project started event. Create a fallback.
evalInfo = new EvalProjectInfo(evalContext, e.ProjectFile, null, null);
}

TerminalProjectInfo projectInfo = new(c, evalInfo, _createStopwatch?.Invoke());
_projects[c] = projectInfo;
Expand Down
Loading