Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ jobs:
--ignore-exit-code 8 `
--filter-not-trait "Category=IntegrationDisabled" `
--parallel-algorithm aggressive `
--max-threads 4.0x
--max-threads 2.0x
env:
# Cosmos DB Emulator connection settings
COSMOSDB_ENDPOINT: https://localhost:8081
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,9 @@ private async Task WaitForConditionAsync(Func<Task<bool>> condition, string mess

private async Task RunSampleTestAsync(string samplePath, Func<IReadOnlyList<OutputLog>, Task> testAction)
{
// Build the sample project first (it may not have been built as part of the solution)
await this.BuildSampleAsync(samplePath);

// Start the Azure Functions app
List<OutputLog> logsContainer = [];
using Process funcProcess = this.StartFunctionApp(samplePath, logsContainer);
Expand All @@ -811,12 +814,39 @@ private async Task RunSampleTestAsync(string samplePath, Func<IReadOnlyList<Outp

private sealed record OutputLog(DateTime Timestamp, LogLevel Level, string Message);

private async Task BuildSampleAsync(string samplePath)
{
this._outputHelper.WriteLine($"Building sample at {samplePath}...");

ProcessStartInfo buildInfo = new()
{
FileName = "dotnet",
Arguments = $"build -f {s_dotnetTargetFramework}",
WorkingDirectory = samplePath,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};

using Process buildProcess = new() { StartInfo = buildInfo };
buildProcess.Start();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

BuildSampleAsync calls buildProcess.Start() without checking the return value. Elsewhere in this file (e.g., StartFunctionApp / RunCommandAsync) start failures are handled explicitly; doing the same here would produce a clearer failure instead of throwing later with less context.

Suggested change
buildProcess.Start();
if (!buildProcess.Start())
{
throw new InvalidOperationException($"Failed to start build process for sample at {samplePath}.");
}

Copilot uses AI. Check for mistakes.
await buildProcess.WaitForExitAsync();

if (buildProcess.ExitCode != 0)
{
string stderr = await buildProcess.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to build sample at {samplePath}: {stderr}");
Comment on lines +835 to +838
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

On build failure the exception only includes stderr; dotnet build often writes important errors to stdout as well, and the exit code/arguments are useful for diagnosis. Consider capturing and including both stdout+stderr (or logging both) along with the exit code so failures are actionable in CI logs.

Suggested change
if (buildProcess.ExitCode != 0)
{
string stderr = await buildProcess.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to build sample at {samplePath}: {stderr}");
string stdout = await buildProcess.StandardOutput.ReadToEndAsync();
string stderr = await buildProcess.StandardError.ReadToEndAsync();
if (buildProcess.ExitCode != 0)
{
StringBuilder messageBuilder = new();
messageBuilder.AppendLine($"Failed to build sample at {samplePath}.");
messageBuilder.AppendLine($"Command: {buildInfo.FileName} {buildInfo.Arguments}");
messageBuilder.AppendLine($"WorkingDirectory: {buildInfo.WorkingDirectory}");
messageBuilder.AppendLine($"ExitCode: {buildProcess.ExitCode}");
if (!string.IsNullOrWhiteSpace(stdout))
{
messageBuilder.AppendLine("StandardOutput:");
messageBuilder.AppendLine(stdout);
}
if (!string.IsNullOrWhiteSpace(stderr))
{
messageBuilder.AppendLine("StandardError:");
messageBuilder.AppendLine(stderr);
}
throw new InvalidOperationException(messageBuilder.ToString());

Copilot uses AI. Check for mistakes.
Comment on lines +833 to +838
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

BuildSampleAsync redirects stdout/stderr but never drains them; calling WaitForExitAsync() first can hang if the buffers fill (dotnet build can be chatty). Consider following the same pattern as RunCommandAsync (BeginOutputReadLine/BeginErrorReadLine) or reading both streams asynchronously before awaiting process exit, and add a timeout/cancellation token to avoid CI deadlocks.

Suggested change
await buildProcess.WaitForExitAsync();
if (buildProcess.ExitCode != 0)
{
string stderr = await buildProcess.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to build sample at {samplePath}: {stderr}");
// Begin reading stdout and stderr immediately to avoid deadlocks if buffers fill.
Task<string> stdOutTask = buildProcess.StandardOutput.ReadToEndAsync();
Task<string> stdErrTask = buildProcess.StandardError.ReadToEndAsync();
// Apply a timeout to avoid CI deadlocks if the build hangs.
TimeSpan buildTimeout = TimeSpan.FromMinutes(5);
Task waitForExitTask = buildProcess.WaitForExitAsync();
Task completedTask = await Task.WhenAny(waitForExitTask, Task.Delay(buildTimeout));
if (completedTask != waitForExitTask)
{
// Timed out; try to kill the process and surface captured output.
try
{
if (!buildProcess.HasExited)
{
buildProcess.Kill(entireProcessTree: true);
}
}
catch
{
// Ignore failures when attempting to kill the process.
}
string timedOutStdOut = await stdOutTask;
string timedOutStdErr = await stdErrTask;
throw new TimeoutException(
$"Timed out building sample at {samplePath} after {buildTimeout}. " +
$"Stdout: {timedOutStdOut}{Environment.NewLine}Stderr: {timedOutStdErr}");
}
// Ensure the process has fully exited and the streams are drained.
await waitForExitTask;
string stdOut = await stdOutTask;
string stdErr = await stdErrTask;
if (buildProcess.ExitCode != 0)
{
throw new InvalidOperationException(
$"Failed to build sample at {samplePath} (exit code {buildProcess.ExitCode}): {stdErr}");

Copilot uses AI. Check for mistakes.
}

this._outputHelper.WriteLine($"Build completed for {samplePath}.");
}

private Process StartFunctionApp(string samplePath, List<OutputLog> logs)
{
ProcessStartInfo startInfo = new()
{
FileName = "dotnet",
Arguments = $"run -f {s_dotnetTargetFramework} --port {AzureFunctionsPort}",
Arguments = $"run --no-build -f {s_dotnetTargetFramework} --port {AzureFunctionsPort}",
WorkingDirectory = samplePath,
UseShellExecute = false,
RedirectStandardOutput = true,
Expand Down
Loading