pulumi/sdk/python/lib/test/test_invoke.py

165 lines
7.0 KiB
Python

# Copyright 2016-2023, 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.
import pytest
from typing import Awaitable, Optional, Union
from concurrent.futures import ThreadPoolExecutor
import asyncio
import time
import pulumi
from pulumi import InvokeOptions, InvokeOutputOptions
class MyMocks(pulumi.runtime.Mocks):
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
return [args.name + "_id", args.inputs]
def call(self, args: pulumi.runtime.MockCallArgs):
return {} if args.args.get("empty") else {"result": "mock"}
class MockResource(pulumi.CustomResource):
def __init__(self, name: str, opts: Optional[pulumi.ResourceOptions] = None):
super().__init__("python:test:MockResource", name, {}, opts)
@pytest.mark.parametrize(
"tok,version,empty,expected",
[
("test:index:MyFunction", "", True, {}),
("test:index:MyFunction", "invalid", True, {}),
("test:index:MyFunction", "1.0.0", True, {}),
("test:index:MyFunction", "", False, {"result": "mock"}),
("test:index:MyFunction", "invalid", False, {"result": "mock"}),
("test:index:MyFunction", "1.0.0", False, {"result": "mock"}),
("kubernetes:something:new", "", True, {}),
("kubernetes:something:new", "invalid", True, {}),
("kubernetes:something:new", "1.0.0", True, {}),
("kubernetes:something:new", "4.5.3", True, {}),
("kubernetes:something:new", "4.5.4", True, {}),
("kubernetes:something:new", "4.5.5", True, {}),
("kubernetes:something:new", "4.6.0", True, {}),
("kubernetes:something:new", "5.0.0", True, {}),
("kubernetes:something:new", "", False, {"result": "mock"}),
("kubernetes:something:new", "invalid", False, {"result": "mock"}),
("kubernetes:something:new", "1.0.0", False, {"result": "mock"}),
# Expect the legacy behavior of getting None for empty results for these Kubernetes
# function tokens when the version is unspecified, invalid, or <= 4.5.4.
("kubernetes:yaml:decode", "", True, None),
("kubernetes:yaml:decode", "invalid", True, None),
("kubernetes:yaml:decode", "1.0.0", True, None),
("kubernetes:yaml:decode", "4.5.3", True, None),
("kubernetes:yaml:decode", "4.5.4", True, None),
("kubernetes:yaml:decode", "4.5.5", True, {}),
("kubernetes:yaml:decode", "4.6.0", True, {}),
("kubernetes:yaml:decode", "5.0.0", True, {}),
("kubernetes:yaml:decode", "", False, {"result": "mock"}),
("kubernetes:yaml:decode", "invalid", False, {"result": "mock"}),
("kubernetes:yaml:decode", "1.0.0", False, {"result": "mock"}),
("kubernetes:helm:template", "", True, None),
("kubernetes:helm:template", "invalid", True, None),
("kubernetes:helm:template", "1.0.0", True, None),
("kubernetes:helm:template", "4.5.3", True, None),
("kubernetes:helm:template", "4.5.4", True, None),
("kubernetes:helm:template", "4.5.5", True, {}),
("kubernetes:helm:template", "4.6.0", True, {}),
("kubernetes:helm:template", "5.0.0", True, {}),
("kubernetes:helm:template", "", False, {"result": "mock"}),
("kubernetes:helm:template", "invalid", False, {"result": "mock"}),
("kubernetes:helm:template", "1.0.0", False, {"result": "mock"}),
("kubernetes:kustomize:directory", "", True, None),
("kubernetes:kustomize:directory", "invalid", True, None),
("kubernetes:kustomize:directory", "1.0.0", True, None),
("kubernetes:kustomize:directory", "4.5.3", True, None),
("kubernetes:kustomize:directory", "4.5.4", True, None),
("kubernetes:kustomize:directory", "4.5.5", True, {}),
("kubernetes:kustomize:directory", "4.6.0", True, {}),
("kubernetes:kustomize:directory", "5.0.0", True, {}),
("kubernetes:kustomize:directory", "", False, {"result": "mock"}),
("kubernetes:kustomize:directory", "invalid", False, {"result": "mock"}),
("kubernetes:kustomize:directory", "1.0.0", False, {"result": "mock"}),
],
)
@pulumi.runtime.test
def test_invoke_empty_return(tok: str, version: str, empty: bool, expected) -> None:
pulumi.runtime.mocks.set_mocks(MyMocks())
props = {"empty": True} if empty else {}
opts = pulumi.InvokeOptions(version=version) if version else None
assert pulumi.runtime.invoke(tok, props, opts).value == expected
@pulumi.runtime.test
async def test_invoke_depends_on() -> None:
pulumi.runtime.mocks.set_mocks(MyMocks())
dep: pulumi.Resource = MockResource(name="dep1")
dep_future = asyncio.Future()
dep_output = pulumi.Output.from_input(dep_future)
# This function will sleep for 1 second and then resolve dep_future with the
# resource, which in turn resolves the output.
def resolve():
time.sleep(1)
dep_future.set_result(dep)
# Run the resolve function in a separate thread.
with ThreadPoolExecutor(max_workers=2) as executor:
f = executor.submit(resolve)
assert not dep_future.done(), "dep_future should not be done yet"
opts = pulumi.InvokeOutputOptions(depends_on=[dep_output])
o = pulumi.runtime.invoke_output("test::MyFunction", {}, opts)
def check(invoke_result):
assert (
dep_future.done()
), "invoke_output should wait for dep_future to be done"
assert invoke_result == {"result": "mock"}
return o.apply(check)
@pytest.mark.parametrize(
"a,b,expected",
[
(InvokeOptions(version="1.0.0"), InvokeOptions(version="2.0.0"), "2.0.0"),
(None, InvokeOptions(version="2.0.0"), "2.0.0"),
(InvokeOptions(version="1.0.0"), None, "1.0.0"),
(InvokeOptions(version="1.0.0"), InvokeOptions(version=""), ""),
(InvokeOutputOptions(version="1.0.0"), InvokeOptions(version="2.0.0"), "2.0.0"),
(
InvokeOutputOptions(version="1.0.0"),
InvokeOutputOptions(version="2.0.0"),
"2.0.0",
),
(None, InvokeOutputOptions(version="2.0.0"), "2.0.0"),
(InvokeOutputOptions(version="1.0.0"), None, "1.0.0"),
],
)
def test_invoke_merge(
a: Union[InvokeOptions, InvokeOutputOptions],
b: Union[InvokeOptions, InvokeOutputOptions],
expected: str,
) -> None:
if a is not None:
assert a.merge(b).version == expected
if isinstance(a, InvokeOutputOptions):
assert InvokeOutputOptions.merge(a, b).version == expected
else:
assert InvokeOptions.merge(a, b).version == expected