@@ -30,7 +30,7 @@ use swimos_api::{
30
30
AgentConfig , AgentContext , AgentTask , DownlinkKind , HttpLaneRequestChannel , LaneConfig ,
31
31
StoreKind , WarpLaneKind ,
32
32
} ,
33
- error:: { AgentRuntimeError , DownlinkRuntimeError , OpenStoreError } ,
33
+ error:: { AgentRuntimeError , DownlinkFailureReason , DownlinkRuntimeError , OpenStoreError } ,
34
34
} ;
35
35
use swimos_model:: Text ;
36
36
use swimos_utilities:: {
@@ -92,12 +92,32 @@ impl DlTestContext {
92
92
ad_hoc_channel : Arc :: new ( Mutex :: new ( ( ad_hoc_reader, Some ( ad_hoc_writer) ) ) ) ,
93
93
}
94
94
}
95
+
96
+ fn with_errors (
97
+ expected_address : Address < Text > ,
98
+ expected_kind : DownlinkKind ,
99
+ errors : Vec < DownlinkRuntimeError > ,
100
+ io : Option < ( ByteWriter , ByteReader ) > ,
101
+ ) -> Self {
102
+ let ( ad_hoc_writer, ad_hoc_reader) = byte_channel ( BUFFER_SIZE ) ;
103
+ DlTestContext {
104
+ expected_address,
105
+ expected_kind,
106
+ responses : Arc :: new ( Mutex :: new ( DownlinkResponses :: new ( errors, io) ) ) ,
107
+ ad_hoc_channel : Arc :: new ( Mutex :: new ( ( ad_hoc_reader, Some ( ad_hoc_writer) ) ) ) ,
108
+ }
109
+ }
95
110
}
96
111
97
112
impl AgentContext for DlTestContext {
98
113
fn ad_hoc_commands ( & self ) -> BoxFuture < ' static , Result < ByteWriter , DownlinkRuntimeError > > {
99
114
let DlTestContext { ad_hoc_channel, .. } = self ;
100
- ready ( Ok ( ad_hoc_channel. lock ( ) . 1 . take ( ) . expect ( "Reader taken twice." ) ) ) . boxed ( )
115
+ ready ( Ok ( ad_hoc_channel
116
+ . lock ( )
117
+ . 1
118
+ . take ( )
119
+ . expect ( "Reader taken twice." ) ) )
120
+ . boxed ( )
101
121
}
102
122
103
123
fn add_lane (
@@ -126,7 +146,7 @@ impl AgentContext for DlTestContext {
126
146
assert_eq ! ( addr, expected_address. borrow_parts( ) ) ;
127
147
assert_eq ! ( kind, * expected_kind) ;
128
148
let result = responses. lock ( ) . pop ( ) ;
129
- ready ( result) . boxed ( )
149
+ async move { result } . boxed ( )
130
150
}
131
151
132
152
fn add_store (
@@ -146,6 +166,7 @@ impl AgentContext for DlTestContext {
146
166
}
147
167
148
168
struct TestContext {
169
+ _tx : mpsc:: UnboundedSender < DlResult > ,
149
170
rx : mpsc:: UnboundedReceiver < DlResult > ,
150
171
}
151
172
@@ -163,6 +184,7 @@ async fn init_agent(context: Box<DlTestContext>) -> (AgentTask, TestContext) {
163
184
let address = addr ( ) ;
164
185
165
186
let ( tx, rx) = mpsc:: unbounded_channel ( ) ;
187
+ let tx_cpy = tx. clone ( ) ;
166
188
167
189
let model =
168
190
AgentModel :: < EmptyAgent , StartDownlinkLifecycle > :: from_fn ( EmptyAgent :: default, move || {
@@ -173,7 +195,7 @@ async fn init_agent(context: Box<DlTestContext>) -> (AgentTask, TestContext) {
173
195
. initialize_agent ( make_uri ( ) , HashMap :: new ( ) , config ( ) , context)
174
196
. await
175
197
. expect ( "Initialization failed." ) ;
176
- ( task, TestContext { rx } )
198
+ ( task, TestContext { rx, _tx : tx_cpy } )
177
199
}
178
200
179
201
type DlResult = Result < ( ) , DownlinkRuntimeError > ;
@@ -207,10 +229,72 @@ async fn immediately_successful_downlink() {
207
229
208
230
let agent_context = DlTestContext :: new ( addr ( ) , DownlinkKind :: Value , ( out_tx, in_rx) ) ;
209
231
210
- let ( agent_task, TestContext { mut rx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
232
+ let ( agent_task, TestContext { mut rx, _tx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
233
+
234
+ let check = async move {
235
+ let result = rx. recv ( ) . await . expect ( "Result expected." ) ;
236
+ assert ! ( result. is_ok( ) ) ;
237
+ } ;
238
+
239
+ let ( result, _) = join ( agent_task, check) . await ;
240
+ assert ! ( result. is_ok( ) ) ;
241
+ } )
242
+ . await
243
+ . expect ( "Test timed out." ) ;
244
+ }
245
+
246
+ #[ tokio:: test]
247
+ async fn immediate_fatal_error_downlink ( ) {
248
+ tokio:: time:: timeout ( TEST_TIMEOUT , async {
249
+ let agent_context = DlTestContext :: with_errors (
250
+ addr ( ) ,
251
+ DownlinkKind :: Value ,
252
+ vec ! [ DownlinkRuntimeError :: DownlinkConnectionFailed (
253
+ DownlinkFailureReason :: InvalidUrl ,
254
+ ) ] ,
255
+ None ,
256
+ ) ;
257
+
258
+ let ( agent_task, TestContext { mut rx, _tx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
259
+
260
+ let check = async move {
261
+ let result = rx. recv ( ) . await . expect ( "Result expected." ) ;
262
+
263
+ assert ! ( matches!(
264
+ result,
265
+ Err ( DownlinkRuntimeError :: DownlinkConnectionFailed (
266
+ DownlinkFailureReason :: InvalidUrl
267
+ ) )
268
+ ) ) ;
269
+ } ;
270
+
271
+ let ( result, _) = join ( agent_task, check) . await ;
272
+ assert ! ( result. is_ok( ) ) ;
273
+ } )
274
+ . await
275
+ . expect ( "Test timed out." ) ;
276
+ }
277
+
278
+ #[ tokio:: test]
279
+ async fn error_recovery_open_downlink ( ) {
280
+ tokio:: time:: timeout ( TEST_TIMEOUT , async {
281
+ let ( _in_tx, in_rx) = byte_channel ( BUFFER_SIZE ) ;
282
+ let ( out_tx, _out_rx) = byte_channel ( BUFFER_SIZE ) ;
283
+
284
+ let agent_context = DlTestContext :: with_errors (
285
+ addr ( ) ,
286
+ DownlinkKind :: Value ,
287
+ vec ! [ DownlinkRuntimeError :: DownlinkConnectionFailed (
288
+ DownlinkFailureReason :: DownlinkStopped ,
289
+ ) ] ,
290
+ Some ( ( out_tx, in_rx) ) ,
291
+ ) ;
292
+
293
+ let ( agent_task, TestContext { mut rx, _tx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
211
294
212
295
let check = async move {
213
296
let result = rx. recv ( ) . await . expect ( "Result expected." ) ;
297
+
214
298
assert ! ( result. is_ok( ) ) ;
215
299
} ;
216
300
@@ -220,3 +304,81 @@ async fn immediately_successful_downlink() {
220
304
. await
221
305
. expect ( "Test timed out." ) ;
222
306
}
307
+
308
+ #[ tokio:: test]
309
+ async fn eventual_fatal_error_downlink ( ) {
310
+ tokio:: time:: timeout ( TEST_TIMEOUT , async {
311
+ let agent_context = DlTestContext :: with_errors (
312
+ addr ( ) ,
313
+ DownlinkKind :: Value ,
314
+ vec ! [
315
+ DownlinkRuntimeError :: DownlinkConnectionFailed (
316
+ DownlinkFailureReason :: DownlinkStopped ,
317
+ ) ,
318
+ DownlinkRuntimeError :: DownlinkConnectionFailed ( DownlinkFailureReason :: InvalidUrl ) ,
319
+ ] ,
320
+ None ,
321
+ ) ;
322
+
323
+ let ( agent_task, TestContext { mut rx, _tx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
324
+
325
+ let check = async move {
326
+ let result = rx. recv ( ) . await . expect ( "Result expected." ) ;
327
+
328
+ assert ! ( matches!(
329
+ result,
330
+ Err ( DownlinkRuntimeError :: DownlinkConnectionFailed (
331
+ DownlinkFailureReason :: InvalidUrl
332
+ ) )
333
+ ) ) ;
334
+ } ;
335
+
336
+ let ( result, _) = join ( agent_task, check) . await ;
337
+ assert ! ( result. is_ok( ) ) ;
338
+ } )
339
+ . await
340
+ . expect ( "Test timed out." ) ;
341
+ }
342
+
343
+ #[ tokio:: test]
344
+ async fn exhaust_open_downlink_retries ( ) {
345
+ tokio:: time:: timeout ( TEST_TIMEOUT , async {
346
+ let ( _in_tx, in_rx) = byte_channel ( BUFFER_SIZE ) ;
347
+ let ( out_tx, _out_rx) = byte_channel ( BUFFER_SIZE ) ;
348
+
349
+ let agent_context = DlTestContext :: with_errors (
350
+ addr ( ) ,
351
+ DownlinkKind :: Value ,
352
+ vec ! [
353
+ DownlinkRuntimeError :: DownlinkConnectionFailed (
354
+ DownlinkFailureReason :: DownlinkStopped ,
355
+ ) ,
356
+ DownlinkRuntimeError :: DownlinkConnectionFailed (
357
+ DownlinkFailureReason :: DownlinkStopped ,
358
+ ) ,
359
+ DownlinkRuntimeError :: DownlinkConnectionFailed (
360
+ DownlinkFailureReason :: DownlinkStopped ,
361
+ ) ,
362
+ ] ,
363
+ Some ( ( out_tx, in_rx) ) ,
364
+ ) ;
365
+
366
+ let ( agent_task, TestContext { mut rx, _tx } ) = init_agent ( Box :: new ( agent_context) ) . await ;
367
+
368
+ let check = async move {
369
+ let result = rx. recv ( ) . await . expect ( "Result expected." ) ;
370
+
371
+ assert ! ( matches!(
372
+ result,
373
+ Err ( DownlinkRuntimeError :: DownlinkConnectionFailed (
374
+ DownlinkFailureReason :: DownlinkStopped
375
+ ) )
376
+ ) ) ;
377
+ } ;
378
+
379
+ let ( result, _) = join ( agent_task, check) . await ;
380
+ assert ! ( result. is_ok( ) ) ;
381
+ } )
382
+ . await
383
+ . expect ( "Test timed out." ) ;
384
+ }
0 commit comments