@temporalio/core-bridge 1.5.2 → 1.7.0
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.
- package/Cargo.lock +304 -112
- package/lib/index.d.ts +8 -6
- package/lib/index.js.map +1 -1
- package/package.json +9 -4
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.buildkite/docker/Dockerfile +2 -2
- package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
- package/sdk-core/.buildkite/pipeline.yml +2 -4
- package/sdk-core/.cargo/config.toml +5 -2
- package/sdk-core/.github/workflows/heavy.yml +29 -0
- package/sdk-core/Cargo.toml +1 -1
- package/sdk-core/README.md +20 -10
- package/sdk-core/client/src/lib.rs +215 -39
- package/sdk-core/client/src/metrics.rs +17 -8
- package/sdk-core/client/src/raw.rs +4 -4
- package/sdk-core/client/src/retry.rs +32 -20
- package/sdk-core/core/Cargo.toml +25 -12
- package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
- package/sdk-core/core/src/abstractions.rs +204 -14
- package/sdk-core/core/src/core_tests/activity_tasks.rs +143 -50
- package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
- package/sdk-core/core/src/core_tests/determinism.rs +165 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +431 -43
- package/sdk-core/core/src/core_tests/queries.rs +34 -16
- package/sdk-core/core/src/core_tests/workers.rs +8 -5
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +588 -55
- package/sdk-core/core/src/ephemeral_server/mod.rs +113 -12
- package/sdk-core/core/src/internal_flags.rs +155 -0
- package/sdk-core/core/src/lib.rs +16 -9
- package/sdk-core/core/src/protosext/mod.rs +1 -1
- package/sdk-core/core/src/replay/mod.rs +16 -27
- package/sdk-core/core/src/telemetry/log_export.rs +1 -1
- package/sdk-core/core/src/telemetry/metrics.rs +69 -35
- package/sdk-core/core/src/telemetry/mod.rs +60 -21
- package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
- package/sdk-core/core/src/test_help/mod.rs +73 -14
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
- package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
- package/sdk-core/core/src/worker/activities/local_activities.rs +379 -129
- package/sdk-core/core/src/worker/activities.rs +350 -175
- package/sdk-core/core/src/worker/client/mocks.rs +22 -2
- package/sdk-core/core/src/worker/client.rs +18 -2
- package/sdk-core/core/src/worker/mod.rs +183 -64
- package/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
- package/sdk-core/core/src/worker/workflow/history_update.rs +916 -277
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +216 -183
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +9 -12
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +7 -9
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +160 -87
- package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +13 -14
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +7 -9
- package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +14 -17
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +242 -110
- package/sdk-core/core/src/worker/workflow/machines/mod.rs +27 -19
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +9 -11
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +321 -206
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +13 -18
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +20 -29
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +257 -51
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +310 -150
- package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +17 -20
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +31 -15
- package/sdk-core/core/src/worker/workflow/managed_run.rs +1052 -380
- package/sdk-core/core/src/worker/workflow/mod.rs +598 -390
- package/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
- package/sdk-core/core/src/worker/workflow/wft_extraction.rs +137 -0
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
- package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +469 -718
- package/sdk-core/core-api/Cargo.toml +2 -1
- package/sdk-core/core-api/src/errors.rs +1 -34
- package/sdk-core/core-api/src/lib.rs +19 -9
- package/sdk-core/core-api/src/telemetry.rs +4 -6
- package/sdk-core/core-api/src/worker.rs +19 -1
- package/sdk-core/etc/deps.svg +115 -140
- package/sdk-core/etc/regen-depgraph.sh +5 -0
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +86 -61
- package/sdk-core/fsm/rustfsm_trait/src/lib.rs +29 -71
- package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
- package/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
- package/sdk-core/histories/old_change_marker_format.bin +0 -0
- package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
- package/sdk-core/protos/api_upstream/Makefile +6 -6
- package/sdk-core/protos/api_upstream/build/go.mod +7 -0
- package/sdk-core/protos/api_upstream/build/go.sum +5 -0
- package/sdk-core/protos/api_upstream/build/tools.go +29 -0
- package/sdk-core/protos/api_upstream/go.mod +6 -0
- package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +9 -2
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -26
- package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +3 -7
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +8 -8
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +25 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +24 -19
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +49 -26
- package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +4 -2
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +5 -2
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +57 -0
- package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
- package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +71 -6
- package/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -28
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -4
- package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
- package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
- package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
- package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +67 -60
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
- package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
- package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
- package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
- package/sdk-core/sdk/Cargo.toml +5 -4
- package/sdk-core/sdk/src/lib.rs +108 -26
- package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
- package/sdk-core/sdk/src/workflow_context.rs +24 -17
- package/sdk-core/sdk/src/workflow_future.rs +16 -15
- package/sdk-core/sdk-core-protos/Cargo.toml +5 -2
- package/sdk-core/sdk-core-protos/build.rs +36 -2
- package/sdk-core/sdk-core-protos/src/history_builder.rs +138 -106
- package/sdk-core/sdk-core-protos/src/history_info.rs +10 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +272 -87
- package/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
- package/sdk-core/test-utils/Cargo.toml +3 -1
- package/sdk-core/test-utils/src/canned_histories.rs +106 -296
- package/sdk-core/test-utils/src/histfetch.rs +1 -1
- package/sdk-core/test-utils/src/lib.rs +82 -23
- package/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
- package/sdk-core/test-utils/src/workflows.rs +29 -0
- package/sdk-core/tests/fuzzy_workflow.rs +130 -0
- package/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +10 -5
- package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
- package/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
- package/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
- package/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +161 -72
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +94 -200
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +34 -28
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +76 -7
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +7 -8
- package/sdk-core/tests/integ_tests/workflow_tests.rs +13 -14
- package/sdk-core/tests/main.rs +3 -13
- package/sdk-core/tests/runner.rs +75 -36
- package/sdk-core/tests/wf_input_replay.rs +32 -0
- package/src/conversions.rs +14 -8
- package/src/runtime.rs +9 -8
- package/ts/index.ts +8 -6
- package/sdk-core/bridge-ffi/Cargo.toml +0 -24
- package/sdk-core/bridge-ffi/LICENSE.txt +0 -23
- package/sdk-core/bridge-ffi/build.rs +0 -25
- package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -224
- package/sdk-core/bridge-ffi/src/lib.rs +0 -746
- package/sdk-core/bridge-ffi/src/wrappers.rs +0 -221
- package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
- package/sdk-core/sdk/src/conversions.rs +0 -8
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
2
2
|
use temporal_client::{ClientOptionsBuilder, TestService, WorkflowService};
|
|
3
3
|
use temporal_sdk_core::ephemeral_server::{
|
|
4
|
-
EphemeralExe, EphemeralExeVersion, EphemeralServer,
|
|
5
|
-
TestServerConfigBuilder,
|
|
4
|
+
EphemeralExe, EphemeralExeVersion, EphemeralServer, TemporalDevServerConfigBuilder,
|
|
5
|
+
TemporaliteConfigBuilder, TestServerConfigBuilder,
|
|
6
6
|
};
|
|
7
7
|
use temporal_sdk_core_protos::temporal::api::workflowservice::v1::DescribeNamespaceRequest;
|
|
8
8
|
use temporal_sdk_core_test_utils::{default_cached_download, NAMESPACE};
|
|
9
9
|
use url::Url;
|
|
10
10
|
|
|
11
|
+
#[tokio::test]
|
|
12
|
+
async fn temporal_cli_default() {
|
|
13
|
+
let config = TemporalDevServerConfigBuilder::default()
|
|
14
|
+
.exe(default_cached_download())
|
|
15
|
+
.build()
|
|
16
|
+
.unwrap();
|
|
17
|
+
let mut server = config.start_server().await.unwrap();
|
|
18
|
+
assert_ephemeral_server(&server).await;
|
|
19
|
+
server.shutdown().await.unwrap();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[tokio::test]
|
|
23
|
+
async fn temporal_cli_fixed() {
|
|
24
|
+
let config = TemporalDevServerConfigBuilder::default()
|
|
25
|
+
.exe(fixed_cached_download("v0.4.0"))
|
|
26
|
+
.build()
|
|
27
|
+
.unwrap();
|
|
28
|
+
let mut server = config.start_server().await.unwrap();
|
|
29
|
+
assert_ephemeral_server(&server).await;
|
|
30
|
+
server.shutdown().await.unwrap();
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
#[tokio::test]
|
|
12
34
|
async fn temporalite_default() {
|
|
13
35
|
let config = TemporaliteConfigBuilder::default()
|
|
@@ -22,7 +44,7 @@ async fn temporalite_default() {
|
|
|
22
44
|
#[tokio::test]
|
|
23
45
|
async fn temporalite_fixed() {
|
|
24
46
|
let config = TemporaliteConfigBuilder::default()
|
|
25
|
-
.exe(fixed_cached_download("v0.
|
|
47
|
+
.exe(fixed_cached_download("v0.2.0"))
|
|
26
48
|
.build()
|
|
27
49
|
.unwrap();
|
|
28
50
|
let mut server = config.start_server().await.unwrap();
|
|
@@ -13,10 +13,15 @@ use temporal_sdk_core_protos::{
|
|
|
13
13
|
workflow_completion::WorkflowActivationCompletion,
|
|
14
14
|
ActivityHeartbeat, ActivityTaskCompletion, AsJsonPayloadExt, IntoCompletion,
|
|
15
15
|
},
|
|
16
|
-
temporal::api::
|
|
16
|
+
temporal::api::{
|
|
17
|
+
common::v1::{Payload, RetryPolicy},
|
|
18
|
+
enums::v1::TimeoutType,
|
|
19
|
+
},
|
|
20
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
17
21
|
};
|
|
18
22
|
use temporal_sdk_core_test_utils::{
|
|
19
|
-
init_core_and_create_wf, schedule_activity_cmd, CoreWfStarter,
|
|
23
|
+
drain_pollers_and_shutdown, init_core_and_create_wf, schedule_activity_cmd, CoreWfStarter,
|
|
24
|
+
WorkerTestHelpers,
|
|
20
25
|
};
|
|
21
26
|
use tokio::time::sleep;
|
|
22
27
|
|
|
@@ -46,7 +51,7 @@ async fn activity_heartbeat() {
|
|
|
46
51
|
assert_matches!(
|
|
47
52
|
task.variant,
|
|
48
53
|
Some(activity_task::Variant::Start(start_activity)) => {
|
|
49
|
-
assert_eq!(start_activity.activity_type,
|
|
54
|
+
assert_eq!(start_activity.activity_type, DEFAULT_ACTIVITY_TYPE.to_string())
|
|
50
55
|
}
|
|
51
56
|
);
|
|
52
57
|
// Heartbeat timeout is set to 1 second, this loop is going to send heartbeat every 100ms.
|
|
@@ -166,7 +171,7 @@ async fn many_act_fails_with_heartbeats() {
|
|
|
166
171
|
},]
|
|
167
172
|
);
|
|
168
173
|
core.complete_execution(&task.run_id).await;
|
|
169
|
-
core
|
|
174
|
+
drain_pollers_and_shutdown(&core).await;
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
#[tokio::test]
|
|
@@ -196,7 +201,7 @@ async fn activity_doesnt_heartbeat_hits_timeout_then_completes() {
|
|
|
196
201
|
..Default::default()
|
|
197
202
|
})
|
|
198
203
|
.await;
|
|
199
|
-
|
|
204
|
+
assert_eq!(res.timed_out(), Some(TimeoutType::Heartbeat));
|
|
200
205
|
Ok(().into())
|
|
201
206
|
});
|
|
202
207
|
|
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
use
|
|
2
|
-
use
|
|
3
|
-
use
|
|
4
|
-
use
|
|
5
|
-
use
|
|
1
|
+
use std::{sync::Arc, time::Duration};
|
|
2
|
+
use temporal_client::{WorkflowClientTrait, WorkflowOptions, WorkflowService};
|
|
3
|
+
use temporal_sdk_core::{init_worker, CoreRuntime};
|
|
4
|
+
use temporal_sdk_core_api::{telemetry::MetricsExporter, worker::WorkerConfigBuilder, Worker};
|
|
5
|
+
use temporal_sdk_core_protos::{
|
|
6
|
+
coresdk::{
|
|
7
|
+
activity_result::ActivityExecutionResult,
|
|
8
|
+
workflow_commands::{ScheduleActivity, ScheduleLocalActivity},
|
|
9
|
+
workflow_completion::WorkflowActivationCompletion,
|
|
10
|
+
ActivityTaskCompletion,
|
|
11
|
+
},
|
|
12
|
+
temporal::api::{enums::v1::WorkflowIdReusePolicy, workflowservice::v1::ListNamespacesRequest},
|
|
13
|
+
};
|
|
14
|
+
use temporal_sdk_core_test_utils::{get_integ_server_options, get_integ_telem_options, NAMESPACE};
|
|
15
|
+
use tokio::sync::Barrier;
|
|
16
|
+
|
|
17
|
+
static ANY_PORT: &str = "127.0.0.1:0";
|
|
18
|
+
|
|
19
|
+
async fn get_text(endpoint: String) -> String {
|
|
20
|
+
reqwest::get(endpoint).await.unwrap().text().await.unwrap()
|
|
21
|
+
}
|
|
6
22
|
|
|
7
23
|
#[tokio::test]
|
|
8
24
|
async fn prometheus_metrics_exported() {
|
|
9
25
|
let mut telemopts = get_integ_telem_options();
|
|
10
|
-
|
|
11
|
-
telemopts.metrics = Some(MetricsExporter::Prometheus(addr.parse().unwrap()));
|
|
26
|
+
telemopts.metrics = Some(MetricsExporter::Prometheus(ANY_PORT.parse().unwrap()));
|
|
12
27
|
let rt = CoreRuntime::new_assume_tokio(telemopts).unwrap();
|
|
28
|
+
let addr = rt.telemetry().prom_port().unwrap();
|
|
13
29
|
let opts = get_integ_server_options();
|
|
14
30
|
let mut raw_client = opts
|
|
15
|
-
.connect_no_namespace(rt.metric_meter(), None)
|
|
31
|
+
.connect_no_namespace(rt.metric_meter().as_deref(), None)
|
|
16
32
|
.await
|
|
17
33
|
.unwrap();
|
|
18
34
|
assert!(raw_client.get_client().capabilities().is_some());
|
|
@@ -22,16 +38,202 @@ async fn prometheus_metrics_exported() {
|
|
|
22
38
|
.await
|
|
23
39
|
.unwrap();
|
|
24
40
|
|
|
25
|
-
let body =
|
|
26
|
-
.await
|
|
27
|
-
.unwrap()
|
|
28
|
-
.text()
|
|
29
|
-
.await
|
|
30
|
-
.unwrap();
|
|
41
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
31
42
|
assert!(body.contains(
|
|
32
|
-
"
|
|
43
|
+
"temporal_request_latency_count{operation=\"ListNamespaces\",service_name=\"temporal-core-sdk\"} 1"
|
|
33
44
|
));
|
|
34
45
|
assert!(body.contains(
|
|
35
|
-
"
|
|
46
|
+
"temporal_request_latency_count{operation=\"GetSystemInfo\",service_name=\"temporal-core-sdk\"} 1"
|
|
36
47
|
));
|
|
37
48
|
}
|
|
49
|
+
|
|
50
|
+
#[tokio::test]
|
|
51
|
+
async fn one_slot_worker_reports_available_slot() {
|
|
52
|
+
let mut telemopts = get_integ_telem_options();
|
|
53
|
+
let tq = "one_slot_worker_tq";
|
|
54
|
+
telemopts.metrics = Some(MetricsExporter::Prometheus(ANY_PORT.parse().unwrap()));
|
|
55
|
+
let rt = CoreRuntime::new_assume_tokio(telemopts).unwrap();
|
|
56
|
+
let addr = rt.telemetry().prom_port().unwrap();
|
|
57
|
+
|
|
58
|
+
let worker_cfg = WorkerConfigBuilder::default()
|
|
59
|
+
.namespace(NAMESPACE)
|
|
60
|
+
.task_queue(tq)
|
|
61
|
+
.worker_build_id("test_build_id")
|
|
62
|
+
.max_cached_workflows(1_usize)
|
|
63
|
+
.max_outstanding_activities(1_usize)
|
|
64
|
+
.max_outstanding_local_activities(1_usize)
|
|
65
|
+
.max_outstanding_workflow_tasks(1_usize)
|
|
66
|
+
.build()
|
|
67
|
+
.unwrap();
|
|
68
|
+
|
|
69
|
+
let client = Arc::new(
|
|
70
|
+
get_integ_server_options()
|
|
71
|
+
.connect(worker_cfg.namespace.clone(), None, None)
|
|
72
|
+
.await
|
|
73
|
+
.expect("Must connect"),
|
|
74
|
+
);
|
|
75
|
+
let worker = init_worker(&rt, worker_cfg, client.clone()).expect("Worker inits cleanly");
|
|
76
|
+
let wf_task_barr = Barrier::new(2);
|
|
77
|
+
let act_task_barr = Barrier::new(2);
|
|
78
|
+
|
|
79
|
+
let wf_polling = async {
|
|
80
|
+
let task = worker.poll_workflow_activation().await.unwrap();
|
|
81
|
+
wf_task_barr.wait().await;
|
|
82
|
+
wf_task_barr.wait().await;
|
|
83
|
+
worker
|
|
84
|
+
.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
85
|
+
task.run_id,
|
|
86
|
+
ScheduleActivity {
|
|
87
|
+
seq: 1,
|
|
88
|
+
activity_id: "1".to_string(),
|
|
89
|
+
activity_type: "test_act".to_string(),
|
|
90
|
+
task_queue: tq.to_string(),
|
|
91
|
+
start_to_close_timeout: Some(prost_dur!(from_secs(30))),
|
|
92
|
+
..Default::default()
|
|
93
|
+
}
|
|
94
|
+
.into(),
|
|
95
|
+
))
|
|
96
|
+
.await
|
|
97
|
+
.unwrap();
|
|
98
|
+
wf_task_barr.wait().await;
|
|
99
|
+
|
|
100
|
+
let task = worker.poll_workflow_activation().await.unwrap();
|
|
101
|
+
worker
|
|
102
|
+
.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
103
|
+
task.run_id,
|
|
104
|
+
ScheduleLocalActivity {
|
|
105
|
+
seq: 2,
|
|
106
|
+
activity_id: "2".to_string(),
|
|
107
|
+
activity_type: "test_act".to_string(),
|
|
108
|
+
start_to_close_timeout: Some(prost_dur!(from_secs(30))),
|
|
109
|
+
..Default::default()
|
|
110
|
+
}
|
|
111
|
+
.into(),
|
|
112
|
+
))
|
|
113
|
+
.await
|
|
114
|
+
.unwrap();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
let act_polling = async {
|
|
118
|
+
let task = worker.poll_activity_task().await.unwrap();
|
|
119
|
+
act_task_barr.wait().await;
|
|
120
|
+
worker
|
|
121
|
+
.complete_activity_task(ActivityTaskCompletion {
|
|
122
|
+
task_token: task.task_token,
|
|
123
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
124
|
+
})
|
|
125
|
+
.await
|
|
126
|
+
.unwrap();
|
|
127
|
+
act_task_barr.wait().await;
|
|
128
|
+
|
|
129
|
+
let task = worker.poll_activity_task().await.unwrap();
|
|
130
|
+
act_task_barr.wait().await;
|
|
131
|
+
act_task_barr.wait().await;
|
|
132
|
+
worker
|
|
133
|
+
.complete_activity_task(ActivityTaskCompletion {
|
|
134
|
+
task_token: task.task_token,
|
|
135
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
136
|
+
})
|
|
137
|
+
.await
|
|
138
|
+
.unwrap();
|
|
139
|
+
act_task_barr.wait().await;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let testing = async {
|
|
143
|
+
// Wait just a beat for the poller to initiate
|
|
144
|
+
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
145
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
146
|
+
assert!(body.contains(&format!(
|
|
147
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
148
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
149
|
+
worker_type=\"WorkflowWorker\"}} 1"
|
|
150
|
+
)));
|
|
151
|
+
|
|
152
|
+
// Start a workflow so that a task will get delivered
|
|
153
|
+
client
|
|
154
|
+
.start_workflow(
|
|
155
|
+
vec![],
|
|
156
|
+
tq.to_owned(),
|
|
157
|
+
"one_slot_metric_test".to_owned(),
|
|
158
|
+
"whatever".to_string(),
|
|
159
|
+
None,
|
|
160
|
+
WorkflowOptions {
|
|
161
|
+
id_reuse_policy: WorkflowIdReusePolicy::TerminateIfRunning,
|
|
162
|
+
execution_timeout: Some(Duration::from_secs(5)),
|
|
163
|
+
..Default::default()
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
.await
|
|
167
|
+
.unwrap();
|
|
168
|
+
|
|
169
|
+
wf_task_barr.wait().await;
|
|
170
|
+
|
|
171
|
+
// At this point the workflow task is outstanding, so there should be 0 slots, and
|
|
172
|
+
// the activities haven't started, so there should be 1 each.
|
|
173
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
174
|
+
assert!(body.contains(&format!(
|
|
175
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
176
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
177
|
+
worker_type=\"WorkflowWorker\"}} 0"
|
|
178
|
+
)));
|
|
179
|
+
assert!(body.contains(&format!(
|
|
180
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
181
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
182
|
+
worker_type=\"ActivityWorker\"}} 1"
|
|
183
|
+
)));
|
|
184
|
+
assert!(body.contains(&format!(
|
|
185
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
186
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
187
|
+
worker_type=\"LocalActivityWorker\"}} 1"
|
|
188
|
+
)));
|
|
189
|
+
|
|
190
|
+
// Now we allow the complete to proceed. Once it goes through, there should be 1 WFT slot
|
|
191
|
+
// open but 0 activity slots
|
|
192
|
+
wf_task_barr.wait().await;
|
|
193
|
+
wf_task_barr.wait().await;
|
|
194
|
+
// Sometimes the recording takes an extra bit. 🤷
|
|
195
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
196
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
197
|
+
assert!(body.contains(&format!(
|
|
198
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
199
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
200
|
+
worker_type=\"WorkflowWorker\"}} 1"
|
|
201
|
+
)));
|
|
202
|
+
assert!(body.contains(&format!(
|
|
203
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
204
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
205
|
+
worker_type=\"ActivityWorker\"}} 0"
|
|
206
|
+
)));
|
|
207
|
+
|
|
208
|
+
// Now complete the activity and watch it go up
|
|
209
|
+
act_task_barr.wait().await;
|
|
210
|
+
// Wait for completion to finish
|
|
211
|
+
act_task_barr.wait().await;
|
|
212
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
213
|
+
assert!(body.contains(&format!(
|
|
214
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
215
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
216
|
+
worker_type=\"ActivityWorker\"}} 1"
|
|
217
|
+
)));
|
|
218
|
+
|
|
219
|
+
// Proceed to local activity command
|
|
220
|
+
act_task_barr.wait().await;
|
|
221
|
+
// Ensure that, once we have the LA task, slots are 0
|
|
222
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
223
|
+
assert!(body.contains(&format!(
|
|
224
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
225
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
226
|
+
worker_type=\"LocalActivityWorker\"}} 0"
|
|
227
|
+
)));
|
|
228
|
+
// When completion is done, we have 1 again
|
|
229
|
+
act_task_barr.wait().await;
|
|
230
|
+
act_task_barr.wait().await;
|
|
231
|
+
let body = get_text(format!("http://{addr}/metrics")).await;
|
|
232
|
+
assert!(body.contains(&format!(
|
|
233
|
+
"temporal_worker_task_slots_available{{namespace=\"{NAMESPACE}\",\
|
|
234
|
+
service_name=\"temporal-core-sdk\",task_queue=\"one_slot_worker_tq\",\
|
|
235
|
+
worker_type=\"LocalActivityWorker\"}} 1"
|
|
236
|
+
)));
|
|
237
|
+
};
|
|
238
|
+
tokio::join!(wf_polling, act_polling, testing);
|
|
239
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
use assert_matches::assert_matches;
|
|
2
|
-
use futures::future::join_all;
|
|
3
2
|
use std::time::Duration;
|
|
4
|
-
use temporal_client::WorkflowOptions;
|
|
5
|
-
use temporal_sdk::{WfContext, WorkflowResult};
|
|
6
3
|
use temporal_sdk_core_protos::coresdk::{
|
|
7
4
|
activity_task::activity_task as act_task,
|
|
8
5
|
workflow_activation::{workflow_activation_job, FireTimer, WorkflowActivationJob},
|
|
@@ -11,7 +8,7 @@ use temporal_sdk_core_protos::coresdk::{
|
|
|
11
8
|
IntoCompletion,
|
|
12
9
|
};
|
|
13
10
|
use temporal_sdk_core_test_utils::{
|
|
14
|
-
init_core_and_create_wf, schedule_activity_cmd,
|
|
11
|
+
init_core_and_create_wf, schedule_activity_cmd, WorkerTestHelpers,
|
|
15
12
|
};
|
|
16
13
|
use tokio::time::timeout;
|
|
17
14
|
|
|
@@ -43,15 +40,10 @@ async fn out_of_order_completion_doesnt_hang() {
|
|
|
43
40
|
)
|
|
44
41
|
.await
|
|
45
42
|
.unwrap();
|
|
46
|
-
// Poll activity and verify that it's been scheduled
|
|
47
|
-
//
|
|
43
|
+
// Poll activity and verify that it's been scheduled, we don't expect to complete it in this
|
|
44
|
+
// test as activity is try-cancelled.
|
|
48
45
|
let activity_task = core.poll_activity_task().await.unwrap();
|
|
49
|
-
assert_matches!(
|
|
50
|
-
activity_task.variant,
|
|
51
|
-
Some(act_task::Variant::Start(start_activity)) => {
|
|
52
|
-
assert_eq!(start_activity.activity_type, "test_activity".to_string())
|
|
53
|
-
}
|
|
54
|
-
);
|
|
46
|
+
assert_matches!(activity_task.variant, Some(act_task::Variant::Start(_)));
|
|
55
47
|
// Poll workflow task and verify that activity has failed.
|
|
56
48
|
let task = core.poll_workflow_activation().await.unwrap();
|
|
57
49
|
assert_matches!(
|
|
@@ -96,38 +88,3 @@ async fn out_of_order_completion_doesnt_hang() {
|
|
|
96
88
|
|
|
97
89
|
jh.await.unwrap();
|
|
98
90
|
}
|
|
99
|
-
|
|
100
|
-
pub async fn many_parallel_timers_longhist(ctx: WfContext) -> WorkflowResult<()> {
|
|
101
|
-
for _ in 0..20 {
|
|
102
|
-
let mut futs = vec![];
|
|
103
|
-
for _ in 0..1000 {
|
|
104
|
-
futs.push(ctx.timer(Duration::from_millis(100)));
|
|
105
|
-
}
|
|
106
|
-
join_all(futs).await;
|
|
107
|
-
}
|
|
108
|
-
Ok(().into())
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Ignored for now because I can't actually get this to produce pages. Need to generate some
|
|
112
|
-
// large payloads I think.
|
|
113
|
-
#[tokio::test]
|
|
114
|
-
#[ignore]
|
|
115
|
-
async fn can_paginate_long_history() {
|
|
116
|
-
let wf_name = "can_paginate_long_history";
|
|
117
|
-
let mut starter = CoreWfStarter::new(wf_name);
|
|
118
|
-
// Do not use sticky queues so we are forced to paginate once history gets long
|
|
119
|
-
starter.max_cached_workflows(0);
|
|
120
|
-
|
|
121
|
-
let mut worker = starter.worker().await;
|
|
122
|
-
worker.register_wf(wf_name.to_owned(), many_parallel_timers_longhist);
|
|
123
|
-
worker
|
|
124
|
-
.submit_wf(
|
|
125
|
-
wf_name.to_owned(),
|
|
126
|
-
wf_name.to_owned(),
|
|
127
|
-
vec![],
|
|
128
|
-
WorkflowOptions::default(),
|
|
129
|
-
)
|
|
130
|
-
.await
|
|
131
|
-
.unwrap();
|
|
132
|
-
worker.run_until_done().await.unwrap();
|
|
133
|
-
}
|
|
@@ -10,7 +10,9 @@ use temporal_sdk_core_protos::{
|
|
|
10
10
|
},
|
|
11
11
|
temporal::api::{failure::v1::Failure, query::v1::WorkflowQuery},
|
|
12
12
|
};
|
|
13
|
-
use temporal_sdk_core_test_utils::{
|
|
13
|
+
use temporal_sdk_core_test_utils::{
|
|
14
|
+
drain_pollers_and_shutdown, init_core_and_create_wf, WorkerTestHelpers,
|
|
15
|
+
};
|
|
14
16
|
|
|
15
17
|
#[tokio::test]
|
|
16
18
|
async fn simple_query_legacy() {
|
|
@@ -112,7 +114,7 @@ async fn simple_query_legacy() {
|
|
|
112
114
|
async fn query_after_execution_complete(#[case] do_evict: bool) {
|
|
113
115
|
let query_resp = b"response";
|
|
114
116
|
let mut starter =
|
|
115
|
-
init_core_and_create_wf(&format!("query_after_execution_complete-{}"
|
|
117
|
+
init_core_and_create_wf(&format!("query_after_execution_complete-{do_evict}")).await;
|
|
116
118
|
let core = &starter.get_worker().await;
|
|
117
119
|
let workflow_id = &starter.get_task_queue().to_string();
|
|
118
120
|
|
|
@@ -205,132 +207,7 @@ async fn query_after_execution_complete(#[case] do_evict: bool) {
|
|
|
205
207
|
query_futs.push(do_workflow(true).map(|_| ()).boxed());
|
|
206
208
|
}
|
|
207
209
|
while query_futs.next().await.is_some() {}
|
|
208
|
-
core
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
#[ignore]
|
|
212
|
-
#[tokio::test]
|
|
213
|
-
async fn repros_query_dropped_on_floor() {
|
|
214
|
-
// This test reliably repros the server dropping one of the two simultaneously issued queries.
|
|
215
|
-
let q1_resp = b"query_1_resp";
|
|
216
|
-
let q2_resp = b"query_2_resp";
|
|
217
|
-
let mut wf_starter = CoreWfStarter::new("repros_query_dropped_on_floor");
|
|
218
|
-
// Easiest way I discovered to reliably trigger new query path is with a WFT timeout
|
|
219
|
-
wf_starter.wft_timeout(Duration::from_secs(1));
|
|
220
|
-
let core = wf_starter.get_worker().await;
|
|
221
|
-
let task_q = wf_starter.get_task_queue().to_string();
|
|
222
|
-
wf_starter.start_wf().await;
|
|
223
|
-
let client = wf_starter.get_client().await;
|
|
224
|
-
|
|
225
|
-
let task = core.poll_workflow_activation().await.unwrap();
|
|
226
|
-
core.complete_timer(&task.run_id, 1, Duration::from_millis(500))
|
|
227
|
-
.await;
|
|
228
|
-
|
|
229
|
-
// Poll for a task we will time out
|
|
230
|
-
let task = core.poll_workflow_activation().await.unwrap();
|
|
231
|
-
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
232
|
-
// Complete now-timed-out task (add a new timer)
|
|
233
|
-
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
234
|
-
task.run_id.clone(),
|
|
235
|
-
vec![],
|
|
236
|
-
))
|
|
237
|
-
.await
|
|
238
|
-
.unwrap();
|
|
239
|
-
|
|
240
|
-
let run_id = task.run_id.to_string();
|
|
241
|
-
let q1_fut = async {
|
|
242
|
-
client
|
|
243
|
-
.query_workflow_execution(
|
|
244
|
-
task_q.clone(),
|
|
245
|
-
run_id,
|
|
246
|
-
WorkflowQuery {
|
|
247
|
-
query_type: "query_1".to_string(),
|
|
248
|
-
query_args: Some(b"hi 1".into()),
|
|
249
|
-
header: None,
|
|
250
|
-
},
|
|
251
|
-
)
|
|
252
|
-
.await
|
|
253
|
-
.unwrap()
|
|
254
|
-
};
|
|
255
|
-
let run_id = task.run_id.to_string();
|
|
256
|
-
let q2_fut = async {
|
|
257
|
-
client
|
|
258
|
-
.query_workflow_execution(
|
|
259
|
-
task_q.clone(),
|
|
260
|
-
run_id,
|
|
261
|
-
WorkflowQuery {
|
|
262
|
-
query_type: "query_2".to_string(),
|
|
263
|
-
query_args: Some(b"hi 2".into()),
|
|
264
|
-
header: None,
|
|
265
|
-
},
|
|
266
|
-
)
|
|
267
|
-
.await
|
|
268
|
-
.unwrap()
|
|
269
|
-
};
|
|
270
|
-
let workflow_completions_future = async {
|
|
271
|
-
let mut seen_q1 = false;
|
|
272
|
-
let mut seen_q2 = false;
|
|
273
|
-
while !seen_q1 || !seen_q2 {
|
|
274
|
-
let task = core.poll_workflow_activation().await.unwrap();
|
|
275
|
-
|
|
276
|
-
if matches!(
|
|
277
|
-
task.jobs[0],
|
|
278
|
-
WorkflowActivationJob {
|
|
279
|
-
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
280
|
-
}
|
|
281
|
-
) {
|
|
282
|
-
let task = core.poll_workflow_activation().await.unwrap();
|
|
283
|
-
core.complete_timer(&task.run_id, 1, Duration::from_millis(500))
|
|
284
|
-
.await;
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if matches!(
|
|
289
|
-
task.jobs[0],
|
|
290
|
-
WorkflowActivationJob {
|
|
291
|
-
variant: Some(workflow_activation_job::Variant::FireTimer(_)),
|
|
292
|
-
}
|
|
293
|
-
) {
|
|
294
|
-
// If we get the timer firing after replay, be done.
|
|
295
|
-
core.complete_execution(&task.run_id).await;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// There should be a query job (really, there should be both... server only sends one?)
|
|
299
|
-
let query = assert_matches!(
|
|
300
|
-
task.jobs.as_slice(),
|
|
301
|
-
[WorkflowActivationJob {
|
|
302
|
-
variant: Some(workflow_activation_job::Variant::QueryWorkflow(q)),
|
|
303
|
-
}] => q
|
|
304
|
-
);
|
|
305
|
-
let resp = if query.query_type == "query_1" {
|
|
306
|
-
seen_q1 = true;
|
|
307
|
-
q1_resp
|
|
308
|
-
} else {
|
|
309
|
-
seen_q2 = true;
|
|
310
|
-
q2_resp
|
|
311
|
-
};
|
|
312
|
-
// Complete the query
|
|
313
|
-
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
314
|
-
task.run_id,
|
|
315
|
-
vec![QueryResult {
|
|
316
|
-
query_id: query.query_id.clone(),
|
|
317
|
-
variant: Some(
|
|
318
|
-
QuerySuccess {
|
|
319
|
-
response: Some(resp.into()),
|
|
320
|
-
}
|
|
321
|
-
.into(),
|
|
322
|
-
),
|
|
323
|
-
}
|
|
324
|
-
.into()],
|
|
325
|
-
))
|
|
326
|
-
.await
|
|
327
|
-
.unwrap();
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
let (q1_res, q2_res, _) = tokio::join!(q1_fut, q2_fut, workflow_completions_future);
|
|
331
|
-
// Ensure query responses are as expected
|
|
332
|
-
assert_eq!(&q1_res.unwrap()[0].data, q1_resp);
|
|
333
|
-
assert_eq!(&q2_res.unwrap()[0].data, q2_resp);
|
|
210
|
+
drain_pollers_and_shutdown(core).await;
|
|
334
211
|
}
|
|
335
212
|
|
|
336
213
|
#[tokio::test]
|