Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class MockedCommonState(MockedBaseState[CommonStateField]):
def add_inspection_data(self, env: TestStateEnvironment):
state = self._wrapped

if state._is_language_query_jsonpath():
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

New test uncovered that JSONPath-related inspection data were added to states that use JSONata.

self._add_jsonpath_inspection_data(env)

def _add_jsonpath_inspection_data(self, env: TestStateEnvironment):

state = self._wrapped

if not isinstance(state, StatePass):
if not self.is_single_state:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ProgramRetriable,
)
from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
from localstack.services.stepfunctions.backend.activity import Activity
from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock

Expand Down Expand Up @@ -50,6 +51,9 @@ def __init__(
variable_store=variable_store,
)
self.inspection_data = InspectionData()
variables = variable_store.to_dict()
if variables:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we explicitly set variables to an empty dict, would we expect it to come up in inspection data? Suppose I'm wondering if this should be a None check or an is_truthy check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Empty dict doesn't come up in inspection data. Good point, thanks! 👍 I've added a validated test for this situation.

self.inspection_data["variables"] = to_json_str(variables)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the key change, the rest is passing variables data through the call stack.

self.mock = mock

def is_test_state_mocked_mode(self) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ class VariableStore:
_outer_variable_declaration_cache: VariableDeclarations | None
_variable_declarations_cache: VariableDeclarations | None

def __init__(self):
def __init__(self, variables: dict | None = None):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

With test state, variables store can now be pre-populated rather than being the result of evaluation.

self._outer_scope = {}
self._inner_scope = {}
self._declaration_tracing = set()
self._outer_variable_declaration_cache = None
self._variable_declarations_cache = None

if variables:
for key, value in variables.items():
self.set(key, value)
Comment on lines +65 to +66
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Q: should we be doing a deep copy here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, since variables is used only once to set up execution environment for the test state. If any variable is modified later in the variable store it won't matter.


@classmethod
def as_inner_scope_of(cls, outer_variable_store: VariableStore) -> VariableStore:
inner_variable_store = cls()
Expand All @@ -85,6 +89,14 @@ def get_assigned_variables(self) -> dict[str, str]:
assigned_variables[traced_declaration_identifier] = traced_declaration_value_json_str
return assigned_variables

def to_dict(self) -> dict[str, str]:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This new function is similar to get_assigned_variables right above, but doesn't add any additional formatting.

assigned_variables: dict[str, str] = {}
for traced_declaration_identifier in self._declaration_tracing:
assigned_variables[traced_declaration_identifier] = self.get(
traced_declaration_identifier
)
return assigned_variables

def get(self, variable_identifier: VariableIdentifier) -> VariableValue:
if variable_identifier in self._inner_scope:
return self._inner_scope[variable_identifier]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
TestStateOutput,
Timestamp,
)
from localstack.services.stepfunctions.asl.eval.evaluation_details import EvaluationDetails
from localstack.services.stepfunctions.asl.eval.evaluation_details import (
EvaluationDetails,
)
from localstack.services.stepfunctions.asl.eval.program_state import (
ProgramEnded,
ProgramError,
Expand Down Expand Up @@ -43,6 +45,7 @@ class TestStateExecution(Execution):
next_state: str | None
state_name: str | None
mock: TestStateMock | None
variables: dict | None

class TestCaseExecutionWorkerCommunication(BaseExecutionWorkerCommunication):
_execution: TestStateExecution
Expand Down Expand Up @@ -79,6 +82,7 @@ def __init__(
state_name: str | None = None,
input_data: dict | None = None,
mock: TestStateMock | None = None,
variables: dict | None = None,
):
super().__init__(
name=name,
Expand All @@ -98,6 +102,7 @@ def __init__(
self.next_state = None
self.state_name = state_name
self.mock = mock
self.variables = variables

def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication:
return self.TestCaseExecutionWorkerCommunication(self)
Expand All @@ -114,6 +119,7 @@ def _get_start_execution_worker(self) -> TestStateExecutionWorker:
activity_store=self._activity_store,
state_name=self.state_name,
mock=self.mock,
variables=self.variables,
)

def publish_execution_status_change_event(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
StateMachineData,
)
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore
from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
TestStateAmazonStateLanguageParser,
)
Expand All @@ -29,6 +30,7 @@ class TestStateExecutionWorker(SyncExecutionWorker):
env: TestStateEnvironment | None
state_name: str | None = None
mock: TestStateMock | None
variables: dict | None

def __init__(
self,
Expand All @@ -38,6 +40,7 @@ def __init__(
activity_store: dict[Arn, Activity],
state_name: StateName | None = None,
mock: TestStateMock | None = None,
variables: dict | None = None,
):
super().__init__(
evaluation_details,
Expand All @@ -48,6 +51,7 @@ def __init__(
)
self.state_name = state_name
self.mock = mock
self.variables = variables

def _get_evaluation_entrypoint(self) -> EvalComponent:
return TestStateAmazonStateLanguageParser.parse(
Expand All @@ -74,5 +78,6 @@ def _get_evaluation_environment(self) -> Environment:
event_history_context=EventHistoryContext.of_program_start(),
cloud_watch_logging_session=self._cloud_watch_logging_session,
activity_store=self._activity_store,
variable_store=VariableStore(self.variables),
mock=self.mock,
)
4 changes: 4 additions & 0 deletions localstack-core/localstack/services/stepfunctions/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,9 @@ def test_state(
if input_json := request.get("input", {}):
input_json = json.loads(input_json)

if variables_json := request.get("variables"):
variables_json = json.loads(variables_json)

execution = TestStateExecution(
name=exec_name,
role_arn=role_arn,
Expand All @@ -1578,6 +1581,7 @@ def test_state(
state_name=state_name,
activity_store=self.get_store(context).activities,
mock=state_mock,
variables=variables_json,
)
execution.start()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": {
"recorded-date": "04-11-2024, 13:15:46",
"recorded-date": "19-02-2026, 14:47:34",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Revalidated output tests since initially it looked like output-related problem. Keeping revalidated snapshots, even if they are not strictly related to this PR.

"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -78,7 +78,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": {
"recorded-date": "04-11-2024, 13:16:06",
"recorded-date": "19-02-2026, 14:47:49",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -266,7 +266,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": {
"recorded-date": "04-11-2024, 13:16:22",
"recorded-date": "19-02-2026, 14:48:04",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -463,7 +463,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": {
"recorded-date": "04-11-2024, 13:16:38",
"recorded-date": "19-02-2026, 14:48:17",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -587,7 +587,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": {
"recorded-date": "04-11-2024, 14:01:00",
"recorded-date": "19-02-2026, 14:48:36",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -813,7 +813,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": {
"recorded-date": "04-11-2024, 14:15:19",
"recorded-date": "19-02-2026, 14:48:54",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -920,9 +920,7 @@
"Connection": [
"keep-alive"
],
"x-amzn-RequestId": [
"<uuid:1>"
],
"x-amzn-RequestId": "x-amzn-RequestId",
"Content-Length": [
"60"
],
Expand All @@ -939,13 +937,13 @@
"Date": "date",
"X-Amz-Executed-Version": "$LATEST",
"x-amzn-Remapped-Content-Length": "0",
"x-amzn-RequestId": "<uuid:1>",
"x-amzn-RequestId": "x-amzn-RequestId",
"X-Amzn-Trace-Id": "X-Amzn-Trace-Id"
},
"HttpStatusCode": 200
},
"SdkResponseMetadata": {
"RequestId": "<uuid:1>"
"RequestId": "RequestId"
},
"StatusCode": 200
},
Expand Down Expand Up @@ -996,9 +994,7 @@
"Connection": [
"keep-alive"
],
"x-amzn-RequestId": [
"<uuid:1>"
],
"x-amzn-RequestId": "x-amzn-RequestId",
"Content-Length": [
"60"
],
Expand All @@ -1015,13 +1011,13 @@
"Date": "date",
"X-Amz-Executed-Version": "$LATEST",
"x-amzn-Remapped-Content-Length": "0",
"x-amzn-RequestId": "<uuid:1>",
"x-amzn-RequestId": "x-amzn-RequestId",
"X-Amzn-Trace-Id": "X-Amzn-Trace-Id"
},
"HttpStatusCode": 200
},
"SdkResponseMetadata": {
"RequestId": "<uuid:1>"
"RequestId": "RequestId"
},
"StatusCode": 200
}
Expand Down Expand Up @@ -1068,9 +1064,7 @@
"Connection": [
"keep-alive"
],
"x-amzn-RequestId": [
"<uuid:1>"
],
"x-amzn-RequestId": "x-amzn-RequestId",
"Content-Length": [
"60"
],
Expand All @@ -1087,13 +1081,13 @@
"Date": "date",
"X-Amz-Executed-Version": "$LATEST",
"x-amzn-Remapped-Content-Length": "0",
"x-amzn-RequestId": "<uuid:1>",
"x-amzn-RequestId": "x-amzn-RequestId",
"X-Amzn-Trace-Id": "X-Amzn-Trace-Id"
},
"HttpStatusCode": 200
},
"SdkResponseMetadata": {
"RequestId": "<uuid:1>"
"RequestId": "RequestId"
},
"StatusCode": 200
}
Expand All @@ -1116,7 +1110,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": {
"recorded-date": "20-11-2024, 18:24:00",
"recorded-date": "19-02-2026, 14:49:08",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1184,7 +1178,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": {
"recorded-date": "20-11-2024, 18:24:15",
"recorded-date": "19-02-2026, 14:49:23",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1252,7 +1246,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": {
"recorded-date": "20-11-2024, 18:24:31",
"recorded-date": "19-02-2026, 14:49:37",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1320,7 +1314,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": {
"recorded-date": "20-11-2024, 18:24:46",
"recorded-date": "19-02-2026, 14:49:51",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1388,7 +1382,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": {
"recorded-date": "20-11-2024, 18:25:01",
"recorded-date": "19-02-2026, 14:50:05",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1456,7 +1450,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": {
"recorded-date": "20-11-2024, 18:25:16",
"recorded-date": "19-02-2026, 14:50:20",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1528,7 +1522,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": {
"recorded-date": "20-11-2024, 18:25:31",
"recorded-date": "19-02-2026, 14:50:35",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1596,7 +1590,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": {
"recorded-date": "20-11-2024, 18:25:47",
"recorded-date": "19-02-2026, 14:50:49",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1664,7 +1658,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": {
"recorded-date": "27-12-2024, 14:50:09",
"recorded-date": "19-02-2026, 14:51:09",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down Expand Up @@ -1758,7 +1752,7 @@
}
},
"tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": {
"recorded-date": "27-12-2024, 14:50:24",
"recorded-date": "19-02-2026, 14:51:23",
"recorded-content": {
"get_execution_history": {
"events": [
Expand Down
Loading
Loading