Skip to content

Step Functions: Mock Mode Improvements #12560

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

Merged
merged 13 commits into from
Apr 30, 2025
Merged

Step Functions: Mock Mode Improvements #12560

merged 13 commits into from
Apr 30, 2025

Conversation

MEPalma
Copy link
Contributor

@MEPalma MEPalma commented Apr 28, 2025

Motivation

Currently, the Step Functions interpreter’s support for Mocked Integrations has several limitations. Specifically:

  • Mocked responses for .waitForTaskToken are not evaluated but always emulated.
  • The repeat count for ranges of mocked responses is incorrectly computed, causing some mocked responses to be skipped.
  • Interleaving usage of mocked integration and emulation is not yet supported.
  • Return mocked responses are restricted to JSON objects, whereas SFN Local allows any JSON value.
  • Errors during loading and parsing of the mock configuration file result in unintuitive feedback, making it unclear why a test case is unavailable.

Changes

  • Evaluate mocked responses for .waitForTaskToken instead of always emulating.
  • Add support for interleaved use of mocked integrations and emulation.
  • Correct repeat count computation for ranges of mocked responses.
  • Allow Return mocked responses to be any JSON value, not just JSON objects.
  • Improve error feedback when loading or parsing the mock configuration file.
  • Extend the test utils for mocked integrations.
  • Add a number of positive and negative test cases for mocked integrations

Future Work

A known limitation is that, in mock mode, when simulating a failure, the TaskSubmitted event is not recorded in the event history. This should be addressed in future efforts in this space. The automatic reloading on changes of the mock configuration file should also be tested.

@MEPalma MEPalma added the semver: minor Non-breaking changes which can be included in minor releases, but not in patch releases label Apr 28, 2025
@MEPalma MEPalma added this to the 4.4 milestone Apr 28, 2025
@MEPalma MEPalma self-assigned this Apr 28, 2025
Copy link

github-actions bot commented Apr 28, 2025

LocalStack Community integration with Pro

    2 files  ±0      2 suites  ±0   1h 42m 12s ⏱️ +24s
4 399 tests +6  4 037 ✅ +5  362 💤 +1  0 ❌ ±0 
4 401 runs  +6  4 037 ✅ +5  364 💤 +1  0 ❌ ±0 

Results for commit dba8979. ± Comparison against base commit 936c697.

This pull request removes 14 and adds 20 tests. Note that renamed tests count towards both.
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_dynamic]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_mapping_scenarios[update_string_referencing_resource]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_conditions
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_direct_update
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_dynamic_update
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_execute_with_ref
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_mappings_with_parameter_lookup
…
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_dynamic]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_mapping_scenarios[update_string_referencing_resource]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_conditions
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_direct_update
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_dynamic_update
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_execute_with_ref
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_mappings_with_parameter_lookup
…
This pull request removes 14 skipped tests and adds 14 skipped tests. Note that renamed tests count towards both.
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_dynamic]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_base_mapping_scenarios[update_string_referencing_resource]
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_conditions
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_direct_update
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_dynamic_update
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_execute_with_ref
tests.aws.services.cloudformation.api.test_changesets.TestCaptureUpdateProcess ‑ test_mappings_with_parameter_lookup
…
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_dynamic]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_base_mapping_scenarios[update_string_referencing_resource]
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_conditions
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_direct_update
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_dynamic_update
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_execute_with_ref
tests.aws.services.cloudformation.v2.test_change_sets.TestCaptureUpdateProcess ‑ test_mappings_with_parameter_lookup
…
This pull request skips 1 test.
tests.aws.services.cloudformation.v2.test_change_sets ‑ test_single_resource_static_update

♻️ This comment has been updated with latest results.

@MEPalma MEPalma changed the title [draft] Step Functions: Mock Mode Improvements Step Functions: Mock Mode Improvements Apr 29, 2025
@MEPalma MEPalma marked this pull request as ready for review April 29, 2025 15:39
@MEPalma MEPalma removed the request for review from dominikschubert April 29, 2025 15:41
Copy link
Member

@joe4dev joe4dev left a comment

Choose a reason for hiding this comment

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

I have one question related to error logging DevX, but nothing blocking.

Thanks for all these improvements and clarifying the open limitation clearly 👏

@@ -334,6 +328,9 @@ def _after_eval_execution(
normalised_parameters: dict,
state_credentials: StateCredentials,
) -> None:
# TODO: In Mock mode, when simulating a failure, the mock response is handled by
Copy link
Member

Choose a reason for hiding this comment

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

praise: Thanks for clarifying this parity limitation, specifying where and why clearly 👍

outcome = self._eval_wait_for_task_token(
env=env,
timeout_seconds=timeout_seconds,
callback_endpoint=callback_endpoint,
heartbeat_endpoint=heartbeat_endpoint,
)
elif env.is_mocked_mode():
Copy link
Member

Choose a reason for hiding this comment

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

praise: neat simplification

return self.mock_test_case is not None
return (
self.mock_test_case is not None
and self.next_state_name in self.mock_test_case.state_mocked_responses
Copy link
Member

Choose a reason for hiding this comment

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

thought: interesting that the test case itself is unsufficient and we need to check forthe next state as well. Should we add a comment clarifying in which scenarios this is needed?

message,
error_type,
)
LOG.info(
Copy link
Member

Choose a reason for hiding this comment

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

question: What changes will/can be applied in this case of having validation errors?

Same for JSONDecodeError.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was hinting to the users' ability to edit the file and try again without restarting localstack. However, I'll comment this out with a TODO as this is currently not being tested ahead of 4.4

Copy link
Member

Choose a reason for hiding this comment

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

I see. Maybe, we can reformulate this using active voice suggesting to the user to update and save the file, indicating that no LocalStack restart is needed. Maybe something alone the lines:

Please update and save the file and try again. No LocalStack restart needed.

Do we need a dedicated info message for this? Wouldn't it be nicer to include actionable advice right next to the error message containing potential validation suggestions?

Copy link
Contributor Author

@MEPalma MEPalma Apr 30, 2025

Choose a reason for hiding this comment

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

As this feature won't make it publicly into 4.4 I'd be happy to revise this in future changes; the rephrasing should be nice. For the sakes of good order, these were the motivations I had for the current design:

  • On parse error, the runtime provides multiple detailed logs for all of the issues encountered. This is done with one log error message per error and are of the form "Mock configuration file error at <location>: <message> (<type>)". This allows the user to easily read each error about the configuration file. I am of the opinion that chaining all of the errors in one log would not always be readable, especially if there are multiple errors. Hence:
    • Appending at the end of this the suggestion of hot-reloading being available I would say is not ideal as it would not be seen by all.
    • At the same time, appending this suggestion at the end of each message seems perhaps excessive and not always readable is the error is long, but otherwise I reckon closer to a better solution
  • Informing (info) the user regarding the hot-reloading feature is different in nature from an error message (error). However, using an active language in doing so might reduce the significance of this consideration

"ErrorEquals": [
"States.ALL"
],
// The aws demo calls for "MaxAttempts: 3" and 4 retry outcomes in "RetryPath" test case.
Copy link
Member

Choose a reason for hiding this comment

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

docs: Can we add a reference/source to the aws demo?

],
// The aws demo calls for "MaxAttempts: 3" and 4 retry outcomes in "RetryPath" test case.
// However, through snapshot testing, we know that this is 1 too many retry outcomes for
// this definition. Hence, in an effort to keep parity with AWS Step Functions, we the
Copy link
Member

Choose a reason for hiding this comment

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

nit: missing verb "we the attempts numbers was adjusted to 4."

assert event_10["type"] == "TaskFailed"

event_13 = events[13]
assert json.loads(event_13["taskSucceededEventDetails"]["output"]) == {
Copy link
Member

Choose a reason for hiding this comment

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

praise: kudos for tackling all these manual asserts 💯

@markers.aws.validated
@markers.snapshot.skip_snapshot_verify(
paths=[
# Reason: skipping events validation because in mock‐failure mode the
Copy link
Member

Choose a reason for hiding this comment

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

praise: thank you for giving an explicit reason here 🙇

"previousEventId": 13,
"stateExitedEventDetails": {
"name": "ParallelState",
"output": "[{\"ExecutedVersion\":\"$LATEST\",\"Payload\":[\"string-literal\"],\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"],\"Content-Length\":[\"18\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"18\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"},\"StatusCode\":200},{\"ExecutedVersion\":\"$LATEST\",\"Payload\":\"input-event-['string-literal']\",\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"],\"Content-Length\":[\"32\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"32\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"},\"StatusCode\":200}]",
Copy link
Member

Choose a reason for hiding this comment

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

note[non-blocking]: this would require some transformation containing many changing items. It's ok to skip them, maybe worth mentioning the reason in the snapshot skip.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes indeed, transforming this field would make it easier to assert and work on; but it is also true that with SFN we have some desire to match the string formatting too #11953.
We have an open project on this aspect, and will definitely like to come back to this in future efforts

return execution_arn


def create_and_run_mock(
Copy link
Member

Choose a reason for hiding this comment

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

praise: neat utility ✨

@MEPalma MEPalma merged commit e3f68a8 into master Apr 30, 2025
33 checks passed
@MEPalma MEPalma deleted the MEP-SFN-mock_comp branch April 30, 2025 13:10
simonrw pushed a commit that referenced this pull request May 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver: minor Non-breaking changes which can be included in minor releases, but not in patch releases
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants