parent
f01208af3b
commit
a95a4d1195
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
**/.vscode/
|
||||
**/.vs/
|
||||
**/.ionide/
|
||||
**/.idea/
|
||||
coverage.cov
|
||||
*.coverprofile
|
||||
|
||||
|
|
|
@ -13,12 +13,15 @@ CHANGELOG
|
|||
|
||||
- Ensure Python overlays work as part of our SDK generation
|
||||
[#4043](https://github.com/pulumi/pulumi/pull/4043)
|
||||
|
||||
- Fix terminal gets into a state where UP/DOWN don't work with prompts.
|
||||
[#4042](https://github.com/pulumi/pulumi/pull/4042)
|
||||
|
||||
- Ensure old provider is not used when configuration has changed
|
||||
[#4051](https://github.com/pulumi/pulumi/pull/4051)
|
||||
|
||||
- Support for unit testing and mocking in the .NET SDK.
|
||||
[#3696](https://github.com/pulumi/pulumi/pull/3696)
|
||||
|
||||
## 1.12.0 (2020-03-04)
|
||||
- Avoid Configuring providers which are not used during preview.
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Pulumi.Tests
|
|||
|
||||
Deployment.Instance = mock.Object;
|
||||
await func().ConfigureAwait(false);
|
||||
Deployment.Instance = null!;
|
||||
}
|
||||
|
||||
protected static Task RunInPreview(Action action)
|
||||
|
|
|
@ -15,10 +15,10 @@ namespace Pulumi.Tests
|
|||
private class ValidStack : Stack
|
||||
{
|
||||
[Output("foo")]
|
||||
public Output<string> ExplicitName { get; }
|
||||
public Output<string> ExplicitName { get; set; }
|
||||
|
||||
[Output]
|
||||
public Output<string> ImplicitName { get; }
|
||||
public Output<string> ImplicitName { get; set; }
|
||||
|
||||
public ValidStack()
|
||||
{
|
||||
|
@ -51,7 +51,7 @@ namespace Pulumi.Tests
|
|||
}
|
||||
catch (RunException ex)
|
||||
{
|
||||
Assert.Contains("foo", ex.Message);
|
||||
Assert.Contains("Output(s) 'foo' have no value assigned", ex.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace Pulumi.Tests
|
|||
private class InvalidOutputTypeStack : Stack
|
||||
{
|
||||
[Output("foo")]
|
||||
public string Foo { get; }
|
||||
public string Foo { get; set; }
|
||||
|
||||
public InvalidOutputTypeStack()
|
||||
{
|
||||
|
@ -78,7 +78,7 @@ namespace Pulumi.Tests
|
|||
}
|
||||
catch (RunException ex)
|
||||
{
|
||||
Assert.Contains("foo", ex.Message);
|
||||
Assert.Contains("Output(s) 'foo' have incorrect type", ex.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,14 @@ namespace Pulumi
|
|||
{
|
||||
private readonly object _logGate = new object();
|
||||
private readonly IDeploymentInternal _deployment;
|
||||
private readonly Engine.EngineClient _engine;
|
||||
private readonly IEngine _engine;
|
||||
|
||||
// We serialize all logging tasks so that the engine doesn't hear about them out of order.
|
||||
// This is necessary for streaming logs to be maintained in the right order.
|
||||
private Task _lastLogTask = Task.CompletedTask;
|
||||
private int _errorCount;
|
||||
|
||||
public Logger(IDeploymentInternal deployment, Engine.EngineClient engine)
|
||||
public Logger(IDeploymentInternal deployment, IEngine engine)
|
||||
{
|
||||
_deployment = deployment;
|
||||
_engine = engine;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Pulumirpc;
|
||||
using Pulumi.Testing;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
|
@ -34,6 +33,7 @@ namespace Pulumi
|
|||
public sealed partial class Deployment : IDeploymentInternal
|
||||
{
|
||||
private static IDeployment? _instance;
|
||||
private static readonly object _instanceLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The current running deployment instance. This is only available from inside the function
|
||||
|
@ -42,13 +42,12 @@ namespace Pulumi
|
|||
public static IDeployment Instance
|
||||
{
|
||||
get => _instance ?? throw new InvalidOperationException("Trying to acquire Deployment.Instance before 'Run' was called.");
|
||||
internal set => _instance = (value ?? throw new ArgumentNullException(nameof(value)));
|
||||
internal set => _instance = value;
|
||||
}
|
||||
|
||||
internal static IDeploymentInternal InternalInstance
|
||||
=> (IDeploymentInternal)Instance;
|
||||
|
||||
private readonly Options _options;
|
||||
private readonly string _projectName;
|
||||
private readonly string _stackName;
|
||||
private readonly bool _isDryRun;
|
||||
|
@ -56,8 +55,8 @@ namespace Pulumi
|
|||
private readonly ILogger _logger;
|
||||
private readonly IRunner _runner;
|
||||
|
||||
internal Engine.EngineClient Engine { get; }
|
||||
internal ResourceMonitor.ResourceMonitorClient Monitor { get; }
|
||||
internal IEngine Engine { get; }
|
||||
internal IMonitor Monitor { get; }
|
||||
|
||||
internal Stack? _stack;
|
||||
internal Stack Stack
|
||||
|
@ -93,27 +92,37 @@ namespace Pulumi
|
|||
_stackName = stack;
|
||||
_projectName = project;
|
||||
|
||||
_options = new Options(
|
||||
queryMode: queryModeValue, parallel: parallelValue, pwd: pwd,
|
||||
monitor: monitor, engine: engine, tracing: tracing);
|
||||
|
||||
Serilog.Log.Debug("Creating Deployment Engine.");
|
||||
this.Engine = new Engine.EngineClient(new Channel(engine, ChannelCredentials.Insecure));
|
||||
this.Engine = new GrpcEngine(engine);
|
||||
Serilog.Log.Debug("Created Deployment Engine.");
|
||||
|
||||
Serilog.Log.Debug("Creating Deployment Monitor.");
|
||||
this.Monitor = new ResourceMonitor.ResourceMonitorClient(new Channel(monitor, ChannelCredentials.Insecure));
|
||||
this.Monitor = new GrpcMonitor(monitor);
|
||||
Serilog.Log.Debug("Created Deployment Monitor.");
|
||||
|
||||
_runner = new Runner(this);
|
||||
_logger = new Logger(this, this.Engine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This constructor is called from <see cref="TestAsync{TStack}"/> with
|
||||
/// a mocked monitor and dummy values for project and stack.
|
||||
/// </summary>
|
||||
private Deployment(IEngine engine, IMonitor monitor, TestOptions? options)
|
||||
{
|
||||
_isDryRun = options?.IsPreview ?? true;
|
||||
_stackName = options?.StackName ?? "stack";
|
||||
_projectName = options?.ProjectName ?? "project";
|
||||
this.Engine = engine;
|
||||
this.Monitor = monitor;
|
||||
_runner = new Runner(this);
|
||||
_logger = new Logger(this, this.Engine);
|
||||
}
|
||||
|
||||
string IDeployment.ProjectName => _projectName;
|
||||
string IDeployment.StackName => _stackName;
|
||||
bool IDeployment.IsDryRun => _isDryRun;
|
||||
|
||||
Options IDeploymentInternal.Options => _options;
|
||||
ILogger IDeploymentInternal.Logger => _logger;
|
||||
IRunner IDeploymentInternal.Runner => _runner;
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Pulumi
|
|||
request.Dependencies.AddRange(prepareResult.AllDirectDependencyURNs);
|
||||
|
||||
// Now run the operation, serializing the invocation if necessary.
|
||||
var response = await this.Monitor.ReadResourceAsync(request);
|
||||
var response = await this.Monitor.ReadResourceAsync(resource, request);
|
||||
|
||||
return (response.Urn, id, response.Properties);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Pulumi
|
|||
PopulateRequest(request, prepareResult);
|
||||
|
||||
Log.Debug($"Registering resource monitor start: t={type}, name={name}, custom={custom}");
|
||||
var result = await this.Monitor.RegisterResourceAsync(request);
|
||||
var result = await this.Monitor.RegisterResourceAsync(resource, request);
|
||||
Log.Debug($"Registering resource monitor end: t={type}, name={name}, custom={custom}");
|
||||
return (result.Urn, result.Id, result.Object);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Pulumi
|
|||
Log.Debug($"RegisterResourceOutputs RPC prepared: urn={urn}" +
|
||||
(_excessiveDebugOutput ? $", outputs ={JsonFormatter.Default.Format(serialized)}" : ""));
|
||||
|
||||
await Monitor.RegisterResourceOutputsAsync(new RegisterResourceOutputsRequest()
|
||||
await Monitor.RegisterResourceOutputsAsync(new RegisterResourceOutputsRequest
|
||||
{
|
||||
Urn = urn,
|
||||
Outputs = serialized,
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Pulumi.Testing;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
|
@ -87,20 +89,65 @@ namespace Pulumi
|
|||
public static Task<int> RunAsync<TStack>() where TStack : Stack, new()
|
||||
=> CreateRunner().RunAsync<TStack>();
|
||||
|
||||
/// <summary>
|
||||
/// Entry point to test a Pulumi application. Deployment will
|
||||
/// instantiate a new stack instance based on the type passed as TStack
|
||||
/// type parameter. This method creates no real resources.
|
||||
/// Note: Currently, unit tests that call <see cref="TestAsync{TStack}"/>
|
||||
/// must run serially; parallel execution is not supported.
|
||||
/// </summary>
|
||||
/// <param name="mocks">Hooks to mock the engine calls.</param>
|
||||
/// <param name="options">Optional settings for the test run.</param>
|
||||
/// <typeparam name="TStack">The type of the stack to test.</typeparam>
|
||||
/// <returns>Test result containing created resources and errors, if any.</returns>
|
||||
public static async Task<ImmutableArray<Resource>> TestAsync<TStack>(IMocks mocks, TestOptions? options = null) where TStack : Stack, new()
|
||||
{
|
||||
var engine = new MockEngine();
|
||||
var monitor = new MockMonitor(mocks);
|
||||
Deployment deployment;
|
||||
lock (_instanceLock)
|
||||
{
|
||||
if (_instance != null)
|
||||
throw new NotSupportedException($"Mulitple executions of {nameof(TestAsync)} must run serially. Please configure your unit test suite to run tests one-by-one.");
|
||||
|
||||
deployment = new Deployment(engine, monitor, options);
|
||||
Instance = deployment;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await deployment._runner.RunAsync<TStack>();
|
||||
return engine.Errors.Count switch
|
||||
{
|
||||
1 => throw new RunException(engine.Errors.Single()),
|
||||
int v when v > 1 => throw new AggregateException(engine.Errors.Select(e => new RunException(e))),
|
||||
_ => monitor.Resources.ToImmutableArray()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_instanceLock)
|
||||
{
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IRunner CreateRunner()
|
||||
{
|
||||
// Serilog.Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger();
|
||||
|
||||
Serilog.Log.Debug("Deployment.Run called.");
|
||||
if (_instance != null)
|
||||
lock (_instanceLock)
|
||||
{
|
||||
throw new NotSupportedException("Deployment.Run can only be called a single time.");
|
||||
}
|
||||
if (_instance != null)
|
||||
throw new NotSupportedException("Deployment.Run can only be called a single time.");
|
||||
|
||||
Serilog.Log.Debug("Creating new Deployment.");
|
||||
var deployment = new Deployment();
|
||||
Instance = deployment;
|
||||
return deployment._runner;
|
||||
Serilog.Log.Debug("Creating new Deployment.");
|
||||
var deployment = new Deployment();
|
||||
Instance = deployment;
|
||||
return deployment._runner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
27
sdk/dotnet/Pulumi/Deployment/GrpcEngine.cs
Normal file
27
sdk/dotnet/Pulumi/Deployment/GrpcEngine.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
internal class GrpcEngine : IEngine
|
||||
{
|
||||
private readonly Engine.EngineClient _engine;
|
||||
|
||||
public GrpcEngine(string engine)
|
||||
{
|
||||
this._engine = new Engine.EngineClient(new Channel(engine, ChannelCredentials.Insecure));
|
||||
}
|
||||
|
||||
public async Task LogAsync(LogRequest request)
|
||||
=> await this._engine.LogAsync(request);
|
||||
|
||||
public async Task<SetRootResourceResponse> SetRootResourceAsync(SetRootResourceRequest request)
|
||||
=> await this._engine.SetRootResourceAsync(request);
|
||||
|
||||
public async Task<GetRootResourceResponse> GetRootResourceAsync(GetRootResourceRequest request)
|
||||
=> await this._engine.GetRootResourceAsync(request);
|
||||
}
|
||||
}
|
30
sdk/dotnet/Pulumi/Deployment/GrpcMonitor.cs
Normal file
30
sdk/dotnet/Pulumi/Deployment/GrpcMonitor.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
internal class GrpcMonitor : IMonitor
|
||||
{
|
||||
private readonly ResourceMonitor.ResourceMonitorClient _client;
|
||||
|
||||
public GrpcMonitor(string monitor)
|
||||
{
|
||||
this._client = new ResourceMonitor.ResourceMonitorClient(new Channel(monitor, ChannelCredentials.Insecure));
|
||||
}
|
||||
|
||||
public async Task<InvokeResponse> InvokeAsync(InvokeRequest request)
|
||||
=> await this._client.InvokeAsync(request);
|
||||
|
||||
public async Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request)
|
||||
=> await this._client.ReadResourceAsync(request);
|
||||
|
||||
public async Task<RegisterResourceResponse> RegisterResourceAsync(Resource resource, RegisterResourceRequest request)
|
||||
=> await this._client.RegisterResourceAsync(request);
|
||||
|
||||
public async Task RegisterResourceOutputsAsync(RegisterResourceOutputsRequest request)
|
||||
=> await this._client.RegisterResourceOutputsAsync(request);
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ namespace Pulumi
|
|||
{
|
||||
internal interface IDeploymentInternal : IDeployment
|
||||
{
|
||||
Options Options { get; }
|
||||
string? GetConfig(string fullKey);
|
||||
|
||||
Stack Stack { get; set; }
|
||||
|
|
16
sdk/dotnet/Pulumi/Deployment/IEngine.cs
Normal file
16
sdk/dotnet/Pulumi/Deployment/IEngine.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
internal interface IEngine
|
||||
{
|
||||
Task LogAsync(LogRequest request);
|
||||
|
||||
Task<SetRootResourceResponse> SetRootResourceAsync(SetRootResourceRequest request);
|
||||
|
||||
Task<GetRootResourceResponse> GetRootResourceAsync(GetRootResourceRequest request);
|
||||
}
|
||||
}
|
18
sdk/dotnet/Pulumi/Deployment/IMonitor.cs
Normal file
18
sdk/dotnet/Pulumi/Deployment/IMonitor.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi
|
||||
{
|
||||
internal interface IMonitor
|
||||
{
|
||||
Task<InvokeResponse> InvokeAsync(InvokeRequest request);
|
||||
|
||||
Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request);
|
||||
|
||||
Task<RegisterResourceResponse> RegisterResourceAsync(Resource resource, RegisterResourceRequest request);
|
||||
|
||||
Task RegisterResourceOutputsAsync(RegisterResourceOutputsRequest request);
|
||||
}
|
||||
}
|
|
@ -198,6 +198,17 @@ Pulumi.StackReferenceArgs.Name.set -> void
|
|||
Pulumi.StackReferenceArgs.StackReferenceArgs() -> void
|
||||
Pulumi.StringAsset
|
||||
Pulumi.StringAsset.StringAsset(string text) -> void
|
||||
Pulumi.Testing.IMocks
|
||||
Pulumi.Testing.IMocks.CallAsync(string token, System.Collections.Immutable.ImmutableDictionary<string, object> args, string provider) -> System.Threading.Tasks.Task<object>
|
||||
Pulumi.Testing.IMocks.NewResourceAsync(string type, string name, System.Collections.Immutable.ImmutableDictionary<string, object> inputs, string provider, string id) -> System.Threading.Tasks.Task<(string id, object state)>
|
||||
Pulumi.Testing.TestOptions
|
||||
Pulumi.Testing.TestOptions.TestOptions() -> void
|
||||
Pulumi.Testing.TestOptions.ProjectName.get -> string
|
||||
Pulumi.Testing.TestOptions.ProjectName.set -> void
|
||||
Pulumi.Testing.TestOptions.StackName.get -> string
|
||||
Pulumi.Testing.TestOptions.StackName.set -> void
|
||||
Pulumi.Testing.TestOptions.IsPreview.get -> bool?
|
||||
Pulumi.Testing.TestOptions.IsPreview.set -> void
|
||||
Pulumi.Union<T0, T1>
|
||||
Pulumi.Union<T0, T1>.AsT0.get -> T0
|
||||
Pulumi.Union<T0, T1>.AsT1.get -> T1
|
||||
|
@ -223,6 +234,7 @@ static Pulumi.Deployment.RunAsync(System.Action action) -> System.Threading.Task
|
|||
static Pulumi.Deployment.RunAsync(System.Func<System.Collections.Generic.IDictionary<string, object>> func) -> System.Threading.Tasks.Task<int>
|
||||
static Pulumi.Deployment.RunAsync(System.Func<System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, object>>> func) -> System.Threading.Tasks.Task<int>
|
||||
static Pulumi.Deployment.RunAsync<TStack>() -> System.Threading.Tasks.Task<int>
|
||||
static Pulumi.Deployment.TestAsync<TStack>(Pulumi.Testing.IMocks mocks, Pulumi.Testing.TestOptions options = null) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableArray<Pulumi.Resource>>
|
||||
static Pulumi.Input<T>.implicit operator Pulumi.Input<T>(Pulumi.Output<T> value) -> Pulumi.Input<T>
|
||||
static Pulumi.Input<T>.implicit operator Pulumi.Input<T>(T value) -> Pulumi.Input<T>
|
||||
static Pulumi.Input<T>.implicit operator Pulumi.Output<T>(Pulumi.Input<T> input) -> Pulumi.Output<T>
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace Pulumi.Serialization
|
|||
{
|
||||
var propType = prop.PropertyType;
|
||||
var propFullName = $"[Output] {resource.GetType().FullName}.{prop.Name}";
|
||||
if (!propType.IsConstructedGenericType &&
|
||||
if (!propType.IsConstructedGenericType ||
|
||||
propType.GetGenericTypeDefinition() != typeof(Output<>))
|
||||
{
|
||||
throw new InvalidOperationException($"{propFullName} was not an Output<T>");
|
||||
|
|
36
sdk/dotnet/Pulumi/Testing/IMocks.cs
Normal file
36
sdk/dotnet/Pulumi/Testing/IMocks.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pulumi.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Hooks to mock the engine that provide test doubles for offline unit testing of stacks.
|
||||
/// </summary>
|
||||
public interface IMocks
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when a new resource is created by the program.
|
||||
/// </summary>
|
||||
/// <param name="type">Resource type name.</param>
|
||||
/// <param name="name">Resource name.</param>
|
||||
/// <param name="inputs">Dictionary of resource input properties.</param>
|
||||
/// <param name="provider">Provider.</param>
|
||||
/// <param name="id">Resource identifier.</param>
|
||||
/// <returns>A tuple of a resource identifier and resource state. State can be either a POCO
|
||||
/// or a dictionary bag.</returns>
|
||||
Task<(string id, object state)> NewResourceAsync(string type, string name,
|
||||
ImmutableDictionary<string, object> inputs, string? provider, string? id);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the program needs to call a provider to load data (e.g., to retrieve an existing
|
||||
/// resource).
|
||||
/// </summary>
|
||||
/// <param name="token">Function token.</param>
|
||||
/// <param name="args">Dictionary of input arguments.</param>
|
||||
/// <param name="provider">Provider.</param>
|
||||
/// <returns>Invocation result, can be either a POCO or a dictionary bag.</returns>
|
||||
Task<object> CallAsync(string token, ImmutableDictionary<string, object> args, string? provider);
|
||||
}
|
||||
}
|
56
sdk/dotnet/Pulumi/Testing/MockEngine.cs
Normal file
56
sdk/dotnet/Pulumi/Testing/MockEngine.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Pulumi.Serialization;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi.Testing
|
||||
{
|
||||
internal class MockEngine : IEngine
|
||||
{
|
||||
private string? _rootResourceUrn;
|
||||
private readonly object _rootResourceUrnLock = new object();
|
||||
|
||||
public readonly List<string> Errors = new List<string>();
|
||||
|
||||
public Task LogAsync(LogRequest request)
|
||||
{
|
||||
if (request.Severity == LogSeverity.Error)
|
||||
{
|
||||
lock (this.Errors)
|
||||
{
|
||||
this.Errors.Add(request.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<SetRootResourceResponse> SetRootResourceAsync(SetRootResourceRequest request)
|
||||
{
|
||||
lock (_rootResourceUrnLock)
|
||||
{
|
||||
if (_rootResourceUrn != null && _rootResourceUrn != request.Urn)
|
||||
throw new InvalidOperationException(
|
||||
$"An invalid attempt to set the root resource to {request.Urn} while it's already set to {_rootResourceUrn}");
|
||||
|
||||
_rootResourceUrn = request.Urn;
|
||||
}
|
||||
|
||||
return Task.FromResult(new SetRootResourceResponse());
|
||||
}
|
||||
|
||||
public Task<GetRootResourceResponse> GetRootResourceAsync(GetRootResourceRequest request)
|
||||
{
|
||||
lock (_rootResourceUrnLock)
|
||||
{
|
||||
if (_rootResourceUrn == null)
|
||||
throw new InvalidOperationException("Root resource is not set");
|
||||
|
||||
return Task.FromResult(new GetRootResourceResponse {Urn = _rootResourceUrn});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
sdk/dotnet/Pulumi/Testing/MockMonitor.cs
Normal file
103
sdk/dotnet/Pulumi/Testing/MockMonitor.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Pulumi.Serialization;
|
||||
using Pulumirpc;
|
||||
|
||||
namespace Pulumi.Testing
|
||||
{
|
||||
internal class MockMonitor : IMonitor
|
||||
{
|
||||
private readonly IMocks _mocks;
|
||||
private readonly Serializer _serializer = new Serializer();
|
||||
|
||||
public readonly List<Resource> Resources = new List<Resource>();
|
||||
|
||||
public MockMonitor(IMocks mocks)
|
||||
{
|
||||
_mocks = mocks;
|
||||
}
|
||||
|
||||
public async Task<InvokeResponse> InvokeAsync(InvokeRequest request)
|
||||
{
|
||||
var result = await _mocks.CallAsync(request.Tok, ToDictionary(request.Args), request.Provider)
|
||||
.ConfigureAwait(false);
|
||||
return new InvokeResponse {Return = await SerializeAsync(result).ConfigureAwait(false)};
|
||||
}
|
||||
|
||||
public async Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request)
|
||||
{
|
||||
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name,
|
||||
ToDictionary(request.Properties), request.Provider, request.Id).ConfigureAwait(false);
|
||||
|
||||
lock (this.Resources)
|
||||
{
|
||||
this.Resources.Add(resource);
|
||||
}
|
||||
|
||||
return new ReadResourceResponse
|
||||
{
|
||||
Urn = NewUrn(request.Parent, request.Type, request.Name),
|
||||
Properties = await SerializeAsync(state).ConfigureAwait(false)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<RegisterResourceResponse> RegisterResourceAsync(Resource resource, RegisterResourceRequest request)
|
||||
{
|
||||
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name, ToDictionary(request.Object),
|
||||
request.Provider, request.ImportId).ConfigureAwait(false);
|
||||
|
||||
lock (this.Resources)
|
||||
{
|
||||
this.Resources.Add(resource);
|
||||
}
|
||||
|
||||
return new RegisterResourceResponse
|
||||
{
|
||||
Id = id ?? request.ImportId,
|
||||
Urn = NewUrn(request.Parent, request.Type, request.Name),
|
||||
Object = await SerializeAsync(state).ConfigureAwait(false)
|
||||
};
|
||||
}
|
||||
|
||||
public Task RegisterResourceOutputsAsync(RegisterResourceOutputsRequest request) => Task.CompletedTask;
|
||||
|
||||
private static string NewUrn(string parent, string type, string name)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(parent))
|
||||
{
|
||||
var qualifiedType = parent.Split("::")[2];
|
||||
var parentType = qualifiedType.Split("$").First();
|
||||
type = parentType + "$" + type;
|
||||
}
|
||||
return "urn:pulumi:" + string.Join("::", new[] { Deployment.Instance.StackName, Deployment.Instance.ProjectName, type, name });
|
||||
}
|
||||
|
||||
private static ImmutableDictionary<string, object> ToDictionary(Struct s)
|
||||
{
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object>();
|
||||
foreach (var (key, value) in s.Fields)
|
||||
{
|
||||
var data = Deserializer.Deserialize(value);
|
||||
if (data.IsKnown && data.Value != null)
|
||||
{
|
||||
builder.Add(key, data.Value);
|
||||
}
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
private async Task<Struct> SerializeAsync(object o)
|
||||
{
|
||||
var dict = (o as IDictionary<string, object>)?.ToImmutableDictionary()
|
||||
?? await _serializer.SerializeAsync("", o).ConfigureAwait(false) as ImmutableDictionary<string, object>
|
||||
?? throw new InvalidOperationException($"{o.GetType().FullName} is not a supported argument type");
|
||||
return Serializer.CreateStruct(dict);
|
||||
}
|
||||
}
|
||||
}
|
23
sdk/dotnet/Pulumi/Testing/TestOptions.cs
Normal file
23
sdk/dotnet/Pulumi/Testing/TestOptions.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
namespace Pulumi.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional settings for <see cref="Deployment.TestAsync{T}"/>.
|
||||
/// </summary>
|
||||
public class TestOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Project name. Defaults to <b>"project"</b> if not specified.
|
||||
/// </summary>
|
||||
public string? ProjectName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stack name. Defaults to <b>"stack"</b> if not specified.
|
||||
/// </summary>
|
||||
public string? StackName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the test runs in Preview mode. Defaults to <b>true</b> if not specified.
|
||||
/// </summary>
|
||||
public bool? IsPreview { get; set; }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue