From eadcae213efbcdb6c83c76f965c4a36037c96f18 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 9 Mar 2026 13:13:58 +0000 Subject: [PATCH 1/2] Use shared AdbRunner from android-tools for device listing Refactor GetAvailableAndroidDevices MSBuild task to delegate parsing and merging logic to AdbRunner from Xamarin.Android.Tools.AndroidSdk (via the xamarin-android-tools submodule at d3c269d). - Remove local Regex-based parsing, DeviceType enum, and helper methods - Use AdbRunner.ParseAdbDevicesOutput, BuildDeviceDescription, MergeDevicesAndEmulators, FormatDisplayName, MapAdbStateToStatus - Update tests to use AdbRunner APIs and AdbDeviceInfo/AdbDeviceType Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetAvailableAndroidDevices.cs | 263 ++++-------------- .../Tasks/GetAvailableAndroidDevicesTests.cs | 218 ++++++--------- 2 files changed, 133 insertions(+), 348 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs index d1da642cde0..0ab15b04de0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs @@ -2,12 +2,11 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Text.RegularExpressions; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; namespace Xamarin.Android.Tasks; @@ -16,20 +15,11 @@ namespace Xamarin.Android.Tasks; /// and 'emulator -list-avds'. Merges the results to provide a complete list of available /// devices including emulators that are not currently running. /// Returns a list of devices with metadata for device selection in dotnet run. +/// +/// Parsing and merging logic is delegated to in Xamarin.Android.Tools.AndroidSdk. /// public class GetAvailableAndroidDevices : AndroidAdb { - enum DeviceType - { - Device, - Emulator - } - - // Pattern to match device lines: [key:value ...] - // Example: emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 - static readonly Regex AdbDevicesRegex = new(@"^([^\s]+)\s+(device|offline|unauthorized|no permissions)\s*(.*)$", RegexOptions.Compiled); - static readonly Regex ApiRegex = new(@"\bApi\b", RegexOptions.Compiled); - readonly List output = []; /// @@ -64,23 +54,62 @@ public override bool RunTask () if (!base.RunTask ()) return false; - // Parse devices from adb - var adbDevices = ParseAdbDevicesOutput (output); + // Parse devices from adb using shared AdbRunner logic + var adbDevices = AdbRunner.ParseAdbDevicesOutput (output); Log.LogDebugMessage ($"Found {adbDevices.Count} device(s) from adb"); + // For emulators, query AVD names + foreach (var device in adbDevices) { + if (device.Type == AdbDeviceType.Emulator) { + device.AvdName = GetEmulatorAvdName (device.Serial); + device.Description = AdbRunner.BuildDeviceDescription (device, this.CreateTaskLogger ()); + } + } + // Get available emulators from 'emulator -list-avds' var availableEmulators = GetAvailableEmulators (); Log.LogDebugMessage ($"Found {availableEmulators.Count} available emulator(s) from 'emulator -list-avds'"); - // Merge the lists - var mergedDevices = MergeDevicesAndEmulators (adbDevices, availableEmulators); - Devices = mergedDevices.ToArray (); + // Merge using shared logic + var mergedDevices = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators, this.CreateTaskLogger ()); + + // Convert to ITaskItem array + Devices = ConvertToTaskItems (mergedDevices); Log.LogDebugMessage ($"Total {Devices.Length} Android device(s)/emulator(s) after merging"); return !Log.HasLoggedErrors; } + /// + /// Converts AdbDeviceInfo list to ITaskItem array for MSBuild output. + /// + internal static ITaskItem [] ConvertToTaskItems (IReadOnlyList devices) + { + var items = new ITaskItem [devices.Count]; + for (int i = 0; i < devices.Count; i++) { + var device = devices [i]; + var item = new TaskItem (device.Serial); + item.SetMetadata ("Description", device.Description); + item.SetMetadata ("Type", device.Type.ToString ()); + item.SetMetadata ("Status", device.Status.ToString ()); + + if (!device.AvdName.IsNullOrEmpty ()) + item.SetMetadata ("AvdName", device.AvdName); + if (!device.Model.IsNullOrEmpty ()) + item.SetMetadata ("Model", device.Model); + if (!device.Product.IsNullOrEmpty ()) + item.SetMetadata ("Product", device.Product); + if (!device.Device.IsNullOrEmpty ()) + item.SetMetadata ("Device", device.Device); + if (!device.TransportId.IsNullOrEmpty ()) + item.SetMetadata ("TransportId", device.TransportId); + + items [i] = item; + } + return items; + } + /// /// Gets the list of available AVDs using 'emulator -list-avds'. /// @@ -125,186 +154,7 @@ protected virtual List GetAvailableEmulators () } /// - /// Merges devices from adb with available emulators. - /// Running emulators (already in adb list) are not duplicated. - /// Non-running emulators are added with Status="NotRunning". - /// Results are sorted: online devices first, then not-running emulators, alphabetically by description within each group. - /// - internal List MergeDevicesAndEmulators (List adbDevices, List availableEmulators) - { - var result = new List (adbDevices); - - // Build a set of AVD names that are already running (from adb devices) - var runningAvdNames = new HashSet (StringComparer.OrdinalIgnoreCase); - foreach (var device in adbDevices) { - var avdName = device.GetMetadata ("AvdName"); - if (!avdName.IsNullOrEmpty ()) { - runningAvdNames.Add (avdName); - } - } - - Log.LogDebugMessage ($"Running emulators AVD names: {string.Join (", ", runningAvdNames)}"); - - // Add non-running emulators - foreach (var avdName in availableEmulators) { - if (runningAvdNames.Contains (avdName)) { - Log.LogDebugMessage ($"Emulator '{avdName}' is already running, skipping"); - continue; - } - - // Create item for non-running emulator - // Use the AVD name as the ItemSpec since there's no serial yet - var item = new TaskItem (avdName); - var displayName = FormatDisplayName (avdName, avdName); - item.SetMetadata ("Description", $"{displayName} (Not Running)"); - item.SetMetadata ("Type", DeviceType.Emulator.ToString ()); - item.SetMetadata ("Status", "NotRunning"); - item.SetMetadata ("AvdName", avdName); - - result.Add (item); - Log.LogDebugMessage ($"Added non-running emulator: {avdName}"); - } - - // Sort: online devices first, then not-running emulators, alphabetically by description within each group - result.Sort ((a, b) => { - var aNotRunning = string.Equals (a.GetMetadata ("Status"), "NotRunning", StringComparison.OrdinalIgnoreCase); - var bNotRunning = string.Equals (b.GetMetadata ("Status"), "NotRunning", StringComparison.OrdinalIgnoreCase); - - if (aNotRunning != bNotRunning) { - return aNotRunning ? 1 : -1; - } - - return string.Compare (a.GetMetadata ("Description"), b.GetMetadata ("Description"), StringComparison.OrdinalIgnoreCase); - }); - - return result; - } - - /// - /// Parses the output of 'adb devices -l' command. - /// Example output: - /// List of devices attached - /// emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1 - /// 0A041FDD400327 device usb:1-1 product:raven model:Pixel_6_Pro device:raven transport_id:2 - /// - List ParseAdbDevicesOutput (List lines) - { - var devices = new List (); - - foreach (var line in lines) { - // Skip the header line "List of devices attached" - if (line.Contains ("List of devices") || line.IsNullOrWhiteSpace ()) - continue; - - var match = AdbDevicesRegex.Match (line); - if (!match.Success) - continue; - - var serial = match.Groups [1].Value.Trim (); - var state = match.Groups [2].Value.Trim (); - var properties = match.Groups [3].Value.Trim (); - - // Parse key:value pairs from the properties string - var propDict = new Dictionary (StringComparer.OrdinalIgnoreCase); - if (!properties.IsNullOrWhiteSpace ()) { - // Split by whitespace and parse key:value pairs - var pairs = properties.Split ([' '], StringSplitOptions.RemoveEmptyEntries); - foreach (var pair in pairs) { - var colonIndex = pair.IndexOf (':'); - if (colonIndex > 0 && colonIndex < pair.Length - 1) { - var key = pair.Substring (0, colonIndex); - var value = pair.Substring (colonIndex + 1); - propDict [key] = value; - } - } - } - - // Determine device type: Emulator or Device - var deviceType = serial.StartsWith ("emulator-", StringComparison.OrdinalIgnoreCase) ? DeviceType.Emulator : DeviceType.Device; - - // For emulators, get the AVD name for duplicate detection - string? avdName = null; - if (deviceType == DeviceType.Emulator) { - avdName = GetEmulatorAvdName (serial); - } - - // Build a friendly description - var description = BuildDeviceDescription (serial, propDict, deviceType, avdName); - - // Map adb state to device status - var status = MapAdbStateToStatus (state); - - // Create the MSBuild item - var item = new TaskItem (serial); - item.SetMetadata ("Description", description); - item.SetMetadata ("Type", deviceType.ToString ()); - item.SetMetadata ("Status", status); - - // Add AVD name for emulators (used for duplicate detection) - if (!avdName.IsNullOrEmpty ()) { - item.SetMetadata ("AvdName", avdName); - } - - // Add optional metadata for additional information - if (propDict.TryGetValue ("model", out var model)) - item.SetMetadata ("Model", model); - if (propDict.TryGetValue ("product", out var product)) - item.SetMetadata ("Product", product); - if (propDict.TryGetValue ("device", out var device)) - item.SetMetadata ("Device", device); - if (propDict.TryGetValue ("transport_id", out var transportId)) - item.SetMetadata ("TransportId", transportId); - - devices.Add (item); - } - - return devices; - } - - string BuildDeviceDescription (string serial, Dictionary properties, DeviceType deviceType, string? avdName) - { - // Try to build a human-friendly description - // Priority: AVD name (for emulators) > model > product > device > serial - - // For emulators, try to get the AVD display name - if (deviceType == DeviceType.Emulator && !avdName.IsNullOrEmpty ()) { - return FormatDisplayName (serial, avdName!); - } - - if (properties.TryGetValue ("model", out var model) && !model.IsNullOrEmpty ()) { - // Clean up model name - replace underscores with spaces - model = model.Replace ('_', ' '); - return model; - } - - if (properties.TryGetValue ("product", out var product) && !product.IsNullOrEmpty ()) { - product = product.Replace ('_', ' '); - return product; - } - - if (properties.TryGetValue ("device", out var device) && !device.IsNullOrEmpty ()) { - device = device.Replace ('_', ' '); - return device; - } - - // Fallback to serial number - return serial; - } - - static string MapAdbStateToStatus (string adbState) - { - // Map adb device states to the spec's status values - return adbState.ToLowerInvariant () switch { - "device" => "Online", - "offline" => "Offline", - "unauthorized" => "Unauthorized", - "no permissions" => "NoPermissions", - _ => "Unknown", - }; - } - - /// - /// Queries the emulator for its AVD name using 'adb -s emu avd name'. + /// Queries the emulator for its AVD name using 'adb -s <serial> emu avd name'. /// Returns the raw AVD name (not formatted). /// protected virtual string? GetEmulatorAvdName (string serial) @@ -339,21 +189,4 @@ static string MapAdbStateToStatus (string adbState) return null; } - - /// - /// Formats the AVD name into a more user-friendly display name. Replace underscores with spaces and title case. - /// - public string FormatDisplayName (string serial, string avdName) - { - Log.LogDebugMessage ($"Emulator {serial}, original AVD name: {avdName}"); - - // Title case and replace underscores with spaces - var textInfo = CultureInfo.InvariantCulture.TextInfo; - avdName = textInfo.ToTitleCase (avdName.Replace ('_', ' ')); - - // Replace "Api" with "API" - avdName = ApiRegex.Replace (avdName, "API"); - Log.LogDebugMessage ($"Emulator {serial}, formatted AVD display name: {avdName}"); - return avdName; - } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GetAvailableAndroidDevicesTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GetAvailableAndroidDevicesTests.cs index 5dca488ebd7..83a2027b363 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GetAvailableAndroidDevicesTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GetAvailableAndroidDevicesTests.cs @@ -1,9 +1,11 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; +using System; using System.Collections.Generic; -using System.Reflection; +using System.Linq; using Xamarin.Android.Tasks; +using Xamarin.Android.Tools; namespace Xamarin.Android.Build.Tests { @@ -41,6 +43,11 @@ class MockGetAvailableAndroidDevices : GetAvailableAndroidDevices return null; } + /// + /// Public accessor for tests to call the protected GetEmulatorAvdName. + /// + public string? GetEmulatorAvdNameForTest (string serial) => GetEmulatorAvdName (serial); + protected override List GetAvailableEmulators () { return MockAvailableEmulators; @@ -48,14 +55,22 @@ protected override List GetAvailableEmulators () } /// - /// Helper method to invoke the private ParseAdbDevicesOutput method via reflection + /// Helper method that parses adb output using AdbRunner, applies mock AVD name resolution, + /// and converts to ITaskItem array via GetAvailableAndroidDevices.ConvertToTaskItems. /// ITaskItem [] ParseAdbDevicesOutput (MockGetAvailableAndroidDevices task, List lines) { - var method = typeof (GetAvailableAndroidDevices).GetMethod ("ParseAdbDevicesOutput", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.IsNotNull (method, "ParseAdbDevicesOutput method should exist"); - var result = (List) method.Invoke (task, [lines]); - return result.ToArray (); + var devices = AdbRunner.ParseAdbDevicesOutput (lines); + + // Apply AVD name resolution for emulators (same logic as RunTask) + foreach (var device in devices) { + if (device.Type == AdbDeviceType.Emulator) { + device.AvdName = task.GetEmulatorAvdNameForTest (device.Serial); + device.Description = AdbRunner.BuildDeviceDescription (device); + } + } + + return GetAvailableAndroidDevices.ConvertToTaskItems (devices); } [Test] @@ -451,11 +466,7 @@ public void ParseAdbDaemonStarting () [Test] public void FormatDisplayName_ReplacesUnderscoresWithSpaces () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel_7_pro"); + var result = AdbRunner.FormatDisplayName ("pixel_7_pro"); Assert.AreEqual ("Pixel 7 Pro", result, "Should replace underscores with spaces"); } @@ -463,11 +474,7 @@ public void FormatDisplayName_ReplacesUnderscoresWithSpaces () [Test] public void FormatDisplayName_AppliesTitleCase () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel 7 pro"); + var result = AdbRunner.FormatDisplayName ("pixel 7 pro"); Assert.AreEqual ("Pixel 7 Pro", result, "Should apply title case"); } @@ -475,11 +482,7 @@ public void FormatDisplayName_AppliesTitleCase () [Test] public void FormatDisplayName_ReplacesApiWithAPIUppercase () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel_5_api_34"); + var result = AdbRunner.FormatDisplayName ("pixel_5_api_34"); Assert.AreEqual ("Pixel 5 API 34", result, "Should replace 'Api' with 'API'"); } @@ -487,11 +490,7 @@ public void FormatDisplayName_ReplacesApiWithAPIUppercase () [Test] public void FormatDisplayName_HandlesMultipleApiOccurrences () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "test_api_device_api_35"); + var result = AdbRunner.FormatDisplayName ("test_api_device_api_35"); Assert.AreEqual ("Test API Device API 35", result, "Should replace all 'Api' occurrences with 'API'"); } @@ -499,11 +498,7 @@ public void FormatDisplayName_HandlesMultipleApiOccurrences () [Test] public void FormatDisplayName_HandlesMixedCaseInput () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "PiXeL_7_API_35"); + var result = AdbRunner.FormatDisplayName ("PiXeL_7_API_35"); Assert.AreEqual ("Pixel 7 API 35", result, "Should normalize mixed case input"); } @@ -511,11 +506,7 @@ public void FormatDisplayName_HandlesMixedCaseInput () [Test] public void FormatDisplayName_HandlesComplexNames () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel_9_pro_xl_api_36"); + var result = AdbRunner.FormatDisplayName ("pixel_9_pro_xl_api_36"); Assert.AreEqual ("Pixel 9 Pro Xl API 36", result, "Should format complex names correctly"); } @@ -523,11 +514,7 @@ public void FormatDisplayName_HandlesComplexNames () [Test] public void FormatDisplayName_PreservesNumbersAndSpecialChars () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel_7-pro_api_35"); + var result = AdbRunner.FormatDisplayName ("pixel_7-pro_api_35"); Assert.AreEqual ("Pixel 7-Pro API 35", result, "Should preserve hyphens and numbers"); } @@ -535,11 +522,7 @@ public void FormatDisplayName_PreservesNumbersAndSpecialChars () [Test] public void FormatDisplayName_HandlesEmptyString () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", ""); + var result = AdbRunner.FormatDisplayName (""); Assert.AreEqual ("", result, "Should handle empty string"); } @@ -547,11 +530,7 @@ public void FormatDisplayName_HandlesEmptyString () [Test] public void FormatDisplayName_HandlesSingleWord () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "pixel"); + var result = AdbRunner.FormatDisplayName ("pixel"); Assert.AreEqual ("Pixel", result, "Should capitalize single word"); } @@ -559,11 +538,7 @@ public void FormatDisplayName_HandlesSingleWord () [Test] public void FormatDisplayName_DoesNotReplaceApiInsideWords () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var result = task.FormatDisplayName ("emulator-5554", "erapidevice"); + var result = AdbRunner.FormatDisplayName ("erapidevice"); Assert.AreEqual ("Erapidevice", result, "Should not replace 'api' when it's part of a larger word"); } @@ -571,36 +546,30 @@ public void FormatDisplayName_DoesNotReplaceApiInsideWords () [Test] public void MergeDevicesAndEmulators_NoEmulators_ReturnsAdbDevicesOnly () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var adbDevices = new List { - CreateDeviceItem ("0A041FDD400327", "Pixel 5", "Device", "Online"), + var adbDevices = new List { + CreateDeviceInfo ("0A041FDD400327", "Pixel 5", AdbDeviceType.Device, AdbDeviceStatus.Online), }; var availableEmulators = new List (); - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (1, result.Count, "Should return only adb devices"); + Assert.AreEqual (1, result.Length, "Should return only adb devices"); Assert.AreEqual ("0A041FDD400327", result [0].ItemSpec); } [Test] public void MergeDevicesAndEmulators_NoRunningEmulators_AddsAllAvailableEmulators () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var adbDevices = new List { - CreateDeviceItem ("0A041FDD400327", "Pixel 5", "Device", "Online"), + var adbDevices = new List { + CreateDeviceInfo ("0A041FDD400327", "Pixel 5", AdbDeviceType.Device, AdbDeviceStatus.Online), }; var availableEmulators = new List { "pixel_7_api_35", "pixel_9_api_36" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (3, result.Count, "Should return adb device + 2 available emulators"); + Assert.AreEqual (3, result.Length, "Should return adb device + 2 available emulators"); // First item: physical device (online, sorted first) Assert.AreEqual ("0A041FDD400327", result [0].ItemSpec); @@ -621,20 +590,17 @@ public void MergeDevicesAndEmulators_NoRunningEmulators_AddsAllAvailableEmulator [Test] public void MergeDevicesAndEmulators_RunningEmulator_NoDuplicate () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - // Emulator is running (has adb entry with AvdName metadata) - var runningEmulator = CreateDeviceItem ("emulator-5554", "Pixel 7 API 35", "Emulator", "Online"); - runningEmulator.SetMetadata ("AvdName", "pixel_7_api_35"); + var runningEmulator = CreateDeviceInfo ("emulator-5554", "Pixel 7 API 35", AdbDeviceType.Emulator, AdbDeviceStatus.Online); + runningEmulator.AvdName = "pixel_7_api_35"; - var adbDevices = new List { runningEmulator }; + var adbDevices = new List { runningEmulator }; var availableEmulators = new List { "pixel_7_api_35" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (1, result.Count, "Should not duplicate running emulator"); + Assert.AreEqual (1, result.Length, "Should not duplicate running emulator"); Assert.AreEqual ("emulator-5554", result [0].ItemSpec, "Should keep the running emulator entry"); Assert.AreEqual ("Online", result [0].GetMetadata ("Status")); } @@ -642,22 +608,19 @@ public void MergeDevicesAndEmulators_RunningEmulator_NoDuplicate () [Test] public void MergeDevicesAndEmulators_MixedRunningAndNotRunning () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - // One emulator is running - var runningEmulator = CreateDeviceItem ("emulator-5554", "Pixel 7 API 35", "Emulator", "Online"); - runningEmulator.SetMetadata ("AvdName", "pixel_7_api_35"); + var runningEmulator = CreateDeviceInfo ("emulator-5554", "Pixel 7 API 35", AdbDeviceType.Emulator, AdbDeviceStatus.Online); + runningEmulator.AvdName = "pixel_7_api_35"; - var physicalDevice = CreateDeviceItem ("0A041FDD400327", "Pixel 5", "Device", "Online"); + var physicalDevice = CreateDeviceInfo ("0A041FDD400327", "Pixel 5", AdbDeviceType.Device, AdbDeviceStatus.Online); - var adbDevices = new List { runningEmulator, physicalDevice }; + var adbDevices = new List { runningEmulator, physicalDevice }; var availableEmulators = new List { "pixel_7_api_35", "pixel_9_api_36", "nexus_5_api_30" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (4, result.Count, "Should have: 1 running emulator + 1 device + 2 non-running emulators"); + Assert.AreEqual (4, result.Length, "Should have: 1 running emulator + 1 device + 2 non-running emulators"); // Online devices come first, sorted alphabetically by description Assert.AreEqual ("0A041FDD400327", result [0].ItemSpec); @@ -679,36 +642,30 @@ public void MergeDevicesAndEmulators_MixedRunningAndNotRunning () [Test] public void MergeDevicesAndEmulators_CaseInsensitiveAvdNameMatching () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - // Running emulator with different case - var runningEmulator = CreateDeviceItem ("emulator-5554", "Pixel 7 API 35", "Emulator", "Online"); - runningEmulator.SetMetadata ("AvdName", "Pixel_7_API_35"); + var runningEmulator = CreateDeviceInfo ("emulator-5554", "Pixel 7 API 35", AdbDeviceType.Emulator, AdbDeviceStatus.Online); + runningEmulator.AvdName = "Pixel_7_API_35"; - var adbDevices = new List { runningEmulator }; + var adbDevices = new List { runningEmulator }; var availableEmulators = new List { "pixel_7_api_35" }; // lowercase - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (1, result.Count, "Should match AVD names case-insensitively"); + Assert.AreEqual (1, result.Length, "Should match AVD names case-insensitively"); Assert.AreEqual ("emulator-5554", result [0].ItemSpec); } [Test] public void MergeDevicesAndEmulators_EmptyAdbDevices_ReturnsAllAvailableEmulators () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var adbDevices = new List (); + var adbDevices = new List (); var availableEmulators = new List { "pixel_7_api_35", "pixel_9_api_36" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (2, result.Count, "Should return all available emulators"); + Assert.AreEqual (2, result.Length, "Should return all available emulators"); Assert.AreEqual ("pixel_7_api_35", result [0].ItemSpec); Assert.AreEqual ("Pixel 7 API 35 (Not Running)", result [0].GetMetadata ("Description")); Assert.AreEqual ("pixel_9_api_36", result [1].ItemSpec); @@ -718,53 +675,48 @@ public void MergeDevicesAndEmulators_EmptyAdbDevices_ReturnsAllAvailableEmulator [Test] public void MergeDevicesAndEmulators_AllEmulatorsRunning_NoDuplicates () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; + var emulator1 = CreateDeviceInfo ("emulator-5554", "Pixel 7 API 35", AdbDeviceType.Emulator, AdbDeviceStatus.Online); + emulator1.AvdName = "pixel_7_api_35"; - var emulator1 = CreateDeviceItem ("emulator-5554", "Pixel 7 API 35", "Emulator", "Online"); - emulator1.SetMetadata ("AvdName", "pixel_7_api_35"); + var emulator2 = CreateDeviceInfo ("emulator-5556", "Pixel 9 API 36", AdbDeviceType.Emulator, AdbDeviceStatus.Online); + emulator2.AvdName = "pixel_9_api_36"; - var emulator2 = CreateDeviceItem ("emulator-5556", "Pixel 9 API 36", "Emulator", "Online"); - emulator2.SetMetadata ("AvdName", "pixel_9_api_36"); - - var adbDevices = new List { emulator1, emulator2 }; + var adbDevices = new List { emulator1, emulator2 }; var availableEmulators = new List { "pixel_7_api_35", "pixel_9_api_36" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (2, result.Count, "Should not add duplicates when all emulators are running"); + Assert.AreEqual (2, result.Length, "Should not add duplicates when all emulators are running"); Assert.AreEqual ("Pixel 7 API 35", result [0].GetMetadata ("Description"), "First should be alphabetically first"); Assert.AreEqual ("Pixel 9 API 36", result [1].GetMetadata ("Description"), "Second should be alphabetically second"); - Assert.IsTrue (result.TrueForAll (d => d.GetMetadata ("Status") == "Online"), "All should be Online (running)"); + Assert.IsTrue (result.All (d => d.GetMetadata ("Status") == "Online"), "All should be Online (running)"); } [Test] public void MergeDevicesAndEmulators_NonRunningEmulatorHasFormattedDescription () { - var task = new MockGetAvailableAndroidDevices { - BuildEngine = engine, - }; - - var adbDevices = new List (); + var adbDevices = new List (); var availableEmulators = new List { "pixel_7_pro_api_35" }; - var result = task.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var merged = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators); + var result = GetAvailableAndroidDevices.ConvertToTaskItems (merged); - Assert.AreEqual (1, result.Count); + Assert.AreEqual (1, result.Length); Assert.AreEqual ("Pixel 7 Pro API 35 (Not Running)", result [0].GetMetadata ("Description"), "Description should be formatted with (Not Running) suffix"); } /// - /// Helper method to create a device ITaskItem for testing + /// Helper method to create an AdbDeviceInfo for testing /// - static ITaskItem CreateDeviceItem (string serial, string description, string type, string status) + static AdbDeviceInfo CreateDeviceInfo (string serial, string description, AdbDeviceType type, AdbDeviceStatus status) { - var item = new TaskItem (serial); - item.SetMetadata ("Description", description); - item.SetMetadata ("Type", type); - item.SetMetadata ("Status", status); - return item; + return new AdbDeviceInfo { + Serial = serial, + Description = description, + Type = type, + Status = status, + }; } } } From b3087f9a73f677e301b159c9aaf39e7ce5053d9b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 9 Mar 2026 14:06:30 -0500 Subject: [PATCH 2/2] Update GetAvailableAndroidDevices.cs --- .../Tasks/GetAvailableAndroidDevices.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs index 0ab15b04de0..bb2dbcde7ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs @@ -59,10 +59,11 @@ public override bool RunTask () Log.LogDebugMessage ($"Found {adbDevices.Count} device(s) from adb"); // For emulators, query AVD names + var logger = this.CreateTaskLogger (); foreach (var device in adbDevices) { if (device.Type == AdbDeviceType.Emulator) { device.AvdName = GetEmulatorAvdName (device.Serial); - device.Description = AdbRunner.BuildDeviceDescription (device, this.CreateTaskLogger ()); + device.Description = AdbRunner.BuildDeviceDescription (device, logger); } } @@ -71,7 +72,7 @@ public override bool RunTask () Log.LogDebugMessage ($"Found {availableEmulators.Count} available emulator(s) from 'emulator -list-avds'"); // Merge using shared logic - var mergedDevices = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators, this.CreateTaskLogger ()); + var mergedDevices = AdbRunner.MergeDevicesAndEmulators (adbDevices, availableEmulators, logger); // Convert to ITaskItem array Devices = ConvertToTaskItems (mergedDevices);