From f37f9e751ae2472a35f2db3863c9bde0bfc395d5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 31 Aug 2026 09:16:39 -0500 Subject: [PATCH 1/3] Isolate debugging task SDK state Restore process environment variables and Xamarin.AndroidTools SDK singletons after TestResolveToolsExists uses temporary SDK fixtures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../DebuggingTasksTests.cs | 115 +++++++++++------- 1 file changed, 71 insertions(+), 44 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs index 8f7d5ede5c5..d0171bd1b0d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs @@ -7,6 +7,7 @@ using Xamarin.Android.Build; using Xamarin.Android.Tasks; using Xamarin.ProjectTools; +using AT = Xamarin.AndroidTools; namespace Xamarin.Android.Build.Tests { @@ -24,53 +25,79 @@ public void SetUp () [Repeat (10)] public void TestResolveToolsExists () { - List errors = new List(); - List messages = new List(); - - var path = Path.Combine ("temp", TestName); - if (Directory.Exists (Path.Combine (Root, path))) - Directory.Delete (Path.Combine (Root, path), recursive: true); - - var engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages); + var javaHome = Environment.GetEnvironmentVariable ("JAVA_HOME"); + var environmentPath = Environment.GetEnvironmentVariable ("PATH"); + var androidSdkPath = AT.AndroidSdk.AndroidSdkPath ?? AndroidSdkPath; + var androidNdkPath = AT.AndroidSdk.AndroidNdkPath; + var javaSdkPath = AT.AndroidSdk.JavaSdkPath ?? AndroidSdkResolver.GetJavaSdkPath (); + var monoAndroidToolsPath = AT.MonoDroidSdk.RuntimePath ?? TestEnvironment.AndroidMSBuildDirectory; + var monoAndroidBinPath = AT.MonoDroidSdk.BinPath ?? TestEnvironment.OSBinDirectory; var frameworksRoot = Path.Combine (TestEnvironment.DotNetPreviewDirectory, "packs", "Microsoft.NETCore.App.Ref"); var mscorlibDll = Directory.GetFiles (frameworksRoot, "mscorlib.dll", SearchOption.AllDirectories).LastOrDefault (); var frameworksPath = Path.GetDirectoryName (mscorlibDll); - var androidSdk = CreateFauxAndroidSdkDirectory (Path.Combine (path, "Sdk"), "24.0.1", new[] - { - new ApiInfo { Id = "23", Level = 23, Name = "Marshmallow", FrameworkVersion = "v6.0", Stable = true }, - new ApiInfo { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, - new ApiInfo { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, - new ApiInfo { Id = "28", Level = 28, Name = "Pie", FrameworkVersion = "v9.0", Stable = true }, - }); - //var androidNdk = CreateFauxAndroidNdkDirectory (Path.Combine (path, "Ndk")); - var javaSdk = CreateFauxJavaSdkDirectory (Path.Combine(path, "Java"), "1.8.0", out string javaExe, out string javacExe); - var task = new ResolveXamarinAndroidTools () { - BuildEngine = engine, - AndroidNdkPath = null, - AndroidSdkPath = androidSdk, - JavaSdkPath = javaSdk, - MonoAndroidToolsPath = TestEnvironment.AndroidMSBuildDirectory, - ReferenceAssemblyPaths = new string[] { - frameworksPath, - TestEnvironment.MonoAndroidFrameworkDirectory, - }, - }; - Assert.True (task.Execute (), "Task should have completed successfully."); - Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); - var expected = $" Found FrameworkPath at {Path.GetFullPath (frameworksPath)}"; - var firstTaskExecMessages = messages.Select (x => x.Message)?.ToList (); - Assert.IsNotNull (firstTaskExecMessages, "First execution did not contain any messages!"); - CollectionAssert.Contains (firstTaskExecMessages, expected); - CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached AndroidSdk values"); - CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached MonoDroidSdk values"); - - Assert.True (task.Execute (), "Task should have completed successfully."); - Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); - var secondTaskExecMessages = messages.Select (x => x.Message)?.ToList (); - Assert.IsNotNull (secondTaskExecMessages, "Second execution did not contain any messages!"); - CollectionAssert.Contains (secondTaskExecMessages, expected); - CollectionAssert.Contains (secondTaskExecMessages, " Using cached AndroidSdk values"); - CollectionAssert.Contains (secondTaskExecMessages, " Using cached MonoDroidSdk values"); + var monoAndroidFrameworkPath = AT.MonoDroidSdk.FrameworkPath ?? frameworksPath; + + try { + List errors = new List(); + List messages = new List(); + + var path = Path.Combine ("temp", TestName); + if (Directory.Exists (Path.Combine (Root, path))) + Directory.Delete (Path.Combine (Root, path), recursive: true); + + var engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages); + var androidSdk = CreateFauxAndroidSdkDirectory (Path.Combine (path, "Sdk"), "24.0.1", new[] + { + new ApiInfo { Id = "23", Level = 23, Name = "Marshmallow", FrameworkVersion = "v6.0", Stable = true }, + new ApiInfo { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo { Id = "28", Level = 28, Name = "Pie", FrameworkVersion = "v9.0", Stable = true }, + }); + //var androidNdk = CreateFauxAndroidNdkDirectory (Path.Combine (path, "Ndk")); + var javaSdk = CreateFauxJavaSdkDirectory (Path.Combine(path, "Java"), "1.8.0", out string javaExe, out string javacExe); + var task = new ResolveXamarinAndroidTools () { + BuildEngine = engine, + AndroidNdkPath = null, + AndroidSdkPath = androidSdk, + JavaSdkPath = javaSdk, + MonoAndroidToolsPath = monoAndroidToolsPath, + MonoAndroidBinDirectory = monoAndroidBinPath, + ReferenceAssemblyPaths = new string[] { + frameworksPath, + monoAndroidFrameworkPath, + }, + }; + Assert.True (task.Execute (), "Task should have completed successfully."); + Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); + var expected = $" Found FrameworkPath at {Path.GetFullPath (frameworksPath)}"; + var firstTaskExecMessages = messages.Select (x => x.Message)?.ToList (); + Assert.IsNotNull (firstTaskExecMessages, "First execution did not contain any messages!"); + CollectionAssert.Contains (firstTaskExecMessages, expected); + CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached AndroidSdk values"); + CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached MonoDroidSdk values"); + + Assert.True (task.Execute (), "Task should have completed successfully."); + Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); + var secondTaskExecMessages = messages.Select (x => x.Message)?.ToList (); + Assert.IsNotNull (secondTaskExecMessages, "Second execution did not contain any messages!"); + CollectionAssert.Contains (secondTaskExecMessages, expected); + CollectionAssert.Contains (secondTaskExecMessages, " Using cached AndroidSdk values"); + CollectionAssert.Contains (secondTaskExecMessages, " Using cached MonoDroidSdk values"); + } finally { + AT.AndroidSdk.Refresh (androidSdkPath, androidNdkPath, javaSdkPath); + AT.MonoDroidSdk.Refresh (monoAndroidToolsPath, monoAndroidBinPath, monoAndroidFrameworkPath); + Environment.SetEnvironmentVariable ("JAVA_HOME", javaHome); + Environment.SetEnvironmentVariable ("PATH", environmentPath); + } + + Assert.AreEqual (javaHome, Environment.GetEnvironmentVariable ("JAVA_HOME")); + Assert.AreEqual (environmentPath, Environment.GetEnvironmentVariable ("PATH")); + Assert.AreEqual (androidSdkPath, AT.AndroidSdk.AndroidSdkPath); + Assert.AreEqual (androidNdkPath, AT.AndroidSdk.AndroidNdkPath); + Assert.AreEqual (javaSdkPath, AT.AndroidSdk.JavaSdkPath); + Assert.AreEqual (monoAndroidToolsPath, AT.MonoDroidSdk.RuntimePath); + Assert.AreEqual (monoAndroidBinPath, AT.MonoDroidSdk.BinPath); + Assert.AreEqual (monoAndroidFrameworkPath, AT.MonoDroidSdk.FrameworkPath); } [Test] From 1b060dc31ac65e1bb8e89860acc33a84e623f2f4 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 31 Aug 2026 09:21:59 -0500 Subject: [PATCH 2/3] Minimize debugging task state cleanup Keep the existing test setup intact and restore only JAVA_HOME, PATH, and AndroidSdk state after executing the task. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../DebuggingTasksTests.cs | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs index d0171bd1b0d..e159940e8ab 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs @@ -25,48 +25,45 @@ public void SetUp () [Repeat (10)] public void TestResolveToolsExists () { - var javaHome = Environment.GetEnvironmentVariable ("JAVA_HOME"); - var environmentPath = Environment.GetEnvironmentVariable ("PATH"); - var androidSdkPath = AT.AndroidSdk.AndroidSdkPath ?? AndroidSdkPath; - var androidNdkPath = AT.AndroidSdk.AndroidNdkPath; - var javaSdkPath = AT.AndroidSdk.JavaSdkPath ?? AndroidSdkResolver.GetJavaSdkPath (); - var monoAndroidToolsPath = AT.MonoDroidSdk.RuntimePath ?? TestEnvironment.AndroidMSBuildDirectory; - var monoAndroidBinPath = AT.MonoDroidSdk.BinPath ?? TestEnvironment.OSBinDirectory; + List errors = new List(); + List messages = new List(); + + var path = Path.Combine ("temp", TestName); + if (Directory.Exists (Path.Combine (Root, path))) + Directory.Delete (Path.Combine (Root, path), recursive: true); + + var engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages); var frameworksRoot = Path.Combine (TestEnvironment.DotNetPreviewDirectory, "packs", "Microsoft.NETCore.App.Ref"); var mscorlibDll = Directory.GetFiles (frameworksRoot, "mscorlib.dll", SearchOption.AllDirectories).LastOrDefault (); var frameworksPath = Path.GetDirectoryName (mscorlibDll); - var monoAndroidFrameworkPath = AT.MonoDroidSdk.FrameworkPath ?? frameworksPath; + var androidSdk = CreateFauxAndroidSdkDirectory (Path.Combine (path, "Sdk"), "24.0.1", new[] + { + new ApiInfo { Id = "23", Level = 23, Name = "Marshmallow", FrameworkVersion = "v6.0", Stable = true }, + new ApiInfo { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo { Id = "28", Level = 28, Name = "Pie", FrameworkVersion = "v9.0", Stable = true }, + }); + //var androidNdk = CreateFauxAndroidNdkDirectory (Path.Combine (path, "Ndk")); + var javaSdk = CreateFauxJavaSdkDirectory (Path.Combine(path, "Java"), "1.8.0", out string javaExe, out string javacExe); + var task = new ResolveXamarinAndroidTools () { + BuildEngine = engine, + AndroidNdkPath = null, + AndroidSdkPath = androidSdk, + JavaSdkPath = javaSdk, + MonoAndroidToolsPath = TestEnvironment.AndroidMSBuildDirectory, + ReferenceAssemblyPaths = new string[] { + frameworksPath, + TestEnvironment.MonoAndroidFrameworkDirectory, + }, + }; + // ResolveXamarinAndroidTools replaces process-wide AndroidSdk state and updates JAVA_HOME/PATH on Windows. + var javaHome = Environment.GetEnvironmentVariable ("JAVA_HOME"); + var environmentPath = Environment.GetEnvironmentVariable ("PATH"); + var actualAndroidSdk = AndroidSdkPath; + var actualAndroidNdk = AndroidNdkPath; + var actualJavaSdk = AndroidSdkResolver.GetJavaSdkPath (); try { - List errors = new List(); - List messages = new List(); - - var path = Path.Combine ("temp", TestName); - if (Directory.Exists (Path.Combine (Root, path))) - Directory.Delete (Path.Combine (Root, path), recursive: true); - - var engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages); - var androidSdk = CreateFauxAndroidSdkDirectory (Path.Combine (path, "Sdk"), "24.0.1", new[] - { - new ApiInfo { Id = "23", Level = 23, Name = "Marshmallow", FrameworkVersion = "v6.0", Stable = true }, - new ApiInfo { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, - new ApiInfo { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, - new ApiInfo { Id = "28", Level = 28, Name = "Pie", FrameworkVersion = "v9.0", Stable = true }, - }); - //var androidNdk = CreateFauxAndroidNdkDirectory (Path.Combine (path, "Ndk")); - var javaSdk = CreateFauxJavaSdkDirectory (Path.Combine(path, "Java"), "1.8.0", out string javaExe, out string javacExe); - var task = new ResolveXamarinAndroidTools () { - BuildEngine = engine, - AndroidNdkPath = null, - AndroidSdkPath = androidSdk, - JavaSdkPath = javaSdk, - MonoAndroidToolsPath = monoAndroidToolsPath, - MonoAndroidBinDirectory = monoAndroidBinPath, - ReferenceAssemblyPaths = new string[] { - frameworksPath, - monoAndroidFrameworkPath, - }, - }; Assert.True (task.Execute (), "Task should have completed successfully."); Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); var expected = $" Found FrameworkPath at {Path.GetFullPath (frameworksPath)}"; @@ -84,20 +81,10 @@ public void TestResolveToolsExists () CollectionAssert.Contains (secondTaskExecMessages, " Using cached AndroidSdk values"); CollectionAssert.Contains (secondTaskExecMessages, " Using cached MonoDroidSdk values"); } finally { - AT.AndroidSdk.Refresh (androidSdkPath, androidNdkPath, javaSdkPath); - AT.MonoDroidSdk.Refresh (monoAndroidToolsPath, monoAndroidBinPath, monoAndroidFrameworkPath); + AT.AndroidSdk.Refresh (actualAndroidSdk, actualAndroidNdk, actualJavaSdk); Environment.SetEnvironmentVariable ("JAVA_HOME", javaHome); Environment.SetEnvironmentVariable ("PATH", environmentPath); } - - Assert.AreEqual (javaHome, Environment.GetEnvironmentVariable ("JAVA_HOME")); - Assert.AreEqual (environmentPath, Environment.GetEnvironmentVariable ("PATH")); - Assert.AreEqual (androidSdkPath, AT.AndroidSdk.AndroidSdkPath); - Assert.AreEqual (androidNdkPath, AT.AndroidSdk.AndroidNdkPath); - Assert.AreEqual (javaSdkPath, AT.AndroidSdk.JavaSdkPath); - Assert.AreEqual (monoAndroidToolsPath, AT.MonoDroidSdk.RuntimePath); - Assert.AreEqual (monoAndroidBinPath, AT.MonoDroidSdk.BinPath); - Assert.AreEqual (monoAndroidFrameworkPath, AT.MonoDroidSdk.FrameworkPath); } [Test] From ffdb03805d99d904dac4247ab6ad961288b2974d Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 31 Aug 2026 09:23:48 -0500 Subject: [PATCH 3/3] Narrow test cleanup to task execution Keep existing assertions outside the cleanup block so the final diff only wraps the calls that mutate process-wide SDK state. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../DebuggingTasksTests.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs index e159940e8ab..1b42fbf181c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DebuggingTasksTests.cs @@ -62,29 +62,31 @@ public void TestResolveToolsExists () var actualAndroidSdk = AndroidSdkPath; var actualAndroidNdk = AndroidNdkPath; var actualJavaSdk = AndroidSdkResolver.GetJavaSdkPath (); + List firstTaskExecMessages; try { Assert.True (task.Execute (), "Task should have completed successfully."); Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); - var expected = $" Found FrameworkPath at {Path.GetFullPath (frameworksPath)}"; - var firstTaskExecMessages = messages.Select (x => x.Message)?.ToList (); - Assert.IsNotNull (firstTaskExecMessages, "First execution did not contain any messages!"); - CollectionAssert.Contains (firstTaskExecMessages, expected); - CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached AndroidSdk values"); - CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached MonoDroidSdk values"); - + firstTaskExecMessages = messages.Select (x => x.Message)?.ToList (); Assert.True (task.Execute (), "Task should have completed successfully."); - Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); - var secondTaskExecMessages = messages.Select (x => x.Message)?.ToList (); - Assert.IsNotNull (secondTaskExecMessages, "Second execution did not contain any messages!"); - CollectionAssert.Contains (secondTaskExecMessages, expected); - CollectionAssert.Contains (secondTaskExecMessages, " Using cached AndroidSdk values"); - CollectionAssert.Contains (secondTaskExecMessages, " Using cached MonoDroidSdk values"); } finally { AT.AndroidSdk.Refresh (actualAndroidSdk, actualAndroidNdk, actualJavaSdk); Environment.SetEnvironmentVariable ("JAVA_HOME", javaHome); Environment.SetEnvironmentVariable ("PATH", environmentPath); } + + var expected = $" Found FrameworkPath at {Path.GetFullPath (frameworksPath)}"; + Assert.IsNotNull (firstTaskExecMessages, "First execution did not contain any messages!"); + CollectionAssert.Contains (firstTaskExecMessages, expected); + CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached AndroidSdk values"); + CollectionAssert.DoesNotContain (firstTaskExecMessages, " Using cached MonoDroidSdk values"); + + Assert.AreEqual (0, errors.Count, "No Errors should have been raised"); + var secondTaskExecMessages = messages.Select (x => x.Message)?.ToList (); + Assert.IsNotNull (secondTaskExecMessages, "Second execution did not contain any messages!"); + CollectionAssert.Contains (secondTaskExecMessages, expected); + CollectionAssert.Contains (secondTaskExecMessages, " Using cached AndroidSdk values"); + CollectionAssert.Contains (secondTaskExecMessages, " Using cached MonoDroidSdk values"); } [Test]