From 6823a2b4fe0d130da44a71cffeae9369512456c0 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 12 Nov 2025 12:52:21 +0100 Subject: [PATCH 1/4] [net11.0] [msbuild/dotnet] Add support for listing the devices and simulators available to run on. This consists of two parts: * Add an MSBuild target that lists all the available devices (`ComputeAvailableDevices`), by returning them in a `$(Devices)` item group. * Add support for the `$(Device)` property to specify the device or simulator to use. Regarding the device list, we'll filter the returned list by: * Platform (don't return an Apple TV simulator for an iOS app). * Minimum OS version (not return an iOS 18.0 device when the app's minimum OS version is 26.0). * Only devices that are actually available, as reported by `devicectl` or `simctl`. References: * https://github.com/dotnet/android/pull/10576 * https://github.com/dotnet/sdk/blob/2b9fc02a265c735f2132e4e3626e94962e48bdf5/documentation/specs/dotnet-run-for-maui.md Fixes https://github.com/dotnet/macios/issues/23995. --- docs/building-apps/build-properties.md | 14 + docs/building-apps/build-targets.md | 32 +- dotnet/Makefile | 2 + dotnet/targets/Microsoft.Sdk.Desktop.targets | 28 + dotnet/targets/Microsoft.Sdk.Mobile.targets | 176 +++ dotnet/targets/Xamarin.Shared.Sdk.targets | 169 +-- .../Tasks/GetAvailableDevices.cs | 447 +++++++ .../Tasks/GetMlaunchArguments.cs | 25 +- .../Xamarin.MacDev.Tasks.csproj | 3 + msbuild/Xamarin.Shared/Xamarin.Shared.targets | 4 +- tests/common/shared-dotnet.mk | 2 +- tests/dotnet/MultiFrameworkApp/AppDelegate.cs | 17 + tests/dotnet/MultiFrameworkApp/Makefile | 2 + .../MultiFrameworkApp.csproj | 15 + .../TaskTests/GetAvailableDevicesTest.cs | 1040 +++++++++++++++++ tools/common/JsonExtensions.cs | 106 ++ 16 files changed, 1894 insertions(+), 188 deletions(-) create mode 100644 dotnet/targets/Microsoft.Sdk.Desktop.targets create mode 100644 dotnet/targets/Microsoft.Sdk.Mobile.targets create mode 100644 msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs create mode 100644 tests/dotnet/MultiFrameworkApp/AppDelegate.cs create mode 100644 tests/dotnet/MultiFrameworkApp/Makefile create mode 100644 tests/dotnet/MultiFrameworkApp/MultiFrameworkApp.csproj create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs create mode 100644 tools/common/JsonExtensions.cs diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 5f9d4c27a853..3496aa9e8ad7 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -344,6 +344,20 @@ Only applicable to macOS and Mac Catalyst projects. See [BuildIpa](#buildipa) for iOS and tvOS projects. +## Device + +Specifies which mobile device or emulator to target when using `dotnet run +--device ` or MSBuild targets that interact with devices (such as +`Run`, `Install`, or `Uninstall`). + +The value can be anything the command-line tools `simctl` or `devicectl` +accept for the device name; this is typically either the UDID or the name of +the device. For example, for the device `My iOS Device` with UDID `0000-aaaabbbb`, use +either `-p:Device="My iOS Device"` or `-p:Device=0000-aaaabbbb`. + +For more information about device selection, see the +[.NET SDK device selection specification](https://github.com/dotnet/sdk/blob/2b9fc02a265c735f2132e4e3626e94962e48bdf5/documentation/specs/dotnet-run-for-maui.md). + ## DeviceSpecificBuild If the build should be specific to the selected device. diff --git a/docs/building-apps/build-targets.md b/docs/building-apps/build-targets.md index fb548e6691ce..86a0ff27503a 100644 --- a/docs/building-apps/build-targets.md +++ b/docs/building-apps/build-targets.md @@ -16,11 +16,39 @@ Builds the source code within a project and all dependencies. Removes all files generated by the build process. +## ComputeAvailableDevices + +Queries and returns a list of available iOS or tvOS devices and simulators that can be used with `dotnet run`. + +This target is called automatically by the .NET SDK's `dotnet run` command to +support device selection via the `--device` option. It returns a `@(Devices)` +item group where each device has the following metadata: + +- **Description**: The name of the device (e.g., "iPhone 16 - iOS 26.0" for simulators, "My iPhone 16" for physical devices) +- **Type**: Either "Device" or "Simulator" +- **OSVersion**: The OS version of the device +- **UDID**: The UDID of the device +- **RuntimeIdentifier**: The RuntimeIdentifier of the device + +For example, to list all available devices: + +```shell +$ dotnet build -t:ComputeAvailableDevices +``` + +This target is part of the [.NET SDK device selection specification](https://github.com/dotnet/sdk/blob/2b9fc02a265c735f2132e4e3626e94962e48bdf5/documentation/specs/dotnet-run-for-maui.md) and enables commands like: + +```shell +$ dotnet run --device UDID +``` + +Added in .NET 11. + ## Run Builds the source code within a project and all dependencies, and then deploys and runs it -on a default simulator/device. A specific deployment target can be set by using the `$(_DeviceName)` property. +on a default simulator/device. A specific deployment target can be set by using the `$(Device)` property. ```dotnetcli -dotnet build -t:Run project.csproj -p:_DeviceName=$(MY_DEVICE_UDID) +dotnet build -t:Run project.csproj -p:Device=$(MY_DEVICE_UDID) ``` diff --git a/dotnet/Makefile b/dotnet/Makefile index 5c92eaa1beee..b8729d992617 100644 --- a/dotnet/Makefile +++ b/dotnet/Makefile @@ -33,7 +33,9 @@ $(1)_NUGET_TARGETS = \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.$(1).Sdk.props \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.$(1).Sdk.Versions.props \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.$(1).Sdk.targets \ + $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.Sdk.Desktop.targets \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.Sdk.Eol.targets \ + $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.Sdk.Mobile.targets \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.DefaultItems.targets \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.Publish.targets \ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.TargetFrameworkInference.props \ diff --git a/dotnet/targets/Microsoft.Sdk.Desktop.targets b/dotnet/targets/Microsoft.Sdk.Desktop.targets new file mode 100644 index 000000000000..ab7dc89c24d9 --- /dev/null +++ b/dotnet/targets/Microsoft.Sdk.Desktop.targets @@ -0,0 +1,28 @@ + + + + + + <_OpenArguments Condition="'$(XamarinDebugMode)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_MODE__=$(XamarinDebugMode) + <_OpenArguments Condition="'$(XamarinDebugPort)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_PORT__=$(XamarinDebugPort) + <_OpenArguments Condition="'$(XamarinDebugHosts)' != ''">$(_OpenArguments) --env "__XAMARIN_DEBUG_HOSTS__=$(XamarinDebugHosts)" + <_OpenArguments Condition="'$(XamarinDebugConnectTimeout)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_CONNECT_TIMEOUT__=$(XamarinDebugConnectTimeout) + <_OpenArguments Condition="'$(StandardOutputPath)' != ''">$(_OpenArguments) --stdout "$(StandardOutputPath)" + <_OpenArguments Condition="'$(StandardErrorPath)' != ''">$(_OpenArguments) --stderr "$(StandardErrorPath)" + <_OpenArguments Condition="'$(StandardInputPath)' != ''">$(_OpenArguments) --stdin "$(StandardInputPath)" + <_OpenArgumentsPre Condition="'$(OpenNewInstance)' == 'true'">-n + <_OpenArgumentsPre Condition="'$(OpenWaitForExit)' == 'true'">$(_OpenArgumentsPre) -W + <_OpenArguments>$(_OpenArguments) $(RunEnvironment) + open + $(_OpenArgumentsPre) -a "$(TargetDir)/$(AssemblyName).app" $(OpenArguments) $(_OpenArguments) --args + + + + $(TargetDir)/$(AssemblyName).app/Contents/MacOS/$(AssemblyName) + + + + diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets new file mode 100644 index 000000000000..9fa3eafa8d24 --- /dev/null +++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets @@ -0,0 +1,176 @@ + + + + + + + + $(_MlaunchPath) + $(_XamarinSdkRootDirectory)tools\bin\mlaunch + <_MlaunchPath Condition="'$(_MlaunchPath)' == ''">$(MlaunchPath) + + + $(_RelativeMlaunchPath) + $(XamarinRelativeSdkRootDirectory)tools\bin\mlaunch + <_RelativeMlaunchPath Condition="'$(_RelativeMlaunchPath)' == ''">$(RelativeMlaunchPath) + + + $(_DeviceName) + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_MlaunchCaptureOutput Condition="'$(_MlaunchCaptureOutput)' == ''">true + + <_MlaunchWaitForExit Condition="'$(_MlaunchWaitForExit)' == ''">true + + + + + + + + + + + + + + + + + + $(MlaunchPath) + $(MlaunchRunArguments) -- + + + + + + + + + + + <_FilterDevicesToRuntimeIdentifier Condition="'$(_XamarinUsingDefaultRuntimeIdentifier)' != 'true'">$(RuntimeIdentifier) + + + + + + + + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index c0e454decf27..d056dc813c54 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -87,6 +87,10 @@ + + + + <_PlatformAssemblyName>Microsoft.$(_PlatformName) @@ -2436,171 +2440,6 @@ - - - - - $(_MlaunchPath) - $(_XamarinSdkRootDirectory)tools\bin\mlaunch - <_MlaunchPath Condition="'$(_MlaunchPath)' == ''">$(MlaunchPath) - - - $(_RelativeMlaunchPath) - $(XamarinRelativeSdkRootDirectory)tools\bin\mlaunch - <_RelativeMlaunchPath Condition="'$(_RelativeMlaunchPath)' == ''">$(RelativeMlaunchPath) - - $(_DeviceName) - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_MlaunchCaptureOutput Condition="'$(_MlaunchCaptureOutput)' == ''">true - - <_MlaunchWaitForExit Condition="'$(_MlaunchWaitForExit)' == ''">true - - - - - - - - - - - - - - - - - - $(MlaunchPath) - $(MlaunchRunArguments) -- - - - - - - - - - - - <_OpenArguments Condition="'$(XamarinDebugMode)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_MODE__=$(XamarinDebugMode) - <_OpenArguments Condition="'$(XamarinDebugPort)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_PORT__=$(XamarinDebugPort) - <_OpenArguments Condition="'$(XamarinDebugHosts)' != ''">$(_OpenArguments) --env "__XAMARIN_DEBUG_HOSTS__=$(XamarinDebugHosts)" - <_OpenArguments Condition="'$(XamarinDebugConnectTimeout)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_CONNECT_TIMEOUT__=$(XamarinDebugConnectTimeout) - <_OpenArguments Condition="'$(StandardOutputPath)' != ''">$(_OpenArguments) --stdout "$(StandardOutputPath)" - <_OpenArguments Condition="'$(StandardErrorPath)' != ''">$(_OpenArguments) --stderr "$(StandardErrorPath)" - <_OpenArguments Condition="'$(StandardInputPath)' != ''">$(_OpenArguments) --stdin "$(StandardInputPath)" - <_OpenArgumentsPre Condition="'$(OpenNewInstance)' == 'true'">-n - <_OpenArgumentsPre Condition="'$(OpenWaitForExit)' == 'true'">$(_OpenArgumentsPre) -W - <_OpenArguments>$(_OpenArguments) $(RunEnvironment) - open - $(_OpenArgumentsPre) -a "$(TargetDir)/$(AssemblyName).app" $(OpenArguments) $(_OpenArguments) --args - - - - $(TargetDir)/$(AssemblyName).app/Contents/MacOS/$(AssemblyName) - - - - <_CreateAppManifest>$(_CanOutputAppBundle) <_CreateAppManifest Condition="'$(IsAppDistribution)' == 'true'">false @@ -3222,6 +3222,8 @@ Copyright (C) 2018 Microsoft. All rights reserved. <_AppXpcServicesRelativePath Condition="'$(_PlatformName)' == 'macOS' Or '$(_PlatformName)' == 'MacCatalyst'">Contents\XPCServices\ <_AppXpcServicesRelativePath Condition="'$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS' Or '$(_PlatformName)' == 'watchOS'">XPCServices\ <_AppXpcServicesPath>$(_AppBundlePath)$(_AppXpcServicesRelativePath) + + <_AppBundleManifestPath>$(_AppBundlePath)$(_AppBundleManifestRelativePath)Info.plist diff --git a/tests/common/shared-dotnet.mk b/tests/common/shared-dotnet.mk index ecf0296f4718..14f1e384b7d0 100644 --- a/tests/common/shared-dotnet.mk +++ b/tests/common/shared-dotnet.mk @@ -178,7 +178,7 @@ run-bare: # Get the list of applicable simulators, and pick the first in the list. # Make sure to have a matching simulator runtime installed, otherwise this won't work. -run-old: RUN_ARGUMENTS=-p:_DeviceName=$(shell xcrun simctl list devices "$(PLATFORM) $(MIN_$(PLATFORM_UPPERCASE)_SIMULATOR_VERSION)" -j | jq -c '.[][][].udid' | head -1 | sed 's/"//g') +run-old: RUN_ARGUMENTS=-p:Device=$(shell xcrun simctl list devices "$(PLATFORM) $(MIN_$(PLATFORM_UPPERCASE)_SIMULATOR_VERSION)" -j | jq -c '.[][][].udid' | head -1 | sed 's/"//g') run-old: export RUNTIMEIDENTIFIER= run-old: $(MAKE) run diff --git a/tests/dotnet/MultiFrameworkApp/AppDelegate.cs b/tests/dotnet/MultiFrameworkApp/AppDelegate.cs new file mode 100644 index 000000000000..db627351190b --- /dev/null +++ b/tests/dotnet/MultiFrameworkApp/AppDelegate.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + + return args.Length; + } + } +} diff --git a/tests/dotnet/MultiFrameworkApp/Makefile b/tests/dotnet/MultiFrameworkApp/Makefile new file mode 100644 index 000000000000..6affa45ff122 --- /dev/null +++ b/tests/dotnet/MultiFrameworkApp/Makefile @@ -0,0 +1,2 @@ +TOP=../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/MultiFrameworkApp/MultiFrameworkApp.csproj b/tests/dotnet/MultiFrameworkApp/MultiFrameworkApp.csproj new file mode 100644 index 000000000000..b50ed1aa5aa4 --- /dev/null +++ b/tests/dotnet/MultiFrameworkApp/MultiFrameworkApp.csproj @@ -0,0 +1,15 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios;net$(BundledNETCoreAppTargetFrameworkVersion)-tvos;net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst;net$(BundledNETCoreAppTargetFrameworkVersion)-macos + Exe + + MultiFrameworkApp + com.xamarin.multiframeworkapp + + true + true + + + + diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs new file mode 100644 index 000000000000..2174cfe84cac --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs @@ -0,0 +1,1040 @@ +using System.IO; +using System.Linq; + +using Xamarin.Tests; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class GetAvailableDevicesTests : TestBase { + class GetAvailableDevicesTaskWrapper : GetAvailableDevices { + public string SimCtlJson = string.Empty; + public string DeviceCtlJson = string.Empty; + protected override async System.Threading.Tasks.Task ExecuteCtlAsync (params string [] args) + { + switch (args [0]) { + case "simctl": + return SimCtlJson; + case "devicectl": + return DeviceCtlJson; + default: + throw new Exception (); + } + } + } + + GetAvailableDevicesTaskWrapper CreateTask (ApplePlatform platform, string simctlJson, string devicectlJson, string appManifest = "") + { + var task = new GetAvailableDevicesTaskWrapper () { + SimCtlJson = simctlJson, + DeviceCtlJson = devicectlJson, + }; + task.SdkDevPath = Configuration.xcode_root; + task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); + + if (!string.IsNullOrEmpty (appManifest)) { + var tmpdir = Cache.CreateTemporaryDirectory (); + var appManifestPath = Path.Combine (tmpdir, "Info.plist"); + File.WriteAllText (appManifestPath, appManifest); + task.AppBundleManifestPath = appManifestPath; + } + + return task; + } + + [Test] + [TestCase ("", "")] + [TestCase ("{}", "{}")] + [TestCase ("[]", "[]")] + [TestCase ("{ \"devicetypes\": {}, \"runtimes\": {}, \"devices\": {} }", "")] + [TestCase ("{ \"devicetypes\": {}, \"runtimes\": {} }", "")] + [TestCase ("{ \"devicetypes\": {}, \"devices\": {} }", "")] + [TestCase ("{ \"devices\": {} }", "")] + [TestCase ("", "{\"result\" : {} }")] + [TestCase ("", "{\"result\" : { \"devices\": {} } }")] + public void EmptyJsons (string simctl, string devicectl) + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, simctl, devicectl); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.That (task.Devices.Count, Is.EqualTo (0), "Devices should be empty."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (0), "No devices should have been discarded."); + } + + [Test] + public void DeviceCtl1 () + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, "", DEVICECTL_JSON_1); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (3), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (1), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 Platform mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 2 Platform mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [2].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Device 3 mismatch."); + Assert.That (task.Devices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Device 3 Name mismatch."); + Assert.That (task.Devices [2].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Device 3 Platform mismatch."); + Assert.That (task.Devices [2].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [2].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 1 Description mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 1 reason mismatch."); + }); + } + + [Test] + public void SimCtl1 () + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, SIMCTL_JSON_1, ""); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (2), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (3), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 1 mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 2 mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 1 mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 1 Name mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 2 mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 2 Name mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Device 3 mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Device 3 Name mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Device 3 discarded reason mismatch."); + }); + } + [Test] + public void Ctl1 () + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (5), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (4), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [2].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 3 Name mismatch."); + Assert.That (task.Devices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 3 OSVersion mismatch."); + Assert.That (task.Devices [2].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [2].GetMetadata ("DiscardedReason"), Is.Empty, "Device 3 discarded reason mismatch."); + + Assert.That (task.Devices [3].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Device 4 UDID mismatch."); + Assert.That (task.Devices [3].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Device 4 Name mismatch."); + Assert.That (task.Devices [3].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Device 4 OSVersion mismatch."); + Assert.That (task.Devices [3].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Device 4 UDID mismatch."); + Assert.That (task.Devices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [3].GetMetadata ("DiscardedReason"), Is.Empty, "Device 4 discarded reason mismatch."); + + Assert.That (task.Devices [4].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 5 UDID mismatch."); + Assert.That (task.Devices [4].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Device 5 Name mismatch."); + Assert.That (task.Devices [4].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 5 OSVersion mismatch."); + Assert.That (task.Devices [4].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 5 UDID mismatch."); + Assert.That (task.Devices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [4].GetMetadata ("DiscardedReason"), Is.Empty, "Device 5 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 1 Description mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 2 Description mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 3 Name mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Device 4 Name mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Device 4 discarded reason mismatch."); + }); + } + + [Test] + public void Ctl1_iPhone () + { + var platform = ApplePlatform.iOS; + var appManifestXml = + """ + + + + UIDeviceFamily + + 1 + + + + """; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1, appManifestXml); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (5), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (4), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [2].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 3 Name mismatch."); + Assert.That (task.Devices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 3 OSVersion mismatch."); + Assert.That (task.Devices [2].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [2].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [3].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Device 4 UDID mismatch."); + Assert.That (task.Devices [3].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Device 4 Name mismatch."); + Assert.That (task.Devices [3].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Device 4 OSVersion mismatch."); + Assert.That (task.Devices [3].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Device 4 UDID mismatch."); + Assert.That (task.Devices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [3].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [4].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 5 UDID mismatch."); + Assert.That (task.Devices [4].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Device 5 Name mismatch."); + Assert.That (task.Devices [4].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 5 OSVersion mismatch."); + Assert.That (task.Devices [4].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Device 5 UDID mismatch."); + Assert.That (task.Devices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [4].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 1 Description mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 2 Description mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 3 Name mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Discarded Device 4 Name mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Discarded Device 4 discarded reason mismatch."); + }); + } + + [Test] + public void Ctl1_iPad () + { + var platform = ApplePlatform.iOS; + var appManifestXml = + """ + + + + UIMinimumOSVersion + 16.0 + UIDeviceFamily + + 2 + + + + """; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1, appManifestXml); + + + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (2), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (7), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Device 1 Description mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 1 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Discarded Device 1 Description mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not an iPad, but the app only supports iPads"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Discarded Device 2 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Discarded Device 2 Description mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not an iPad, but the app only supports iPads"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 3 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 3 Description mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 4 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 4 Description mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 4 reason mismatch."); + + Assert.That (task.DiscardedDevices [4].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 5 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 5 Description mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 5 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 5 reason mismatch."); + + Assert.That (task.DiscardedDevices [5].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 6 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Discarded Device 6 Description mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 6 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 6 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not an iPad, but the app only supports iPads"), "Discarded Device 6 reason mismatch."); + + Assert.That (task.DiscardedDevices [6].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 7 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Discarded Device 7 Description mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 7 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 7 UDID mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 7 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Discarded Device 7 reason mismatch."); + }); + } + + [Test] + public void Ctl1_OSVersion () + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); + + var tmpdir = Cache.CreateTemporaryDirectory (); + var appManifestPath = Path.Combine (tmpdir, "Info.plist"); + var appManifestXml = + """ + + + + MinimumOSVersion + 26.0 + + + """; + File.WriteAllText (appManifestPath, appManifestXml); + task.AppBundleManifestPath = appManifestPath; + + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (3), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (6), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 3 discarded reason mismatch."); + + Assert.That (task.Devices [2].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 3 Name mismatch."); + Assert.That (task.Devices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 3 OSVersion mismatch."); + Assert.That (task.Devices [2].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [2].GetMetadata ("DiscardedReason"), Is.Empty, "Device 3 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Discarded Device 1 Name mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device OS version '18.7.1' is lower than the app's minimum OS version '26.0'"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 2 Name mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 3 Name mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 4 Name mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 4 reason mismatch."); + + Assert.That (task.DiscardedDevices [4].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Discarded Device 5 Name mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 5 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device OS version '13.0.0' is lower than the app's minimum OS version '26.0'"), "Discarded Device 5 reason mismatch."); + + Assert.That (task.DiscardedDevices [5].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Discarded Device 6 Name mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 6 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 6 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Discarded Device 6 reason mismatch."); + }); + } + + [Test] + public void Ctl1_RuntimeIdentifier () + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); + + var tmpdir = Cache.CreateTemporaryDirectory (); + var appManifestPath = Path.Combine (tmpdir, "Info.plist"); + var appManifestXml = + """ + + + + + + """; + File.WriteAllText (appManifestPath, appManifestXml); + task.AppBundleManifestPath = appManifestPath; + task.RuntimeIdentifier = $"ios-arm64"; + + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (3), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (6), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.Devices [1].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Device 2 Name mismatch."); + Assert.That (task.Devices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Device 2 OSVersion mismatch."); + Assert.That (task.Devices [1].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Device 2 UDID mismatch."); + Assert.That (task.Devices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [1].GetMetadata ("DiscardedReason"), Is.Empty, "Device 3 discarded reason mismatch."); + + Assert.That (task.Devices [2].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Device 3 Name mismatch."); + Assert.That (task.Devices [2].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Device 3 OSVersion mismatch."); + Assert.That (task.Devices [2].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Device 3 UDID mismatch."); + Assert.That (task.Devices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [2].GetMetadata ("DiscardedReason"), Is.Empty, "Device 3 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 1 Name mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Discarded Device 2 Name mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'tvOS' does not match the requested platform 'iOS'"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 3 Name mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Discarded Device 4 Name mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device runtime identifier(s) 'iossimulator-arm64' incompatible with the requested runtime identifier 'ios-arm64'"), "Discarded Device 4 reason mismatch."); + + Assert.That (task.DiscardedDevices [4].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Discarded Device 5 Name mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 5 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device runtime identifier(s) 'iossimulator-arm64' incompatible with the requested runtime identifier 'ios-arm64'"), "Discarded Device 5 reason mismatch."); + + Assert.That (task.DiscardedDevices [5].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Discarded Device 6 Name mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 6 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 6 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Discarded Device 6 reason mismatch."); + }); + } + + [Test] + public void Ctl1_AppleTV () + { + var platform = ApplePlatform.TVOS; + var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); + Assert.IsTrue (task.Execute (), "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (1), "Devices count mismatch."); + Assert.That (task.DiscardedDevices.Count, Is.EqualTo (8), "Discarded device count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Device 1 ItemSpec mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("Apple TV - tvOS 26.1"), "Device 1 Description mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("60ED31BD-80CE-420A-B0CB-756D2CD38201"), "Device 1 UDID mismatch."); + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("tvossimulator-arm64"), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + + Assert.That (task.DiscardedDevices [0].ItemSpec, Is.EqualTo ("00008001-012301230123ABCD"), "Discarded Device 1 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPad Pro 3rd Gen"), "Discarded Device 1 Description mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.0"), "Discarded Device 1 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("UDID"), Is.EqualTo ("00008001-012301230123ABCD"), "Discarded Device 1 UDID mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [0].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'iOS' does not match the requested platform 'tvOS'"), "Discarded Device 1 reason mismatch."); + + Assert.That (task.DiscardedDevices [1].ItemSpec, Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 2 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 13"), "Discarded Device 2 Description mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("OSVersion"), Is.EqualTo ("18.7.1"), "Discarded Device 2 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("UDID"), Is.EqualTo ("00008002-012301230123ABCD"), "Discarded Device 2 UDID mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 2 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [1].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'iOS' does not match the requested platform 'tvOS'"), "Discarded Device 2 reason mismatch."); + + Assert.That (task.DiscardedDevices [2].ItemSpec, Is.EqualTo ("00008003-012301230123ABCD"), "Discarded Device 3 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("Description"), Is.EqualTo ("Rolf's iPhone 15"), "Discarded Device 3 Description mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 3 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("UDID"), Is.EqualTo ("00008003-012301230123ABCD"), "Discarded Device 3 UDID mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("ios-arm64"), "Discarded Device 3 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [2].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'iOS' does not match the requested platform 'tvOS'"), "Discarded Device 3 reason mismatch."); + + Assert.That (task.DiscardedDevices [3].ItemSpec, Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 4 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("Description"), Is.EqualTo ("Rolf’s Apple Watch Series 7"), "Discarded Device 4 Description mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("OSVersion"), Is.EqualTo ("11.5"), "Discarded Device 4 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("UDID"), Is.EqualTo ("00008004-012301230123ABCD"), "Discarded Device 4 UDID mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 4 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [3].GetMetadata ("DiscardedReason"), Is.EqualTo ("'appleWatch' devices are not supported"), "Discarded Device 4 reason mismatch."); + + Assert.That (task.DiscardedDevices [4].ItemSpec, Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 5 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("Description"), Is.EqualTo ("iPhone 17 Pro"), "Discarded Device 5 Description mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("OSVersion"), Is.EqualTo (""), "Discarded Device 5 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("UDID"), Is.EqualTo ("D4D95709-144A-4CAA-8469-89566EC1C935"), "Discarded Device 5 UDID mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (""), "Discarded Device 5 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [4].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device is not available: runtime profile not found using \"System\" match policy"), "Discarded Device 5 reason mismatch."); + + Assert.That (task.DiscardedDevices [5].ItemSpec, Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 6 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Discarded Device 6 Description mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 6 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("UDID"), Is.EqualTo ("D40CE982-3E65-4756-8162-90EFE50AF7FA"), "Discarded Device 6 UDID mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 6 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [5].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'iOS' does not match the requested platform 'tvOS'"), "Discarded Device 6 reason mismatch."); + + Assert.That (task.DiscardedDevices [6].ItemSpec, Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Discarded Device 7 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 13-inch (M5)"), "Discarded Device 7 Description mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 7 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("UDID"), Is.EqualTo ("3F1C114D-FC3D-481A-9CA1-499EE1339390"), "Discarded Device 7 UDID mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 7 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [6].GetMetadata ("DiscardedReason"), Is.EqualTo ("Device platform 'iOS' does not match the requested platform 'tvOS'"), "Discarded Device 7 reason mismatch."); + + Assert.That (task.DiscardedDevices [7].ItemSpec, Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 8 ItemSpec mismatch."); + Assert.That (task.DiscardedDevices [7].GetMetadata ("Description"), Is.EqualTo ("iPad Pro 11-inch (M5)"), "Discarded Device 8 Description mismatch."); + Assert.That (task.DiscardedDevices [7].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Discarded Device 8 OSVersion mismatch."); + Assert.That (task.DiscardedDevices [7].GetMetadata ("UDID"), Is.EqualTo ("F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4"), "Discarded Device 8 UDID mismatch."); + Assert.That (task.DiscardedDevices [7].GetMetadata ("RuntimeIdentifier"), Is.EqualTo ("iossimulator-arm64"), "Discarded Device 8 RuntimeIdentifier mismatch."); + Assert.That (task.DiscardedDevices [7].GetMetadata ("DiscardedReason"), Is.EqualTo ("Unknown device type identifier 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB'"), "Discarded Device 8 reason mismatch."); + }); + } + + const string DEVICECTL_JSON_1 = + """ + { + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "x.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "jsonVersion" : 2, + "outcome" : "success", + "version" : "477.39" + }, + "result" : { + "devices" : [ + { + "connectionProperties" : { + "tunnelState" : "unavailable" + }, + "deviceProperties" : { + "name" : "Rolf's iPad Pro 3rd Gen", + "osBuildUpdate" : "23A341", + "osVersionNumber" : "26.0", + "releaseType" : "Beta" + }, + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + "deviceType" : "iPad", + "ecid" : 18446744073709551615, + "hardwareModel" : "J317AP", + "internalStorageCapacity" : 64000000000, + "isProductionFused" : true, + "marketingName" : "iPad Pro (11-inch)", + "platform" : "iOS", + "productType" : "iPad8,1", + "reality" : "physical", + "serialNumber" : "SERIAL_1", + "supportedCPUTypes" : [ + { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 0, + "type" : 16777228 + } + ], + "supportedDeviceFamilies" : [ + 1, + 2 + ], + "thinningProductType" : "iPad8,1", + "udid" : "00008001-012301230123ABCD" + }, + "identifier" : "11111111-AAAA-BBBB-CCCC-DDDDDDDDDDDD", + "tags" : [ + + ], + "visibilityClass" : "default" + }, + { + "connectionProperties" : { + "pairingState" : "paired", + }, + "deviceProperties" : { + "bootState" : "booted", + "name" : "Rolf's iPhone 13", + "osBuildUpdate" : "22H31", + "osVersionNumber" : "18.7.1" + }, + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + "deviceType" : "iPhone", + "ecid" : 0, + "hardwareModel" : "D63AP", + "internalStorageCapacity" : 128000000000, + "isProductionFused" : true, + "marketingName" : "iPhone 13 Pro", + "platform" : "iOS", + "productType" : "iPhone14,2", + "reality" : "physical", + "serialNumber" : "SERIAL_2", + "supportedCPUTypes" : [ + { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 0, + "type" : 16777228 + }, + { + "name" : "armv8", + "subType" : 13, + "type" : 12 + } + ], + "supportedDeviceFamilies" : [ + 1 + ], + "thinningProductType" : "iPhone14,2", + "udid" : "00008002-012301230123ABCD" + }, + "identifier" : "22222222-AAAA-BBBB-CCCC-DDDDDDDDDDDD", + "tags" : [ + + ], + "visibilityClass" : "default" + }, + { + "connectionProperties" : { + "pairingState" : "paired", + "tunnelState" : "unavailable" + }, + "deviceProperties" : { + "name" : "Rolf's iPhone 15", + "osBuildUpdate" : "23B85", + "osVersionNumber" : "26.1" + }, + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + "deviceType" : "iPhone", + "ecid" : 1, + "hardwareModel" : "D83AP", + "internalStorageCapacity" : 128000000000, + "isProductionFused" : true, + "marketingName" : "iPhone 15 Pro", + "platform" : "iOS", + "productType" : "iPhone16,1", + "reality" : "physical", + "serialNumber" : "SERIAL_3", + "supportedCPUTypes" : [ + { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 0, + "type" : 16777228 + } + ], + "supportedDeviceFamilies" : [ + 1 + ], + "thinningProductType" : "iPhone16,1", + "udid" : "00008003-012301230123ABCD" + }, + "identifier" : "33333333-AAAA-BBBB-CCCC-DDDDDDDDDDDD", + "tags" : [ + + ], + "visibilityClass" : "default" + }, + { + "connectionProperties" : { + "pairingState" : "paired", + "transportType" : "localNetwork", + "tunnelState" : "disconnected", + "tunnelTransportProtocol" : "tcp" + }, + "deviceProperties" : { + "name" : "Rolf’s Apple Watch Series 7", + "osBuildUpdate" : "22T572", + "osVersionNumber" : "11.5", + }, + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64_32", + "subType" : 1, + "type" : 33554444 + }, + "deviceType" : "appleWatch", + "ecid" : 2, + "hardwareModel" : "N187sAP", + "isProductionFused" : true, + "marketingName" : "Apple Watch Series 7", + "platform" : "watchOS", + "productType" : "Watch6,6", + "reality" : "physical", + "serialNumber" : "SERIAL_4", + "thinningProductType" : "Watch6,6", + "udid" : "00008004-012301230123ABCD" + }, + "identifier" : "44444444-AAAA-BBBB-CCCC-DDDDDDDDDDDD", + "tags" : [ + + ], + "visibilityClass" : "default" + } + ] + } + } + """; + + const string SIMCTL_JSON_1 = + """ + { + "devicetypes" : [ + { + "productFamily" : "iPhone", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 11.simdevicetype", + "maxRuntimeVersion" : 4294967295, + "maxRuntimeVersionString" : "65535.255.255", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11", + "modelIdentifier" : "iPhone12,1", + "minRuntimeVersionString" : "13.0.0", + "minRuntimeVersion" : 851968, + "name" : "iPhone 11" + }, + { + "productFamily" : "iPad", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPad Pro 13-inch (M5).simdevicetype", + "maxRuntimeVersion" : 4294967295, + "maxRuntimeVersionString" : "65535.255.255", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-12GB", + "modelIdentifier" : "iPad17,4", + "minRuntimeVersionString" : "26.0.0", + "minRuntimeVersion" : 1703936, + "name" : "iPad Pro 13-inch (M5)" + }, + { + "productFamily" : "Apple TV", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/Apple TV 4K (3rd generation).simdevicetype", + "maxRuntimeVersion" : 4294967295, + "maxRuntimeVersionString" : "65535.255.255", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K", + "modelIdentifier" : "AppleTV14,1", + "minRuntimeVersionString" : "16.1.0", + "minRuntimeVersion" : 1048832, + "name" : "Apple TV 4K (3rd generation)" + }, + ], + "runtimes" : [ + { + "isAvailable" : true, + "version" : "26.1", + "isInternal" : false, + "buildversion" : "23B80", + "supportedArchitectures" : [ + "arm64" + ], + "supportedDeviceTypes" : [ + { + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 17 Pro.simdevicetype", + "name" : "iPhone 17 Pro", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro", + "productFamily" : "iPhone" + } + ], + "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-26-1", + "platform" : "iOS", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_23B80\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 26.1.simruntime", + "runtimeRoot" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_23B80\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 26.1.simruntime\/Contents\/Resources\/RuntimeRoot", + "lastUsage" : { + "arm64" : "2025-11-11T18:49:51Z" + }, + "name" : "iOS 26.1" + }, + { + "isAvailable" : true, + "version" : "26.1", + "isInternal" : false, + "buildversion" : "23J579", + "supportedArchitectures" : [ + "arm64" + ], + "supportedDeviceTypes" : [ + { + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/Apple TV 4K (3rd generation).simdevicetype", + "name" : "Apple TV 4K (3rd generation)", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K", + "productFamily" : "Apple TV" + }, + ], + "identifier" : "com.apple.CoreSimulator.SimRuntime.tvOS-26-1", + "platform" : "tvOS", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Volumes\/tvOS_23J579\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/tvOS 26.1.simruntime", + "runtimeRoot" : "\/Library\/Developer\/CoreSimulator\/Volumes\/tvOS_23J579\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/tvOS 26.1.simruntime\/Contents\/Resources\/RuntimeRoot", + "lastUsage" : { + "arm64" : "2025-11-11T18:49:38Z" + }, + "name" : "tvOS 26.1" + } + ], + "devices" : { + "com.apple.CoreSimulator.SimRuntime.tvOS-26-1" : [ + { + "lastBootedAt" : "2025-11-03T15:42:10Z", + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/60ED31BD-80CE-420A-B0CB-756D2CD38201\/data", + "dataPathSize" : 1172033536, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/60ED31BD-80CE-420A-B0CB-756D2CD38201", + "udid" : "60ED31BD-80CE-420A-B0CB-756D2CD38201", + "isAvailable" : true, + "logPathSize" : 266240, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K", + "state" : "Shutdown", + "name" : "Apple TV - tvOS 26.1" + } + ], + "com.apple.CoreSimulator.SimRuntime.iOS-26-0" : [ + { + "lastBootedAt" : "2025-11-04T08:20:55Z", + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/D4D95709-144A-4CAA-8469-89566EC1C935\/data", + "dataPathSize" : 1880317952, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/D4D95709-144A-4CAA-8469-89566EC1C935", + "udid" : "D4D95709-144A-4CAA-8469-89566EC1C935", + "isAvailable" : false, + "availabilityError" : "runtime profile not found using \"System\" match policy", + "logPathSize" : 253952, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro", + "state" : "Shutdown", + "name" : "iPhone 17 Pro" + }, + ], + "com.apple.CoreSimulator.SimRuntime.iOS-26-1" : [ + { + "lastBootedAt" : "2025-11-06T12:53:03Z", + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/D40CE982-3E65-4756-8162-90EFE50AF7FA\/data", + "dataPathSize" : 2274861056, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/D40CE982-3E65-4756-8162-90EFE50AF7FA", + "udid" : "D40CE982-3E65-4756-8162-90EFE50AF7FA", + "isAvailable" : true, + "logPathSize" : 253952, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11", + "state" : "Shutdown", + "name" : "iPhone 11 - iOS 26.1" + }, + { + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/3F1C114D-FC3D-481A-9CA1-499EE1339390\/data", + "dataPathSize" : 18337792, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/3F1C114D-FC3D-481A-9CA1-499EE1339390", + "udid" : "3F1C114D-FC3D-481A-9CA1-499EE1339390", + "isAvailable" : true, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-12GB", + "state" : "Shutdown", + "name" : "iPad Pro 13-inch (M5)" + }, + { + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4\/data", + "dataPathSize" : 18337792, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4", + "udid" : "F8BEDB0B-441A-4D05-AED6-E9724DEA6BF4", + "isAvailable" : true, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB", + "state" : "Shutdown", + "name" : "iPad Pro 11-inch (M5)" + } + ] + }, + "pairs" : { + + } + } + """; + } +} diff --git a/tools/common/JsonExtensions.cs b/tools/common/JsonExtensions.cs new file mode 100644 index 000000000000..c8770a0a0eff --- /dev/null +++ b/tools/common/JsonExtensions.cs @@ -0,0 +1,106 @@ +using System.Text.Json; + +namespace Xamarin.Utils; + +public static class JsonExtensions { + public static JsonElement? GetNullableProperty (this JsonElement element, string propertyName) + { + if (element.TryGetProperty (propertyName, out var value)) + return value; + return null; + } + + public static JsonElement? GetNullableProperty (this JsonElement? element, string propertyName) + { + if (element?.TryGetProperty (propertyName, out var value) == true) + return value; + return null; + } + + public static string? GetStringProperty (this JsonElement? element, string propertyName) + { + return GetNullableProperty (element, propertyName)?.GetString (); + } + + public static string? GetStringProperty (this JsonElement element, params string [] propertyName) + { + return FindProperty (element, propertyName)?.GetString (); + } + + public static string GetStringPropertyOrEmpty (this JsonElement? element, string propertyName) + { + return GetStringProperty (element, propertyName) ?? string.Empty; + } + + public static string GetStringPropertyOrEmpty (this JsonElement element, params string [] propertyName) + { + return GetStringProperty (element, propertyName) ?? string.Empty; + } + + public static long? GetInt64Property (this JsonElement? element, params string [] nodes) + { + return FindProperty (element, nodes)?.GetInt64 (); + } + + public static ulong? GetUInt64Property (this JsonElement? element, params string [] nodes) + { + return FindProperty (element, nodes)?.GetUInt64 (); + } + + public static int? GetInt32Property (this JsonElement element, params string [] nodes) + { + return FindProperty (element, nodes)?.GetInt32 (); + } + + public static uint? GetUInt32Property (this JsonElement element, params string [] nodes) + { + return FindProperty (element, nodes)?.GetUInt32 (); + } + + public static bool? GetBooleanProperty (this JsonElement element, params string [] nodes) + { + return FindProperty (element, nodes)?.GetBoolean (); + } + + public static JsonElement? FindProperty (this JsonDocument doc, params string [] nodes) + { + return FindProperty (doc.RootElement, nodes); + } + + public static JsonElement? FindProperty (this JsonElement? element, params string [] nodes) + { + if (element is null) + return null; + return FindProperty (element.Value, nodes); + } + + public static JsonElement? FindProperty (this JsonElement element, params string [] nodes) + { + foreach (var node in nodes) { + if (element.ValueKind != JsonValueKind.Object) + return null; + + if (!element.TryGetProperty (node, out element)) + return null; + } + return element; + } + + public static bool TryGetProperty (this JsonDocument element, string propertyName, out JsonElement value) + { + value = default; + + if (element.RootElement.ValueKind != JsonValueKind.Object) + return false; + + return element.RootElement.TryGetProperty (propertyName, out value); + } + + public static IEnumerable EnumerateIfArray (this JsonElement element) + { + if (element.ValueKind == JsonValueKind.Array) { + foreach (var item in element.EnumerateArray ()) + yield return item; + } + } +} From af00277e22568d946afb758b25305f88245e3b8a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 12 Jan 2026 10:01:28 +0100 Subject: [PATCH 2/4] Improve comment. --- dotnet/targets/Microsoft.Sdk.Mobile.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets index 9fa3eafa8d24..0524dea71dfd 100644 --- a/dotnet/targets/Microsoft.Sdk.Mobile.targets +++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets @@ -147,7 +147,8 @@ Target that queries available devices and simulators. This target is called by 'dotnet run' to support device selection. - Returns @(Devices) items with metadata: ??????? + Returns @(Devices) items that always has these metadata: Description, Type, OSVersion, UDID, RuntimeIdentifier + (but others might be present too, for debug purposes, depending on whether it's a simulator or a device) See: https://github.com/dotnet/sdk/blob/2b9fc02a265c735f2132e4e3626e94962e48bdf5/documentation/specs/dotnet-run-for-maui.md *********************************************************************************************** From f361db7aaf8951150bc6b1a4a3a293bf488276f8 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 12 Jan 2026 10:13:00 +0100 Subject: [PATCH 3/4] review --- .../Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs index f15d2d0a03e7..fba87e8e8fcf 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs @@ -11,6 +11,7 @@ using Xamarin.Localization.MSBuild; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; + namespace Xamarin.MacDev.Tasks; public class GetAvailableDevices : XamarinTask, ICancelableTask { @@ -24,7 +25,6 @@ public class GetAvailableDevices : XamarinTask, ICancelableTask { [Output] public ITaskItem [] DiscardedDevices { get; set; } = Array.Empty (); - [Output] public string RuntimeIdentifier { get; set; } = ""; public string SdkDevPath { get; set; } = ""; @@ -268,7 +268,7 @@ async System.Threading.Tasks.Task> RunDeviceCtlAsync () Version.TryParse (productVersion, out var minimumOSVersion); var maximumOSVersion = new Version (65535, 255, 255); - rv.Add (new DeviceInfo (item, [runtimeIdentifier], platform, deviceType, minimumOSVersion, maximumOSVersion, discardedReason)); + rv.Add (new DeviceInfo (item, [runtimeIdentifier], platform, deviceType, minimumOSVersion ?? new Version (0, 0), maximumOSVersion, discardedReason)); } } return rv; @@ -403,8 +403,10 @@ System.Threading.Tasks.Task> RunSimCtlAsync () discardedReason = $"Unknown product family '{productFamily}'"; break; } - Version.TryParse (deviceTypeElement.GetStringProperty ("minRuntimeVersionString"), out minimumOSVersion); - Version.TryParse (deviceTypeElement.GetStringProperty ("maxRuntimeVersionString"), out maximumOSVersion); + if (Version.TryParse (deviceTypeElement.GetStringProperty ("minRuntimeVersionString"), out var parsedMinimumOSVersion)) + minimumOSVersion = parsedMinimumOSVersion; + if (Version.TryParse (deviceTypeElement.GetStringProperty ("maxRuntimeVersionString"), out var parsedMaximumOSVersion)) + maximumOSVersion = parsedMaximumOSVersion; } else { discardedReason = $"Unknown device type identifier '{deviceTypeIdentifier}'"; } From f04e4de26f652ce8f1ee271366c0c58962266c55 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 12 Jan 2026 10:21:34 +0100 Subject: [PATCH 4/4] skip on x64 --- .../TaskTests/GetAvailableDevicesTest.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs index 2174cfe84cac..82f44ecf1451 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs @@ -106,6 +106,9 @@ public void DeviceCtl1 () [Test] public void SimCtl1 () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var task = CreateTask (platform, SIMCTL_JSON_1, ""); Assert.IsTrue (task.Execute (), "Task should have succeeded."); @@ -152,6 +155,9 @@ public void SimCtl1 () [Test] public void Ctl1 () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); Assert.IsTrue (task.Execute (), "Task should have succeeded."); @@ -227,6 +233,9 @@ public void Ctl1 () [Test] public void Ctl1_iPhone () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var appManifestXml = """ @@ -314,6 +323,9 @@ public void Ctl1_iPhone () [Test] public void Ctl1_iPad () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var appManifestXml = """ @@ -405,6 +417,9 @@ public void Ctl1_iPad () [Test] public void Ctl1_OSVersion () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); @@ -496,6 +511,9 @@ public void Ctl1_OSVersion () [Test] public void Ctl1_RuntimeIdentifier () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.iOS; var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); @@ -586,6 +604,9 @@ public void Ctl1_RuntimeIdentifier () [Test] public void Ctl1_AppleTV () { + if (!Configuration.CanRunArm64) + Assert.Ignore ("This test currently only works on arm64"); // because the set of available simulators is different on x64 + var platform = ApplePlatform.TVOS; var task = CreateTask (platform, SIMCTL_JSON_1, DEVICECTL_JSON_1); Assert.IsTrue (task.Execute (), "Task should have succeeded.");