@temporalio/core-bridge 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/Cargo.lock +239 -382
  2. package/Cargo.toml +11 -11
  3. package/lib/native.d.ts +10 -3
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +71 -11
  11. package/sdk-core/.clippy.toml +1 -0
  12. package/sdk-core/.github/workflows/heavy.yml +2 -0
  13. package/sdk-core/.github/workflows/per-pr.yml +50 -18
  14. package/sdk-core/ARCHITECTURE.md +44 -48
  15. package/sdk-core/Cargo.toml +26 -7
  16. package/sdk-core/README.md +4 -0
  17. package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
  18. package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
  19. package/sdk-core/arch_docs/sdks_intro.md +299 -0
  20. package/sdk-core/client/Cargo.toml +8 -7
  21. package/sdk-core/client/src/callback_based.rs +1 -2
  22. package/sdk-core/client/src/lib.rs +485 -299
  23. package/sdk-core/client/src/metrics.rs +32 -8
  24. package/sdk-core/client/src/proxy.rs +124 -5
  25. package/sdk-core/client/src/raw.rs +598 -307
  26. package/sdk-core/client/src/replaceable.rs +253 -0
  27. package/sdk-core/client/src/retry.rs +9 -6
  28. package/sdk-core/client/src/worker_registry/mod.rs +19 -3
  29. package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
  30. package/sdk-core/core/Cargo.toml +100 -31
  31. package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
  32. package/sdk-core/core/src/core_tests/mod.rs +2 -8
  33. package/sdk-core/core/src/core_tests/queries.rs +3 -5
  34. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
  35. package/sdk-core/core/src/core_tests/updates.rs +4 -5
  36. package/sdk-core/core/src/core_tests/workers.rs +4 -3
  37. package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
  38. package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
  39. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
  40. package/sdk-core/core/src/internal_flags.rs +11 -1
  41. package/sdk-core/core/src/lib.rs +50 -36
  42. package/sdk-core/core/src/pollers/mod.rs +5 -5
  43. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  44. package/sdk-core/core/src/protosext/mod.rs +13 -5
  45. package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
  46. package/sdk-core/core/src/retry_logic.rs +256 -108
  47. package/sdk-core/core/src/telemetry/metrics.rs +1 -0
  48. package/sdk-core/core/src/telemetry/mod.rs +8 -2
  49. package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
  50. package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
  51. package/sdk-core/core/src/test_help/mod.rs +10 -1100
  52. package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
  53. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
  55. package/sdk-core/core/src/worker/activities.rs +10 -3
  56. package/sdk-core/core/src/worker/client/mocks.rs +3 -3
  57. package/sdk-core/core/src/worker/client.rs +130 -93
  58. package/sdk-core/core/src/worker/heartbeat.rs +12 -13
  59. package/sdk-core/core/src/worker/mod.rs +31 -21
  60. package/sdk-core/core/src/worker/nexus.rs +14 -3
  61. package/sdk-core/core/src/worker/slot_provider.rs +9 -0
  62. package/sdk-core/core/src/worker/tuner.rs +159 -0
  63. package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
  64. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
  65. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
  66. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
  67. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
  68. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
  69. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
  70. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
  71. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
  72. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
  73. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
  74. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
  75. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
  76. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
  77. package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
  78. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
  79. package/sdk-core/core-api/Cargo.toml +4 -4
  80. package/sdk-core/core-api/src/envconfig.rs +153 -54
  81. package/sdk-core/core-api/src/lib.rs +68 -0
  82. package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
  83. package/sdk-core/core-api/src/telemetry.rs +13 -0
  84. package/sdk-core/core-c-bridge/Cargo.toml +13 -8
  85. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
  86. package/sdk-core/core-c-bridge/src/client.rs +462 -184
  87. package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
  88. package/sdk-core/core-c-bridge/src/lib.rs +1 -0
  89. package/sdk-core/core-c-bridge/src/random.rs +4 -4
  90. package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
  91. package/sdk-core/core-c-bridge/src/testing.rs +1 -4
  92. package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
  93. package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
  94. package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
  95. package/sdk-core/core-c-bridge/src/worker.rs +319 -66
  96. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
  97. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
  98. package/sdk-core/sdk/Cargo.toml +8 -2
  99. package/sdk-core/sdk/src/activity_context.rs +1 -1
  100. package/sdk-core/sdk/src/app_data.rs +1 -1
  101. package/sdk-core/sdk/src/interceptors.rs +1 -4
  102. package/sdk-core/sdk/src/lib.rs +1 -5
  103. package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
  104. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  105. package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
  106. package/sdk-core/sdk-core-protos/build.rs +10 -23
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
  115. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  116. package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
  117. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
  118. package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
  119. package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
  120. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
  121. package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
  122. package/sdk-core/tests/cloud_tests.rs +10 -8
  123. package/sdk-core/tests/common/http_proxy.rs +134 -0
  124. package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
  125. package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
  126. package/sdk-core/tests/fuzzy_workflow.rs +1 -1
  127. package/sdk-core/tests/global_metric_tests.rs +8 -7
  128. package/sdk-core/tests/heavy_tests.rs +7 -3
  129. package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
  130. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
  131. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
  132. package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
  133. package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
  134. package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
  135. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
  136. package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
  137. package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
  138. package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
  139. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
  140. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
  141. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
  142. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
  143. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
  144. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
  145. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
  146. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
  147. package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
  148. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
  149. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
  150. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
  151. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
  152. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
  153. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
  154. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
  155. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
  156. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
  157. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
  158. package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
  159. package/sdk-core/tests/main.rs +26 -17
  160. package/sdk-core/tests/manual_tests.rs +5 -1
  161. package/sdk-core/tests/runner.rs +22 -40
  162. package/sdk-core/tests/shared_tests/mod.rs +1 -1
  163. package/sdk-core/tests/shared_tests/priority.rs +1 -1
  164. package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
  165. package/src/client.rs +97 -20
  166. package/src/helpers/callbacks.rs +4 -4
  167. package/src/helpers/errors.rs +7 -1
  168. package/src/helpers/handles.rs +1 -0
  169. package/src/helpers/try_from_js.rs +4 -3
  170. package/src/lib.rs +3 -2
  171. package/src/metrics.rs +3 -0
  172. package/src/runtime.rs +5 -2
  173. package/src/worker.rs +9 -12
  174. package/ts/native.ts +13 -3
  175. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
  176. package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
  177. package/sdk-core/core/src/core_tests/determinism.rs +0 -318
  178. package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
  179. package/sdk-core/test-utils/Cargo.toml +0 -38
  180. package/sdk-core/test-utils/src/histfetch.rs +0 -28
  181. package/sdk-core/test-utils/src/interceptors.rs +0 -46
@@ -1,1103 +1,13 @@
1
- pub(crate) use temporal_sdk_core_test_utils::canned_histories;
1
+ #![allow(missing_docs)]
2
2
 
3
- use crate::{
4
- TaskToken, Worker, WorkerConfig, WorkerConfigBuilder,
5
- pollers::{BoxedPoller, MockManualPoller, MockPoller},
6
- protosext::ValidPollWFTQResponse,
7
- replay::TestHistoryBuilder,
8
- sticky_q_name_for_worker,
9
- worker::{
10
- TaskPollers,
11
- client::{
12
- LegacyQueryResult, MockWorkerClient, WorkerClient, WorkflowTaskCompletion,
13
- mocks::mock_worker_client,
14
- },
15
- },
16
- };
17
- use async_trait::async_trait;
18
- use bimap::BiMap;
19
- use futures_util::{FutureExt, Stream, StreamExt, future::BoxFuture, stream, stream::BoxStream};
20
- use mockall::TimesRange;
21
- use parking_lot::RwLock;
22
- use std::{
23
- collections::{BTreeMap, HashMap, HashSet, VecDeque},
24
- fmt::Debug,
25
- ops::{Deref, DerefMut},
26
- pin::Pin,
27
- sync::{
28
- Arc,
29
- atomic::{AtomicBool, Ordering},
30
- },
31
- task::{Context, Poll},
32
- time::Duration,
33
- };
34
- use temporal_sdk::interceptors::FailOnNondeterminismInterceptor;
35
- use temporal_sdk_core_api::{
36
- Worker as WorkerTrait,
37
- errors::PollError,
38
- worker::{PollerBehavior, WorkerVersioningStrategy},
39
- };
40
- use temporal_sdk_core_protos::{
41
- coresdk::{
42
- workflow_activation::{WorkflowActivation, workflow_activation_job},
43
- workflow_commands::workflow_command,
44
- workflow_completion::{self, WorkflowActivationCompletion, workflow_activation_completion},
45
- },
46
- temporal::api::{
47
- common::v1::WorkflowExecution,
48
- enums::v1::WorkflowTaskFailedCause,
49
- failure::v1::Failure,
50
- protocol,
51
- protocol::v1::message,
52
- update,
53
- workflowservice::v1::{
54
- PollActivityTaskQueueResponse, PollNexusTaskQueueResponse,
55
- PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse,
56
- },
57
- },
58
- utilities::pack_any,
59
- };
60
- use temporal_sdk_core_test_utils::{NAMESPACE, TestWorker};
61
- use tokio::sync::{Notify, mpsc::unbounded_channel};
62
- use tokio_stream::wrappers::UnboundedReceiverStream;
3
+ // Re-export unit test helpers (cfg(test) only)
4
+ #[cfg(test)]
5
+ pub use unit_helpers::*;
63
6
 
64
- pub(crate) const TEST_Q: &str = "q";
7
+ #[cfg(any(feature = "test-utilities", test))]
8
+ pub use integ_helpers::*;
65
9
 
66
- pub(crate) fn test_worker_cfg() -> WorkerConfigBuilder {
67
- let mut wcb = WorkerConfigBuilder::default();
68
- wcb.namespace(NAMESPACE)
69
- .task_queue(TEST_Q)
70
- .versioning_strategy(WorkerVersioningStrategy::None {
71
- build_id: "test_bin_id".to_string(),
72
- })
73
- .ignore_evicts_on_shutdown(true)
74
- // Serial polling since it makes mocking much easier.
75
- .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize));
76
- wcb
77
- }
78
-
79
- /// When constructing responses for mocks, indicates how a given response should be built
80
- #[derive(derive_more::From)]
81
- #[allow(clippy::large_enum_variant)] // Test only code, whatever.
82
- pub(crate) enum ResponseType {
83
- ToTaskNum(usize),
84
- /// Returns just the history from the WFT completed of the provided task number - 1, through to
85
- /// the next WFT started. Simulating the incremental history for just the provided task number
86
- #[from(ignore)]
87
- OneTask(usize),
88
- /// Waits until the future resolves before responding as `ToTaskNum` with the provided number
89
- UntilResolved(BoxFuture<'static, ()>, usize),
90
- /// Waits until the future resolves before responding with the provided response
91
- UntilResolvedRaw(BoxFuture<'static, ()>, PollWorkflowTaskQueueResponse),
92
- AllHistory,
93
- Raw(PollWorkflowTaskQueueResponse),
94
- }
95
-
96
- #[derive(Eq, PartialEq, Hash)]
97
- pub(crate) enum HashableResponseType {
98
- ToTaskNum(usize),
99
- OneTask(usize),
100
- UntilResolved(usize),
101
- UntilResolvedRaw(TaskToken),
102
- AllHistory,
103
- Raw(TaskToken),
104
- }
105
-
106
- impl ResponseType {
107
- fn hashable(&self) -> HashableResponseType {
108
- match self {
109
- ResponseType::ToTaskNum(x) => HashableResponseType::ToTaskNum(*x),
110
- ResponseType::OneTask(x) => HashableResponseType::OneTask(*x),
111
- ResponseType::AllHistory => HashableResponseType::AllHistory,
112
- ResponseType::Raw(r) => HashableResponseType::Raw(r.task_token.clone().into()),
113
- ResponseType::UntilResolved(_, x) => HashableResponseType::UntilResolved(*x),
114
- ResponseType::UntilResolvedRaw(_, r) => {
115
- HashableResponseType::UntilResolvedRaw(r.task_token.clone().into())
116
- }
117
- }
118
- }
119
- }
120
-
121
- impl From<&usize> for ResponseType {
122
- fn from(u: &usize) -> Self {
123
- Self::ToTaskNum(*u)
124
- }
125
- }
126
-
127
- /// Given identifiers for a workflow/run, and a test history builder, construct an instance of
128
- /// the a worker with a mock server client that will produce the responses as appropriate.
129
- ///
130
- /// `response_batches` is used to control the fake [PollWorkflowTaskQueueResponse]s returned. For
131
- /// each number in the input list, a fake response will be prepared which includes history up to the
132
- /// workflow task with that number, as in [TestHistoryBuilder::get_history_info].
133
- pub(crate) fn build_fake_worker(
134
- wf_id: &str,
135
- t: TestHistoryBuilder,
136
- response_batches: impl IntoIterator<Item = impl Into<ResponseType>>,
137
- ) -> Worker {
138
- let response_batches = response_batches.into_iter().map(Into::into).collect();
139
- let mock_holder = build_multihist_mock_sg(
140
- vec![FakeWfResponses {
141
- wf_id: wf_id.to_owned(),
142
- hist: t,
143
- response_batches,
144
- }],
145
- true,
146
- 0,
147
- );
148
- mock_worker(mock_holder)
149
- }
150
-
151
- pub(crate) fn build_fake_sdk(mock_cfg: MockPollCfg) -> temporal_sdk::Worker {
152
- let mut mock = build_mock_pollers(mock_cfg);
153
- mock.worker_cfg(|c| {
154
- c.max_cached_workflows = 1;
155
- c.ignore_evicts_on_shutdown = false;
156
- });
157
- let core = mock_worker(mock);
158
- let mut worker = temporal_sdk::Worker::new_from_core(Arc::new(core), "replay_q".to_string());
159
- worker.set_worker_interceptor(FailOnNondeterminismInterceptor {});
160
- worker
161
- }
162
-
163
- pub(crate) fn mock_worker(mocks: MocksHolder) -> Worker {
164
- let sticky_q = sticky_q_name_for_worker("unit-test", &mocks.inputs.config);
165
- let act_poller = if mocks.inputs.config.no_remote_activities {
166
- None
167
- } else {
168
- mocks.inputs.act_poller
169
- };
170
- Worker::new_with_pollers(
171
- mocks.inputs.config,
172
- sticky_q,
173
- mocks.client,
174
- TaskPollers::Mocked {
175
- wft_stream: mocks.inputs.wft_stream,
176
- act_poller,
177
- nexus_poller: mocks
178
- .inputs
179
- .nexus_poller
180
- .unwrap_or_else(|| mock_poller_from_resps([])),
181
- },
182
- None,
183
- None,
184
- )
185
- }
186
-
187
- pub(crate) fn mock_sdk(poll_cfg: MockPollCfg) -> TestWorker {
188
- mock_sdk_cfg(poll_cfg, |_| {})
189
- }
190
- pub(crate) fn mock_sdk_cfg(
191
- mut poll_cfg: MockPollCfg,
192
- mutator: impl FnOnce(&mut WorkerConfig),
193
- ) -> TestWorker {
194
- poll_cfg.using_rust_sdk = true;
195
- let mut mock = build_mock_pollers(poll_cfg);
196
- mock.worker_cfg(mutator);
197
- let core = mock_worker(mock);
198
- TestWorker::new(Arc::new(core), TEST_Q.to_string())
199
- }
200
-
201
- pub(crate) struct FakeWfResponses {
202
- pub(crate) wf_id: String,
203
- pub(crate) hist: TestHistoryBuilder,
204
- pub(crate) response_batches: Vec<ResponseType>,
205
- }
206
-
207
- // TODO: Should be all-internal to this module
208
- pub(crate) struct MocksHolder {
209
- client: Arc<dyn WorkerClient>,
210
- inputs: MockWorkerInputs,
211
- pub(crate) outstanding_task_map: Option<OutstandingWFTMap>,
212
- }
213
-
214
- impl MocksHolder {
215
- pub(crate) fn worker_cfg(&mut self, mutator: impl FnOnce(&mut WorkerConfig)) {
216
- mutator(&mut self.inputs.config);
217
- }
218
-
219
- pub(crate) fn set_act_poller(&mut self, poller: BoxedPoller<PollActivityTaskQueueResponse>) {
220
- self.inputs.act_poller = Some(poller);
221
- }
222
-
223
- /// Can be used for tests that need to avoid auto-shutdown due to running out of mock responses
224
- pub(crate) fn make_wft_stream_interminable(&mut self) {
225
- let old_stream = std::mem::replace(&mut self.inputs.wft_stream, stream::pending().boxed());
226
- self.inputs.wft_stream = old_stream.chain(stream::pending()).boxed();
227
- }
228
- }
229
-
230
- pub(crate) struct MockWorkerInputs {
231
- pub(crate) wft_stream: BoxStream<'static, Result<ValidPollWFTQResponse, tonic::Status>>,
232
- pub(crate) act_poller: Option<BoxedPoller<PollActivityTaskQueueResponse>>,
233
- pub(crate) nexus_poller: Option<BoxedPoller<PollNexusTaskQueueResponse>>,
234
- pub(crate) config: WorkerConfig,
235
- }
236
-
237
- impl Default for MockWorkerInputs {
238
- fn default() -> Self {
239
- Self::new(stream::pending().boxed())
240
- }
241
- }
242
-
243
- impl MockWorkerInputs {
244
- pub(crate) fn new(
245
- wft_stream: BoxStream<'static, Result<ValidPollWFTQResponse, tonic::Status>>,
246
- ) -> Self {
247
- Self {
248
- wft_stream,
249
- act_poller: None,
250
- nexus_poller: None,
251
- config: test_worker_cfg().build().unwrap(),
252
- }
253
- }
254
- }
255
-
256
- impl MocksHolder {
257
- pub(crate) fn from_mock_worker(
258
- client: impl WorkerClient + 'static,
259
- mock_worker: MockWorkerInputs,
260
- ) -> Self {
261
- Self {
262
- client: Arc::new(client),
263
- inputs: mock_worker,
264
- outstanding_task_map: None,
265
- }
266
- }
267
-
268
- /// Uses the provided list of tasks to create a mock poller for the `TEST_Q`
269
- pub(crate) fn from_client_with_activities<ACT>(
270
- client: impl WorkerClient + 'static,
271
- act_tasks: ACT,
272
- ) -> Self
273
- where
274
- ACT: IntoIterator<Item = QueueResponse<PollActivityTaskQueueResponse>>,
275
- <ACT as IntoIterator>::IntoIter: Send + 'static,
276
- {
277
- let wft_stream = stream::pending().boxed();
278
- let mock_act_poller = mock_poller_from_resps(act_tasks);
279
- let mock_worker = MockWorkerInputs {
280
- wft_stream,
281
- act_poller: Some(mock_act_poller),
282
- nexus_poller: None,
283
- config: test_worker_cfg().build().unwrap(),
284
- };
285
- Self {
286
- client: Arc::new(client),
287
- inputs: mock_worker,
288
- outstanding_task_map: None,
289
- }
290
- }
291
-
292
- /// Uses the provided task responses and delivers them as quickly as possible when polled.
293
- /// This is only useful to test buffering, as typically you do not want to pretend that
294
- /// the server is delivering WFTs super fast for the same run.
295
- pub(crate) fn from_wft_stream(
296
- client: impl WorkerClient + 'static,
297
- stream: impl Stream<Item = PollWorkflowTaskQueueResponse> + Send + 'static,
298
- ) -> Self {
299
- let wft_stream = stream
300
- .map(|r| Ok(r.try_into().expect("Mock responses must be valid work")))
301
- .boxed();
302
- let mock_worker = MockWorkerInputs {
303
- wft_stream,
304
- act_poller: None,
305
- nexus_poller: None,
306
- config: test_worker_cfg().build().unwrap(),
307
- };
308
- Self {
309
- client: Arc::new(client),
310
- inputs: mock_worker,
311
- outstanding_task_map: None,
312
- }
313
- }
314
- }
315
-
316
- // TODO: Un-pub ideally
317
- pub(crate) fn mock_poller_from_resps<T, I>(tasks: I) -> BoxedPoller<T>
318
- where
319
- T: Send + Sync + 'static,
320
- I: IntoIterator<Item = QueueResponse<T>>,
321
- <I as IntoIterator>::IntoIter: Send + 'static,
322
- {
323
- let mut mock_poller = mock_manual_poller();
324
- let mut tasks = tasks.into_iter();
325
- mock_poller.expect_poll().returning(move || {
326
- if let Some(t) = tasks.next() {
327
- async move {
328
- if let Some(f) = t.delay_until {
329
- f.await;
330
- }
331
- Some(Ok(t.resp))
332
- }
333
- .boxed()
334
- } else {
335
- async { None }.boxed()
336
- }
337
- });
338
- Box::new(mock_poller) as BoxedPoller<T>
339
- }
340
-
341
- pub(crate) fn mock_poller<T>() -> MockPoller<T>
342
- where
343
- T: Send + Sync + 'static,
344
- {
345
- let mut mock_poller = MockPoller::new();
346
- mock_poller.expect_shutdown_box().return_const(());
347
- mock_poller.expect_notify_shutdown().return_const(());
348
- mock_poller
349
- }
350
-
351
- pub(crate) fn mock_manual_poller<T>() -> MockManualPoller<T>
352
- where
353
- T: Send + Sync + 'static,
354
- {
355
- let mut mock_poller = MockManualPoller::new();
356
- mock_poller
357
- .expect_shutdown_box()
358
- .returning(|| async {}.boxed());
359
- mock_poller.expect_notify_shutdown().return_const(());
360
- mock_poller
361
- }
362
-
363
- /// Build a mock server client capable of returning multiple different histories for different
364
- /// workflows. It does so by tracking outstanding workflow tasks like is also happening in core
365
- /// (which is unfortunately a bit redundant, we could provide hooks in core but that feels a little
366
- /// nasty). If there is an outstanding task for a given workflow, new chunks of its history are not
367
- /// returned. If there is not, the next batch of history is returned for any workflow without an
368
- /// outstanding task. Outstanding tasks are cleared on completion, failure, or eviction.
369
- ///
370
- /// `num_expected_fails` can be provided to set a specific number of expected failed workflow tasks
371
- /// sent to the server.
372
- pub(crate) fn build_multihist_mock_sg(
373
- hists: impl IntoIterator<Item = FakeWfResponses>,
374
- enforce_correct_number_of_polls: bool,
375
- num_expected_fails: usize,
376
- ) -> MocksHolder {
377
- let mh = MockPollCfg::new(
378
- hists.into_iter().collect(),
379
- enforce_correct_number_of_polls,
380
- num_expected_fails,
381
- );
382
- build_mock_pollers(mh)
383
- }
384
-
385
- /// See [build_multihist_mock_sg] -- one history convenience version
386
- pub(crate) fn single_hist_mock_sg(
387
- wf_id: &str,
388
- t: TestHistoryBuilder,
389
- response_batches: impl IntoIterator<Item = impl Into<ResponseType>>,
390
- mock_client: MockWorkerClient,
391
- enforce_num_polls: bool,
392
- ) -> MocksHolder {
393
- let mut mh = MockPollCfg::from_resp_batches(wf_id, t, response_batches, mock_client);
394
- mh.enforce_correct_number_of_polls = enforce_num_polls;
395
- build_mock_pollers(mh)
396
- }
397
-
398
- type WFTCompletionMockFn = dyn FnMut(&WorkflowTaskCompletion) -> Result<RespondWorkflowTaskCompletedResponse, tonic::Status>
399
- + Send;
400
-
401
- #[allow(clippy::type_complexity)]
402
- pub(crate) struct MockPollCfg {
403
- pub(crate) hists: Vec<FakeWfResponses>,
404
- pub(crate) enforce_correct_number_of_polls: bool,
405
- pub(crate) num_expected_fails: usize,
406
- pub(crate) num_expected_legacy_query_resps: usize,
407
- pub(crate) mock_client: MockWorkerClient,
408
- /// All calls to fail WFTs must match this predicate
409
- pub(crate) expect_fail_wft_matcher:
410
- Box<dyn Fn(&TaskToken, &WorkflowTaskFailedCause, &Option<Failure>) -> bool + Send>,
411
- /// All calls to legacy query responses must match this predicate
412
- pub(crate) expect_legacy_query_matcher:
413
- Box<dyn Fn(&TaskToken, &LegacyQueryResult) -> bool + Send>,
414
- pub(crate) completion_mock_fn: Option<Box<WFTCompletionMockFn>>,
415
- pub(crate) num_expected_completions: Option<TimesRange>,
416
- /// If being used with the Rust SDK, this is set true. It ensures pollers will not error out
417
- /// early with no work, since we cannot know the exact number of times polling will happen.
418
- /// Instead, they will just block forever.
419
- pub(crate) using_rust_sdk: bool,
420
- pub(crate) make_poll_stream_interminable: bool,
421
- }
422
-
423
- impl MockPollCfg {
424
- pub(crate) fn new(
425
- hists: Vec<FakeWfResponses>,
426
- enforce_correct_number_of_polls: bool,
427
- num_expected_fails: usize,
428
- ) -> Self {
429
- Self {
430
- hists,
431
- enforce_correct_number_of_polls,
432
- num_expected_fails,
433
- num_expected_legacy_query_resps: 0,
434
- mock_client: mock_worker_client(),
435
- expect_fail_wft_matcher: Box::new(|_, _, _| true),
436
- expect_legacy_query_matcher: Box::new(|_, _| true),
437
- completion_mock_fn: None,
438
- num_expected_completions: None,
439
- using_rust_sdk: false,
440
- make_poll_stream_interminable: false,
441
- }
442
- }
443
-
444
- /// Builds a config which will hand out each WFT in the history builder one by one
445
- pub(crate) fn from_hist_builder(t: TestHistoryBuilder) -> Self {
446
- let full_hist_info = t.get_full_history_info().unwrap();
447
- let tasks = 1..=full_hist_info.wf_task_count();
448
- Self::from_resp_batches("fake_wf_id", t, tasks, mock_worker_client())
449
- }
450
-
451
- pub(crate) fn from_resps(
452
- t: TestHistoryBuilder,
453
- resps: impl IntoIterator<Item = impl Into<ResponseType>>,
454
- ) -> Self {
455
- Self::from_resp_batches("fake_wf_id", t, resps, mock_worker_client())
456
- }
457
-
458
- pub(crate) fn from_resp_batches(
459
- wf_id: &str,
460
- t: TestHistoryBuilder,
461
- resps: impl IntoIterator<Item = impl Into<ResponseType>>,
462
- mock_client: MockWorkerClient,
463
- ) -> Self {
464
- Self {
465
- hists: vec![FakeWfResponses {
466
- wf_id: wf_id.to_owned(),
467
- hist: t,
468
- response_batches: resps.into_iter().map(Into::into).collect(),
469
- }],
470
- enforce_correct_number_of_polls: true,
471
- num_expected_fails: 0,
472
- num_expected_legacy_query_resps: 0,
473
- mock_client,
474
- expect_fail_wft_matcher: Box::new(|_, _, _| true),
475
- expect_legacy_query_matcher: Box::new(|_, _| true),
476
- completion_mock_fn: None,
477
- num_expected_completions: None,
478
- using_rust_sdk: false,
479
- make_poll_stream_interminable: false,
480
- }
481
- }
482
-
483
- pub(crate) fn completion_asserts_from_expectations(
484
- &mut self,
485
- builder_fn: impl FnOnce(CompletionAssertsBuilder<'_>),
486
- ) {
487
- let builder = CompletionAssertsBuilder {
488
- dest: &mut self.completion_mock_fn,
489
- assertions: Default::default(),
490
- };
491
- builder_fn(builder);
492
- }
493
- }
494
-
495
- #[allow(clippy::type_complexity)]
496
- pub(crate) struct CompletionAssertsBuilder<'a> {
497
- dest: &'a mut Option<Box<WFTCompletionMockFn>>,
498
- assertions: VecDeque<Box<dyn FnOnce(&WorkflowTaskCompletion) + Send>>,
499
- }
500
-
501
- impl CompletionAssertsBuilder<'_> {
502
- pub(crate) fn then(
503
- &mut self,
504
- assert: impl FnOnce(&WorkflowTaskCompletion) + Send + 'static,
505
- ) -> &mut Self {
506
- self.assertions.push_back(Box::new(assert));
507
- self
508
- }
509
- }
510
-
511
- impl Drop for CompletionAssertsBuilder<'_> {
512
- fn drop(&mut self) {
513
- let mut asserts = std::mem::take(&mut self.assertions);
514
- *self.dest = Some(Box::new(move |wtc| {
515
- if let Some(fun) = asserts.pop_front() {
516
- fun(wtc);
517
- }
518
- Ok(Default::default())
519
- }));
520
- }
521
- }
522
-
523
- #[derive(Default, Clone)]
524
- pub(crate) struct OutstandingWFTMap {
525
- map: Arc<RwLock<BiMap<String, TaskToken>>>,
526
- waker: Arc<Notify>,
527
- all_work_delivered: Arc<AtomicBool>,
528
- }
529
-
530
- impl OutstandingWFTMap {
531
- fn has_run(&self, run_id: &str) -> bool {
532
- self.map.read().contains_left(run_id)
533
- }
534
-
535
- fn put_token(&self, run_id: String, token: TaskToken) {
536
- self.map.write().insert(run_id, token);
537
- }
538
-
539
- fn release_token(&self, token: &TaskToken) {
540
- self.map.write().remove_by_right(token);
541
- self.waker.notify_one();
542
- }
543
-
544
- pub(crate) fn release_run(&self, run_id: &str) {
545
- self.map.write().remove_by_left(run_id);
546
- self.waker.notify_waiters();
547
- }
548
-
549
- pub(crate) fn all_work_delivered(&self) -> bool {
550
- self.all_work_delivered.load(Ordering::Acquire)
551
- }
552
- }
553
-
554
- struct EnsuresWorkDoneWFTStream {
555
- inner: UnboundedReceiverStream<ValidPollWFTQResponse>,
556
- all_work_was_completed: Arc<AtomicBool>,
557
- }
558
- impl Stream for EnsuresWorkDoneWFTStream {
559
- type Item = ValidPollWFTQResponse;
560
-
561
- fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
562
- self.inner.poll_next_unpin(cx)
563
- }
564
- }
565
- impl Drop for EnsuresWorkDoneWFTStream {
566
- fn drop(&mut self) {
567
- if !self.all_work_was_completed.load(Ordering::Acquire) && !std::thread::panicking() {
568
- panic!("Not all workflow tasks were taken from mock!");
569
- }
570
- }
571
- }
572
-
573
- /// Given an iterable of fake responses, return the mocks & associated data to work with them
574
- pub(crate) fn build_mock_pollers(mut cfg: MockPollCfg) -> MocksHolder {
575
- let mut task_q_resps: BTreeMap<String, VecDeque<_>> = BTreeMap::new();
576
- let all_work_delivered = if cfg.enforce_correct_number_of_polls && !cfg.using_rust_sdk {
577
- Arc::new(AtomicBool::new(false))
578
- } else {
579
- Arc::new(AtomicBool::new(true))
580
- };
581
-
582
- let outstanding_wf_task_tokens = OutstandingWFTMap {
583
- map: Arc::new(Default::default()),
584
- waker: Arc::new(Default::default()),
585
- all_work_delivered: all_work_delivered.clone(),
586
- };
587
-
588
- for hist in cfg.hists {
589
- let full_hist_info = hist.hist.get_full_history_info().unwrap();
590
- // Ensure no response batch is trying to return more tasks than the history contains
591
- for respt in &hist.response_batches {
592
- if let ResponseType::ToTaskNum(rb_wf_num) = respt {
593
- assert!(
594
- *rb_wf_num <= full_hist_info.wf_task_count(),
595
- "Wf task count {} is not <= total task count {}",
596
- rb_wf_num,
597
- full_hist_info.wf_task_count()
598
- );
599
- }
600
- }
601
-
602
- // Convert history batches into poll responses, while also tracking how many times a given
603
- // history has been returned so we can increment the associated attempt number on the WFT.
604
- // NOTE: This is hard to use properly with the `AfterEveryReply` testing eviction mode.
605
- // Such usages need a history different from other eviction modes which would include
606
- // WFT timeouts or something to simulate the task getting dropped.
607
- let mut attempts_at_task_num = HashMap::new();
608
- let responses: Vec<_> = hist
609
- .response_batches
610
- .into_iter()
611
- .map(|response| {
612
- let cur_attempt = attempts_at_task_num.entry(response.hashable()).or_insert(1);
613
- let mut r = hist_to_poll_resp(&hist.hist, hist.wf_id.clone(), response);
614
- r.attempt = *cur_attempt;
615
- *cur_attempt += 1;
616
- r
617
- })
618
- .collect();
619
-
620
- let tasks = VecDeque::from(responses);
621
- task_q_resps.insert(hist.wf_id, tasks);
622
- }
623
-
624
- // The poller will return history from any workflow runs that do not have currently
625
- // outstanding tasks.
626
- let outstanding = outstanding_wf_task_tokens.clone();
627
- let outstanding_wakeup = outstanding.waker.clone();
628
- let (wft_tx, wft_rx) = unbounded_channel();
629
- tokio::task::spawn(async move {
630
- loop {
631
- let mut resp = None;
632
- let mut resp_iter = task_q_resps.iter_mut();
633
- for (_, tasks) in &mut resp_iter {
634
- // Must extract run id from a workflow task associated with this workflow
635
- // TODO: Case where run id changes for same workflow id is not handled here
636
- if let Some(t) = tasks.front() {
637
- let rid = t.workflow_execution.as_ref().unwrap().run_id.clone();
638
- if !outstanding.has_run(&rid) {
639
- let t = tasks.pop_front().unwrap();
640
- outstanding.put_token(rid, TaskToken(t.task_token.clone()));
641
- resp = Some(t);
642
- break;
643
- }
644
- }
645
- }
646
- let no_tasks_for_anyone = resp_iter.next().is_none();
647
-
648
- if let Some(resp) = resp {
649
- if let Some(d) = resp.delay_until {
650
- d.await;
651
- }
652
- if wft_tx
653
- .send(
654
- resp.resp
655
- .try_into()
656
- .expect("Mock responses must be valid work"),
657
- )
658
- .is_err()
659
- {
660
- dbg!("Exiting mock WFT task because rcv half of stream was dropped");
661
- break;
662
- }
663
- }
664
-
665
- // No more work to do
666
- if task_q_resps.values().all(|q| q.is_empty()) {
667
- outstanding
668
- .all_work_delivered
669
- .store(true, Ordering::Release);
670
- break;
671
- }
672
-
673
- if no_tasks_for_anyone {
674
- tokio::select! {
675
- _ = outstanding_wakeup.notified() => {}
676
- _ = tokio::time::sleep(Duration::from_secs(60)) => {}
677
- }
678
- }
679
- }
680
- });
681
- let mock_worker = MockWorkerInputs::new(
682
- EnsuresWorkDoneWFTStream {
683
- inner: UnboundedReceiverStream::new(wft_rx),
684
- all_work_was_completed: all_work_delivered,
685
- }
686
- .map(Ok)
687
- .boxed(),
688
- );
689
-
690
- let outstanding = outstanding_wf_task_tokens.clone();
691
- let expect_completes = cfg.mock_client.expect_complete_workflow_task();
692
- if let Some(range) = cfg.num_expected_completions {
693
- expect_completes.times(range);
694
- } else if cfg.completion_mock_fn.is_some() {
695
- expect_completes.times(1..);
696
- }
697
- expect_completes.returning(move |comp| {
698
- let r = if let Some(ass) = cfg.completion_mock_fn.as_mut() {
699
- // tee hee
700
- ass(&comp)
701
- } else {
702
- Ok(RespondWorkflowTaskCompletedResponse::default())
703
- };
704
- outstanding.release_token(&comp.task_token);
705
- r
706
- });
707
- let outstanding = outstanding_wf_task_tokens.clone();
708
- cfg.mock_client
709
- .expect_fail_workflow_task()
710
- .withf(cfg.expect_fail_wft_matcher)
711
- .times::<TimesRange>(cfg.num_expected_fails.into())
712
- .returning(move |tt, _, _| {
713
- outstanding.release_token(&tt);
714
- Ok(Default::default())
715
- });
716
- let outstanding = outstanding_wf_task_tokens.clone();
717
- cfg.mock_client
718
- .expect_respond_legacy_query()
719
- .withf(cfg.expect_legacy_query_matcher)
720
- .times::<TimesRange>(cfg.num_expected_legacy_query_resps.into())
721
- .returning(move |tt, _| {
722
- outstanding.release_token(&tt);
723
- Ok(Default::default())
724
- });
725
-
726
- let mut mh = MocksHolder {
727
- client: Arc::new(cfg.mock_client),
728
- inputs: mock_worker,
729
- outstanding_task_map: Some(outstanding_wf_task_tokens),
730
- };
731
- if cfg.make_poll_stream_interminable {
732
- mh.make_wft_stream_interminable();
733
- }
734
- mh
735
- }
736
-
737
- pub(crate) struct QueueResponse<T> {
738
- pub(crate) resp: T,
739
- pub(crate) delay_until: Option<BoxFuture<'static, ()>>,
740
- }
741
-
742
- impl<T> From<T> for QueueResponse<T> {
743
- fn from(resp: T) -> Self {
744
- QueueResponse {
745
- resp,
746
- delay_until: None,
747
- }
748
- }
749
- }
750
- impl From<QueueResponse<PollWorkflowTaskQueueResponse>> for ResponseType {
751
- fn from(qr: QueueResponse<PollWorkflowTaskQueueResponse>) -> Self {
752
- if let Some(du) = qr.delay_until {
753
- ResponseType::UntilResolvedRaw(du, qr.resp)
754
- } else {
755
- ResponseType::Raw(qr.resp)
756
- }
757
- }
758
- }
759
- impl<T> Deref for QueueResponse<T> {
760
- type Target = T;
761
-
762
- fn deref(&self) -> &Self::Target {
763
- &self.resp
764
- }
765
- }
766
- impl<T> DerefMut for QueueResponse<T> {
767
- fn deref_mut(&mut self) -> &mut Self::Target {
768
- &mut self.resp
769
- }
770
- }
771
- impl<T> Debug for QueueResponse<T>
772
- where
773
- T: Debug,
774
- {
775
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
776
- self.resp.fmt(f)
777
- }
778
- }
779
-
780
- pub(crate) trait PollWFTRespExt {
781
- /// Add an update request to the poll response, using the update name "update_fn" and no args.
782
- /// Returns the inner request body.
783
- fn add_update_request(
784
- &mut self,
785
- update_id: impl ToString,
786
- after_event_id: i64,
787
- ) -> update::v1::Request;
788
- }
789
-
790
- impl PollWFTRespExt for PollWorkflowTaskQueueResponse {
791
- fn add_update_request(
792
- &mut self,
793
- update_id: impl ToString,
794
- after_event_id: i64,
795
- ) -> update::v1::Request {
796
- let upd_req_body = update::v1::Request {
797
- meta: Some(update::v1::Meta {
798
- update_id: update_id.to_string(),
799
- identity: "agent_id".to_string(),
800
- }),
801
- input: Some(update::v1::Input {
802
- header: None,
803
- name: "update_fn".to_string(),
804
- args: None,
805
- }),
806
- };
807
- self.messages.push(protocol::v1::Message {
808
- id: format!("update-{}", update_id.to_string()),
809
- protocol_instance_id: update_id.to_string(),
810
- body: Some(
811
- pack_any(
812
- "type.googleapis.com/temporal.api.update.v1.Request".to_string(),
813
- &upd_req_body,
814
- )
815
- .unwrap(),
816
- ),
817
- sequencing_id: Some(message::SequencingId::EventId(after_event_id)),
818
- });
819
- upd_req_body
820
- }
821
- }
822
-
823
- pub(crate) fn hist_to_poll_resp(
824
- t: &TestHistoryBuilder,
825
- wf_id: impl Into<String>,
826
- response_type: ResponseType,
827
- ) -> QueueResponse<PollWorkflowTaskQueueResponse> {
828
- let run_id = t.get_orig_run_id();
829
- let wf = WorkflowExecution {
830
- workflow_id: wf_id.into(),
831
- run_id: run_id.to_string(),
832
- };
833
- let mut delay_until = None;
834
- let hist_info = match response_type {
835
- ResponseType::ToTaskNum(tn) => t.get_history_info(tn).unwrap(),
836
- ResponseType::OneTask(tn) => t.get_one_wft(tn).unwrap(),
837
- ResponseType::AllHistory => t.get_full_history_info().unwrap(),
838
- ResponseType::Raw(r) => {
839
- return QueueResponse {
840
- resp: r,
841
- delay_until: None,
842
- };
843
- }
844
- ResponseType::UntilResolved(fut, tn) => {
845
- delay_until = Some(fut);
846
- t.get_history_info(tn).unwrap()
847
- }
848
- ResponseType::UntilResolvedRaw(fut, r) => {
849
- return QueueResponse {
850
- resp: r,
851
- delay_until: Some(fut),
852
- };
853
- }
854
- };
855
- let mut resp = hist_info.as_poll_wft_response();
856
- resp.workflow_execution = Some(wf);
857
- QueueResponse { resp, delay_until }
858
- }
859
-
860
- type AsserterWithReply<'a> = (
861
- &'a dyn Fn(&WorkflowActivation),
862
- workflow_activation_completion::Status,
863
- );
864
-
865
- /// Determines when workflows are kept in the cache or evicted for [poll_and_reply] type tests
866
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
867
- pub(crate) enum WorkflowCachingPolicy {
868
- /// Workflows are evicted after each workflow task completion. Note that this is *not* after
869
- /// each workflow activation - there are often multiple activations per workflow task.
870
- NonSticky,
871
-
872
- /// Not a real mode, but good for imitating crashes. Evict workflows after *every* reply,
873
- /// even if there are pending activations
874
- #[cfg(test)]
875
- AfterEveryReply,
876
- }
877
-
878
- /// This function accepts a list of asserts and replies to workflow activations to run against the
879
- /// provided instance of fake core.
880
- ///
881
- /// It handles the business of re-sending the same activation replies over again in the event
882
- /// of eviction or workflow activation failure. Activation failures specifically are only run once,
883
- /// since they clearly can't be returned every time we replay the workflow, or it could never
884
- /// proceed
885
- pub(crate) async fn poll_and_reply<'a>(
886
- worker: &'a Worker,
887
- eviction_mode: WorkflowCachingPolicy,
888
- expect_and_reply: &'a [AsserterWithReply<'a>],
889
- ) {
890
- poll_and_reply_clears_outstanding_evicts(worker, None, eviction_mode, expect_and_reply).await;
891
- }
892
-
893
- pub(crate) async fn poll_and_reply_clears_outstanding_evicts<'a>(
894
- worker: &'a Worker,
895
- outstanding_map: Option<OutstandingWFTMap>,
896
- eviction_mode: WorkflowCachingPolicy,
897
- expect_and_reply: &'a [AsserterWithReply<'a>],
898
- ) {
899
- let mut evictions = 0;
900
- let expected_evictions = expect_and_reply.len() - 1;
901
- let mut executed_failures = HashSet::new();
902
- let expected_fail_count = expect_and_reply
903
- .iter()
904
- .filter(|(_, reply)| !reply.is_success())
905
- .count();
906
-
907
- 'outer: loop {
908
- let expect_iter = expect_and_reply.iter();
909
-
910
- for (i, interaction) in expect_iter.enumerate() {
911
- let (asserter, reply) = interaction;
912
- let complete_is_failure = !reply.is_success();
913
- // Only send activation failures once
914
- if executed_failures.contains(&i) {
915
- continue;
916
- }
917
-
918
- let mut res = worker.poll_workflow_activation().await.unwrap();
919
- if res.jobs.iter().any(|j| {
920
- matches!(
921
- j.variant,
922
- Some(workflow_activation_job::Variant::RemoveFromCache(_))
923
- )
924
- }) && res.jobs.len() > 1
925
- {
926
- panic!("Saw an activation with an eviction & other work! {res:?}");
927
- }
928
- let is_eviction = res.is_only_eviction();
929
-
930
- let mut do_release = false;
931
-
932
- if is_eviction {
933
- // If the job is an eviction, clear it, since in the tests we don't explicitly
934
- // specify evict assertions
935
- res.jobs.clear();
936
- do_release = true;
937
- }
938
-
939
- // TODO: Can remove this if?
940
- if !res.jobs.is_empty() {
941
- asserter(&res);
942
- }
943
-
944
- let reply = if res.jobs.is_empty() {
945
- // Just an eviction
946
- WorkflowActivationCompletion::empty(res.run_id.clone())
947
- } else {
948
- // Eviction plus some work, we still want to issue the reply
949
- WorkflowActivationCompletion {
950
- run_id: res.run_id.clone(),
951
- status: Some(reply.clone()),
952
- }
953
- };
954
-
955
- let ends_execution = reply.has_execution_ending();
956
-
957
- worker.complete_workflow_activation(reply).await.unwrap();
958
-
959
- if do_release && let Some(omap) = outstanding_map.as_ref() {
960
- omap.release_run(&res.run_id);
961
- }
962
- // Restart assertions from the beginning if it was an eviction (and workflow execution
963
- // isn't over)
964
- if is_eviction && !ends_execution {
965
- continue 'outer;
966
- }
967
-
968
- if complete_is_failure {
969
- executed_failures.insert(i);
970
- }
971
-
972
- match eviction_mode {
973
- WorkflowCachingPolicy::NonSticky => (),
974
- WorkflowCachingPolicy::AfterEveryReply => {
975
- if evictions < expected_evictions {
976
- worker.request_workflow_eviction(&res.run_id);
977
- evictions += 1;
978
- }
979
- }
980
- }
981
- }
982
-
983
- break;
984
- }
985
-
986
- assert_eq!(expected_fail_count, executed_failures.len());
987
- assert_eq!(worker.outstanding_workflow_tasks().await, 0);
988
- }
989
-
990
- pub(crate) fn gen_assert_and_reply(
991
- asserter: &dyn Fn(&WorkflowActivation),
992
- reply_commands: Vec<workflow_command::Variant>,
993
- ) -> AsserterWithReply<'_> {
994
- (
995
- asserter,
996
- workflow_completion::Success::from_variants(reply_commands).into(),
997
- )
998
- }
999
-
1000
- pub(crate) fn gen_assert_and_fail(asserter: &dyn Fn(&WorkflowActivation)) -> AsserterWithReply<'_> {
1001
- (
1002
- asserter,
1003
- workflow_completion::Failure {
1004
- failure: Some(Failure {
1005
- message: "Intentional test failure".to_string(),
1006
- ..Default::default()
1007
- }),
1008
- ..Default::default()
1009
- }
1010
- .into(),
1011
- )
1012
- }
1013
-
1014
- /// Generate asserts for [poll_and_reply] by passing patterns to match against the job list
1015
- #[macro_export]
1016
- macro_rules! job_assert {
1017
- ($($pat:pat),+) => {
1018
- |res| {
1019
- assert_matches!(
1020
- res.jobs.as_slice(),
1021
- [$(WorkflowActivationJob {
1022
- variant: Some($pat),
1023
- }),+]
1024
- );
1025
- }
1026
- };
1027
- }
1028
-
1029
- /// Forcibly drive a future a number of times, enforcing it is always returning Pending. This is
1030
- /// useful for ensuring some future has proceeded "enough" before racing it against another future.
1031
- #[macro_export]
1032
- macro_rules! advance_fut {
1033
- ($fut:ident) => {
1034
- ::futures_util::pin_mut!($fut);
1035
- {
1036
- let waker = ::futures_util::task::noop_waker();
1037
- let mut cx = core::task::Context::from_waker(&waker);
1038
- for _ in 0..10 {
1039
- assert_matches!($fut.poll_unpin(&mut cx), core::task::Poll::Pending);
1040
- ::tokio::task::yield_now().await;
1041
- }
1042
- }
1043
- };
1044
- }
1045
-
1046
- /// Helps easily construct prost proto durations from stdlib duration constructors
1047
- #[macro_export]
1048
- macro_rules! prost_dur {
1049
- ($dur_call:ident $args:tt) => {
1050
- std::time::Duration::$dur_call$args
1051
- .try_into()
1052
- .expect("test duration fits")
1053
- };
1054
- }
1055
-
1056
- #[async_trait]
1057
- pub(crate) trait WorkerExt {
1058
- /// Initiate shutdown, drain the pollers, and wait for shutdown to complete.
1059
- async fn drain_pollers_and_shutdown(self);
1060
- /// Initiate shutdown, drain the *activity* poller, and wait for shutdown to complete.
1061
- /// Takes a ref because of that one test that needs it.
1062
- async fn drain_activity_poller_and_shutdown(&self);
1063
- }
1064
-
1065
- #[async_trait]
1066
- impl WorkerExt for Worker {
1067
- async fn drain_pollers_and_shutdown(self) {
1068
- self.initiate_shutdown();
1069
- tokio::join!(
1070
- async {
1071
- assert_matches!(
1072
- self.poll_activity_task().await.unwrap_err(),
1073
- PollError::ShutDown
1074
- );
1075
- },
1076
- async {
1077
- loop {
1078
- match self.poll_workflow_activation().await {
1079
- Err(PollError::ShutDown) => break,
1080
- Ok(a) if a.is_only_eviction() => {
1081
- self.complete_workflow_activation(WorkflowActivationCompletion::empty(
1082
- a.run_id,
1083
- ))
1084
- .await
1085
- .unwrap();
1086
- }
1087
- o => panic!("Unexpected activation while draining: {o:?}"),
1088
- }
1089
- }
1090
- }
1091
- );
1092
- self.finalize_shutdown().await;
1093
- }
1094
-
1095
- async fn drain_activity_poller_and_shutdown(&self) {
1096
- self.initiate_shutdown();
1097
- assert_matches!(
1098
- self.poll_activity_task().await.unwrap_err(),
1099
- PollError::ShutDown
1100
- );
1101
- self.shutdown().await;
1102
- }
1103
- }
10
+ #[cfg(any(feature = "test-utilities", test))]
11
+ mod integ_helpers;
12
+ #[cfg(test)]
13
+ mod unit_helpers;