From 51c59b7a149cb8d9083b9854586e264a29b0e0b1 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 8 Nov 2024 22:18:54 -0500 Subject: [PATCH] perf: improve RuntimeSupport integration test performance --- ...bda.RuntimeSupport.IntegrationTests.csproj | 28 +----- ...spNetCoreMinimalApiCustomSerializerTest.cs | 1 + .../CustomRuntimeAspNetCoreMinimalApiTest.cs | 1 + .../CustomRuntimeTests.cs | 2 + .../Helpers/CommandLineWrapper.cs | 90 +++++++++++++++++++ .../Helpers/LambdaToolsHelper.cs | 78 ++++++++++++++++ .../IntegrationTestCollection.cs | 9 ++ .../IntegrationTestFixture.cs | 46 ++++++++++ buildtools/build.proj | 4 +- buildtools/ci.buildspec.yml | 3 - 10 files changed, 233 insertions(+), 29 deletions(-) create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj index 1e37f5d9b..7f816e917 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 SKIP_RUNTIME_SUPPORT_INTEG_TESTS @@ -23,13 +23,13 @@ - + - + all runtime; build; native; contentfiles; analyzers - + @@ -39,24 +39,4 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs index 2d2a52b03..2661e4d1e 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs @@ -18,6 +18,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests { + [Collection("Integration Tests")] public class CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest : BaseCustomRuntimeTest { public CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest() diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs index 0064568b9..52f433b09 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs @@ -18,6 +18,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests { + [Collection("Integration Tests")] public class CustomRuntimeAspNetCoreMinimalApiTest : BaseCustomRuntimeTest { public CustomRuntimeAspNetCoreMinimalApiTest() diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs index e4af2b9bc..4ab778a30 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs @@ -31,6 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests { + [Collection("Integration Tests")] public class CustomRuntimeNET6Tests : CustomRuntimeTests { public CustomRuntimeNET6Tests() @@ -49,6 +50,7 @@ public async Task TestAllNET6HandlersAsync() } } + [Collection("Integration Tests")] public class CustomRuntimeNET8Tests : CustomRuntimeTests { public CustomRuntimeNET8Tests() diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs new file mode 100644 index 000000000..aa8651eae --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests.Helpers; + +public static class CommandLineWrapper +{ + public static async Task Run(string command, string arguments, string workingDirectory, CancellationToken cancellationToken = default) + { + var processStartInfo = new ProcessStartInfo + { + FileName = command, + Arguments = arguments, + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using (var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true }) + { + var tcs = new TaskCompletionSource(); + + // Handle process exit event + process.Exited += (sender, args) => + { + tcs.TrySetResult(true); + }; + + try + { + // Attach event handlers + process.OutputDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + Console.WriteLine(args.Data); + } + }; + + process.ErrorDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + Console.WriteLine(args.Data); + } + }; + + // Start the process + process.Start(); + + // Begin asynchronous read operations + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + // Wait for the process to exit or cancellation + var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(Timeout.Infinite, cancellationToken)); + + if (completedTask == tcs.Task) + { + // Process exited normally + await tcs.Task; // Just to propagate any exceptions + } + else + { + // Cancellation requested + if (!process.HasExited) + { + process.Kill(); + } + throw new OperationCanceledException(cancellationToken); + } + } + catch (Exception ex) + { + Console.WriteLine("Exception: " + ex); + if (!process.HasExited) + { + process.Kill(); + } + } + + Assert.True(process.ExitCode == 0, $"Command '{command} {arguments}' failed."); + } + } +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs new file mode 100644 index 000000000..067d6f689 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs @@ -0,0 +1,78 @@ +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests.Helpers; + +public static class LambdaToolsHelper +{ + private static readonly string FunctionArchitecture = RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm64 ? "arm64" : "x86_64"; + + public static string GetTempTestAppDirectory(string workingDirectory, string testAppPath) + { + var customTestAppPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(customTestAppPath); + + var currentDir = new DirectoryInfo(workingDirectory); + CopyDirectory(currentDir, customTestAppPath); + + return Path.Combine(customTestAppPath, testAppPath); + } + + public static async Task InstallLambdaTools() + { + var customToolPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(customToolPath); + await CommandLineWrapper.Run( + "dotnet", + $"tool install Amazon.Lambda.Tools --tool-path {customToolPath}", + Directory.GetCurrentDirectory()); + return customToolPath; + } + + public static async Task LambdaPackage(string toolPath, string framework, string workingDirectory) + { + string lambdaToolPath = Path.Combine(toolPath, "dotnet-lambda"); + await CommandLineWrapper.Run( + lambdaToolPath, + $"package -c Release --framework {framework} --function-architecture {FunctionArchitecture}", + workingDirectory); + } + + public static void CleanUp(string toolPath) + { + if (!string.IsNullOrEmpty(toolPath) && Directory.Exists(toolPath)) + { + Directory.Delete(toolPath, true); + } + } + + /// + /// + /// + private static void CopyDirectory(DirectoryInfo dir, string destDirName) + { + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {dir.FullName}"); + } + + var dirs = dir.GetDirectories(); + + Directory.CreateDirectory(destDirName); + + var files = dir.GetFiles(); + foreach (var file in files) + { + var tempPath = Path.Combine(destDirName, file.Name); + file.CopyTo(tempPath, false); + } + + foreach (var subdir in dirs) + { + var tempPath = Path.Combine(destDirName, subdir.Name); + var subDir = new DirectoryInfo(subdir.FullName); + CopyDirectory(subDir, tempPath); + } + } +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs new file mode 100644 index 000000000..c9ce90e35 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests; + +[CollectionDefinition("Integration Tests")] +public class IntegrationTestCollection : ICollectionFixture +{ + +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs new file mode 100644 index 000000000..eba66b47b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.IntegrationTests.Helpers; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests; + +public class IntegrationTestFixture : IAsyncLifetime +{ + private readonly List _tempPaths = new(); + + public async Task InitializeAsync() + { + var testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( + "../../../../../../..", + "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest"); + var toolPath = await LambdaToolsHelper.InstallLambdaTools(); + _tempPaths.AddRange([testAppPath, toolPath] ); + await LambdaToolsHelper.LambdaPackage(toolPath, "net6.0", testAppPath); + await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); + + testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( + "../../../../../../..", + "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest"); + toolPath = await LambdaToolsHelper.InstallLambdaTools(); + _tempPaths.AddRange([testAppPath, toolPath] ); + await LambdaToolsHelper.LambdaPackage(toolPath, "net6.0", testAppPath); + + testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( + "../../../../../../..", + "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest"); + toolPath = await LambdaToolsHelper.InstallLambdaTools(); + _tempPaths.AddRange([testAppPath, toolPath] ); + await LambdaToolsHelper.LambdaPackage(toolPath, "net6.0", testAppPath); + } + + public Task DisposeAsync() + { + foreach (var tempPath in _tempPaths) + { + LambdaToolsHelper.CleanUp(tempPath); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/buildtools/build.proj b/buildtools/build.proj index f38fd1a07..90d792318 100644 --- a/buildtools/build.proj +++ b/buildtools/build.proj @@ -198,8 +198,8 @@ - - + + diff --git a/buildtools/ci.buildspec.yml b/buildtools/ci.buildspec.yml index 9094387d9..bacb43342 100644 --- a/buildtools/ci.buildspec.yml +++ b/buildtools/ci.buildspec.yml @@ -7,9 +7,6 @@ phases: commands: # The tests need .NET 3.1, 6 and 8. .NET6 is installed by default. .NET8 is added in the runtime-versions. .NET 3.1 is installed manually. - curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 3.1 - # Mono is needed to run the unit tests on Linux - - curl https://download.mono-project.com/repo/centos8-stable.repo | tee /etc/yum.repos.d/mono-stable.repo - - dnf install -y mono-complete mono-devel build: commands: - dotnet msbuild buildtools/build.proj /t:unit-tests /p:Cicd=true