pulumi/sdk/python/lib/test/langhost
Justin Van Patten 5dd813ea47 [sdk/python] Fix hang due to component children cycles
When a resource depends on a local component resource, rather than setting the component resource itself as a dependency, each of the component's descendants is added as a dependency. This can lead to hangs when cycles are introduced.

For example, consider the following parent/child hierarchy, where `ComponentA` is the parent of `ComponentB` and `ComponentB` is the parent of `CustomC`:

ComponentA
    |
ComponentB
    |
 CustomC

If `ComponentB` specifies it has a dependency on `ComponentA`, the following takes place as part determining the full set of transitive dependencies:

1. `ComponentA`  is a component resource so it isn't added as a dependency, its children are.
2. `ComponentA` has one child: `ComponentB`
3. `ComponentB`  is a component resource so it isn't added as a dependency, its children are.
4. `ComponentB` has one child: `CustomC`, a custom resource.
5. Since `CustomC` is a custom resource, it is added to the set of dependencies.
6. We try to await its URN, but we'll never get it because `RegisterResource` hasn't yet been called for it. And we hang waiting.

To address this, skip looking at a component's children if it is the component from which the dependency is being added.

In the example, with the fix, at step 3 the dependency expansion will stop: we won't look at `ComponentB`'s children because we're adding the dependency from `ComponentB`.

PR feedback
2023-04-07 07:23:04 -07:00
..
aliases Delegate alias computation to the engine 2023-01-30 14:45:00 +01:00
asset Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
chained_failure [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
component_dependencies [sdk/python] Fix hang due to component children cycles 2023-04-07 07:23:04 -07:00
component_provider_resolution [sdk/python] Remove merge options promotion (#8770) 2022-01-21 14:08:39 -08:00
component_resource_list_of_providers Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
component_resource_single_provider Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
config Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
delete_before_replace Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
empty Python Language Host Tests (#1577) 2018-06-29 14:08:58 -07:00
first_class_provider Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
first_class_provider_invoke Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
first_class_provider_unknown Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
future_failure [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
future_input Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
ignore_changes Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
inherit_defaults Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
inheritance_translation Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
inheritance_types Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
input_type_mismatch Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
input_values_for_outputs [sdk/python] Don't error on type mismatches when using input values for outputs 2022-11-21 11:03:50 -08:00
invalid_property_dependency [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
invoke [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
invoke_future Fix a reentrancy issue in _sync_await. (#3056) 2019-08-08 19:51:11 -07:00
invoke_types Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
large_resource Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
marshal_failure [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
one_complex_resource Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
one_resource Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
output_all Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
output_nested Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
output_property_dependencies [sdk/python] Add a smoke test for output deps (#8113) 2021-10-02 09:06:11 -07:00
outputs_future Make `pulumi.runtime.invoke` synchronous. (#3019) 2019-08-02 14:19:56 -07:00
preview Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
property_dependencies [sdk/python] Transitive component dependencies. (#7732) 2021-08-11 21:52:16 -05:00
property_renaming Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
protect Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
read Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
remote_component_dependencies Don't add children of remote componet resources (#9665) 2022-05-25 11:02:52 -07:00
replace_on_changes Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
resource_op_bad_inputs [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
resource_op_fail [sdk] deduplicate python error logs (#10348) 2022-08-11 10:38:34 -07:00
resource_thens [sdk/python] Fix makefile target ordering, +x .py 2022-01-19 14:38:21 -08:00
runtime_settings Add getOrganization to nodejs and python (#10504) 2022-08-31 10:33:29 +01:00
stack_output fix(sdk/python): Allow for duplicate output values in python programs 2022-12-07 11:59:09 -05:00
ten_resources Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
types Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
versions Add `replaceOnChanges` resource option (#7226) 2021-07-01 13:32:08 -06:00
README.md Fix an issue where we fail to rethrow exceptions arising from failed resource operations 2018-06-29 16:32:39 -07:00
__init__.py Python Language Host Tests (#1577) 2018-06-29 14:08:58 -07:00
util.py Add getOrganization to nodejs and python (#10504) 2022-08-31 10:33:29 +01:00

README.md

Python Language Host Tests

The tests in this directory test the language host directly by posing as the engine and running programs in the same context that they would be run by the CLI. Programs run by these tests can create resources, read resource, invoke data sources, and generally do anything that a Pulumi program can do.

Language host tests provide a program to be run and an implementation of the LanghostTest class, which provides implementations for the four resource monitor endpoints that the language host speaks to:

  • invoke, for invoking data sources,
  • read_resource, for reading existing resources,
  • register_resource, for creating new resources,
  • register_resource_outputs, for registering outputs on component resources

Classes deriving from LanghostTest can override any of these methods to provide custom test functionality. This is commonly used to perform assertions or place the language host in unexpected situations.

Adding a new test

To add a new language host test, you can:

  1. Create a new directory in this directory with the name of your test
  2. Place an __init__.py and __main__.py in this directory. __init__.py convinces Python that this directory is a module, while __main__.py indicates to Python that this module is runnable.
  3. Write your Pulumi program in __main__.py. If you want to do assertions, use the assert keyword to do so.
  4. Add a test file, which can have any name. In this test file you'll want to provide a subclass of LanghostTest that drives your test. An example minimal test would be something like this:
from os import path
from ..util import LanghostTest


class EmptyTests(LanghostTest):
    def test_empty(self):
        self.run_test(
            program=path.join(self.base_path(), "empty"), # If your test is in the empty/ subdirectory
            expected_resource_count=0)                    # Assert there are 0 resource registrations

Your class can have any number of test_* methods in them. Language host tests are launched by invoking the run_test method inherited from LanghostTest. run_test accepts the following keyword arguments:

  • project - The name of the project that will be exposed to the running program
  • stack - The name of the stack that will be exposed to the running program
  • program - A path to the program to be run, relative to the working directory.
  • pwd - The working directory to use.
  • args - Command-line arguments to pass to the program.
  • config - A dict of configuration keys and values to pass to the program.
  • expected_resource_count - The number of resources this test is expected to register.
  • expected_error - If non-None, the exact error text that is expected to be received.
  • expected_stderr_contains - If non-None, asserts that the given substring exists in stderr

If expected_error is None, the expected error is asserted to be the empty string.

Note that your test method must begin with test_*, since this is how Python discovers what tests to run.

One additional thing to note is that this test harness explicitly ignores the registration of the top-level Stack resource, pulumi:pulumi:Stack, because it is annoying to write tests around. All expected resource counts do not count this resource as a registration and overridden resource monitor methods will never see a registration for pulumi:pulumi:Stack.