pulumi/sdk/python/lib/test_with_mocks/test_testing_with_mocks.py
Justin Van Patten 1e09626bc7
[sdk/{nodejs,python}] Fix errors when testing remote components with mocks (#8053)
v3.13.0 introduces support for serializing outputs in inputs as special output value objects in the Node.js and Python SDKs when serializing inputs for remote components and method calls. This functionality is currently disabled by default in the engine (setting the `PULUMI_ENABLE_OUTPUT_VALUES` envvar to a truthy value enables it).

However, unit testing remote components with mocks results in errors being raised in v3.13.0, related to the new output value support. This is due to the mock monitor implementation saying it supports all features (which now includes output values), so the SDK serializers are serializing outputs as output values, which the mock monitor can't handle correctly.

This change addresses the issue by updating the mock monitor implementation in the Node.js and Python SDKs to indicate the specific features that are supported, excluding support for output values. New tests with mocks fail before the change and pass after.
2021-09-24 06:08:13 -07:00

197 lines
5.7 KiB
Python

# Copyright 2016-2021, Pulumi 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.
from concurrent.futures import ThreadPoolExecutor
import asyncio
import grpc
import pulumi
import pytest
import resources
import unittest
@pytest.fixture
def my_resources():
loop = asyncio.get_event_loop()
loop.set_default_executor(ImmediateExecutor())
old_settings = pulumi.runtime.settings.SETTINGS
try:
pulumi.runtime.mocks.set_mocks(MyMocks())
yield resources.define_resources()
finally:
pulumi.runtime.settings.configure(old_settings)
loop.set_default_executor(ThreadPoolExecutor())
@pulumi.runtime.test
def test_component(my_resources):
def check_outprop(outprop):
assert outprop == 'output: hello'
return my_resources['mycomponent'].outprop.apply(check_outprop)
@pulumi.runtime.test
def test_remote_component(my_resources):
def check_outprop(outprop):
assert outprop.startswith("output: hello: ")
return my_resources['myremotecomponent'].outprop.apply(check_outprop)
@pulumi.runtime.test
def test_custom(my_resources):
def check_ip(ip):
assert ip == '203.0.113.12'
return my_resources['myinstance'].public_ip.apply(check_ip)
@pulumi.runtime.test
def test_custom_resource_reference(my_resources):
def check_instance(instance):
assert isinstance(instance, resources.Instance)
def check_ip(ip):
assert ip == '203.0.113.12'
instance.public_ip.apply(check_ip)
return my_resources['mycustom'].instance.apply(check_instance)
@pulumi.runtime.test
def test_invoke(my_resources):
assert my_resources['invoke_result'] == 59
@pulumi.runtime.test
def test_invoke_failures(my_resources):
caught = False
try:
pulumi.runtime.invoke("test:index:FailFunction", props={})
except Exception as e:
caught = str(e)
assert 'this function fails!' in caught
@pulumi.runtime.test
def test_invoke_throws(my_resources):
caught = None
try:
pulumi.runtime.invoke("test:index:ThrowFunction", props={})
except Exception as e:
caught = str(e)
assert 'this function throws!' in caught
@pulumi.runtime.test
def test_stack_reference(my_resources):
def check_outputs(outputs):
assert outputs["haha"] == "business"
my_resources['dns_ref'].outputs.apply(check_outputs)
class GrpcError(grpc.RpcError):
def __init__(self, code, details):
self._code = code
self._details = details
def code(self):
return self._code
def details(self):
return self._details
class MyMocks(pulumi.runtime.Mocks):
def call(self, args: pulumi.runtime.MockCallArgs):
if args.token == 'test:index:MyFunction':
return {
'out_value': 59,
}
elif args.token == 'test:index:FailFunction':
return ({}, [('none', 'this function fails!')])
elif args.token == 'test:index:ThrowFunction':
raise GrpcError(42, 'this function throws!')
else:
return {}
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
if args.typ == 'aws:ec2/securityGroup:SecurityGroup':
state = {
'arn': 'arn:aws:ec2:us-west-2:123456789012:security-group/sg-12345678',
'name': args.inputs['name'] if 'name' in args.inputs else args.name + '-sg',
}
return ['sg-12345678', dict(args.inputs, **state)]
elif args.typ == 'aws:ec2/instance:Instance':
state = {
'arn': 'arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0',
'instanceState': 'running',
'primaryNetworkInterfaceId': 'eni-12345678',
'privateDns': 'ip-10-0-1-17.ec2.internal',
'public_dns': 'ec2-203-0-113-12.compute-1.amazonaws.com',
'public_ip': '203.0.113.12',
}
return ['i-1234567890abcdef0', dict(args.inputs, **state)]
elif args.typ == 'pkg:index:MyCustom':
return [args.name + '_id', args.inputs]
elif args.typ == 'pulumi:pulumi:StackReference' and 'dns' in args.name:
return [args.name, {'outputs': {'haha': 'business'}}]
elif args.typ == 'pkg:index:MyRemoteComponent':
state = {
'outprop': f"output: {args.inputs['inprop']}",
}
return [args.name + '_id', dict(args.inputs, **state)]
else:
return ['', {}]
class ImmediateExecutor(ThreadPoolExecutor):
"""This removes multithreading from current tests. Unfortunately in
presence of multithreading the tests are flaky. The proper fix is
postponed - see https://github.com/pulumi/pulumi/issues/7663
"""
def __init__(self):
super()
self._default_executor = ThreadPoolExecutor()
def submit(self, fn, *args, **kwargs):
v = fn(*args, **kwargs)
return self._default_executor.submit(ImmediateExecutor._identity, v)
def map(self, func, *iterables, timeout=None, chunksize=1):
raise Exception('map not implemented')
def shutdown(self, wait=True, cancel_futures=False):
raise Exception('shutdown not implemented')
@staticmethod
def _identity(x):
return x