diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml
index 7c83e8f2..ce23fb5b 100644
--- a/azure-pipelines.yaml
+++ b/azure-pipelines.yaml
@@ -41,6 +41,11 @@ jobs:
inputs:
version: $(DotNetCoreVersion)
+ - task: UseDotNet@2
+ displayName: Use .NET Core 8.0.x
+ inputs:
+ version: 8.0.x
+
- task: DotNetCoreCLI@2
displayName: Build solution Xamarin.Android.Tools.sln
inputs:
diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
deleted file mode 100644
index a04d86ff..00000000
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
-
-using System;
-using Xamarin.Build;
-using static System.Threading.Tasks.TaskExtensions;
-
-namespace Microsoft.Android.Build.Tasks
-{
- public abstract class AndroidAsyncTask : AsyncTask
- {
- public abstract string TaskPrefix { get; }
-
- public override bool Execute ()
- {
- try {
- return RunTask ();
- } catch (Exception ex) {
- this.LogUnhandledException (TaskPrefix, ex);
- return false;
- }
- }
-
- ///
- /// Typically `RunTaskAsync` will be the preferred method to override,
- /// however this method can be overridden instead for Tasks that will
- /// run quickly and do not need to be asynchronous.
- ///
- public virtual bool RunTask ()
- {
- Yield ();
- try {
- this.RunTask (() => RunTaskAsync ())
- .Unwrap ()
- .ContinueWith (Complete);
-
- // This blocks on AsyncTask.Execute, until Complete is called
- return base.Execute ();
- } finally {
- Reacquire ();
- }
- }
-
- ///
- /// Override this method for simplicity of AsyncTask usage:
- /// * Yield / Reacquire is handled for you
- /// * RunTaskAsync is already on a background thread
- ///
- public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
-
- protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
- }
-}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
new file mode 100644
index 00000000..7a8a6072
--- /dev/null
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs
@@ -0,0 +1,437 @@
+// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27
+// https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/db4ce14dacfef47435c238b1b681c124e60ea1a0/Xamarin.Build.AsyncTask/AsyncTask.cs
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Utilities;
+using Microsoft.Build.Framework;
+using System.Threading;
+using static System.Threading.Tasks.TaskExtensions;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Android.Build.Tasks
+{
+ ///
+ /// Base class for tasks that need long-running cancellable asynchronous tasks
+ /// that don't block the UI thread in the IDE.
+ ///
+ public abstract class AsyncTask : Task, ICancelableTask
+ {
+ public abstract string TaskPrefix { get; }
+
+ readonly CancellationTokenSource cts = new CancellationTokenSource ();
+ readonly Queue logMessageQueue = new Queue ();
+ readonly Queue warningMessageQueue = new Queue ();
+ readonly Queue errorMessageQueue = new Queue ();
+ readonly Queue customMessageQueue = new Queue ();
+ readonly Queue telemetryMessageQueue = new Queue ();
+ readonly ManualResetEvent logDataAvailable = new ManualResetEvent (false);
+ readonly ManualResetEvent errorDataAvailable = new ManualResetEvent (false);
+ readonly ManualResetEvent warningDataAvailable = new ManualResetEvent (false);
+ readonly ManualResetEvent customDataAvailable = new ManualResetEvent (false);
+ readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent (false);
+ readonly ManualResetEvent taskCancelled = new ManualResetEvent (false);
+ readonly ManualResetEvent completed = new ManualResetEvent (false);
+ bool isRunning = true;
+ object eventlock = new object ();
+ int uiThreadId = 0;
+
+ ///
+ /// Indicates if the task will yield the node during tool execution.
+ ///
+ public bool YieldDuringToolExecution { get; set; }
+
+ ///
+ /// The cancellation token to notify the cancellation requests
+ ///
+ public CancellationToken CancellationToken => cts.Token;
+
+ ///
+ /// Gets the current working directory for the task, which is captured at task
+ /// creation time from .
+ ///
+ protected string WorkingDirectory { get; private set; }
+
+ [Obsolete ("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")]
+ private new TaskLoggingHelper Log => base.Log;
+
+ ///
+ /// Initializes the task.
+ ///
+ protected AsyncTask ()
+ {
+ YieldDuringToolExecution = false;
+ WorkingDirectory = Directory.GetCurrentDirectory ();
+ uiThreadId = Thread.CurrentThread.ManagedThreadId;
+ }
+
+ public void Cancel ()
+ => taskCancelled.Set ();
+
+ protected void Complete (System.Threading.Tasks.Task task)
+ {
+ if (task.Exception != null) {
+ var ex = task.Exception.GetBaseException ();
+ this.LogUnhandledException (TaskPrefix, ex);
+ }
+ Complete ();
+ }
+
+ public void Complete ()
+ => completed.Set ();
+
+ public void LogDebugTaskItems (string message, string [] items)
+ {
+ LogDebugMessage (message);
+
+ if (items == null)
+ return;
+
+ foreach (var item in items)
+ LogDebugMessage (" {0}", item);
+ }
+
+ public void LogDebugTaskItems (string message, ITaskItem [] items)
+ {
+ LogDebugMessage (message);
+
+ if (items == null)
+ return;
+
+ foreach (var item in items)
+ LogDebugMessage (" {0}", item.ItemSpec);
+ }
+
+ public void LogTelemetry (string eventName, IDictionary properties)
+ {
+ if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+#pragma warning disable 618
+ Log.LogTelemetry (eventName, properties);
+ return;
+#pragma warning restore 618
+ }
+
+ var data = new TelemetryEventArgs () {
+ EventName = eventName,
+ Properties = properties
+ };
+ EnqueueMessage (telemetryMessageQueue, data, telemetryDataAvailable);
+ }
+
+ public void LogMessage (string message)
+ => LogMessage (message, importance: MessageImportance.Normal);
+
+ public void LogMessage (string message, params object [] messageArgs)
+ => LogMessage (string.Format (message, messageArgs));
+
+ public void LogDebugMessage (string message)
+ => LogMessage (message, importance: MessageImportance.Low);
+
+ public void LogDebugMessage (string message, params object [] messageArgs)
+ => LogMessage (string.Format (message, messageArgs), importance: MessageImportance.Low);
+
+ public void LogMessage (string message, MessageImportance importance = MessageImportance.Normal)
+ {
+ if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+#pragma warning disable 618
+ Log.LogMessage (importance, message);
+ return;
+#pragma warning restore 618
+ }
+
+ var data = new BuildMessageEventArgs (
+ message: message,
+ helpKeyword: null,
+ senderName: null,
+ importance: importance
+ );
+ EnqueueMessage (logMessageQueue, data, logDataAvailable);
+ }
+
+ public void LogError (string message)
+ => LogCodedError (code: null, message: message, file: null, lineNumber: 0);
+
+ public void LogError (string message, params object [] messageArgs)
+ => LogCodedError (code: null, message: string.Format (message, messageArgs));
+
+ public void LogCodedError (string code, string message)
+ => LogCodedError (code: code, message: message, file: null, lineNumber: 0);
+
+ public void LogCodedError (string code, string message, params object [] messageArgs)
+ => LogCodedError (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0);
+
+ public void LogCodedError (string code, string file, int lineNumber, string message, params object [] messageArgs)
+ => LogCodedError (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber);
+
+ public void LogCodedError (string code, string message, string file, int lineNumber)
+ {
+ if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+#pragma warning disable 618
+ Log.LogError (
+ subcategory: null,
+ errorCode: code,
+ helpKeyword: null,
+ file: file,
+ lineNumber: lineNumber,
+ columnNumber: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ message: message
+ );
+ return;
+#pragma warning restore 618
+ }
+
+ var data = new BuildErrorEventArgs (
+ subcategory: null,
+ code: code,
+ file: file,
+ lineNumber: lineNumber,
+ columnNumber: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ message: message,
+ helpKeyword: null,
+ senderName: null
+ );
+ EnqueueMessage (errorMessageQueue, data, errorDataAvailable);
+ }
+
+ public void LogWarning (string message)
+ => LogCodedWarning (code: null, message: message, file: null, lineNumber: 0);
+
+ public void LogWarning (string message, params object [] messageArgs)
+ => LogCodedWarning (code: null, message: string.Format (message, messageArgs));
+
+ public void LogCodedWarning (string code, string message)
+ => LogCodedWarning (code: code, message: message, file: null, lineNumber: 0);
+
+ public void LogCodedWarning (string code, string message, params object [] messageArgs)
+ => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0);
+
+ public void LogCodedWarning (string code, string file, int lineNumber, string message, params object [] messageArgs)
+ => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber);
+
+ public void LogCodedWarning (string code, string message, string file, int lineNumber)
+ {
+ if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+#pragma warning disable 618
+ Log.LogWarning (
+ subcategory: null,
+ warningCode: code,
+ helpKeyword: null,
+ file: file,
+ lineNumber: lineNumber,
+ columnNumber: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ message: message
+ );
+ return;
+#pragma warning restore 618
+ }
+ var data = new BuildWarningEventArgs (
+ subcategory: null,
+ code: code,
+ file: file,
+ lineNumber: lineNumber,
+ columnNumber: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ message: message,
+ helpKeyword: null,
+ senderName: null
+ );
+ EnqueueMessage (warningMessageQueue, data, warningDataAvailable);
+ }
+
+ public void LogCustomBuildEvent (CustomBuildEventArgs e)
+ {
+ if (uiThreadId == Thread.CurrentThread.ManagedThreadId) {
+ BuildEngine.LogCustomEvent (e);
+ return;
+ }
+ EnqueueMessage (customMessageQueue, e, customDataAvailable);
+ }
+
+ bool ExecuteWaitForCompletion ()
+ {
+ WaitForCompletion ();
+#pragma warning disable 618
+ return !Log.HasLoggedErrors;
+#pragma warning restore 618
+ }
+
+ void EnqueueMessage (Queue queue, object item, ManualResetEvent resetEvent)
+ {
+ lock (queue.SyncRoot) {
+ queue.Enqueue (item);
+ lock (eventlock) {
+ if (isRunning)
+ resetEvent.Set ();
+ }
+ }
+ }
+
+ void LogInternal (Queue queue, Action action, ManualResetEvent resetEvent)
+ {
+ lock (queue.SyncRoot) {
+ while (queue.Count > 0) {
+ var args = (T) queue.Dequeue ();
+ action (args);
+ }
+ resetEvent.Reset ();
+ }
+ }
+
+ protected void Yield ()
+ {
+ if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+ ((IBuildEngine3) BuildEngine).Yield ();
+ }
+
+ protected void Reacquire ()
+ {
+ if (YieldDuringToolExecution && BuildEngine is IBuildEngine3)
+ ((IBuildEngine3) BuildEngine).Reacquire ();
+ }
+
+ protected void WaitForCompletion ()
+ {
+ WaitHandle [] handles = new WaitHandle [] {
+ logDataAvailable,
+ errorDataAvailable,
+ warningDataAvailable,
+ customDataAvailable,
+ telemetryDataAvailable,
+ taskCancelled,
+ completed,
+ };
+ try {
+ while (isRunning) {
+ var index = (WaitHandleIndex) System.Threading.WaitHandle.WaitAny (handles, TimeSpan.FromMilliseconds (10));
+ switch (index) {
+ case WaitHandleIndex.LogDataAvailable:
+ LogInternal (logMessageQueue, (e) => {
+#pragma warning disable 618
+ Log.LogMessage (e.Importance, e.Message);
+#pragma warning restore 618
+ }, logDataAvailable);
+ break;
+ case WaitHandleIndex.ErrorDataAvailable:
+ LogInternal (errorMessageQueue, (e) => {
+#pragma warning disable 618
+ Log.LogError (
+ subcategory: null,
+ errorCode: e.Code,
+ helpKeyword: null,
+ file: e.File,
+ lineNumber: e.LineNumber,
+ columnNumber: e.ColumnNumber,
+ endLineNumber: e.EndLineNumber,
+ endColumnNumber: e.EndColumnNumber,
+ message: e.Message);
+#pragma warning restore 618
+ }, errorDataAvailable);
+ break;
+ case WaitHandleIndex.WarningDataAvailable:
+ LogInternal (warningMessageQueue, (e) => {
+#pragma warning disable 618
+ Log.LogWarning (subcategory: null,
+ warningCode: e.Code,
+ helpKeyword: null,
+ file: e.File,
+ lineNumber: e.LineNumber,
+ columnNumber: e.ColumnNumber,
+ endLineNumber: e.EndLineNumber,
+ endColumnNumber: e.EndColumnNumber,
+ message: e.Message);
+#pragma warning restore 618
+ }, warningDataAvailable);
+ break;
+ case WaitHandleIndex.CustomDataAvailable:
+ LogInternal (customMessageQueue, (e) => {
+ BuildEngine.LogCustomEvent (e);
+ }, customDataAvailable);
+ break;
+ case WaitHandleIndex.TelemetryDataAvailable:
+ LogInternal (telemetryMessageQueue, (e) => {
+ BuildEngine5.LogTelemetry (e.EventName, e.Properties);
+ }, telemetryDataAvailable);
+ break;
+ case WaitHandleIndex.TaskCancelled:
+ Cancel ();
+ cts.Cancel ();
+ isRunning = false;
+ break;
+ case WaitHandleIndex.Completed:
+ isRunning = false;
+ break;
+ }
+ }
+
+ } finally {
+
+ }
+ }
+
+ public override bool Execute ()
+ {
+ try {
+ return RunTask ();
+ } catch (Exception ex) {
+ this.LogUnhandledException (TaskPrefix, ex);
+ return false;
+ }
+ }
+
+ ///
+ /// Typically `RunTaskAsync` will be the preferred method to override,
+ /// however this method can be overridden instead for Tasks that will
+ /// run quickly and do not need to be asynchronous.
+ ///
+ public virtual bool RunTask ()
+ {
+ Yield ();
+ try {
+ this.RunTask (() => RunTaskAsync ())
+ .Unwrap ()
+ .ContinueWith (Complete);
+
+ // This blocks on Execute, until Complete is called
+ return ExecuteWaitForCompletion ();
+ } finally {
+ Reacquire ();
+ }
+ }
+
+ ///
+ /// Override this method for simplicity of AsyncTask usage:
+ ///
+ /// -
+ /// Yield / Reacquire is handled for you
+ ///
+ /// -
+ /// RunTaskAsync is already on a background thread
+ ///
+ ///
+ ///
+ public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
+
+ protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
+
+ private enum WaitHandleIndex
+ {
+ LogDataAvailable,
+ ErrorDataAvailable,
+ WarningDataAvailable,
+ CustomDataAvailable,
+ TelemetryDataAvailable,
+ TaskCancelled,
+ Completed,
+ }
+ }
+}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
index fce4bc64..6b643ee7 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Xamarin.Build;
namespace Microsoft.Android.Build.Tasks
{
diff --git a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
index fd0909d7..323c874e 100644
--- a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
@@ -9,7 +9,6 @@
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
-using Xamarin.Build;
namespace Microsoft.Android.Build.Tasks
{
diff --git a/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
index 96c56c0e..5d6a62a5 100644
--- a/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
+++ b/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems
@@ -17,7 +17,6 @@
-
diff --git a/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
index 67d20868..4cfe3c0b 100644
--- a/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs
@@ -5,7 +5,6 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Build.Utilities;
-using Xamarin.Build;
namespace Microsoft.Android.Build.Tasks
{
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
index ecfc8667..13a9762d 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs
@@ -2,7 +2,6 @@
using System.Threading.Tasks;
using Microsoft.Android.Build.Tasks;
using NUnit.Framework;
-using Xamarin.Build;
namespace Microsoft.Android.Build.BaseTasks.Tests
{
@@ -11,11 +10,16 @@ public class AsyncTaskExtensionsTests
{
const int Iterations = 32;
+ class TestAsyncTask : AsyncTask
+ {
+ public override string TaskPrefix => "TEST";
+ }
+
[Test]
public async Task RunTask ()
{
bool set = false;
- await new AsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type
+ await new TestAsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type
Assert.IsTrue (set);
}
@@ -23,7 +27,7 @@ public async Task RunTask ()
public async Task RunTaskOfT ()
{
bool set = false;
- Assert.IsTrue (await new AsyncTask ().RunTask (() => set = true), "RunTask should return true");
+ Assert.IsTrue (await new TestAsyncTask ().RunTask (() => set = true), "RunTask should return true");
Assert.IsTrue (set);
}
@@ -31,7 +35,7 @@ public async Task RunTaskOfT ()
public async Task WhenAll ()
{
bool set = false;
- await new AsyncTask ().WhenAll (new [] { 0 }, _ => set = true);
+ await new TestAsyncTask ().WhenAll (new [] { 0 }, _ => set = true);
Assert.IsTrue (set);
}
@@ -40,7 +44,7 @@ public async Task WhenAllWithLock ()
{
var input = new int [Iterations];
var output = new List ();
- await new AsyncTask ().WhenAllWithLock (input, (i, l) => {
+ await new TestAsyncTask ().WhenAllWithLock (input, (i, l) => {
lock (l) output.Add (i);
});
Assert.AreEqual (Iterations, output.Count);
@@ -50,7 +54,7 @@ public async Task WhenAllWithLock ()
public void ParallelForEach ()
{
bool set = false;
- new AsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true);
+ new TestAsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true);
Assert.IsTrue (set);
}
@@ -59,7 +63,7 @@ public void ParallelForEachWithLock ()
{
var input = new int [Iterations];
var output = new List ();
- new AsyncTask ().ParallelForEachWithLock (input, (i, l) => {
+ new TestAsyncTask ().ParallelForEachWithLock (input, (i, l) => {
lock (l) output.Add (i);
});
Assert.AreEqual (Iterations, output.Count);
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
new file mode 100644
index 00000000..912b4c1a
--- /dev/null
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Linq;
+using Microsoft.Android.Build.BaseTasks.Tests.Utilities;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using NUnit.Framework;
+
+namespace Microsoft.Android.Build.BaseTasks.Tests
+{
+ [TestFixture]
+ public class AsyncTaskTests
+ {
+ List errors;
+ List warnings;
+ List messages;
+ MockBuildEngine engine;
+
+ [SetUp]
+ public void TestSetup ()
+ {
+ errors = new List ();
+ warnings = new List ();
+ messages = new List ();
+ engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages);
+ }
+
+ class AsyncTaskTest : AsyncTask
+ {
+ public override string TaskPrefix => "ATT";
+ }
+
+ public class AsyncMessage : AsyncTask
+ {
+ public override string TaskPrefix => "AM";
+
+ public string Text { get; set; }
+
+ public override bool Execute ()
+ {
+ LogTelemetry ("Test", new Dictionary () { { "Property", "Value" } });
+ return base.Execute ();
+ }
+
+ public override async Task RunTaskAsync ()
+ {
+ await Task.Delay (5000);
+ LogMessage (Text);
+ Complete ();
+ }
+ }
+
+ class AsyncTaskExceptionTest : AsyncTask
+ {
+ public override string TaskPrefix => "ATET";
+
+ public string ExceptionMessage { get; set; }
+
+ public override Task RunTaskAsync ()
+ {
+ throw new System.InvalidOperationException (ExceptionMessage);
+ }
+ }
+
+
+ [Test]
+ public void RunAsyncTask ()
+ {
+ var task = new AsyncTaskTest () {
+ BuildEngine = engine,
+ };
+
+ Assert.IsTrue (task.Execute (), "Empty AsyncTask should have ran successfully.");
+ }
+
+ [Test]
+ public void RunAsyncTaskOverride ()
+ {
+ var message = "Hello Async World!";
+ var task = new AsyncMessage () {
+ BuildEngine = engine,
+ Text = message,
+ };
+ var taskSucceeded = task.Execute ();
+ Assert.IsTrue (messages.Any (e => e.Message.Contains (message)),
+ $"Task did not contain expected message text: '{message}'.");
+ }
+
+ [Test]
+ public void RunAsyncTaskExpectedException ()
+ {
+ var expectedException = "test exception!";
+ var task = new AsyncTaskExceptionTest () {
+ BuildEngine = engine,
+ ExceptionMessage = expectedException,
+ };
+
+ Assert.IsFalse (task.Execute (), "Exception AsyncTask should have failed.");
+ Assert.IsTrue (errors.Count == 1, "Exception AsyncTask should have produced one error.");
+ Assert.IsTrue (errors[0].Message.Contains (expectedException),
+ $"Task did not contain expected error text: '{expectedException}'.");
+ }
+
+ }
+}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
index 7f0fb6d7..99bdc267 100644
--- a/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
@@ -4,7 +4,7 @@
- net6.0
+ net8.0
Microsoft.Android.Build.BaseTasks.Tests
false
$(TestOutputFullPath)