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
110 changes: 110 additions & 0 deletions Xamarin.MacDev/SimulatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,116 @@ public bool Delete (string udidOrName)
return RunSimctlBool ("delete", udidOrName);
}

/// <summary>
/// Installs an app bundle (.app) onto a simulator device.
/// Pattern: <c>xcrun simctl install &lt;udid&gt; &lt;appBundlePath&gt;</c>
/// </summary>
public bool Install (string udid, string appBundlePath)
{
if (string.IsNullOrEmpty (udid))
throw new ArgumentException ("UDID must not be null or empty.", nameof (udid));
if (string.IsNullOrEmpty (appBundlePath))
throw new ArgumentException ("App bundle path must not be null or empty.", nameof (appBundlePath));

var result = simctl.Run ("install", udid, appBundlePath);
var success = result is not null;
if (success)
log.LogInfo ("simctl install '{0}' on '{1}' succeeded.", appBundlePath, udid);
return success;
}

/// <summary>
/// Uninstalls an app from a simulator device.
/// Pattern: <c>xcrun simctl uninstall &lt;udid&gt; &lt;bundleIdentifier&gt;</c>
/// </summary>
public bool Uninstall (string udid, string bundleIdentifier)
{
if (string.IsNullOrEmpty (udid))
throw new ArgumentException ("UDID must not be null or empty.", nameof (udid));
if (string.IsNullOrEmpty (bundleIdentifier))
throw new ArgumentException ("Bundle identifier must not be null or empty.", nameof (bundleIdentifier));

var result = simctl.Run ("uninstall", udid, bundleIdentifier);
var success = result is not null;
if (success)
log.LogInfo ("simctl uninstall '{0}' on '{1}' succeeded.", bundleIdentifier, udid);
return success;
}

/// <summary>
/// Launches an app on a booted simulator device.
/// Optional extra arguments are forwarded to the app process.
/// Pattern: <c>xcrun simctl launch &lt;udid&gt; &lt;bundleIdentifier&gt; [args…]</c>
/// </summary>
public bool Launch (string udid, string bundleIdentifier, params string [] extraArgs)
{
if (string.IsNullOrEmpty (udid))
throw new ArgumentException ("UDID must not be null or empty.", nameof (udid));
if (string.IsNullOrEmpty (bundleIdentifier))
throw new ArgumentException ("Bundle identifier must not be null or empty.", nameof (bundleIdentifier));

var args = new string [3 + extraArgs.Length];
args [0] = "launch";
args [1] = udid;
args [2] = bundleIdentifier;
Array.Copy (extraArgs, 0, args, 3, extraArgs.Length);

Comment on lines +151 to +163
var result = simctl.Run (args);
var success = result is not null;
if (success)
log.LogInfo ("simctl launch '{0}' on '{1}' succeeded.", bundleIdentifier, udid);
return success;
}

/// <summary>
/// Terminates a running app on a simulator device.
/// Pattern: <c>xcrun simctl terminate &lt;udid&gt; &lt;bundleIdentifier&gt;</c>
/// </summary>
public bool Terminate (string udid, string bundleIdentifier)
{
if (string.IsNullOrEmpty (udid))
throw new ArgumentException ("UDID must not be null or empty.", nameof (udid));
if (string.IsNullOrEmpty (bundleIdentifier))
throw new ArgumentException ("Bundle identifier must not be null or empty.", nameof (bundleIdentifier));

var result = simctl.Run ("terminate", udid, bundleIdentifier);
var success = result is not null;
if (success)
log.LogInfo ("simctl terminate '{0}' on '{1}' succeeded.", bundleIdentifier, udid);
return success;
}

/// <summary>
/// Returns the path to an app container directory on a simulator device.
/// The optional <paramref name="containerType"/> selects which container to
/// return — typical values are <c>"app"</c>, <c>"data"</c>, and <c>"groups"</c>.
/// When omitted the default container (equivalent to <c>"app"</c>) is returned.
/// Pattern: <c>xcrun simctl get_app_container &lt;udid&gt; &lt;bundleIdentifier&gt; [containerType]</c>
/// </summary>
public string? GetAppContainer (string udid, string bundleIdentifier, string? containerType = null)
{
if (string.IsNullOrEmpty (udid))
throw new ArgumentException ("UDID must not be null or empty.", nameof (udid));
if (string.IsNullOrEmpty (bundleIdentifier))
throw new ArgumentException ("Bundle identifier must not be null or empty.", nameof (bundleIdentifier));

string? output;
if (!string.IsNullOrEmpty (containerType))
output = simctl.Run ("get_app_container", udid, bundleIdentifier, containerType!);
else
output = simctl.Run ("get_app_container", udid, bundleIdentifier);

if (output is null)
return null;

var path = output.Trim ();
if (string.IsNullOrEmpty (path))
return null;

log.LogInfo ("simctl get_app_container '{0}' on '{1}': {2}", bundleIdentifier, udid, path);
return path;
}

bool RunSimctlBool (string subcommand, string target)
{
var result = simctl.Run (subcommand, target);
Expand Down
111 changes: 111 additions & 0 deletions tests/SimulatorServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using NUnit.Framework;
using Xamarin.MacDev;

#nullable enable

namespace tests;

[TestFixture]
public class SimulatorServiceTests {

readonly SimulatorService svc = new SimulatorService (ConsoleLogger.Instance);

[Test]
public void Constructor_ThrowsOnNullLogger ()
{
Assert.Throws<ArgumentNullException> (() => new SimulatorService (null!));
}

// Install

[Test]
public void Install_ThrowsOnNullOrEmptyUdid ()
{
Assert.Throws<ArgumentException> (() => svc.Install (null!, "/path/to/App.app"));
Assert.Throws<ArgumentException> (() => svc.Install ("", "/path/to/App.app"));
}

[Test]
public void Install_ThrowsOnNullOrEmptyAppBundlePath ()
{
Assert.Throws<ArgumentException> (() => svc.Install ("SOME-UDID", null!));
Assert.Throws<ArgumentException> (() => svc.Install ("SOME-UDID", ""));
}

// Uninstall

[Test]
public void Uninstall_ThrowsOnNullOrEmptyUdid ()
{
Assert.Throws<ArgumentException> (() => svc.Uninstall (null!, "com.example.App"));
Assert.Throws<ArgumentException> (() => svc.Uninstall ("", "com.example.App"));
}

[Test]
public void Uninstall_ThrowsOnNullOrEmptyBundleIdentifier ()
{
Assert.Throws<ArgumentException> (() => svc.Uninstall ("SOME-UDID", null!));
Assert.Throws<ArgumentException> (() => svc.Uninstall ("SOME-UDID", ""));
}

// Launch

[Test]
public void Launch_ThrowsOnNullOrEmptyUdid ()
{
Assert.Throws<ArgumentException> (() => svc.Launch (null!, "com.example.App"));
Assert.Throws<ArgumentException> (() => svc.Launch ("", "com.example.App"));
}

[Test]
public void Launch_ThrowsOnNullOrEmptyBundleIdentifier ()
{
Assert.Throws<ArgumentException> (() => svc.Launch ("SOME-UDID", null!));
Assert.Throws<ArgumentException> (() => svc.Launch ("SOME-UDID", ""));
}

// Terminate

[Test]
public void Terminate_ThrowsOnNullOrEmptyUdid ()
{
Assert.Throws<ArgumentException> (() => svc.Terminate (null!, "com.example.App"));
Assert.Throws<ArgumentException> (() => svc.Terminate ("", "com.example.App"));
}

[Test]
public void Terminate_ThrowsOnNullOrEmptyBundleIdentifier ()
{
Assert.Throws<ArgumentException> (() => svc.Terminate ("SOME-UDID", null!));
Assert.Throws<ArgumentException> (() => svc.Terminate ("SOME-UDID", ""));
}

// GetAppContainer

[Test]
public void GetAppContainer_ThrowsOnNullOrEmptyUdid ()
{
Assert.Throws<ArgumentException> (() => svc.GetAppContainer (null!, "com.example.App"));
Assert.Throws<ArgumentException> (() => svc.GetAppContainer ("", "com.example.App"));
}

[Test]
public void GetAppContainer_ThrowsOnNullOrEmptyBundleIdentifier ()
{
Assert.Throws<ArgumentException> (() => svc.GetAppContainer ("SOME-UDID", null!));
Assert.Throws<ArgumentException> (() => svc.GetAppContainer ("SOME-UDID", ""));
}

[Test]
[Platform ("MacOsX")]
public void GetAppContainer_ReturnsNullForNonExistentApp ()
{
// Using a non-existent bundle identifier should return null (simctl exits non-zero)
var result = svc.GetAppContainer ("booted", "com.example.NoSuchApp.DoesNotExist");
Assert.That (result, Is.Null);
}
}
Loading