diff --git a/test/SharedFx.UnitTests/ConsoleReporter.cs b/test/SharedFx.UnitTests/ConsoleReporter.cs new file mode 100644 index 0000000000..190e805dc2 --- /dev/null +++ b/test/SharedFx.UnitTests/ConsoleReporter.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using McMaster.Extensions.CommandLineUtils; + +namespace Microsoft.Extensions.Tools.Internal +{ + public class ConsoleReporter : IReporter + { + private object _writeLock = new object(); + + public ConsoleReporter(IConsole console) + : this(console, verbose: false, quiet: false) + { } + + public ConsoleReporter(IConsole console, bool verbose, bool quiet) + { + Console = console; + IsVerbose = verbose; + IsQuiet = quiet; + } + + protected IConsole Console { get; } + public bool IsVerbose { get; } + public bool IsQuiet { get; } + + protected virtual void WriteLine(TextWriter writer, string message, ConsoleColor? color) + { + lock (_writeLock) + { + if (color.HasValue) + { + Console.ForegroundColor = color.Value; + } + + writer.WriteLine(message); + + if (color.HasValue) + { + Console.ResetColor(); + } + } + } + + public virtual void Error(string message) + => WriteLine(Console.Error, message, ConsoleColor.Red); + public virtual void Warn(string message) + => WriteLine(Console.Out, message, ConsoleColor.Yellow); + + public virtual void Output(string message) + { + if (IsQuiet) + { + return; + } + WriteLine(Console.Out, message, color: null); + } + + public virtual void Verbose(string message) + { + if (!IsVerbose) + { + return; + } + + WriteLine(Console.Out, message, ConsoleColor.DarkGray); + } + } +} diff --git a/test/SharedFx.UnitTests/RetryHelper.cs b/test/SharedFx.UnitTests/RetryHelper.cs new file mode 100644 index 0000000000..2d92113e36 --- /dev/null +++ b/test/SharedFx.UnitTests/RetryHelper.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using McMaster.Extensions.CommandLineUtils; + +namespace TriageBuildFailures +{ + internal static class RetryHelpers + { + /// + /// Constrain the exponential back-off to this many minutes. + /// + private const int MaxRetryMinutes = 15; + + private static int TotalRetriesUsed; + + public static int GetTotalRetriesUsed() + { + return TotalRetriesUsed; + } + + public static async Task RetryAsync(Func action, IReporter reporter) + { + await RetryAsync( + async () => + { + await action(); + return null; + }, + reporter); + } + + public static async Task RetryAsync(Func> action, IReporter reporter) + { + Exception firstException = null; + + var retriesRemaining = 10; + var retryDelayInMinutes = 1; + + while (retriesRemaining > 0) + { + try + { + return await action(); + } + catch (Exception e) + { + firstException = firstException ?? e; + reporter.Output($"Exception thrown! {e.Message}"); + reporter.Output($"Waiting {retryDelayInMinutes} minute(s) to retry ({retriesRemaining} left)..."); + await Task.Delay(retryDelayInMinutes * 60 * 1000); + + // Do exponential back-off, but limit it (1, 2, 4, 8, 15, 15, 15, ...) + // With MaxRetryMinutes=15 and MaxRetries=10, this will delay a maximum of 105 minutes + retryDelayInMinutes = Math.Min(2 * retryDelayInMinutes, MaxRetryMinutes); + retriesRemaining--; + TotalRetriesUsed++; + } + } + throw new InvalidOperationException("Max exception retries reached, giving up.", firstException); + } + } +} diff --git a/test/SharedFx.UnitTests/SharedFx.UnitTests.csproj b/test/SharedFx.UnitTests/SharedFx.UnitTests.csproj index e574f7d305..e439e2842b 100644 --- a/test/SharedFx.UnitTests/SharedFx.UnitTests.csproj +++ b/test/SharedFx.UnitTests/SharedFx.UnitTests.csproj @@ -26,6 +26,10 @@ <_Parameter1>MicrosoftNETCoreAppPackageVersion <_Parameter2>$(RuntimeFrameworkVersion) + + <_Parameter1>PreviousAspNetCoreReleaseVersion + <_Parameter2>$(PreviousAspNetCoreReleaseVersion) + @@ -34,6 +38,7 @@ + diff --git a/test/SharedFx.UnitTests/SharedFxTests.cs b/test/SharedFx.UnitTests/SharedFxTests.cs index 36160360df..74956cde87 100644 --- a/test/SharedFx.UnitTests/SharedFxTests.cs +++ b/test/SharedFx.UnitTests/SharedFxTests.cs @@ -1,14 +1,77 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.IO; +using System.IO.Compression; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using McMaster.Extensions.CommandLineUtils; using Newtonsoft.Json.Linq; +using TriageBuildFailures; using Xunit; namespace Microsoft.AspNetCore { public class SharedFxTests { + + [Theory] + [MemberData(nameof(GetSharedFxConfig))] + public async Task BaselineTest(SharedFxConfig config) + { + var previousVersion = TestData.GetPreviousAspNetCoreReleaseVersion(); + var url = new Uri($"https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/" + previousVersion + "/aspnetcore-runtime-internal-" + previousVersion + "-win-x64.zip"); + var zipName = "assemblies.zip"; + var nugetAssemblyVersions = new Dictionary(); + var root = TestData.GetDotNetRoot(); + var dir = Path.Combine(root, "shared", config.Name, config.Version); + + using (var testClient = new WebClient()) + { + var reporter = new ConsoleReporter(PhysicalConsole.Singleton); + await RetryHelpers.RetryAsync(async () => await testClient.DownloadFileTaskAsync(url, zipName), reporter); + } + + var zipPath = Path.Combine(AppContext.BaseDirectory, zipName); + + if (!Directory.Exists(AppContext.BaseDirectory + "unzipped")) + { + ZipFile.ExtractToDirectory(AppContext.BaseDirectory, "unzipped"); + } + + var nugetAssembliesPath = Path.Combine(AppContext.BaseDirectory, "unzipped", "shared", config.Name, previousVersion); + + var files = Directory.GetFiles(nugetAssembliesPath, "*.dll"); + foreach (var file in files) + { + try + { + var assemblyVersion = AssemblyName.GetAssemblyName(file).Version; + var dllName = Path.GetFileName(file); + nugetAssemblyVersions.Add(dllName, assemblyVersion); + } + catch (BadImageFormatException) { } + } + + files = Directory.GetFiles(dir, "*.dll"); + + Assert.All(files, file => + { + try + { + var localAssemblyVersion = AssemblyName.GetAssemblyName(file).Version; + var dllName = Path.GetFileName(file); + Assert.True(nugetAssemblyVersions.ContainsKey(dllName), $"Expected {dllName} to be in the downloaded dlls"); + Assert.True(localAssemblyVersion.CompareTo(nugetAssemblyVersions[dllName]) >= 0, $"Expected the local version of {dllName} to be greater than or equal to the already released version."); + } + catch (BadImageFormatException) { } + + }); + } + [Theory] [MemberData(nameof(GetSharedFxConfig))] public void ItContainsValidRuntimeConfigFile(SharedFxConfig config) diff --git a/test/SharedFx.UnitTests/TestData.cs b/test/SharedFx.UnitTests/TestData.cs index eb01f60e8b..dd024ae3bd 100644 --- a/test/SharedFx.UnitTests/TestData.cs +++ b/test/SharedFx.UnitTests/TestData.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore { public static string GetPackageVersion() => GetTestDataValue("PackageVersion"); + public static string GetPreviousAspNetCoreReleaseVersion() => GetTestDataValue("PreviousAspNetCoreReleaseVersion"); + public static string GetMicrosoftNETCoreAppPackageVersion() => GetTestDataValue("MicrosoftNETCoreAppPackageVersion"); public static string GetDotNetRoot() => GetTestDataValue("DotNetRoot");