Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8dd8204
feat: add `maui profile` command for Android startup tracing
simonrozsival Apr 2, 2026
777bf3f
fix: drop net11.0 from StartupProfiling targets (CI only has .NET 10 …
simonrozsival Apr 4, 2026
73b478e
fix: suppress NU5118 duplicate README in StartupProfiling package
simonrozsival Apr 4, 2026
d41cb00
Merge branch 'main' of github.com:dotnet/maui-labs into feature/maui-…
simonrozsival Apr 10, 2026
b4b2d5c
Improve maui profile startup tracing
simonrozsival Apr 10, 2026
a3b58ef
Simplify maui profile cleanup
simonrozsival Apr 10, 2026
3960b2c
Address startup profiling review feedback
simonrozsival Apr 10, 2026
3933e8c
Polish profile progress and cancellation UX
simonrozsival Apr 10, 2026
d762c51
Add iOS simulator support to maui profile
simonrozsival Apr 11, 2026
81fe732
Fix iOS simulator Release profiling
simonrozsival Apr 11, 2026
d0b12d2
Handle mixed diagnostics tool installs
simonrozsival Apr 11, 2026
745e838
Refactor ProfileCommand into partial files
simonrozsival Apr 11, 2026
4080a26
Replace partial ProfileCommand split
simonrozsival Apr 11, 2026
fc4e9ce
Simplify profile command structure
simonrozsival Apr 12, 2026
60ac9e4
Add startup subcommand for maui profile
simonrozsival Apr 14, 2026
a692cf9
Fix profile trace stop race
simonrozsival Apr 14, 2026
3d6a45e
Improve Android startup PGO tracing
simonrozsival Apr 15, 2026
fcdf59f
Merge origin/main into feature/maui-profile-command
simonrozsival Apr 15, 2026
c5f0c3a
Restore manual stop for Android startup tracing
simonrozsival Apr 15, 2026
59081e5
Simplify startup profiling cleanup
simonrozsival Apr 15, 2026
12865c8
Refine startup trace finalization
simonrozsival Apr 15, 2026
6e434f5
Harden startup trace review fixes
simonrozsival Apr 15, 2026
f656f2c
Merge origin/main into feature/maui-profile-command
simonrozsival Apr 16, 2026
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
1 change: 1 addition & 0 deletions MauiLabs.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Folder Name="/src/Cli/">
<Project Path="src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj" />
<Project Path="src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj" />
<Project Path="src/Cli/Microsoft.Maui.StartupProfiling/Microsoft.Maui.StartupProfiling.csproj" />
</Folder>
<Folder Name="/src/DevFlow/">
<Project Path="src/DevFlow/Microsoft.Maui.DevFlow.Agent.Core/Microsoft.Maui.DevFlow.Agent.Core.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/Cli/Cli.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"projects": [
"src\\Cli\\Microsoft.Maui.Cli\\Microsoft.Maui.Cli.csproj",
"src\\Cli\\Microsoft.Maui.Cli.UnitTests\\Microsoft.Maui.Cli.UnitTests.csproj",
"src\\Cli\\Microsoft.Maui.StartupProfiling\\Microsoft.Maui.StartupProfiling.csproj",
"src\\DevFlow\\Microsoft.Maui.DevFlow.Driver\\Microsoft.Maui.DevFlow.Driver.csproj"
]
}
Expand Down
93 changes: 93 additions & 0 deletions src/Cli/Microsoft.Maui.Cli.UnitTests/OutputFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text;
using System.Text.Json.Nodes;
using Microsoft.Maui.Cli.Errors;
using Microsoft.Maui.Cli.Commands;
using Microsoft.Maui.Cli.Models;
using Microsoft.Maui.Cli.Output;
using Spectre.Console;
Expand Down Expand Up @@ -109,6 +111,27 @@ private static (SpectreOutputFormatter formatter, TestConsole console) CreateTes
return (formatter, console);
}

private static Process StartTestProcessThatWritesStderr()
{
var startInfo = OperatingSystem.IsWindows()
? new ProcessStartInfo("cmd", "/c echo boom 1>&2")
: new ProcessStartInfo("/bin/bash", "-lc \"echo boom >&2\"");

startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardInput = true;
startInfo.CreateNoWindow = true;

var process = new Process
{
StartInfo = startInfo
};

Assert.True(process.Start());
return process;
}

[Fact]
public void SpectreOutputFormatter_WriteSuccess_OutputsMessage()
{
Expand Down Expand Up @@ -145,6 +168,44 @@ public void SpectreOutputFormatter_WriteInfo_OutputsMessage()
Assert.Contains("ℹ", output);
}

[Fact]
public void Program_HandleCommandException_ForCancellation_WritesCancelledInsteadOfError()
{
var (formatter, console) = CreateTestFormatter();

var exitCode = Program.HandleCommandException(formatter, new OperationCanceledException());

Assert.Equal(130, exitCode);
Assert.Contains("Cancelled.", console.Output);
Assert.DoesNotContain("Error", console.Output);
}

[Fact]
public async Task MonitoredProcess_Attach_DoesNotEchoStderr_WhenNotVerbose()
{
var (formatter, console) = CreateTestFormatter();
using var process = StartTestProcessThatWritesStderr();
using var monitored = MonitoredProcess.Attach(process, formatter, useJson: false, verbose: false, prefix: "trace", CancellationToken.None);

await monitored.WaitForExitAsync();

Assert.DoesNotContain("boom", console.Output);
Assert.Contains("boom", monitored.StandardError.ToString());
}

[Fact]
public async Task MonitoredProcess_Attach_EchoesStderr_AsProgress_WhenVerbose()
{
var (formatter, console) = CreateTestFormatter(verbose: true);
using var process = StartTestProcessThatWritesStderr();
using var monitored = MonitoredProcess.Attach(process, formatter, useJson: false, verbose: true, prefix: "trace", CancellationToken.None);

await monitored.WaitForExitAsync();

Assert.Contains("[trace:stderr] boom", console.Output);
Assert.DoesNotContain("⚠", console.Output);
}

[Fact]
public void SpectreOutputFormatter_WriteError_IncludesErrorCode()
{
Expand Down Expand Up @@ -248,4 +309,36 @@ public void SpectreOutputFormatter_WriteTable_FormatsColumns()
Assert.Contains("Apple", output);
Assert.Contains("Carrot", output);
}

[Theory]
[InlineData(0.4, "0.4s")]
[InlineData(5.4, "5.4s")]
[InlineData(36, "36.0s")]
[InlineData(62.3, "1:02.3s")]
public void SpectreOutputFormatter_FormatElapsed_UsesReadableDurations(double seconds, string expected)
{
var actual = SpectreOutputFormatter.FormatElapsed(TimeSpan.FromSeconds(seconds));
Assert.Equal(expected, actual);
}

[Fact]
public void SpectreOutputFormatter_FormatTimedStatusMarkup_AppendsElapsedToFirstLine()
{
var actual = SpectreOutputFormatter.FormatTimedStatusMarkup(
"Publishing dotnet-pgo...\n[grey] Restored packages[/]",
TimeSpan.FromSeconds(36));

Assert.StartsWith("Publishing dotnet-pgo... [grey](36.0s)[/]", actual);
Assert.Contains("\n[grey] Restored packages[/]", actual);
}

[Fact]
public void SpectreOutputFormatter_FormatCompletedStatusMessage_StripsTrailingEllipsis()
{
var actual = SpectreOutputFormatter.FormatCompletedStatusMessage(
"Building the app...",
TimeSpan.FromSeconds(36));

Assert.Equal("Building the app (36.0s)", actual);
}
}
Loading