Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update test project to consume framework for recorded tests #19171

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Azure.ResourceManager.TestFramework
{
public class ResourceGroupCleanupPolicy : HttpPipelineSynchronousPolicy
{
private readonly object _listLock = new object();
private Regex _resourceGroupPattern = new Regex(@"/subscriptions/[^/]+/resourcegroups/([^?/]+)\?api-version");
private readonly IList<string> _resourceGroupCreated = new List<string>();

Expand All @@ -26,7 +27,10 @@ public override void OnSendingRequest(HttpMessage message)
var match = _resourceGroupPattern.Match(message.Request.Uri.ToString());
if (match.Success)
{
_resourceGroupCreated.Add(match.Groups[1].Value);
lock (_listLock)
{
_resourceGroupCreated.Add(match.Groups[1].Value);
}
}
}
}
Expand Down
163 changes: 163 additions & 0 deletions common/ManagementTestShared/Redesign/ManagementRecordedTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core;
using Azure.Core.TestFramework;
using Azure.ResourceManager.Core;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Azure.ResourceManager.TestFramework
{
public abstract class ManagementRecordedTestBase<TEnvironment> : RecordedTestBase<TEnvironment>
where TEnvironment: TestEnvironment, new()
{
protected ResourceGroupCleanupPolicy CleanupPolicy = new ResourceGroupCleanupPolicy();

protected ResourceGroupCleanupPolicy OneTimeCleanupPolicy = new ResourceGroupCleanupPolicy();

protected AzureResourceManagerClient GlobalClient { get; private set; }

public TestEnvironment SessionEnvironment { get; private set; }

public TestRecording SessionRecording { get; private set; }

private AzureResourceManagerClient _cleanupClient;

protected ManagementRecordedTestBase(bool isAsync) : base(isAsync)
{
SessionEnvironment = new TEnvironment();
SessionEnvironment.Mode = Mode;
}

protected ManagementRecordedTestBase(bool isAsync, RecordedTestMode mode) : base(isAsync, mode)
{
SessionEnvironment = new TEnvironment();
SessionEnvironment.Mode = Mode;
}

private AzureResourceManagerClient GetCleanupClient()
{
if (Mode != RecordedTestMode.Playback)
{
return new AzureResourceManagerClient(
TestEnvironment.SubscriptionId,
TestEnvironment.Credential,
new AzureResourceManagerClientOptions());
}
return null;
}

protected AzureResourceManagerClient GetArmClient()
{
var options = InstrumentClientOptions(new AzureResourceManagerClientOptions());
options.AddPolicy(CleanupPolicy, HttpPipelinePosition.PerCall);

return CreateClient<AzureResourceManagerClient>(
TestEnvironment.SubscriptionId,
TestEnvironment.Credential,
options);
}

[SetUp]
protected void Setup()
{
_cleanupClient ??= GetCleanupClient();
}

[TearDown]
protected void CleanupResourceGroups()
{
if (Mode != RecordedTestMode.Playback)
{
Parallel.ForEach(CleanupPolicy.ResourceGroupsCreated, resourceGroup =>
{
_cleanupClient.GetResourceGroupOperations(TestEnvironment.SubscriptionId, resourceGroup).StartDelete();
});
}
}

private void StartSessionRecording()
{
// Only create test recordings for the latest version of the service
TestContext.TestAdapter test = TestContext.CurrentContext.Test;
if (Mode != RecordedTestMode.Live &&
test.Properties.ContainsKey("SkipRecordings"))
{
throw new IgnoreException((string)test.Properties.Get("SkipRecordings"));
}
SessionRecording = new TestRecording(Mode, GetSessionFilePath(), Sanitizer, Matcher);
SessionEnvironment.SetRecording(SessionRecording);
ValidateClientInstrumentation = SessionRecording.HasRequests;
}

protected void StopSessionRecording()
{
if (ValidateClientInstrumentation)
{
throw new InvalidOperationException("The test didn't instrument any clients but had recordings. Please call InstrumentClient for the client being recorded.");
}

SessionRecording?.Dispose(true);
GlobalClient = null;
}

[OneTimeSetUp]
public void OneTimeSetUp()
{
if (!HasOneTimeSetup())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to base this check on the session being empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless its the first recording right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the check is here to prevent an empty recording file we can check it before writing the file.

return;

StartSessionRecording();

var options = InstrumentClientOptions(new AzureResourceManagerClientOptions(), SessionRecording);
options.AddPolicy(OneTimeCleanupPolicy, HttpPipelinePosition.PerCall);

GlobalClient = CreateClient<AzureResourceManagerClient>(
SessionEnvironment.SubscriptionId,
SessionEnvironment.Credential,
options);
}

private bool HasOneTimeSetup()
{
HashSet<Type> types = new HashSet<Type>();
Type type = GetType();
Type endType = typeof(ManagementRecordedTestBase<TEnvironment>);
while (type != endType)
{
types.Add(type);
type = type.BaseType;
}

var methods = GetType().GetMethods().Where(m => types.Contains(m.DeclaringType));
foreach (var method in methods)
{
foreach(var attr in method.GetCustomAttributes(false))
{
if (attr is OneTimeSetUpAttribute)
return true;
}
}
return false;
}

[OneTimeTearDown]
public void OneTimeCleanupResourceGroups()
{
if (Mode != RecordedTestMode.Playback)
{
Parallel.ForEach(OneTimeCleanupPolicy.ResourceGroupsCreated, resourceGroup =>
{
_cleanupClient.GetResourceGroupOperations(SessionEnvironment.SubscriptionId, resourceGroup).StartDelete();
});
}

if (!(GlobalClient is null))
throw new InvalidOperationException("StopSessionRecording was never called please make sure you call that at the end of your OneTimeSetup");
}
}
}
38 changes: 38 additions & 0 deletions common/ManagementTestShared/Redesign/ResourceGroupCleanupPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Text.RegularExpressions;

using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure.ResourceManager.TestFramework
{
public class ResourceGroupCleanupPolicy : HttpPipelineSynchronousPolicy
{
private readonly object _listLock = new object();
private Regex _resourceGroupPattern = new Regex(@"/subscriptions/[^/]+/resourcegroups/([^?/]+)\?api-version");
private readonly IList<string> _resourceGroupCreated = new List<string>();

public IList<string> ResourceGroupsCreated
{
get { return _resourceGroupCreated; }
}

public override void OnSendingRequest(HttpMessage message)
{
if (message.Request.Method == RequestMethod.Put)
{
var match = _resourceGroupPattern.Match(message.Request.Uri.ToString());
if (match.Success)
{
lock (_listLock)
{
_resourceGroupCreated.Add(match.Groups[1].Value);
}
}
}
}
}
}
9 changes: 7 additions & 2 deletions eng/Azure.Management.Test.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
<RbacSharedSources>$(MSBuildThisFileDirectory)/../sdk/testcommon/Azure.Graph.Rbac/src</RbacSharedSources>
</PropertyGroup>

<ItemGroup Condition="'$(TestHelperProjects)' != ''">
<Compile Include="$(ManagementTestSharedSources)/**/*.cs"
<ItemGroup Condition="'$(TestHelperProjects)' != '' and '$(UseNewMgmtFramework)' != 'true'">
<Compile Include="$(ManagementTestSharedSources)/Current/**/*.cs"
Link="TestShared/%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup Condition="'$(TestHelperProjects)' != '' and '$(UseNewMgmtFramework)' == 'true'">
<Compile Include="$(ManagementTestSharedSources)/Redesign/**/*.cs"
Link="TestShared/%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public void Intercept(IInvocation invocation)
// We don't want to instrument generated rest clients.
if ((type.Name.EndsWith("Client") && !type.Name.EndsWith("RestClient")) ||
// Generated ARM clients will have a property containing the sub-client that ends with Operations.
(invocation.Method.Name.StartsWith("get_") && type.Name.EndsWith("Operations")))
(invocation.Method.Name.StartsWith("get_") && type.Name.EndsWith("Operations")) ||
// Instrument the subscription client that hangs off of the new AzureResouceManagementClient
(type.Name.EndsWith("DefaultSubscription")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can, please, add a comment here about why we need this condition

{
invocation.ReturnValue = _testBase.InstrumentClient(type, result, Array.Empty<IInterceptor>());
}
Expand Down
9 changes: 5 additions & 4 deletions sdk/core/Azure.Core.TestFramework/src/RecordedTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ protected RecordedTestBase(bool isAsync, RecordedTestMode mode) : base(isAsync)
Mode = mode;
}

public T InstrumentClientOptions<T>(T clientOptions) where T : ClientOptions
public T InstrumentClientOptions<T>(T clientOptions, TestRecording recording = default) where T : ClientOptions
{
clientOptions.Transport = Recording.CreateTransport(clientOptions.Transport);
recording ??= Recording;
clientOptions.Transport = recording.CreateTransport(clientOptions.Transport);
if (Mode == RecordedTestMode.Playback)
{
// Not making the timeout zero so retry code still goes async
Expand All @@ -80,7 +81,7 @@ public T InstrumentClientOptions<T>(T clientOptions) where T : ClientOptions
return clientOptions;
}

private string GetSessionFilePath()
protected string GetSessionFilePath()
{
TestContext.TestAdapter testAdapter = TestContext.CurrentContext.Test;

Expand Down Expand Up @@ -141,7 +142,7 @@ public virtual void StartTestRecording()
if (Mode != RecordedTestMode.Live &&
test.Properties.ContainsKey("SkipRecordings"))
{
throw new IgnoreException((string) test.Properties.Get("SkipRecordings"));
throw new IgnoreException((string)test.Properties.Get("SkipRecordings"));
}
Recording = new TestRecording(Mode, GetSessionFilePath(), Sanitizer, Matcher);
ValidateClientInstrumentation = Recording.HasRequests;
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ protected string GetOptionalVariable(string name)
value = Environment.GetEnvironmentVariable(name);
}

if (value == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to support the "AZURE_" prefix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that it can read the regular defaultcredential env variables locally. They are the exact same variables just with AZURE_ prepended so I added that to the check list

{
value = Environment.GetEnvironmentVariable($"AZURE_{name}");
}

if (value == null)
{
_environmentFile.TryGetValue(name, out value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public class AzureResourceManagerClient
/// Initializes a new instance of the <see cref="AzureResourceManagerClient"/> class for mocking.
/// </summary>
protected AzureResourceManagerClient()
: this(null, null, new DefaultAzureCredential(), null)
{
}

Expand Down Expand Up @@ -113,17 +112,17 @@ private AzureResourceManagerClient(
/// <summary>
/// Gets the Api version overrides.
/// </summary>
public Dictionary<string, string> ApiVersionOverrides { get; private set; }
public virtual Dictionary<string, string> ApiVersionOverrides { get; private set; }

/// <summary>
/// Gets the default Azure subscription.
/// </summary>
public Subscription DefaultSubscription { get; private set; }
public virtual Subscription DefaultSubscription { get; private set; }

/// <summary>
/// Gets the Azure resource manager client options.
/// </summary>
internal AzureResourceManagerClientOptions ClientOptions { get; }
internal virtual AzureResourceManagerClientOptions ClientOptions { get; }

/// <summary>
/// Gets the Azure subscription operations.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<DefineConstants>$(DefineConstants);RESOURCES_RP</DefineConstants>
<TestHelperProjects>;</TestHelperProjects>
<UseNewMgmtFramework>true</UseNewMgmtFramework>
</PropertyGroup>
<PropertyGroup>
<NoWarn>SA1649;SA1633;SA1000;SA1028;SA1400;SA1508</NoWarn>
Expand All @@ -16,17 +17,15 @@
<ProjectReference Include="..\src\Azure.ResourceManager.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="ScenarioTests\*.json">
<None Update="Unit\TestAssets\Identity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\Identity\*.json">
<None Update="Unit\TestAssets\UserAssignedIdentity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\UserAssignedIdentity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\SystemAssignedIdentity\*.json">
<None Update="Unit\TestAssets\SystemAssignedIdentity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading