Repository navigation
feat: simulator command enhancements (create/boot/delete) - #35
Conversation
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>
There was a problem hiding this comment.
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) tosimulator createandsimulator boot, including a property/value summary of the resulting simulator. - Add
--forcetosimulator deleteto 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); |
There was a problem hiding this comment.
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.
| var sims = await simctl.GetSimulatorsAsync(cancellationToken: data.CancellationToken).ConfigureAwait(false); | |
| var sims = await simctl.GetSimulatorsAsync(availableOnly: false, cancellationToken: data.CancellationToken).ConfigureAwait(false); |
| 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 }); |
There was a problem hiding this comment.
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.
| 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 }); | |
| } |
| else | ||
| { | ||
| AnsiConsole.MarkupLine($"[green]Simulator '{settings.Target}' booted successfully[/]"); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| AnsiConsole.MarkupLine($"[red]Failed to boot simulator '{settings.Target}'[/]"); | ||
| } |
There was a problem hiding this comment.
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.
| 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)); | ||
|
|
There was a problem hiding this comment.
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.
| { | ||
| await _simCtl.ShutdownAsync(_testSimName); | ||
| } | ||
| catch { } |
There was a problem hiding this comment.
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.
| catch { } | |
| catch (Exception ex) | |
| { | |
| _testOutputHelper.WriteLine($"Shutdown cleanup failed: {ex.Message}"); | |
| } |
| 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}'[/]"); | ||
| } |
There was a problem hiding this comment.
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.
Summary
Enhances the simulator create, boot, and delete CLI commands with structured output and a
--forcedelete option.Changes
Create command (
apple simulator create)FormattableOutputCommandSettings(adds--formatand--verbose)GetSimulatorsAsyncand outputs a Property/Value table (Name, UDID, State, Device Type, Runtime)--format json,--format json-pretty,--format xmlfor structured outputBoot command (
apple simulator boot)FormattableOutputCommandSettings(adds--formatand--verbose)Delete command (
apple simulator delete)--forceflag: shuts down the simulator first, never fails (cleanup semantics)Test timeout fix
SimCtlComplexTests: increasedWaitForBootedAsynctimeout from 120s to 300s (matches the other timeout on line 47, already bumped in PR fix: gRPC retry on PROTOCOL_ERROR for IDB client stability #30)New tests (
SimCtlCreateTests)CreateAsync_CreatesSimulator: verifies simulator does not exist before create, validates UDID format, Name, DeviceTypeIdentifier, StateCreateAndBoot_FullLifecycle: create → find by name → boot → wait → verify bootedDeleteNonExistentSimulator_ReturnsFalse: confirms delete returns false for non-existent targetsNo library changes
SimCtl.csis unchanged — all lookup logic lives in the CLI commands.