Skip to content

Commit ee31e4b

Browse files
rmarinhoCopilot
andcommitted
Use shared ConfigureEnvironment, ProcessUtils.CreateProcessStartInfo, add tests
- Replace manual ProcessStartInfo with ProcessUtils.CreateProcessStartInfo - Use AndroidEnvironmentHelper.ConfigureEnvironment instead of duplicated method - Extract ParseListAvdsOutput as internal static for testability - Return IReadOnlyList<string> from ListAvdNamesAsync - Add RequireEmulatorPath helper - Add EmulatorRunnerTests (7 tests): parsing, path discovery, null/missing sdk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6f75aef commit ee31e4b

2 files changed

Lines changed: 110 additions & 30 deletions

File tree

src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,34 +44,27 @@ public string? EmulatorPath {
4444

4545
public bool IsAvailable => EmulatorPath is not null;
4646

47-
void ConfigureEnvironment (ProcessStartInfo psi)
47+
string RequireEmulatorPath ()
4848
{
49-
var sdkPath = getSdkPath ();
50-
if (!string.IsNullOrEmpty (sdkPath))
51-
psi.EnvironmentVariables ["ANDROID_HOME"] = sdkPath;
49+
return EmulatorPath ?? throw new InvalidOperationException ("Android Emulator not found.");
50+
}
5251

53-
var jdkPath = getJdkPath?.Invoke ();
54-
if (!string.IsNullOrEmpty (jdkPath))
55-
psi.EnvironmentVariables ["JAVA_HOME"] = jdkPath;
52+
void ConfigureEnvironment (ProcessStartInfo psi)
53+
{
54+
AndroidEnvironmentHelper.ConfigureEnvironment (psi, getSdkPath (), getJdkPath?.Invoke ());
5655
}
5756

5857
public Process StartAvd (string avdName, bool coldBoot = false, string? additionalArgs = null)
5958
{
60-
if (!IsAvailable)
61-
throw new InvalidOperationException ("Android Emulator not found.");
59+
var emulatorPath = RequireEmulatorPath ();
6260

63-
var args = $"-avd \"{avdName}\"";
61+
var args = new List<string> { "-avd", avdName };
6462
if (coldBoot)
65-
args += " -no-snapshot-load";
63+
args.Add ("-no-snapshot-load");
6664
if (!string.IsNullOrEmpty (additionalArgs))
67-
args += " " + additionalArgs;
68-
69-
var psi = new ProcessStartInfo {
70-
FileName = EmulatorPath!,
71-
Arguments = args,
72-
UseShellExecute = false,
73-
CreateNoWindow = true
74-
};
65+
args.Add (additionalArgs);
66+
67+
var psi = ProcessUtils.CreateProcessStartInfo (emulatorPath, args.ToArray ());
7568
ConfigureEnvironment (psi);
7669

7770
var process = new Process { StartInfo = psi };
@@ -80,29 +73,27 @@ public Process StartAvd (string avdName, bool coldBoot = false, string? addition
8073
return process;
8174
}
8275

83-
public async Task<List<string>> ListAvdNamesAsync (CancellationToken cancellationToken = default)
76+
public async Task<IReadOnlyList<string>> ListAvdNamesAsync (CancellationToken cancellationToken = default)
8477
{
85-
if (!IsAvailable)
86-
throw new InvalidOperationException ("Android Emulator not found.");
78+
var emulatorPath = RequireEmulatorPath ();
8779

8880
using var stdout = new StringWriter ();
89-
var psi = new ProcessStartInfo {
90-
FileName = EmulatorPath!,
91-
Arguments = "-list-avds",
92-
UseShellExecute = false,
93-
CreateNoWindow = true
94-
};
81+
var psi = ProcessUtils.CreateProcessStartInfo (emulatorPath, "-list-avds");
9582
ConfigureEnvironment (psi);
9683

9784
await ProcessUtils.StartProcess (psi, stdout, null, cancellationToken).ConfigureAwait (false);
9885

86+
return ParseListAvdsOutput (stdout.ToString ());
87+
}
88+
89+
internal static List<string> ParseListAvdsOutput (string output)
90+
{
9991
var avds = new List<string> ();
100-
foreach (var line in stdout.ToString ().Split ('\n')) {
92+
foreach (var line in output.Split ('\n')) {
10193
var trimmed = line.Trim ();
10294
if (!string.IsNullOrEmpty (trimmed))
10395
avds.Add (trimmed);
10496
}
105-
10697
return avds;
10798
}
10899
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.IO;
5+
using NUnit.Framework;
6+
7+
namespace Xamarin.Android.Tools.Tests;
8+
9+
[TestFixture]
10+
public class EmulatorRunnerTests
11+
{
12+
[Test]
13+
public void ParseListAvdsOutput_MultipleAvds ()
14+
{
15+
var output = "Pixel_7_API_35\nMAUI_Emulator\nNexus_5X\n";
16+
17+
var avds = EmulatorRunner.ParseListAvdsOutput (output);
18+
19+
Assert.AreEqual (3, avds.Count);
20+
Assert.AreEqual ("Pixel_7_API_35", avds [0]);
21+
Assert.AreEqual ("MAUI_Emulator", avds [1]);
22+
Assert.AreEqual ("Nexus_5X", avds [2]);
23+
}
24+
25+
[Test]
26+
public void ParseListAvdsOutput_EmptyOutput ()
27+
{
28+
var avds = EmulatorRunner.ParseListAvdsOutput ("");
29+
Assert.AreEqual (0, avds.Count);
30+
}
31+
32+
[Test]
33+
public void ParseListAvdsOutput_WindowsNewlines ()
34+
{
35+
var output = "Pixel_7_API_35\r\nMAUI_Emulator\r\n";
36+
37+
var avds = EmulatorRunner.ParseListAvdsOutput (output);
38+
39+
Assert.AreEqual (2, avds.Count);
40+
Assert.AreEqual ("Pixel_7_API_35", avds [0]);
41+
Assert.AreEqual ("MAUI_Emulator", avds [1]);
42+
}
43+
44+
[Test]
45+
public void ParseListAvdsOutput_BlankLines ()
46+
{
47+
var output = "\nPixel_7_API_35\n\n\nMAUI_Emulator\n\n";
48+
49+
var avds = EmulatorRunner.ParseListAvdsOutput (output);
50+
51+
Assert.AreEqual (2, avds.Count);
52+
}
53+
54+
[Test]
55+
public void EmulatorPath_FindsInSdk ()
56+
{
57+
var tempDir = Path.Combine (Path.GetTempPath (), $"emu-test-{Path.GetRandomFileName ()}");
58+
var emulatorDir = Path.Combine (tempDir, "emulator");
59+
Directory.CreateDirectory (emulatorDir);
60+
61+
try {
62+
var emuName = OS.IsWindows ? "emulator.exe" : "emulator";
63+
File.WriteAllText (Path.Combine (emulatorDir, emuName), "");
64+
65+
var runner = new EmulatorRunner (() => tempDir);
66+
67+
Assert.IsNotNull (runner.EmulatorPath);
68+
Assert.IsTrue (runner.IsAvailable);
69+
} finally {
70+
Directory.Delete (tempDir, true);
71+
}
72+
}
73+
74+
[Test]
75+
public void EmulatorPath_MissingSdk_ReturnsNull ()
76+
{
77+
var runner = new EmulatorRunner (() => "/nonexistent/path");
78+
Assert.IsNull (runner.EmulatorPath);
79+
Assert.IsFalse (runner.IsAvailable);
80+
}
81+
82+
[Test]
83+
public void EmulatorPath_NullSdk_ReturnsNull ()
84+
{
85+
var runner = new EmulatorRunner (() => null);
86+
Assert.IsNull (runner.EmulatorPath);
87+
Assert.IsFalse (runner.IsAvailable);
88+
}
89+
}

0 commit comments

Comments
 (0)