Skip to content

feat: simulator command enhancements (create/boot/delete) - #35

Merged
mattleibow merged 15 commits into
mainfrom
pr/simulator-enhancements
Mar 5, 2026
Merged

mattleibow merged 15 commits into
mainfrom
pr/simulator-enhancements

Conversation

@mattleibow

@mattleibow mattleibow commented Mar 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Enhances the simulator create, boot, and delete CLI commands with structured output and a --force delete option.

Changes

Create command (apple simulator create)

  • Inherits FormattableOutputCommandSettings (adds --format and --verbose)
  • After creating, looks up the simulator via GetSimulatorsAsync and outputs a Property/Value table (Name, UDID, State, Device Type, Runtime)
  • Supports --format json, --format json-pretty, --format xml for structured output

Boot command (apple simulator boot)

  • Inherits FormattableOutputCommandSettings (adds --format and --verbose)
  • After booting, looks up the simulator and outputs the same Property/Value table
  • Matches by UDID or name

Delete command (apple simulator delete)

  • New --force flag: shuts down the simulator first, never fails (cleanup semantics)
  • Useful for CI/CD teardown where the simulator may or may not be running

Test timeout fix

New tests (SimCtlCreateTests)

  • CreateAsync_CreatesSimulator: verifies simulator does not exist before create, validates UDID format, Name, DeviceTypeIdentifier, State
  • CreateAndBoot_FullLifecycle: create → find by name → boot → wait → verify booted
  • DeleteNonExistentSimulator_ReturnsFalse: confirms delete returns false for non-existent targets

No library changes

SimCtl.cs is unchanged — all lookup logic lives in the CLI commands.

mattleibow and others added 15 commits March 5, 2026 00:26
Add CreateWithUdidAsync to SimCtl that returns the UDID of the created
simulator. Enhance CLI commands with structured output and new options.

SimCtl library:
- New CreateWithUdidAsync method returns UDID string (or null on failure)

Create command:
- Use CreateWithUdidAsync, output UDID on success
- Add --boot, --wait, --timeout options for post-creation boot
- Inherit FormattableOutputCommandSettings for JSON/XML output
- JSON output includes udid, name, deviceType, runtime

Boot command:
- Inherit FormattableOutputCommandSettings for JSON/XML output
- Structured JSON output with target and state on success
- Clear error messages for boot and wait-for-ready failures

Delete command:
- Add --force flag: shutdown first, never fails (cleanup semantics)

Tests:
- New SimCtlCreateWithUdidTests: UDID format validation, boot+wait,
  delete non-existent simulator
- SimCtlComplexTests: increase WaitForBootedAsync timeout 120s->300s,
  use SpringBoard predicate (always present) instead of Maps

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep only the WaitForBootedAsync timeout increase (120s->300s) which
matches the other occurrence already at 300s on main. Revert SpringBoard
predicate, Maps launch removal, and ClockKit assertion changes — those
are unrelated to simulator command enhancements.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split create and lookup into two focused methods:
- CreateWithUdidAsync: creates simulator, returns UDID string
- GetSimulatorAsync: looks up a single SimCtlDevice by UDID

CreateSimulatorCommand chains both for structured output.

Tests:
- CreateWithUdidAsync_ReturnsValidUdid
- GetSimulatorAsync_ReturnsDevice
- GetSimulatorAsync_NonExistentUdid_ReturnsNull
- CreateAndBoot_FullLifecycle
- DeleteNonExistentSimulator_ReturnsFalse

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove CreateWithUdidAsync and GetSimulatorAsync. Instead, the
create command uses the existing CreateAsync (bool) then finds the
created simulator by name via GetSimulatorsAsync.

Tests use the same pattern: create, then look up by name to verify.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CreateAsync now creates the simulator then looks it up by name via
GetSimulatorsAsync, returning the full SimCtlDevice or null on failure.
Updated all callers: CLI command, MCP server, and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These are redundant with the existing boot command. Keep create
focused on creation only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SimCtl stays unchanged (CreateAsync returns bool). The create and boot
commands do the GetSimulatorsAsync lookup themselves to return the full
SimCtlDevice in structured output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor simulator creation output handling to simplify logic and ensure output is generated for both existing and new simulators.
Use shared SimulatorColumns with OutputHelper.Output<T> overload that
accepts column definitions, so create and boot render a Property/Value
table instead of calling ToString().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rColumns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow changed the title feat: simulator command enhancements and CreateWithUdidAsync Mar 4, 2026
@mattleibow
mattleibow requested a review from Copilot March 5, 2026 02:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Enhances the apple simulator CLI workflow by adding richer post-action output for create/boot, introducing a --force cleanup-oriented delete option, and extending simulator lifecycle test coverage.

Changes:

  • Add formatted/structured output support (--format, --verbose) to simulator create and simulator boot, including a property/value summary of the resulting simulator.
  • Add --force to simulator delete to shutdown-before-delete and always return success for teardown scenarios.
  • Add simulator create/boot/delete tests and increase a boot-wait timeout in complex lifecycle tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
AppleDev.Tool/Commands/Simulators/DeleteSimulatorCommand.cs Adds --force option to shutdown then delete, and always succeed for cleanup.
AppleDev.Tool/Commands/Simulators/CreateSimulatorCommand.cs Adds --format/--verbose settings inheritance and outputs created simulator details after creation.
AppleDev.Tool/Commands/Simulators/BootSimulatorCommand.cs Adds --format/--verbose settings inheritance and outputs booted simulator details after boot/wait.
AppleDev.Test/SimCtlCreateTests.cs Adds new tests for simulator create and lifecycle behaviors.
AppleDev.Test/SimCtlComplexTests.cs Increases WaitForBootedAsync timeout from 120s to 300s in one lifecycle test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (success)
{
AnsiConsole.MarkupLine($"[green]Successfully created simulator '{settings.Name}'[/]");
var sims = await simctl.GetSimulatorsAsync(cancellationToken: data.CancellationToken).ConfigureAwait(false);

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

GetSimulatorsAsync() defaults to availableOnly: true, so a newly-created simulator that ends up unavailable (e.g., missing runtime) won’t be returned and the command will fall back to the generic success message. If the intent is to always show the created device details when possible, pass availableOnly: false for this lookup.

Suggested change
var sims = await simctl.GetSimulatorsAsync(cancellationToken: data.CancellationToken).ConfigureAwait(false);
var sims = await simctl.GetSimulatorsAsync(availableOnly: false, cancellationToken: data.CancellationToken).ConfigureAwait(false);
Copilot uses AI. Check for mistakes.
Comment on lines +29 to +31
OutputHelper.Output(device, settings.Format,
new[] { "Name", "UDID", "State", "Device Type", "Runtime" },
d => new[] { d.Name, d.Udid, d.State, d.DeviceType?.Name ?? d.DeviceTypeIdentifier, d.Runtime?.Name });

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

This command now inherits FormattableOutputCommandSettings (adding --verbose), but settings.Verbose is never used when producing the property/value output. Either wire Verbose into the output (e.g., add additional properties only when verbose, or use the ColumnInfo overload that supports verbose columns), or avoid exposing a flag that has no effect.

Suggested change
OutputHelper.Output(device, settings.Format,
new[] { "Name", "UDID", "State", "Device Type", "Runtime" },
d => new[] { d.Name, d.Udid, d.State, d.DeviceType?.Name ?? d.DeviceTypeIdentifier, d.Runtime?.Name });
if (settings.Verbose)
{
OutputHelper.Output(device, settings.Format,
new[] { "Name", "UDID", "State", "Device Type", "Runtime", "Details" },
d => new[]
{
d.Name,
d.Udid,
d.State,
d.DeviceType?.Name ?? d.DeviceTypeIdentifier,
d.Runtime?.Name,
$"Device Type Id: {d.DeviceTypeIdentifier}"
});
}
else
{
OutputHelper.Output(device, settings.Format,
new[] { "Name", "UDID", "State", "Device Type", "Runtime" },
d => new[] { d.Name, d.Udid, d.State, d.DeviceType?.Name ?? d.DeviceTypeIdentifier, d.Runtime?.Name });
}
Copilot uses AI. Check for mistakes.
Comment on lines +31 to +39
else
{
AnsiConsole.MarkupLine($"[green]Simulator '{settings.Target}' booted successfully[/]");
}
}
else
{
AnsiConsole.MarkupLine($"[red]Failed to boot simulator '{settings.Target}'[/]");
}

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

When --format is set to json/json-pretty/xml, the success/failure messages are still printed with Spectre markup in the device is null and !success paths. That makes the command output not reliably machine-parseable. Consider switching to Console.WriteLine (no markup) or emitting a structured error payload when settings.Format != OutputFormat.None.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +24
var sims = await simctl.GetSimulatorsAsync(cancellationToken: data.CancellationToken).ConfigureAwait(false);
var device = sims.FirstOrDefault(s =>
string.Equals(s.Udid, settings.Target, StringComparison.OrdinalIgnoreCase) ||
string.Equals(s.Name, settings.Target, StringComparison.Ordinal));

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

GetSimulatorsAsync() defaults to availableOnly: true; if the target simulator is currently unavailable, this lookup won’t find it and the command will fall back to the generic message even though the boot succeeded. Consider passing availableOnly: false for the post-boot lookup so the output is consistent with the requested target.

Copilot uses AI. Check for mistakes.
{
await _simCtl.ShutdownAsync(_testSimName);
}
catch { }

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

This test cleanup swallows exceptions with an empty catch { }, which can hide real failures/leaks and make CI flakes harder to diagnose. Prefer at least logging the exception to ITestOutputHelper, or catching specific expected exceptions only.

Suggested change
catch { }
catch (Exception ex)
{
_testOutputHelper.WriteLine($"Shutdown cleanup failed: {ex.Message}");
}
Copilot uses AI. Check for mistakes.
Comment on lines +24 to +36
var sims = await simctl.GetSimulatorsAsync(cancellationToken: data.CancellationToken).ConfigureAwait(false);
var device = sims.FirstOrDefault(s => string.Equals(s.Name, settings.Name, StringComparison.Ordinal));

if (device is not null)
{
OutputHelper.Output(device, settings.Format,
new[] { "Name", "UDID", "State", "Device Type", "Runtime" },
d => new[] { d.Name, d.Udid, d.State, d.DeviceType?.Name ?? d.DeviceTypeIdentifier, d.Runtime?.Name });
}
else
{
AnsiConsole.MarkupLine($"[green]Successfully created simulator '{settings.Name}'[/]");
}

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

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

When --format is set to json/json-pretty/xml, the fallback path still writes Spectre markup to stdout if the created simulator can’t be found. This breaks machine-parseable output (and contradicts the repository pattern where structured output is emitted via Console.WriteLine). Consider emitting plain text via Console.WriteLine (or a structured error object) when settings.Format != OutputFormat.None, and avoid ANSI markup in that mode.

Copilot uses AI. Check for mistakes.
@mattleibow
mattleibow merged commit b8705db into main Mar 5, 2026
11 of 13 checks passed
@mattleibow
mattleibow deleted the pr/simulator-enhancements branch March 5, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants