diff --git a/Azure.PowerShell.Common.Netcore.sln b/Azure.PowerShell.Common.Netcore.sln index 78b474e3a6..7c2d359a59 100644 --- a/Azure.PowerShell.Common.Netcore.sln +++ b/Azure.PowerShell.Common.Netcore.sln @@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compute.Test.Netcore", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strategies.Test.Netcore", "src\Strategies.Test\Strategies.Test.Netcore.csproj", "{6756A7F2-1141-4065-BA23-0C555D2A2BC3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFx.Netcore", "src\TestFx\TestFx.Netcore.csproj", "{1E20E5A0-3353-4888-9B29-AE1A2C1C8DD0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +127,10 @@ Global {6756A7F2-1141-4065-BA23-0C555D2A2BC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {6756A7F2-1141-4065-BA23-0C555D2A2BC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {6756A7F2-1141-4065-BA23-0C555D2A2BC3}.Release|Any CPU.Build.0 = Release|Any CPU + {1E20E5A0-3353-4888-9B29-AE1A2C1C8DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E20E5A0-3353-4888-9B29-AE1A2C1C8DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E20E5A0-3353-4888-9B29-AE1A2C1C8DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E20E5A0-3353-4888-9B29-AE1A2C1C8DD0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Azure.PowerShell.Common.sln b/Azure.PowerShell.Common.sln index 5e2110e060..d8956bcafc 100644 --- a/Azure.PowerShell.Common.sln +++ b/Azure.PowerShell.Common.sln @@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strategies.Test", "src\Stra EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScenarioTest", "src\ScenarioTest\ScenarioTest.csproj", "{C1BDA476-A5CC-4394-914D-48B0EC31A710}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestFx", "src\TestFx\TestFx.csproj", "{8C625DE3-0067-454A-AF2C-EFD672EEB31A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -149,6 +151,10 @@ Global {C1BDA476-A5CC-4394-914D-48B0EC31A710}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1BDA476-A5CC-4394-914D-48B0EC31A710}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1BDA476-A5CC-4394-914D-48B0EC31A710}.Release|Any CPU.Build.0 = Release|Any CPU + {8C625DE3-0067-454A-AF2C-EFD672EEB31A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C625DE3-0067-454A-AF2C-EFD672EEB31A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C625DE3-0067-454A-AF2C-EFD672EEB31A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C625DE3-0067-454A-AF2C-EFD672EEB31A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ScenarioTest.ResourceManager/EnvironmentSetupHelper.cs b/src/ScenarioTest.ResourceManager/EnvironmentSetupHelper.cs index 199dc2715b..b89a7a60d2 100644 --- a/src/ScenarioTest.ResourceManager/EnvironmentSetupHelper.cs +++ b/src/ScenarioTest.ResourceManager/EnvironmentSetupHelper.cs @@ -585,9 +585,7 @@ public void SetupModulesFromCommon(AzureModule mode, params string[] modules) public void SetupModules(params string[] modules) { - this.modules = new List(); - this.modules.Add("Assert.ps1"); - this.modules.Add("Common.ps1"); + this.modules = new List {"Assert.ps1", "Common.ps1"}; this.modules.AddRange(modules); } @@ -597,14 +595,18 @@ public virtual Collection RunPowerShellTest(params string[] scripts) // permissive record matcher. if (HttpMockServer.Matcher == null || HttpMockServer.Matcher.GetType() == typeof(SimpleRecordMatcher)) { - Dictionary d = new Dictionary(); - d.Add("Microsoft.Resources", null); - d.Add("Microsoft.Features", null); - d.Add("Microsoft.Authorization", null); - d.Add("Microsoft.Compute", null); - d.Add("Microsoft.KeyVault", null); - var providersToIgnore = new Dictionary(); - providersToIgnore.Add("Microsoft.Azure.Management.Resources.ResourceManagementClient", "2016-02-01"); + var d = new Dictionary + { + {"Microsoft.Resources", null}, + {"Microsoft.Features", null}, + {"Microsoft.Authorization", null}, + {"Microsoft.Compute", null}, + {"Microsoft.KeyVault", null} + }; + var providersToIgnore = new Dictionary + { + {"Microsoft.Azure.Management.Resources.ResourceManagementClient", "2016-02-01"} + }; HttpMockServer.Matcher = new PermissiveRecordMatcherWithApiExclusion(true, d, providersToIgnore); } @@ -613,13 +615,10 @@ public virtual Collection RunPowerShellTest(params string[] scripts) SetupPowerShellModules(powershell); Collection output = null; - for (int i = 0; i < scripts.Length; ++i) + foreach (var script in scripts) { - if (TracingInterceptor != null) - { - TracingInterceptor.Information(scripts[i]); - } - powershell.AddScript(scripts[i]); + TracingInterceptor?.Information(script); + powershell.AddScript(script); } try { @@ -627,12 +626,7 @@ public virtual Collection RunPowerShellTest(params string[] scripts) if (powershell.Streams.Error.Count > 0) { throw new RuntimeException( - string.Format( - "Test failed due to a non-empty error stream. First error: {0}{1}", - PowerShellExtensions.FormatErrorRecord(powershell.Streams.Error[0]), - powershell.Streams.Error.Count > 0 - ? "Check the error stream in the test log for additional errors." - : "")); + $"Test failed due to a non-empty error stream. First error: {PowerShellExtensions.FormatErrorRecord(powershell.Streams.Error[0])}{(powershell.Streams.Error.Count > 0 ? "Check the error stream in the test log for additional errors." : "")}"); } return output; @@ -660,13 +654,14 @@ private void SetupPowerShellModules(System.Management.Automation.PowerShell powe } #endif powershell.AddScript("$error.clear()"); - powershell.AddScript(string.Format("Write-Debug \"current directory: {0}\"", System.AppDomain.CurrentDomain.BaseDirectory)); - powershell.AddScript(string.Format("Write-Debug \"current executing assembly: {0}\"", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))); - powershell.AddScript(string.Format("cd \"{0}\"", System.AppDomain.CurrentDomain.BaseDirectory)); + powershell.AddScript($"Write-Debug \"current directory: {System.AppDomain.CurrentDomain.BaseDirectory}\""); + powershell.AddScript( + $"Write-Debug \"current executing assembly: {Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}\""); + powershell.AddScript($"cd \"{System.AppDomain.CurrentDomain.BaseDirectory}\""); - foreach (string moduleName in modules) + foreach (var moduleName in modules) { - powershell.AddScript(string.Format("Import-Module \"{0}\"", moduleName.AsAbsoluteLocation())); + powershell.AddScript($"Import-Module \"{moduleName.AsAbsoluteLocation()}\""); if (moduleName.EndsWith(".psd1")) { #if NETSTANDARD @@ -682,8 +677,8 @@ private void SetupPowerShellModules(System.Management.Automation.PowerShell powe powershell.AddScript("Disable-AzureRmDataCollection -ErrorAction Ignore"); powershell.AddScript( - string.Format("set-location \"{0}\"", System.AppDomain.CurrentDomain.BaseDirectory)); - powershell.AddScript(string.Format(@"$TestOutputRoot='{0}'", System.AppDomain.CurrentDomain.BaseDirectory)); + $"set-location \"{System.AppDomain.CurrentDomain.BaseDirectory}\""); + powershell.AddScript($@"$TestOutputRoot='{System.AppDomain.CurrentDomain.BaseDirectory}'"); powershell.AddScript("$VerbosePreference='Continue'"); powershell.AddScript("$DebugPreference='Continue'"); powershell.AddScript("$ErrorActionPreference='Stop'"); diff --git a/src/TestFx/ITestRunner.cs b/src/TestFx/ITestRunner.cs new file mode 100644 index 0000000000..b833046a97 --- /dev/null +++ b/src/TestFx/ITestRunner.cs @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Commands.TestFx +{ + public interface ITestRunner + { + void RunTestScript(params string[] scripts); + } +} diff --git a/src/TestFx/ITestRunnerFactory.cs b/src/TestFx/ITestRunnerFactory.cs new file mode 100644 index 0000000000..6de9e74085 --- /dev/null +++ b/src/TestFx/ITestRunnerFactory.cs @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.WindowsAzure.Commands.ScenarioTest; + +namespace Microsoft.Azure.Commands.TestFx +{ + public interface ITestRunnerFactory + { + ITestRunner Build(); + ITestRunnerFactory WithProjectSubfolderForTests(string folderName); + ITestRunnerFactory WithCommonPsScripts(string[] psScriptList); + ITestRunnerFactory WithNewPsScriptFilename(string psScriptName); + ITestRunnerFactory WithExtraRmModules(Func buildModuleList); + ITestRunnerFactory WithNewRmModules(Func buildModuleList); + ITestRunnerFactory WithExtraUserAgentsToIgnore(Dictionary userAgentsToIgnore); + ITestRunnerFactory WithBuildMatcher(BuildMatcherDelegate buildMatcher); + } +} diff --git a/src/TestFx/MSSharedLibKey.snk b/src/TestFx/MSSharedLibKey.snk new file mode 100644 index 0000000000..695f1b3877 Binary files /dev/null and b/src/TestFx/MSSharedLibKey.snk differ diff --git a/src/TestFx/Properties/AssemblyInfo.cs b/src/TestFx/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b886e27102 --- /dev/null +++ b/src/TestFx/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft Azure PowerShell TestFx")] +[assembly: AssemblyDescription("Microsoft Azure PowerShell TestFx library. Only for use with the Azure PowerShell runtime. Not intended for general development use.")] +[assembly: AssemblyProduct("Microsoft Azure PowerShell")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyCopyright("Copyright © Microsoft Corporation")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8c625de3-0067-454a-af2c-efd672eeb31a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/TestFx/TestClientFactory.cs b/src/TestFx/TestClientFactory.cs new file mode 100644 index 0000000000..9f6fac9aea --- /dev/null +++ b/src/TestFx/TestClientFactory.cs @@ -0,0 +1,144 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Hyak.Common; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.Common.Authentication.Models; +using Microsoft.Azure.Graph.RBAC.Version1_6; +using Microsoft.Rest; +using Microsoft.Rest.ClientRuntime.Azure.TestFramework; + +namespace Microsoft.Azure.Commands.TestFx +{ + public class TestClientFactory : IClientFactory + { + private readonly MockContext _mockContext; + + public TestClientFactory(MockContext mockContext) + { + _mockContext = mockContext ?? throw new ArgumentNullException(nameof(mockContext)); + } + + public TClient CreateArmClient(IAzureContext context, string endpoint) where TClient : Rest.ServiceClient + { + if (typeof(TClient) != typeof(GraphRbacManagementClient)) + { + return _mockContext.GetServiceClient(); + } + + var graphClient = _mockContext.GetGraphServiceClient(); + graphClient.TenantID = context.Tenant.Id; + return graphClient as TClient; + } + + public TClient CreateCustomArmClient(params object[] parameters) where TClient : Rest.ServiceClient + { + return _mockContext.GetServiceClient(); + } + + public HttpClient CreateHttpClient(string endpoint, ICredentials credentials) + { + throw new NotImplementedException(); + } + + public HttpClient CreateHttpClient(string endpoint, HttpMessageHandler effectiveHandler) + { + throw new NotImplementedException(); + } + + #region Action and Handler + + public void AddAction(IClientAction action) + { + // Do nothing + } + + public void RemoveAction(Type actionType) + { + // Do nothing + } + + public void AddHandler(T handler) where T : DelegatingHandler, ICloneable + { + // Do nothing + } + + public void RemoveHandler(Type handlerType) + { + // Do nothing + } + public DelegatingHandler[] GetCustomHandlers() + { + // Do nothing + return new DelegatingHandler[0]; + } + + #endregion + + #region UserAgent + + public HashSet UniqueUserAgents { get; set; } = new HashSet(); + + public void AddUserAgent(string productName, string productVersion) + { + UniqueUserAgents.Add(new ProductInfoHeaderValue(productName, productVersion)); + } + + public void AddUserAgent(string productName) + { + AddUserAgent(productName, string.Empty); + } + + public void RemoveUserAgent(string name) + { + UniqueUserAgents.RemoveWhere(p => string.Equals(p.Product.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + public ProductInfoHeaderValue[] UserAgents => UniqueUserAgents.ToArray(); + + #endregion + + #region Hyak + + public TClient CreateClient(IAzureContext context, string endpoint) where TClient : Hyak.Common.ServiceClient + { + throw new NotImplementedException(); + } + + public TClient CreateClient(IAzureContextContainer profile, string endpoint) where TClient : Hyak.Common.ServiceClient + { + throw new NotImplementedException(); + } + + public TClient CreateClient(IAzureContextContainer profile, IAzureSubscription subscription, string endpoint) where TClient : Hyak.Common.ServiceClient + { + throw new NotImplementedException(); + } + + public TClient CreateCustomClient(params object[] parameters) where TClient : Hyak.Common.ServiceClient + { + throw new NotImplementedException(); + } + + #endregion + + } +} diff --git a/src/TestFx/TestFx.Netcore.csproj b/src/TestFx/TestFx.Netcore.csproj new file mode 100644 index 0000000000..36f7f1780d --- /dev/null +++ b/src/TestFx/TestFx.Netcore.csproj @@ -0,0 +1,57 @@ + + + + + + netstandard2.0 + Microsoft.Azure.Powershell.TestFx + Microsoft.Azure.Commands.TestFx + false + true + + false + + + + Microsoft Azure PowerShell TestFx + Microsoft Azure PowerShell TestFx library. Only for use with the Azure PowerShell runtime. Not intended for general development use. + azure;powershell;testfx + Microsoft Corporation + Copyright © Microsoft Corporation + https://aka.ms/azps-common-license + https://github.com/Azure/azure-powershell-common + $(ProjectDir)..\..\artifacts\Package\$(Configuration) + 1.0.0-preview + + + + false + TRACE;DEBUG;NETSTANDARD + true + + + + + true + true + MSSharedLibKey.snk + TRACE;RELEASE;NETSTANDARD;SIGN + true + + + + + + + + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/TestFx/TestFx.csproj b/src/TestFx/TestFx.csproj new file mode 100644 index 0000000000..6de6a766a3 --- /dev/null +++ b/src/TestFx/TestFx.csproj @@ -0,0 +1,137 @@ + + + + + Debug + AnyCPU + {8C625DE3-0067-454A-AF2C-EFD672EEB31A} + Library + Properties + Microsoft.Azure.Commands.TestFx + Microsoft.Azure.Powershell.TestFx + v4.5.2 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Hyak.Common.1.0.3\lib\net45\Hyak.Common.dll + + + ..\..\packages\Microsoft.Azure.Test.HttpRecorder.1.8.1\lib\net452\Microsoft.Azure.Test.HttpRecorder.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.28.3\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.28.3\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.2.3.12\lib\net452\Microsoft.Rest.ClientRuntime.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.13\lib\net452\Microsoft.Rest.ClientRuntime.Azure.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.Authentication.2.3.1\lib\net452\Microsoft.Rest.ClientRuntime.Azure.Authentication.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.TestFramework.1.7.2\lib\net452\Microsoft.Rest.ClientRuntime.Azure.TestFramework.dll + + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + + + False + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + + + ..\..\lib\System.Management.Automation.dll + True + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll + True + + + ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + Designer + + + + + {70527617-7598-4aef-b5bd-db9186b8184b} + Authentication.Abstractions + + + {d3804b64-c0d3-48f8-82ec-1f632f833c9e} + Authentication + + + {269ACF73-0A34-42DC-AB9C-4B15931A489D} + Graph.Rbac + + + {69C2EB6B-CD63-480A-89A0-C489706E9299} + Authentication.ResourceManager + + + {3436a126-edc9-4060-8952-9a1be34cdd95} + ScenarioTest.ResourceManager + + + + diff --git a/src/TestFx/TestFx.nuspec b/src/TestFx/TestFx.nuspec new file mode 100644 index 0000000000..0a52095e43 --- /dev/null +++ b/src/TestFx/TestFx.nuspec @@ -0,0 +1,16 @@ + + + + Microsoft.Azure.PowerShell.TestFx + 1.0.0-preview + Microsoft Azure PowerShell TestFx + Microsoft Corporation + Microsoft Corporation + false + https://aka.ms/azps-common-license + https://github.com/Azure/azure-powershell-common + Microsoft Azure PowerShell TestFx library. Only for use with the Azure PowerShell runtime. Not intended for general development use. + Copyright © Microsoft Corporation + azure powershell testfx + + \ No newline at end of file diff --git a/src/TestFx/TestManager.cs b/src/TestFx/TestManager.cs new file mode 100644 index 0000000000..4124bff6f0 --- /dev/null +++ b/src/TestFx/TestManager.cs @@ -0,0 +1,296 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.Common.Authentication.Models; +using Microsoft.Azure.Commands.ResourceManager.Common; +using Microsoft.Azure.ServiceManagemenet.Common.Models; +using Microsoft.Azure.Test.HttpRecorder; +using Microsoft.Rest.ClientRuntime.Azure.TestFramework; +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using Xunit.Abstractions; + +namespace Microsoft.Azure.Commands.TestFx +{ + public delegate IRecordMatcher BuildMatcherDelegate (bool ignoreResourcesClient, Dictionary resourceProviders, Dictionary userAgentsToIgnore); + + public class TestManager : ITestRunnerFactory, ITestRunner + { + private readonly string _callingClassName; + private string _projectSubfolderForTestsName = null; + private string _newPsScriptFilename = null; + private Dictionary _userAgentsToIgnore; + protected EnvironmentSetupHelper Helper; + protected readonly List RmModules; + protected readonly List CommonPsScripts = new List(); + + protected BuildMatcherDelegate BuildMatcher { get; set; } + + protected XunitTracingInterceptor Logger { get; set; } + + /// + /// Factory method + /// + /// + /// + /// + public static ITestRunnerFactory CreateInstance(ITestOutputHelper output, [CallerFilePath] string callerFilePath = null) + { + var callingClassName = string.IsNullOrEmpty(callerFilePath) + ? null + : Path.GetFileNameWithoutExtension(callerFilePath); + return new TestManager(callingClassName).WithTestOutputHelper(output); + } + + /// + /// ctor + /// + /// + protected TestManager(string callingClassName) + { + Helper = new EnvironmentSetupHelper(); + _callingClassName = callingClassName; + + RmModules = new List + { + Helper.RMProfileModule, + Helper.RMResourceModule, + }; + + BuildMatcher = (ignoreResourcesClient, resourceProviders, userAgentsToIgnore) => + new PermissiveRecordMatcherWithApiExclusion(ignoreResourcesClient, resourceProviders, userAgentsToIgnore); + } + + #region Builder impl + + /// + /// Sets a name of the subfolder where a test project keeps tests + /// + /// + /// self + public ITestRunnerFactory WithProjectSubfolderForTests(string folderName) + { + _projectSubfolderForTestsName = folderName ?? "ScenarioTests"; + return this; + } + + /// + /// Add helper scripts + /// + /// + /// self + public ITestRunnerFactory WithCommonPsScripts(string[] psScriptList) + { + CommonPsScripts.AddRange(psScriptList); + return this; + } + + /// + /// Overrided default script name, which by convension is the cs test class name with ps1 extension. + /// + /// + /// self + public ITestRunnerFactory WithNewPsScriptFilename(string psScriptName) + { + _newPsScriptFilename = psScriptName; + return this; + } + + /// + /// Adds extra RM modules in addition to the RMProfileModule and RMResourceModule, + /// witch are added in the constructor. + /// + /// + /// + public ITestRunnerFactory WithExtraRmModules(Func buildModuleList) + { + var moduleList = buildModuleList(Helper); + RmModules.AddRange(moduleList); + return this; + } + + /// + /// Clears default RM modules list and sets a brand new + /// + /// + /// + public ITestRunnerFactory WithNewRmModules(Func buildModuleList) + { + RmModules.Clear(); + var moduleList = buildModuleList(Helper); + RmModules.AddRange(moduleList); + return this; + } + + /// + /// Sets a new HttpMockServer.Matcher implementation. By defauls it's PermissiveRecordMatcherWithApiExclusion + /// + /// delegate + /// self + public ITestRunnerFactory WithBuildMatcher(BuildMatcherDelegate buildMatcher) + { + BuildMatcher = buildMatcher; + return this; + } + + /// + /// + /// + /// + /// Dictionary to store pairs: {user agent name, version-api to ignore}. + /// Initial pair is {"Microsoft.Azure.Management.Resources.ResourceManagementClient", "2016-02-01"} + /// + /// self + public ITestRunnerFactory WithExtraUserAgentsToIgnore(Dictionary userAgentsToIgnore) + { + _userAgentsToIgnore = userAgentsToIgnore; + return this; + } + + public ITestRunnerFactory WithTestOutputHelper(ITestOutputHelper output) + { + Logger = new XunitTracingInterceptor(output); + XunitTracingInterceptor.AddToContext(Logger); + Helper.TracingInterceptor = Logger; + return this; + } + + public ITestRunner Build() + { + SetupSessionAndProfile(); + SetupMockServerMatcher(); + HttpMockServer.RecordsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SessionRecords"); + Helper.SetupModules(AzureModule.AzureResourceManager, BuildModulesList()); + return this; + } + + public void RunTestScript(params string[] scripts) + { + var sf = new StackTrace().GetFrame(1); + var className = sf.GetMethod().ReflectedType?.ToString(); + var methodName = sf.GetMethod().Name; + + using (var mockContext = MockContext.Start(className, methodName)) + { + AzureSession.Instance.ClientFactory = new TestClientFactory(mockContext); + Helper.SetupEnvironment(AzureModule.AzureResourceManager); + SetupAzureContext(); + Helper.RunPowerShellTest(scripts); + } + } + + #endregion + + #region Helpers + + protected string[] BuildModulesList() + { + if (string.IsNullOrEmpty(_callingClassName) + && string.IsNullOrEmpty(_newPsScriptFilename)) + throw new ArgumentNullException($"Both {nameof(_callingClassName)} and {nameof(_newPsScriptFilename)} are null"); + + var allScripts = CommonPsScripts; + allScripts.Add(_newPsScriptFilename ?? $"{_callingClassName}.ps1"); + + var allScriptsWithPath = _projectSubfolderForTestsName == null + ? allScripts + : allScripts.Select(s => Path.Combine(_projectSubfolderForTestsName, s)); + + var allModules = RmModules; + allModules.AddRange(allScriptsWithPath); + + return allModules.ToArray(); + } + + protected void SetupSessionAndProfile() + { + AzureSessionInitializer.InitializeAzureSession(); + AzureSession.Instance.ARMContextSaveMode = ContextSaveMode.Process; + ResourceManagerProfileProvider.InitializeResourceManagerProfile(); + if (!(AzureSession.Instance?.DataStore is MemoryDataStore)) + { + AzureSession.Instance.DataStore = new MemoryDataStore(); + } + } + + protected void SetupAzureContext() + { + const string tenantIdKey = "TenantId"; + const string domainKey = "Domain"; + const string subscriptionIdKey = "SubscriptionId"; + const string undefined = "Undefined"; + var zeroGuild = Guid.Empty.ToString(); + + string tenantId = null; + string userDomain = null; + string subscriptionId = null; + + if (HttpMockServer.Mode == HttpRecorderMode.Record) + { + var environment = TestEnvironmentFactory.GetTestEnvironment(); + tenantId = environment.Tenant; + userDomain = string.IsNullOrEmpty(environment.UserName) + ? string.Empty + : environment.UserName.Split(new[] { "@" }, StringSplitOptions.RemoveEmptyEntries).Last(); + + subscriptionId = environment.SubscriptionId; + } + else if (HttpMockServer.Mode == HttpRecorderMode.Playback) + { + tenantId = HttpMockServer.Variables.ContainsKey(tenantIdKey) + ? HttpMockServer.Variables[tenantIdKey] + : zeroGuild; + userDomain = HttpMockServer.Variables.ContainsKey(domainKey) + ? HttpMockServer.Variables[domainKey] + : "testdomain.onmicrosoft.com"; + subscriptionId = HttpMockServer.Variables.ContainsKey(subscriptionIdKey) + ? HttpMockServer.Variables[subscriptionIdKey] + : zeroGuild; + } + + AzureRmProfileProvider.Instance.Profile.DefaultContext.Tenant.Id = tenantId ?? undefined; + AzureRmProfileProvider.Instance.Profile.DefaultContext.Tenant.Directory = userDomain ?? undefined; + AzureRmProfileProvider.Instance.Profile.DefaultContext.Subscription.Id = subscriptionId ?? undefined; + } + + protected void SetupMockServerMatcher() + { + var resourceProviders = new Dictionary + { + {"Microsoft.Resources", null}, + {"Microsoft.Features", null}, + {"Microsoft.Authorization", null}, + {"Providers.Test", null}, + }; + + var userAgentsToIgnore = new Dictionary + { + {"Microsoft.Azure.Management.Resources.ResourceManagementClient", "2016-02-01"}, + }; + + _userAgentsToIgnore?.Keys.ForEach(k=> userAgentsToIgnore.Add(k, _userAgentsToIgnore[k])); + + HttpMockServer.Matcher = BuildMatcher(true, resourceProviders, userAgentsToIgnore); + } + + #endregion + } +} diff --git a/src/TestFx/TestRunnerBase.cs b/src/TestFx/TestRunnerBase.cs new file mode 100644 index 0000000000..f1a6fa44c1 --- /dev/null +++ b/src/TestFx/TestRunnerBase.cs @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Xunit.Abstractions; + +namespace Microsoft.Azure.Commands.TestFx +{ + public class TestRunnerBase + { + protected readonly ITestRunner TestRunner; + + protected TestRunnerBase(ITestOutputHelper output) + { + TestRunner = TestManager + .CreateInstance(output) + .WithNewPsScriptFilename($"{GetType().Name}.ps1") + .Build(); + } + } +} diff --git a/src/TestFx/packages.config b/src/TestFx/packages.config new file mode 100644 index 0000000000..c85b52e4b5 --- /dev/null +++ b/src/TestFx/packages.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +