From 9b2cb276c2de21039215c4a344fbd13370dec26e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 16 Dec 2025 12:59:04 -0600 Subject: [PATCH] [tests] `Assert.Inconclusive()` tests with http errors MSBuild tests using `DownloadedCache` can randomly fail with HTTP errors such as: System.Net.Http.HttpRequestException : Response status code does not indicate success: 504 (Gateway Time-out). Stack trace at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at Xamarin.ProjectTools.DownloadedCache.GetAsFile(String url, String filename) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DownloadedCache.cs:line 39 at Xamarin.ProjectTools.BuildItem.<>c__DisplayClass70_0.b__0() in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildItem.cs:line 309 at Xamarin.ProjectTools.XamarinProject.<>c.b__110_0(BuildItem s) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/XamarinProject.cs:line 455 at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection) at Xamarin.ProjectTools.XamarinProject.Save(Boolean saveProject) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/XamarinProject.cs:line 455 at Xamarin.ProjectTools.ProjectBuilder.Save(XamarinProject project, Boolean doNotCleanupOnUpdate, Boolean saveProject) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs:line 98 at Xamarin.ProjectTools.ProjectBuilder.Build(XamarinProject project, Boolean doNotCleanupOnUpdate, String[] parameters, Boolean saveProject, Dictionary`2 environmentVariables) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs:line 128 at Xamarin.Android.Build.Tests.BindingBuildTest.BindngFilterUnsupportedNativeAbiLibraries(AndroidRuntime runtime) in /Users/runner/work/1/s/android/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs:line 411 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) Let's call `Assert.Inconclusive()` when we catch such exceptions, so that the test is marked as inconclusive instead of failed. We also log information about the exception. --- .../Common/DownloadedCache.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DownloadedCache.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DownloadedCache.cs index 05a2b5ec841..ebb85861b40 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DownloadedCache.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DownloadedCache.cs @@ -2,8 +2,10 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Security.Cryptography; +using NUnit.Framework; namespace Xamarin.ProjectTools { @@ -35,16 +37,45 @@ public string GetAsFile (string url, string filename = "") if (File.Exists (filename)) return filename; // FIXME: should be clever enough to resolve name conflicts. - using (var response = httpClient.GetAsync (url).GetAwaiter ().GetResult ()) { - response.EnsureSuccessStatusCode (); - using (var fileStream = File.Create (filename)) - using (var httpStream = response.Content.ReadAsStreamAsync ().GetAwaiter ().GetResult ()) { - httpStream.CopyTo (fileStream); + try { + using (var response = httpClient.GetAsync (url).GetAwaiter ().GetResult ()) { + response.EnsureSuccessStatusCode (); + using (var fileStream = File.Create (filename)) + using (var httpStream = response.Content.ReadAsStreamAsync ().GetAwaiter ().GetResult ()) { + httpStream.CopyTo (fileStream); + } } + } catch (HttpRequestException ex) when (IsTransientError (ex)) { + TestContext.WriteLine ($"Transient network error downloading '{url}':"); + TestContext.WriteLine ($" Message: {ex.Message}"); + if (ex.StatusCode.HasValue) { + TestContext.WriteLine ($" HTTP Status Code: {(int)ex.StatusCode.Value} ({ex.StatusCode.Value})"); + } + TestContext.WriteLine ($" URL: {url}"); + TestContext.WriteLine ($" Stack Trace: {ex.StackTrace}"); + Assert.Inconclusive ($"Test skipped due to transient network error: {ex.Message}"); } return filename; } } + + static bool IsTransientError (HttpRequestException ex) + { + // Check for timeout errors + if (ex.Message.Contains ("504") || ex.Message.Contains ("Gateway Time-out")) + return true; + + // Check for other common transient errors + if (ex.StatusCode.HasValue) { + var statusCode = ex.StatusCode.Value; + return statusCode == HttpStatusCode.RequestTimeout || + statusCode == HttpStatusCode.GatewayTimeout || + statusCode == HttpStatusCode.ServiceUnavailable || + statusCode == HttpStatusCode.BadGateway; + } + + return false; + } } }