diff --git a/eng/scripts/New-TestResources-Bootstrapper.ps1 b/eng/scripts/New-TestResources-Bootstrapper.ps1 new file mode 100644 index 0000000000000..6671d7ca7dd15 --- /dev/null +++ b/eng/scripts/New-TestResources-Bootstrapper.ps1 @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +param( +[string] $ServiceDirectory +) +$run = Read-Host "The resources needed to run the live tests could not be located.`nWould you like to run the resource creation script? [y/n]" +if ($run -eq 'y'){ + & "$PSScriptRoot\..\common\TestResources\New-TestResources.ps1" $ServiceDirectory + + Read-Host "Press enter to close this window and resume your test run." +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs b/sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs index 10bcdb0d2e96c..416069b8b9c61 100644 --- a/sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs +++ b/sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs @@ -10,7 +10,10 @@ using System.Threading.Tasks; using Azure.Identity; using System.ComponentModel; +using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; using NUnit.Framework; namespace Azure.Core.TestFramework @@ -24,14 +27,22 @@ public abstract class TestEnvironment [EditorBrowsableAttribute(EditorBrowsableState.Never)] public static string RepositoryRoot { get; } - private static readonly Dictionary s_environmentStateCache = new Dictionary(); + private static readonly Dictionary s_environmentStateCache = new(); private readonly string _prefix; private TokenCredential _credential; private TestRecording _recording; + private readonly string _serviceName; - private readonly Dictionary _environmentFile = new Dictionary(StringComparer.OrdinalIgnoreCase); + private Dictionary _environmentFile; + private readonly string _serviceSdkDirectory; + + private static readonly HashSet s_bootstrappingAttemptedTypes = new(); + private static readonly object s_syncLock = new(); + private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private Exception _bootstrappingException; + private readonly Type _type; protected TestEnvironment() { @@ -42,28 +53,35 @@ protected TestEnvironment() var testProject = GetSourcePath(GetType().Assembly); var sdkDirectory = Path.GetFullPath(Path.Combine(RepositoryRoot, "sdk")); - var serviceName = Path.GetFullPath(testProject) + _serviceName = Path.GetFullPath(testProject) .Substring(sdkDirectory.Length) .Trim(Path.DirectorySeparatorChar) .Split(Path.DirectorySeparatorChar).FirstOrDefault(); - if (string.IsNullOrWhiteSpace(serviceName)) + if (string.IsNullOrWhiteSpace(_serviceName)) { throw new InvalidOperationException($"Unable to determine the service name from test project path {testProject}"); } - var serviceSdkDirectory = Path.Combine(sdkDirectory, serviceName); + _serviceSdkDirectory = Path.Combine(sdkDirectory, _serviceName); if (!Directory.Exists(sdkDirectory)) { - throw new InvalidOperationException($"SDK directory {serviceSdkDirectory} not found"); + throw new InvalidOperationException($"SDK directory {_serviceSdkDirectory} not found"); } - _prefix = serviceName.ToUpperInvariant() + "_"; + _prefix = _serviceName.ToUpperInvariant() + "_"; + _type = GetType(); + + ParseEnvironmentFile(); + } + private void ParseEnvironmentFile() + { + _environmentFile = new Dictionary(StringComparer.OrdinalIgnoreCase); var testEnvironmentFiles = new[] { - Path.Combine(serviceSdkDirectory, "test-resources.bicep.env"), - Path.Combine(serviceSdkDirectory, "test-resources.json.env") + Path.Combine(_serviceSdkDirectory, "test-resources.bicep.env"), + Path.Combine(_serviceSdkDirectory, "test-resources.json.env") }; foreach (var testEnvironmentFile in testEnvironmentFiles) @@ -216,10 +234,10 @@ public async ValueTask WaitForEnvironmentAsync() Task task; lock (s_environmentStateCache) { - if (!s_environmentStateCache.TryGetValue(GetType(), out task)) + if (!s_environmentStateCache.TryGetValue(_type, out task)) { task = WaitForEnvironmentInternalAsync(); - s_environmentStateCache[GetType()] = task; + s_environmentStateCache[_type] = task; } } await task; @@ -304,6 +322,11 @@ protected string GetRecordedVariable(string name) protected string GetRecordedVariable(string name, Action options) { var value = GetRecordedOptionalVariable(name, options); + if (value == null) + { + BootStrapTestResources(); + value = GetRecordedOptionalVariable(name, options); + } EnsureValue(name, value); return value; } @@ -348,6 +371,11 @@ protected string GetOptionalVariable(string name) protected string GetVariable(string name) { var value = GetOptionalVariable(name); + if (value == null) + { + BootStrapTestResources(); + value = GetOptionalVariable(name); + } EnsureValue(name, value); return value; } @@ -356,10 +384,18 @@ private void EnsureValue(string name, string value) { if (value == null) { - var prefixedName = _prefix + name; - throw new InvalidOperationException( - $"Unable to find environment variable {prefixedName} or {name} required by test." + Environment.NewLine + - "Make sure the test environment was initialized using eng/common/TestResources/New-TestResources.ps1 script."); + string prefixedName = _prefix + name; + string message = $"Unable to find environment variable {prefixedName} or {name} required by test." + Environment.NewLine + + "Make sure the test environment was initialized using the eng/common/TestResources/New-TestResources.ps1 script."; + if (_bootstrappingException != null) + { + message += Environment.NewLine + "Resource creation failed during the test run. Make sure PowerShell version 6 or higher is installed."; + throw new InvalidOperationException( + message, + _bootstrappingException); + } + + throw new InvalidOperationException(message); } } @@ -459,5 +495,54 @@ internal static bool GlobalDisableAutoRecording return disableAutoRecording || GlobalIsRunningInCI; } } + + private void BootStrapTestResources() + { + lock (s_syncLock) + { + try + { + if (!s_isWindows || + s_bootstrappingAttemptedTypes.Contains(_type) || + Mode == RecordedTestMode.Playback || + GlobalIsRunningInCI) + { + return; + } + + string path = Path.Combine( + RepositoryRoot, + "eng", + "scripts", + $"New-TestResources-Bootstrapper.ps1 {_serviceName}"); + + var processInfo = new ProcessStartInfo( + @"pwsh.exe", + path) + { + UseShellExecute = true + }; + Process process = null; + try + { + process = Process.Start(processInfo); + } + catch (Exception ex) + { + _bootstrappingException = ex; + } + + if (process != null) + { + process.WaitForExit(); + ParseEnvironmentFile(); + } + } + finally + { + s_bootstrappingAttemptedTypes.Add(_type); + } + } + } } }