@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
@@ -0,0 +1,971 @@
1
+ //! Integration test helpers - available with test-utilities feature or in test mode
2
+
3
+ #![allow(missing_docs)]
4
+
5
+ pub use crate::{
6
+ internal_flags::CoreInternalFlags,
7
+ worker::{LEGACY_QUERY_ID, client::mocks::mock_worker_client},
8
+ };
9
+
10
+ use crate::{
11
+ TaskToken, Worker, WorkerConfig, WorkerConfigBuilder,
12
+ pollers::{BoxedPoller, MockManualPoller},
13
+ protosext::ValidPollWFTQResponse,
14
+ replay::TestHistoryBuilder,
15
+ sticky_q_name_for_worker,
16
+ worker::{
17
+ TaskPollers,
18
+ client::{LegacyQueryResult, MockWorkerClient, WorkerClient, WorkflowTaskCompletion},
19
+ },
20
+ };
21
+ use assert_matches::assert_matches;
22
+ use async_trait::async_trait;
23
+ use bimap::BiMap;
24
+ use futures_util::{FutureExt, Stream, StreamExt, future::BoxFuture, stream, stream::BoxStream};
25
+ use mockall::TimesRange;
26
+ use parking_lot::RwLock;
27
+ use std::{
28
+ collections::{BTreeMap, HashMap, VecDeque},
29
+ fmt::Debug,
30
+ ops::{Deref, DerefMut},
31
+ pin::Pin,
32
+ sync::{
33
+ Arc,
34
+ atomic::{AtomicBool, Ordering},
35
+ },
36
+ task::{Context, Poll},
37
+ time::Duration,
38
+ };
39
+ use temporal_sdk_core_api::{
40
+ Worker as WorkerTrait,
41
+ errors::PollError,
42
+ worker::{PollerBehavior, WorkerVersioningStrategy},
43
+ };
44
+ use temporal_sdk_core_protos::{
45
+ coresdk::{
46
+ workflow_activation::{WorkflowActivationJob, workflow_activation_job},
47
+ workflow_commands::{CompleteWorkflowExecution, StartTimer},
48
+ workflow_completion::WorkflowActivationCompletion,
49
+ },
50
+ temporal::api::{
51
+ common::v1::WorkflowExecution,
52
+ enums::v1::WorkflowTaskFailedCause,
53
+ failure::v1::Failure,
54
+ protocol::{self, v1::message},
55
+ update,
56
+ workflowservice::v1::{
57
+ PollActivityTaskQueueResponse, PollNexusTaskQueueResponse,
58
+ PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse,
59
+ },
60
+ },
61
+ utilities::pack_any,
62
+ };
63
+ use tokio::sync::{Notify, mpsc::unbounded_channel};
64
+ use tokio_stream::wrappers::UnboundedReceiverStream;
65
+
66
+ /// Default namespace for testing
67
+ pub const NAMESPACE: &str = "default";
68
+
69
+ /// Default task queue for testing
70
+ pub const TEST_Q: &str = "q";
71
+
72
+ /// Initiate shutdown, drain the pollers (handling evictions), and wait for shutdown to complete.
73
+ pub async fn drain_pollers_and_shutdown(worker: &dyn WorkerTrait) {
74
+ worker.initiate_shutdown();
75
+ tokio::join!(
76
+ async {
77
+ assert_matches!(
78
+ worker.poll_activity_task().await.unwrap_err(),
79
+ PollError::ShutDown
80
+ );
81
+ },
82
+ async {
83
+ loop {
84
+ match worker.poll_workflow_activation().await {
85
+ Err(PollError::ShutDown) => break,
86
+ Ok(a) if a.is_only_eviction() => {
87
+ worker
88
+ .complete_workflow_activation(WorkflowActivationCompletion::empty(
89
+ a.run_id,
90
+ ))
91
+ .await
92
+ .unwrap();
93
+ }
94
+ o => panic!("Unexpected activation while draining: {o:?}"),
95
+ }
96
+ }
97
+ }
98
+ );
99
+ worker.shutdown().await;
100
+ }
101
+
102
+ pub fn test_worker_cfg() -> WorkerConfigBuilder {
103
+ let mut wcb = WorkerConfigBuilder::default();
104
+ wcb.namespace(NAMESPACE)
105
+ .task_queue(TEST_Q)
106
+ .versioning_strategy(WorkerVersioningStrategy::None {
107
+ build_id: "test_bin_id".to_string(),
108
+ })
109
+ .ignore_evicts_on_shutdown(true)
110
+ // Serial polling since it makes mocking much easier.
111
+ .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize));
112
+ wcb
113
+ }
114
+
115
+ /// When constructing responses for mocks, indicates how a given response should be built
116
+ #[derive(derive_more::From)]
117
+ #[allow(clippy::large_enum_variant)] // Test only code, whatever.
118
+ pub enum ResponseType {
119
+ ToTaskNum(usize),
120
+ /// Returns just the history from the WFT completed of the provided task number - 1, through to
121
+ /// the next WFT started. Simulating the incremental history for just the provided task number
122
+ #[from(ignore)]
123
+ OneTask(usize),
124
+ /// Waits until the future resolves before responding as `ToTaskNum` with the provided number
125
+ UntilResolved(BoxFuture<'static, ()>, usize),
126
+ /// Waits until the future resolves before responding with the provided response
127
+ UntilResolvedRaw(BoxFuture<'static, ()>, PollWorkflowTaskQueueResponse),
128
+ AllHistory,
129
+ Raw(PollWorkflowTaskQueueResponse),
130
+ }
131
+
132
+ #[derive(Eq, PartialEq, Hash)]
133
+ pub enum HashableResponseType {
134
+ ToTaskNum(usize),
135
+ OneTask(usize),
136
+ UntilResolved(usize),
137
+ UntilResolvedRaw(TaskToken),
138
+ AllHistory,
139
+ Raw(TaskToken),
140
+ }
141
+
142
+ impl ResponseType {
143
+ fn hashable(&self) -> HashableResponseType {
144
+ match self {
145
+ ResponseType::ToTaskNum(x) => HashableResponseType::ToTaskNum(*x),
146
+ ResponseType::OneTask(x) => HashableResponseType::OneTask(*x),
147
+ ResponseType::AllHistory => HashableResponseType::AllHistory,
148
+ ResponseType::Raw(r) => HashableResponseType::Raw(r.task_token.clone().into()),
149
+ ResponseType::UntilResolved(_, x) => HashableResponseType::UntilResolved(*x),
150
+ ResponseType::UntilResolvedRaw(_, r) => {
151
+ HashableResponseType::UntilResolvedRaw(r.task_token.clone().into())
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ impl From<&usize> for ResponseType {
158
+ fn from(u: &usize) -> Self {
159
+ Self::ToTaskNum(*u)
160
+ }
161
+ }
162
+
163
+ /// Given identifiers for a workflow/run, and a test history builder, construct an instance of
164
+ /// the a worker with a mock server client that will produce the responses as appropriate.
165
+ ///
166
+ /// `response_batches` is used to control the fake [PollWorkflowTaskQueueResponse]s returned. For
167
+ /// each number in the input list, a fake response will be prepared which includes history up to the
168
+ /// workflow task with that number, as in [TestHistoryBuilder::get_history_info].
169
+ pub fn build_fake_worker(
170
+ wf_id: &str,
171
+ t: TestHistoryBuilder,
172
+ response_batches: impl IntoIterator<Item = impl Into<ResponseType>>,
173
+ ) -> Worker {
174
+ let response_batches = response_batches.into_iter().map(Into::into).collect();
175
+ let mock_holder = build_multihist_mock_sg(
176
+ vec![FakeWfResponses {
177
+ wf_id: wf_id.to_owned(),
178
+ hist: t,
179
+ response_batches,
180
+ }],
181
+ true,
182
+ 0,
183
+ );
184
+ mock_worker(mock_holder)
185
+ }
186
+
187
+ pub fn mock_worker(mocks: MocksHolder) -> Worker {
188
+ let sticky_q = sticky_q_name_for_worker("unit-test", &mocks.inputs.config);
189
+ let act_poller = if mocks.inputs.config.no_remote_activities {
190
+ None
191
+ } else {
192
+ mocks.inputs.act_poller
193
+ };
194
+ Worker::new_with_pollers(
195
+ mocks.inputs.config,
196
+ sticky_q,
197
+ mocks.client,
198
+ TaskPollers::Mocked {
199
+ wft_stream: mocks.inputs.wft_stream,
200
+ act_poller,
201
+ nexus_poller: mocks
202
+ .inputs
203
+ .nexus_poller
204
+ .unwrap_or_else(|| mock_poller_from_resps([])),
205
+ },
206
+ None,
207
+ None,
208
+ )
209
+ }
210
+
211
+ pub struct FakeWfResponses {
212
+ pub wf_id: String,
213
+ pub hist: TestHistoryBuilder,
214
+ pub response_batches: Vec<ResponseType>,
215
+ }
216
+
217
+ // TODO: Should be all-internal to this module
218
+ pub struct MocksHolder {
219
+ client: Arc<dyn WorkerClient>,
220
+ inputs: MockWorkerInputs,
221
+ pub outstanding_task_map: Option<OutstandingWFTMap>,
222
+ }
223
+
224
+ impl MocksHolder {
225
+ pub fn worker_cfg(&mut self, mutator: impl FnOnce(&mut WorkerConfig)) {
226
+ mutator(&mut self.inputs.config);
227
+ }
228
+
229
+ pub(crate) fn set_act_poller(&mut self, poller: BoxedPoller<PollActivityTaskQueueResponse>) {
230
+ self.inputs.act_poller = Some(poller);
231
+ }
232
+
233
+ /// Can be used for tests that need to avoid auto-shutdown due to running out of mock responses
234
+ pub fn make_wft_stream_interminable(&mut self) {
235
+ let old_stream = std::mem::replace(&mut self.inputs.wft_stream, stream::pending().boxed());
236
+ self.inputs.wft_stream = old_stream.chain(stream::pending()).boxed();
237
+ }
238
+ }
239
+
240
+ pub struct MockWorkerInputs {
241
+ pub(crate) wft_stream: BoxStream<'static, Result<ValidPollWFTQResponse, tonic::Status>>,
242
+ pub(crate) act_poller: Option<BoxedPoller<PollActivityTaskQueueResponse>>,
243
+ pub(crate) nexus_poller: Option<BoxedPoller<PollNexusTaskQueueResponse>>,
244
+ pub(crate) config: WorkerConfig,
245
+ }
246
+
247
+ impl Default for MockWorkerInputs {
248
+ fn default() -> Self {
249
+ Self::new(stream::pending().boxed())
250
+ }
251
+ }
252
+
253
+ impl MockWorkerInputs {
254
+ pub(crate) fn new(
255
+ wft_stream: BoxStream<'static, Result<ValidPollWFTQResponse, tonic::Status>>,
256
+ ) -> Self {
257
+ Self {
258
+ wft_stream,
259
+ act_poller: None,
260
+ nexus_poller: None,
261
+ config: test_worker_cfg().build().unwrap(),
262
+ }
263
+ }
264
+ }
265
+
266
+ impl MocksHolder {
267
+ pub fn from_mock_worker(
268
+ client: impl WorkerClient + 'static,
269
+ mock_worker: MockWorkerInputs,
270
+ ) -> Self {
271
+ Self {
272
+ client: Arc::new(client),
273
+ inputs: mock_worker,
274
+ outstanding_task_map: None,
275
+ }
276
+ }
277
+
278
+ /// Uses the provided list of tasks to create a mock poller for the `TEST_Q`
279
+ pub fn from_client_with_activities<ACT>(
280
+ client: impl WorkerClient + 'static,
281
+ act_tasks: ACT,
282
+ ) -> Self
283
+ where
284
+ ACT: IntoIterator<Item = QueueResponse<PollActivityTaskQueueResponse>>,
285
+ <ACT as IntoIterator>::IntoIter: Send + 'static,
286
+ {
287
+ let wft_stream = stream::pending().boxed();
288
+ let mock_act_poller = mock_poller_from_resps(act_tasks);
289
+ let mock_worker = MockWorkerInputs {
290
+ wft_stream,
291
+ act_poller: Some(mock_act_poller),
292
+ nexus_poller: None,
293
+ config: test_worker_cfg().build().unwrap(),
294
+ };
295
+ Self {
296
+ client: Arc::new(client),
297
+ inputs: mock_worker,
298
+ outstanding_task_map: None,
299
+ }
300
+ }
301
+
302
+ /// Uses the provided task responses and delivers them as quickly as possible when polled.
303
+ /// This is only useful to test buffering, as typically you do not want to pretend that
304
+ /// the server is delivering WFTs super fast for the same run.
305
+ pub fn from_wft_stream(
306
+ client: impl WorkerClient + 'static,
307
+ stream: impl Stream<Item = PollWorkflowTaskQueueResponse> + Send + 'static,
308
+ ) -> Self {
309
+ let wft_stream = stream
310
+ .map(|r| Ok(r.try_into().expect("Mock responses must be valid work")))
311
+ .boxed();
312
+ let mock_worker = MockWorkerInputs {
313
+ wft_stream,
314
+ act_poller: None,
315
+ nexus_poller: None,
316
+ config: test_worker_cfg().build().unwrap(),
317
+ };
318
+ Self {
319
+ client: Arc::new(client),
320
+ inputs: mock_worker,
321
+ outstanding_task_map: None,
322
+ }
323
+ }
324
+ }
325
+
326
+ fn mock_poller_from_resps<T, I>(tasks: I) -> BoxedPoller<T>
327
+ where
328
+ T: Send + Sync + 'static,
329
+ I: IntoIterator<Item = QueueResponse<T>>,
330
+ <I as IntoIterator>::IntoIter: Send + 'static,
331
+ {
332
+ let mut mock_poller = mock_manual_poller();
333
+ let mut tasks = tasks.into_iter();
334
+ mock_poller.expect_poll().returning(move || {
335
+ if let Some(t) = tasks.next() {
336
+ async move {
337
+ if let Some(f) = t.delay_until {
338
+ f.await;
339
+ }
340
+ Some(Ok(t.resp))
341
+ }
342
+ .boxed()
343
+ } else {
344
+ async { None }.boxed()
345
+ }
346
+ });
347
+ Box::new(mock_poller) as BoxedPoller<T>
348
+ }
349
+
350
+ pub(crate) fn mock_manual_poller<T>() -> MockManualPoller<T>
351
+ where
352
+ T: Send + Sync + 'static,
353
+ {
354
+ let mut mock_poller = MockManualPoller::new();
355
+ mock_poller
356
+ .expect_shutdown_box()
357
+ .returning(|| async {}.boxed());
358
+ mock_poller.expect_notify_shutdown().return_const(());
359
+ mock_poller
360
+ }
361
+
362
+ /// Build a mock server client capable of returning multiple different histories for different
363
+ /// workflows. It does so by tracking outstanding workflow tasks like is also happening in core
364
+ /// (which is unfortunately a bit redundant, we could provide hooks in core but that feels a little
365
+ /// nasty). If there is an outstanding task for a given workflow, new chunks of its history are not
366
+ /// returned. If there is not, the next batch of history is returned for any workflow without an
367
+ /// outstanding task. Outstanding tasks are cleared on completion, failure, or eviction.
368
+ ///
369
+ /// `num_expected_fails` can be provided to set a specific number of expected failed workflow tasks
370
+ /// sent to the server.
371
+ pub fn build_multihist_mock_sg(
372
+ hists: impl IntoIterator<Item = FakeWfResponses>,
373
+ enforce_correct_number_of_polls: bool,
374
+ num_expected_fails: usize,
375
+ ) -> MocksHolder {
376
+ let mh = MockPollCfg::new(
377
+ hists.into_iter().collect(),
378
+ enforce_correct_number_of_polls,
379
+ num_expected_fails,
380
+ );
381
+ build_mock_pollers(mh)
382
+ }
383
+
384
+ /// See [build_multihist_mock_sg] -- one history convenience version
385
+ pub fn single_hist_mock_sg(
386
+ wf_id: &str,
387
+ t: TestHistoryBuilder,
388
+ response_batches: impl IntoIterator<Item = impl Into<ResponseType>>,
389
+ mock_client: MockWorkerClient,
390
+ enforce_num_polls: bool,
391
+ ) -> MocksHolder {
392
+ let mut mh = MockPollCfg::from_resp_batches(wf_id, t, response_batches, mock_client);
393
+ mh.enforce_correct_number_of_polls = enforce_num_polls;
394
+ build_mock_pollers(mh)
395
+ }
396
+
397
+ type WFTCompletionMockFn = dyn FnMut(&WorkflowTaskCompletion) -> Result<RespondWorkflowTaskCompletedResponse, tonic::Status>
398
+ + Send;
399
+
400
+ #[allow(clippy::type_complexity)]
401
+ pub struct MockPollCfg {
402
+ pub hists: Vec<FakeWfResponses>,
403
+ pub enforce_correct_number_of_polls: bool,
404
+ pub num_expected_fails: usize,
405
+ pub num_expected_legacy_query_resps: usize,
406
+ pub mock_client: MockWorkerClient,
407
+ /// All calls to fail WFTs must match this predicate
408
+ pub expect_fail_wft_matcher:
409
+ Box<dyn Fn(&TaskToken, &WorkflowTaskFailedCause, &Option<Failure>) -> bool + Send>,
410
+ /// All calls to legacy query responses must match this predicate
411
+ pub expect_legacy_query_matcher: Box<dyn Fn(&TaskToken, &LegacyQueryResult) -> bool + Send>,
412
+ pub completion_mock_fn: Option<Box<WFTCompletionMockFn>>,
413
+ pub num_expected_completions: Option<TimesRange>,
414
+ /// If being used with the Rust SDK, this is set true. It ensures pollers will not error out
415
+ /// early with no work, since we cannot know the exact number of times polling will happen.
416
+ /// Instead, they will just block forever.
417
+ pub using_rust_sdk: bool,
418
+ pub make_poll_stream_interminable: bool,
419
+ /// If set, will create a mock activity poller with the provided responeses. If unset, no poller
420
+ /// is created.
421
+ pub activity_responses: Option<Vec<QueueResponse<PollActivityTaskQueueResponse>>>,
422
+ }
423
+
424
+ impl MockPollCfg {
425
+ pub fn new(
426
+ hists: Vec<FakeWfResponses>,
427
+ enforce_correct_number_of_polls: bool,
428
+ num_expected_fails: usize,
429
+ ) -> Self {
430
+ Self {
431
+ hists,
432
+ enforce_correct_number_of_polls,
433
+ num_expected_fails,
434
+ num_expected_legacy_query_resps: 0,
435
+ mock_client: mock_worker_client(),
436
+ expect_fail_wft_matcher: Box::new(|_, _, _| true),
437
+ expect_legacy_query_matcher: Box::new(|_, _| true),
438
+ completion_mock_fn: None,
439
+ num_expected_completions: None,
440
+ using_rust_sdk: false,
441
+ make_poll_stream_interminable: false,
442
+ activity_responses: None,
443
+ }
444
+ }
445
+
446
+ /// Builds a config which will hand out each WFT in the history builder one by one
447
+ pub fn from_hist_builder(t: TestHistoryBuilder) -> Self {
448
+ let full_hist_info = t.get_full_history_info().unwrap();
449
+ let tasks = 1..=full_hist_info.wf_task_count();
450
+ Self::from_resp_batches("fake_wf_id", t, tasks, mock_worker_client())
451
+ }
452
+
453
+ pub fn from_resps(
454
+ t: TestHistoryBuilder,
455
+ resps: impl IntoIterator<Item = impl Into<ResponseType>>,
456
+ ) -> Self {
457
+ Self::from_resp_batches("fake_wf_id", t, resps, mock_worker_client())
458
+ }
459
+
460
+ pub fn from_resp_batches(
461
+ wf_id: &str,
462
+ t: TestHistoryBuilder,
463
+ resps: impl IntoIterator<Item = impl Into<ResponseType>>,
464
+ mock_client: MockWorkerClient,
465
+ ) -> Self {
466
+ Self {
467
+ hists: vec![FakeWfResponses {
468
+ wf_id: wf_id.to_owned(),
469
+ hist: t,
470
+ response_batches: resps.into_iter().map(Into::into).collect(),
471
+ }],
472
+ enforce_correct_number_of_polls: true,
473
+ num_expected_fails: 0,
474
+ num_expected_legacy_query_resps: 0,
475
+ mock_client,
476
+ expect_fail_wft_matcher: Box::new(|_, _, _| true),
477
+ expect_legacy_query_matcher: Box::new(|_, _| true),
478
+ completion_mock_fn: None,
479
+ num_expected_completions: None,
480
+ using_rust_sdk: false,
481
+ make_poll_stream_interminable: false,
482
+ activity_responses: None,
483
+ }
484
+ }
485
+
486
+ pub fn completion_asserts_from_expectations(
487
+ &mut self,
488
+ builder_fn: impl FnOnce(CompletionAssertsBuilder<'_>),
489
+ ) {
490
+ let builder = CompletionAssertsBuilder {
491
+ dest: &mut self.completion_mock_fn,
492
+ assertions: Default::default(),
493
+ };
494
+ builder_fn(builder);
495
+ }
496
+ }
497
+
498
+ #[allow(clippy::type_complexity)]
499
+ pub struct CompletionAssertsBuilder<'a> {
500
+ dest: &'a mut Option<Box<WFTCompletionMockFn>>,
501
+ assertions: VecDeque<Box<dyn FnOnce(&WorkflowTaskCompletion) + Send>>,
502
+ }
503
+
504
+ impl CompletionAssertsBuilder<'_> {
505
+ pub fn then(
506
+ &mut self,
507
+ assert: impl FnOnce(&WorkflowTaskCompletion) + Send + 'static,
508
+ ) -> &mut Self {
509
+ self.assertions.push_back(Box::new(assert));
510
+ self
511
+ }
512
+ }
513
+
514
+ impl Drop for CompletionAssertsBuilder<'_> {
515
+ fn drop(&mut self) {
516
+ let mut asserts = std::mem::take(&mut self.assertions);
517
+ *self.dest = Some(Box::new(move |wtc| {
518
+ if let Some(fun) = asserts.pop_front() {
519
+ fun(wtc);
520
+ }
521
+ Ok(Default::default())
522
+ }));
523
+ }
524
+ }
525
+
526
+ #[derive(Default, Clone)]
527
+ pub struct OutstandingWFTMap {
528
+ map: Arc<RwLock<BiMap<String, TaskToken>>>,
529
+ waker: Arc<Notify>,
530
+ all_work_delivered: Arc<AtomicBool>,
531
+ }
532
+
533
+ impl OutstandingWFTMap {
534
+ fn has_run(&self, run_id: &str) -> bool {
535
+ self.map.read().contains_left(run_id)
536
+ }
537
+
538
+ fn put_token(&self, run_id: String, token: TaskToken) {
539
+ self.map.write().insert(run_id, token);
540
+ }
541
+
542
+ fn release_token(&self, token: &TaskToken) {
543
+ self.map.write().remove_by_right(token);
544
+ self.waker.notify_one();
545
+ }
546
+
547
+ pub fn release_run(&self, run_id: &str) {
548
+ self.map.write().remove_by_left(run_id);
549
+ self.waker.notify_waiters();
550
+ }
551
+
552
+ pub fn all_work_delivered(&self) -> bool {
553
+ self.all_work_delivered.load(Ordering::Acquire)
554
+ }
555
+ }
556
+
557
+ struct EnsuresWorkDoneWFTStream {
558
+ inner: UnboundedReceiverStream<ValidPollWFTQResponse>,
559
+ all_work_was_completed: Arc<AtomicBool>,
560
+ }
561
+ impl Stream for EnsuresWorkDoneWFTStream {
562
+ type Item = ValidPollWFTQResponse;
563
+
564
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
565
+ self.inner.poll_next_unpin(cx)
566
+ }
567
+ }
568
+ impl Drop for EnsuresWorkDoneWFTStream {
569
+ fn drop(&mut self) {
570
+ if !self.all_work_was_completed.load(Ordering::Acquire) && !std::thread::panicking() {
571
+ panic!("Not all workflow tasks were taken from mock!");
572
+ }
573
+ }
574
+ }
575
+
576
+ /// Given an iterable of fake responses, return the mocks & associated data to work with them
577
+ pub fn build_mock_pollers(mut cfg: MockPollCfg) -> MocksHolder {
578
+ let mut task_q_resps: BTreeMap<String, VecDeque<_>> = BTreeMap::new();
579
+ let all_work_delivered = if cfg.enforce_correct_number_of_polls && !cfg.using_rust_sdk {
580
+ Arc::new(AtomicBool::new(false))
581
+ } else {
582
+ Arc::new(AtomicBool::new(true))
583
+ };
584
+
585
+ let outstanding_wf_task_tokens = OutstandingWFTMap {
586
+ map: Arc::new(Default::default()),
587
+ waker: Arc::new(Default::default()),
588
+ all_work_delivered: all_work_delivered.clone(),
589
+ };
590
+
591
+ for hist in cfg.hists {
592
+ let full_hist_info = hist.hist.get_full_history_info().unwrap();
593
+ // Ensure no response batch is trying to return more tasks than the history contains
594
+ for respt in &hist.response_batches {
595
+ if let ResponseType::ToTaskNum(rb_wf_num) = respt {
596
+ assert!(
597
+ *rb_wf_num <= full_hist_info.wf_task_count(),
598
+ "Wf task count {} is not <= total task count {}",
599
+ rb_wf_num,
600
+ full_hist_info.wf_task_count()
601
+ );
602
+ }
603
+ }
604
+
605
+ // Convert history batches into poll responses, while also tracking how many times a given
606
+ // history has been returned so we can increment the associated attempt number on the WFT.
607
+ // NOTE: This is hard to use properly with the `AfterEveryReply` testing eviction mode.
608
+ // Such usages need a history different from other eviction modes which would include
609
+ // WFT timeouts or something to simulate the task getting dropped.
610
+ let mut attempts_at_task_num = HashMap::new();
611
+ let responses: Vec<_> = hist
612
+ .response_batches
613
+ .into_iter()
614
+ .map(|response| {
615
+ let cur_attempt = attempts_at_task_num.entry(response.hashable()).or_insert(1);
616
+ let mut r = hist_to_poll_resp(&hist.hist, hist.wf_id.clone(), response);
617
+ r.attempt = *cur_attempt;
618
+ *cur_attempt += 1;
619
+ r
620
+ })
621
+ .collect();
622
+
623
+ let tasks = VecDeque::from(responses);
624
+ task_q_resps.insert(hist.wf_id, tasks);
625
+ }
626
+
627
+ // The poller will return history from any workflow runs that do not have currently
628
+ // outstanding tasks.
629
+ let outstanding = outstanding_wf_task_tokens.clone();
630
+ let outstanding_wakeup = outstanding.waker.clone();
631
+ let (wft_tx, wft_rx) = unbounded_channel();
632
+ tokio::task::spawn(async move {
633
+ loop {
634
+ let mut resp = None;
635
+ let mut resp_iter = task_q_resps.iter_mut();
636
+ for (_, tasks) in &mut resp_iter {
637
+ // Must extract run id from a workflow task associated with this workflow
638
+ // TODO: Case where run id changes for same workflow id is not handled here
639
+ if let Some(t) = tasks.front() {
640
+ let rid = t.workflow_execution.as_ref().unwrap().run_id.clone();
641
+ if !outstanding.has_run(&rid) {
642
+ let t = tasks.pop_front().unwrap();
643
+ outstanding.put_token(rid, TaskToken(t.task_token.clone()));
644
+ resp = Some(t);
645
+ break;
646
+ }
647
+ }
648
+ }
649
+ let no_tasks_for_anyone = resp_iter.next().is_none();
650
+
651
+ if let Some(resp) = resp {
652
+ if let Some(d) = resp.delay_until {
653
+ d.await;
654
+ }
655
+ if wft_tx
656
+ .send(
657
+ resp.resp
658
+ .try_into()
659
+ .expect("Mock responses must be valid work"),
660
+ )
661
+ .is_err()
662
+ {
663
+ break;
664
+ }
665
+ }
666
+
667
+ // No more work to do
668
+ if task_q_resps.values().all(|q| q.is_empty()) {
669
+ outstanding
670
+ .all_work_delivered
671
+ .store(true, Ordering::Release);
672
+ break;
673
+ }
674
+
675
+ if no_tasks_for_anyone {
676
+ tokio::select! {
677
+ _ = outstanding_wakeup.notified() => {}
678
+ _ = tokio::time::sleep(Duration::from_secs(60)) => {}
679
+ }
680
+ }
681
+ }
682
+ });
683
+ let mock_worker = MockWorkerInputs::new(
684
+ EnsuresWorkDoneWFTStream {
685
+ inner: UnboundedReceiverStream::new(wft_rx),
686
+ all_work_was_completed: all_work_delivered,
687
+ }
688
+ .map(Ok)
689
+ .boxed(),
690
+ );
691
+
692
+ let outstanding = outstanding_wf_task_tokens.clone();
693
+ let expect_completes = cfg.mock_client.expect_complete_workflow_task();
694
+ if let Some(range) = cfg.num_expected_completions {
695
+ expect_completes.times(range);
696
+ } else if cfg.completion_mock_fn.is_some() {
697
+ expect_completes.times(1..);
698
+ }
699
+ expect_completes.returning(move |comp| {
700
+ let r = if let Some(ass) = cfg.completion_mock_fn.as_mut() {
701
+ // tee hee
702
+ ass(&comp)
703
+ } else {
704
+ Ok(RespondWorkflowTaskCompletedResponse::default())
705
+ };
706
+ outstanding.release_token(&comp.task_token);
707
+ r
708
+ });
709
+ let outstanding = outstanding_wf_task_tokens.clone();
710
+ cfg.mock_client
711
+ .expect_fail_workflow_task()
712
+ .withf(cfg.expect_fail_wft_matcher)
713
+ .times::<TimesRange>(cfg.num_expected_fails.into())
714
+ .returning(move |tt, _, _| {
715
+ outstanding.release_token(&tt);
716
+ Ok(Default::default())
717
+ });
718
+ let outstanding = outstanding_wf_task_tokens.clone();
719
+ cfg.mock_client
720
+ .expect_respond_legacy_query()
721
+ .withf(cfg.expect_legacy_query_matcher)
722
+ .times::<TimesRange>(cfg.num_expected_legacy_query_resps.into())
723
+ .returning(move |tt, _| {
724
+ outstanding.release_token(&tt);
725
+ Ok(Default::default())
726
+ });
727
+
728
+ let mut mh = MocksHolder {
729
+ client: Arc::new(cfg.mock_client),
730
+ inputs: mock_worker,
731
+ outstanding_task_map: Some(outstanding_wf_task_tokens),
732
+ };
733
+ if let Some(activity_responses) = cfg.activity_responses {
734
+ let act_poller = mock_poller_from_resps(activity_responses);
735
+ mh.set_act_poller(act_poller);
736
+ }
737
+ if cfg.make_poll_stream_interminable {
738
+ mh.make_wft_stream_interminable();
739
+ }
740
+ mh
741
+ }
742
+
743
+ pub struct QueueResponse<T> {
744
+ pub resp: T,
745
+ pub delay_until: Option<BoxFuture<'static, ()>>,
746
+ }
747
+
748
+ impl<T> From<T> for QueueResponse<T> {
749
+ fn from(resp: T) -> Self {
750
+ QueueResponse {
751
+ resp,
752
+ delay_until: None,
753
+ }
754
+ }
755
+ }
756
+ impl From<QueueResponse<PollWorkflowTaskQueueResponse>> for ResponseType {
757
+ fn from(qr: QueueResponse<PollWorkflowTaskQueueResponse>) -> Self {
758
+ if let Some(du) = qr.delay_until {
759
+ ResponseType::UntilResolvedRaw(du, qr.resp)
760
+ } else {
761
+ ResponseType::Raw(qr.resp)
762
+ }
763
+ }
764
+ }
765
+ impl<T> Deref for QueueResponse<T> {
766
+ type Target = T;
767
+
768
+ fn deref(&self) -> &Self::Target {
769
+ &self.resp
770
+ }
771
+ }
772
+ impl<T> DerefMut for QueueResponse<T> {
773
+ fn deref_mut(&mut self) -> &mut Self::Target {
774
+ &mut self.resp
775
+ }
776
+ }
777
+ impl<T> Debug for QueueResponse<T>
778
+ where
779
+ T: Debug,
780
+ {
781
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
782
+ self.resp.fmt(f)
783
+ }
784
+ }
785
+
786
+ pub trait PollWFTRespExt {
787
+ /// Add an update request to the poll response, using the update name "update_fn" and no args.
788
+ /// Returns the inner request body.
789
+ fn add_update_request(
790
+ &mut self,
791
+ update_id: impl ToString,
792
+ after_event_id: i64,
793
+ ) -> update::v1::Request;
794
+ }
795
+
796
+ impl PollWFTRespExt for PollWorkflowTaskQueueResponse {
797
+ fn add_update_request(
798
+ &mut self,
799
+ update_id: impl ToString,
800
+ after_event_id: i64,
801
+ ) -> update::v1::Request {
802
+ let upd_req_body = update::v1::Request {
803
+ meta: Some(update::v1::Meta {
804
+ update_id: update_id.to_string(),
805
+ identity: "agent_id".to_string(),
806
+ }),
807
+ input: Some(update::v1::Input {
808
+ header: None,
809
+ name: "update_fn".to_string(),
810
+ args: None,
811
+ }),
812
+ };
813
+ self.messages.push(protocol::v1::Message {
814
+ id: format!("update-{}", update_id.to_string()),
815
+ protocol_instance_id: update_id.to_string(),
816
+ body: Some(
817
+ pack_any(
818
+ "type.googleapis.com/temporal.api.update.v1.Request".to_string(),
819
+ &upd_req_body,
820
+ )
821
+ .unwrap(),
822
+ ),
823
+ sequencing_id: Some(message::SequencingId::EventId(after_event_id)),
824
+ });
825
+ upd_req_body
826
+ }
827
+ }
828
+
829
+ pub fn hist_to_poll_resp(
830
+ t: &TestHistoryBuilder,
831
+ wf_id: impl Into<String>,
832
+ response_type: ResponseType,
833
+ ) -> QueueResponse<PollWorkflowTaskQueueResponse> {
834
+ let run_id = t.get_orig_run_id();
835
+ let wf = WorkflowExecution {
836
+ workflow_id: wf_id.into(),
837
+ run_id: run_id.to_string(),
838
+ };
839
+ let mut delay_until = None;
840
+ let hist_info = match response_type {
841
+ ResponseType::ToTaskNum(tn) => t.get_history_info(tn).unwrap(),
842
+ ResponseType::OneTask(tn) => t.get_one_wft(tn).unwrap(),
843
+ ResponseType::AllHistory => t.get_full_history_info().unwrap(),
844
+ ResponseType::Raw(r) => {
845
+ return QueueResponse {
846
+ resp: r,
847
+ delay_until: None,
848
+ };
849
+ }
850
+ ResponseType::UntilResolved(fut, tn) => {
851
+ delay_until = Some(fut);
852
+ t.get_history_info(tn).unwrap()
853
+ }
854
+ ResponseType::UntilResolvedRaw(fut, r) => {
855
+ return QueueResponse {
856
+ resp: r,
857
+ delay_until: Some(fut),
858
+ };
859
+ }
860
+ };
861
+ let mut resp = hist_info.as_poll_wft_response();
862
+ resp.workflow_execution = Some(wf);
863
+ QueueResponse { resp, delay_until }
864
+ }
865
+
866
+ /// Forcibly drive a future a number of times, enforcing it is always returning Pending. This is
867
+ /// useful for ensuring some future has proceeded "enough" before racing it against another future.
868
+ #[macro_export]
869
+ macro_rules! advance_fut {
870
+ ($fut:ident) => {
871
+ ::futures_util::pin_mut!($fut);
872
+ {
873
+ let waker = ::futures_util::task::noop_waker();
874
+ let mut cx = core::task::Context::from_waker(&waker);
875
+ for _ in 0..10 {
876
+ assert_matches!($fut.poll_unpin(&mut cx), core::task::Poll::Pending);
877
+ ::tokio::task::yield_now().await;
878
+ }
879
+ }
880
+ };
881
+ }
882
+
883
+ /// Helps easily construct prost proto durations from stdlib duration constructors
884
+ #[macro_export]
885
+ macro_rules! prost_dur {
886
+ ($dur_call:ident $args:tt) => {
887
+ std::time::Duration::$dur_call$args
888
+ .try_into()
889
+ .expect("test duration fits")
890
+ };
891
+ }
892
+
893
+ #[async_trait]
894
+ pub trait WorkerExt {
895
+ /// Initiate shutdown, drain the pollers, and wait for shutdown to complete.
896
+ async fn drain_pollers_and_shutdown(self);
897
+ /// Initiate shutdown, drain the *activity* poller, and wait for shutdown to complete.
898
+ /// Takes a ref because of that one test that needs it.
899
+ async fn drain_activity_poller_and_shutdown(&self);
900
+ }
901
+
902
+ #[async_trait]
903
+ impl WorkerExt for Worker {
904
+ async fn drain_pollers_and_shutdown(self) {
905
+ drain_pollers_and_shutdown(&self).await;
906
+ self.finalize_shutdown().await;
907
+ }
908
+
909
+ async fn drain_activity_poller_and_shutdown(&self) {
910
+ self.initiate_shutdown();
911
+ assert_matches!(
912
+ self.poll_activity_task().await.unwrap_err(),
913
+ PollError::ShutDown
914
+ );
915
+ self.shutdown().await;
916
+ }
917
+ }
918
+
919
+ #[async_trait::async_trait]
920
+ /// Test helper methods for core workers
921
+ pub trait WorkerTestHelpers {
922
+ /// Complete a workflow execution
923
+ async fn complete_execution(&self, run_id: &str);
924
+ /// Complete a timer with the given sequence number and duration
925
+ async fn complete_timer(&self, run_id: &str, seq: u32, duration: Duration);
926
+ /// Handle workflow eviction from cache
927
+ async fn handle_eviction(&self);
928
+ }
929
+
930
+ #[async_trait::async_trait]
931
+ impl<T> WorkerTestHelpers for T
932
+ where
933
+ T: WorkerTrait + ?Sized,
934
+ {
935
+ async fn complete_execution(&self, run_id: &str) {
936
+ self.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
937
+ run_id.to_string(),
938
+ vec![CompleteWorkflowExecution { result: None }.into()],
939
+ ))
940
+ .await
941
+ .unwrap();
942
+ }
943
+
944
+ async fn complete_timer(&self, run_id: &str, seq: u32, duration: Duration) {
945
+ self.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
946
+ run_id.to_string(),
947
+ vec![
948
+ StartTimer {
949
+ seq,
950
+ start_to_fire_timeout: Some(duration.try_into().expect("duration fits")),
951
+ }
952
+ .into(),
953
+ ],
954
+ ))
955
+ .await
956
+ .unwrap();
957
+ }
958
+
959
+ async fn handle_eviction(&self) {
960
+ let task = self.poll_workflow_activation().await.unwrap();
961
+ assert_matches!(
962
+ task.jobs.as_slice(),
963
+ [WorkflowActivationJob {
964
+ variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
965
+ }]
966
+ );
967
+ self.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
968
+ .await
969
+ .unwrap();
970
+ }
971
+ }