1use crate::{Chunk, Program};
28use crate::natives::std_native;
29
30const N_PRINT: u32 = std_native::PRINT;
32const N_MAKE_MSG: u32 = std_native::MAKE_MSG;
33
34pub const TAG_REQ: i32 = 1;
36pub const TAG_REP: i32 = 2;
37pub const TAG_PING: i32 = 10;
38pub const TAG_PONG: i32 = 11;
39pub const TAG_JUNK: i32 = 99;
41
42pub fn add_forty_two() -> Chunk {
44 let mut p = Program::new("add-forty-two");
45 p.function("main", 0, |f| {
46 let a = f.load_i32(41);
47 let b = f.load_i32(1);
48 let sum = f.add(a, b);
49 f.return_(sum);
50 });
51 p.build()
52}
53
54pub fn ping_pong() -> Chunk {
58 let mut p = Program::new("ping-pong");
59 let pong = p.function("pong", 0, |f| {
60 let msg = f.receive();
61 let payload = f.hop_payload(msg);
62 f.add_imm(payload, 1);
63 f.send_reply(msg, TAG_PONG, payload);
64 f.exit(payload);
65 });
66 p.function("main", 0, |f| {
67 let child = f.spawn(pong, 0);
68 let payload = f.load_i32(1);
69 let req = f.hop_fresh(TAG_PING, payload);
70 let rid = f.hop_request_id(req);
71 f.send(child, req);
72 let reply = f.receive_match_corr_imm(TAG_PONG as u16, rid);
73 let out = f.hop_payload(reply);
74 f.return_(out);
75 });
76 p.build()
77}
78
79pub fn atomic_request_reply() -> Chunk {
81 let mut p = Program::new("atomic-request-reply");
82 let server = p.function("server", 0, |f| {
83 let msg = f.receive();
84 f.native1_on(msg, N_PRINT);
85 let payload = f.hop_payload(msg);
86 f.add_imm(payload, 1);
87 f.send_reply(msg, TAG_REP, payload);
88 f.exit(payload);
89 });
90 p.function("main", 0, |f| {
91 let server_cap = f.spawn(server, 0);
92 let payload = f.load_i32(41);
93 let req = f.hop_fresh(TAG_REQ, payload);
94 let rid = f.hop_request_id(req);
95 f.native1_on(req, N_PRINT);
96 f.send(server_cap, req);
97 let reply = f.receive_match_corr_imm(TAG_REP as u16, rid);
98 f.native1_on(reply, N_PRINT);
99 let out = f.hop_payload(reply);
100 f.return_(out);
101 });
102 p.build()
103}
104
105pub fn selective_receive() -> Chunk {
108 let mut p = Program::new("selective-receive");
109 let server = p.function("server", 0, |f| {
110 let msg = f.receive_match_imm(TAG_REQ as u16);
111 let payload = f.hop_payload(msg);
112 f.add_imm(payload, 1);
113 f.send_reply(msg, TAG_REP, payload);
114 let junk = f.receive();
115 let tag = f.hop_tag(junk);
116 let is_junk = f.eq_imm(tag, TAG_JUNK);
117 let trap_lbl = f.label();
118 f.branch_if_falsy(is_junk, trap_lbl);
119 f.exit(payload);
120 f.bind(trap_lbl);
121 f.trap(2);
122 });
123 p.function("main", 0, |f| {
124 let server_cap = f.spawn(server, 0);
125 let req_id = f.load_i32(1);
126 let zero = f.load_i32(0);
127 let junk = f.hop(req_id, TAG_JUNK, zero);
128 f.send(server_cap, junk);
129 let payload = f.load_i32(41);
130 let req = f.hop(req_id, TAG_REQ, payload);
131 f.send(server_cap, req);
132 let reply = f.receive_match_imm(TAG_REP as u16);
133 let out = f.hop_payload(reply);
134 f.return_(out);
135 });
136 p.build()
137}
138
139pub fn ask_reply() -> Chunk {
141 let mut p = Program::new("ask-reply");
142 let server = p.function("server", 0, |f| {
143 let msg = f.receive_match_imm(TAG_REQ as u16);
144 let payload = f.hop_payload(msg);
145 f.add_imm(payload, 1);
146 f.send_reply(msg, TAG_REP, payload);
147 f.exit(payload);
148 });
149 p.function("main", 0, |f| {
150 let server_cap = f.spawn(server, 0);
151 let payload = f.load_i32(41);
152 let req = f.hop_fresh(TAG_REQ, payload);
153 let reply = f.ask(server_cap, req);
154 let out = f.hop_payload(reply);
155 f.return_(out);
156 });
157 p.build()
158}
159
160pub fn ask_timeout_expires() -> Chunk {
162 let mut p = Program::new("ask-timeout");
163 let server = p.function("server", 0, |f| {
164 let _msg = f.receive();
165 let ms = f.load_i32(10_000);
166 f.sleep(ms);
167 let zero = f.load_i32(0);
168 f.return_(zero);
169 });
170 p.function("main", 0, |f| {
171 let server_cap = f.spawn(server, 0);
172 let req_id = f.load_i32(1);
173 let payload = f.load_i32(0);
174 let req = f.hop(req_id, TAG_REQ, payload);
175 let ms = f.load_i32(40);
176 let reply = f.ask_timeout(server_cap, req, ms);
177 f.return_(reply);
178 });
179 p.build()
180}
181
182pub fn ask_target_exits() -> Chunk {
184 let mut p = Program::new("ask-target-exits");
185 let server = p.function("server", 0, |f| {
186 let _msg = f.receive();
187 let z = f.load_i32(0);
188 f.return_(z);
189 });
190 p.function("main", 0, |f| {
191 let server_cap = f.spawn(server, 0);
192 let req_id = f.load_i32(1);
193 let payload = f.load_i32(0);
194 let req = f.hop(req_id, TAG_REQ, payload);
195 let reply = f.ask(server_cap, req);
196 let tag = f.hop_tag(reply);
197 f.return_(tag);
198 });
199 p.build()
200}
201
202pub fn server_loop() -> Chunk {
204 let mut p = Program::new("server-loop");
205 let server = p.function("server", 0, |f| {
206 let loop_lbl = f.label();
207 f.bind(loop_lbl);
208 let req = f.receive_match_imm(TAG_REQ as u16);
209 let payload = f.hop_payload(req);
210 f.add_imm(payload, 1);
211 f.send_reply(req, TAG_REP, payload);
212 f.jump(loop_lbl);
213 });
214 p.function("main", 0, |f| {
215 let server_cap = f.spawn(server, 0);
216 let payload = f.load_i32(41);
217 let req = f.hop_fresh(TAG_REQ, payload);
218 f.send(server_cap, req);
219 let reply = f.receive_match_imm(TAG_REP as u16);
220 let out = f.hop_payload(reply);
221 f.return_(out);
222 });
223 p.build()
224}
225
226pub fn named_service() -> Chunk {
230 let mut p = Program::new("named-service");
231 let server = p.function("server", 0, |f| {
232 let name = f.load_str("svc");
233 f.register_name(name);
234 let loop_lbl = f.label();
235 f.bind(loop_lbl);
236 let req = f.receive_match_imm(TAG_REQ as u16);
237 let payload = f.hop_payload(req);
238 f.add_imm(payload, 1);
239 f.send_reply(req, TAG_REP, payload);
240 f.jump(loop_lbl);
241 });
242 p.function("main", 0, |f| {
243 let _server = f.spawn(server, 0);
244 let name = f.load_str("svc");
245 let retry = f.label();
246 let miss = f.label();
247 f.bind(retry);
248 let cap = f.whereis(name);
249 f.branch_if_falsy(cap, miss);
250 let payload = f.load_i32(41);
251 let req = f.hop_fresh(TAG_REQ, payload);
252 f.send(cap, req);
253 let reply = f.receive_match_imm(TAG_REP as u16);
254 let out = f.hop_payload(reply);
255 f.return_(out);
256 f.bind(miss);
257 let ms = f.load_i32(1);
260 f.sleep(ms);
261 f.jump(retry);
262 });
263 p.build()
264}
265
266pub fn atomic_actors() -> Chunk {
270 const N: i32 = 8;
271 let mut p = Program::new("atomic-actors");
272 let server = p.function("server", 0, |f| {
273 let loop_lbl = f.label();
274 f.bind(loop_lbl);
275 let req = f.receive_match_imm(TAG_REQ as u16);
276 let payload = f.hop_payload(req);
277 f.add_imm(payload, 1);
278 f.send_reply(req, TAG_REP, payload);
279 f.jump(loop_lbl);
280 });
281 let client = p.function("client", 2, |f| {
282 let server_cap = f.reg(0);
283 let parent_cap = f.reg(1);
284 let acc = f.load_i32(0);
285 let i = f.load_i32(0);
286 let n = f.load_i32(N);
287 f.while_lt(i, n, |f| {
288 let req = f.hop_fresh(TAG_REQ, i);
289 let reply = f.ask(server_cap, req);
290 let got = f.hop_payload(reply);
291 let sum = f.add(acc, got);
292 f.mov(acc, sum);
293 f.add_imm(i, 1);
294 });
295 let done = f.hop_fresh(TAG_REP, acc);
296 f.send(parent_cap, done);
297 f.return_(acc);
298 });
299 p.function("main", 0, |f| {
300 let server_cap = f.spawn(server, 0);
301 let me = f.self_cap();
302 let w1 = f.window(3);
303 f.mov(w1.at(1), server_cap);
304 f.mov(w1.at(2), me);
305 f.spawn_at(w1.at(0), client, 2);
306 let w2 = f.window(3);
307 f.mov(w2.at(1), server_cap);
308 f.mov(w2.at(2), me);
309 f.spawn_at(w2.at(0), client, 2);
310 let a = f.receive_match_imm(TAG_REP as u16);
311 let b = f.receive_match_imm(TAG_REP as u16);
312 let pa = f.hop_payload(a);
313 let pb = f.hop_payload(b);
314 let out = f.add(pa, pb);
315 f.return_(out);
316 });
317 p.build()
318}
319
320pub fn cap_in_payload() -> Chunk {
323 let mut p = Program::new("cap-in-payload");
324 let server = p.function("server", 0, |f| {
325 let msg = f.receive();
326 let dest = f.hop_payload(msg);
327 let payload = f.load_i32(42);
328 let reply = f.hop_fresh(TAG_REP, payload);
329 f.send(dest, reply);
330 f.exit(payload);
331 });
332 p.function("main", 0, |f| {
333 let server_cap = f.spawn(server, 0);
334 let me = f.self_cap();
335 let req = f.hop_fresh(TAG_REQ, me);
336 f.send(server_cap, req);
337 let reply = f.receive_match_imm(TAG_REP as u16);
338 let out = f.hop_payload(reply);
339 f.return_(out);
340 });
341 p.build()
342}
343
344pub fn forged_sender_send() -> Chunk {
346 let mut p = Program::new("forged-sender-send");
347 let server = p.function("server", 0, |f| {
348 let msg = f.receive();
349 let sender = f.hop_sender(msg);
350 f.send_reply(msg, TAG_REP, sender);
351 f.exit(sender);
352 });
353 p.function("main", 0, |f| {
354 let server_cap = f.spawn(server, 0);
355 let forged = f.load_i32(999);
356 let req_id = f.load_i32(1);
357 let zero = f.load_i32(0);
358 let req = f.make_msg_legacy_sender(N_MAKE_MSG, forged, req_id, TAG_REQ, zero);
359 f.send(server_cap, req);
360 let reply = f.receive();
361 let out = f.hop_payload(reply);
362 f.return_(out);
363 });
364 p.build()
365}
366
367pub fn forged_sender_ask() -> Chunk {
369 let mut p = Program::new("forged-sender-ask");
370 let server = p.function("server", 0, |f| {
371 let msg = f.receive_match_imm(TAG_REQ as u16);
372 let sender = f.hop_sender(msg);
373 f.send_reply(msg, TAG_REP, sender);
374 f.exit(sender);
375 });
376 p.function("main", 0, |f| {
377 let server_cap = f.spawn(server, 0);
378 let forged = f.load_i32(999);
379 let req_id = f.load_i32(1);
380 let zero = f.load_i32(0);
381 let req = f.make_msg_legacy_sender(N_MAKE_MSG, forged, req_id, TAG_REQ, zero);
382 let reply = f.ask(server_cap, req);
383 let out = f.hop_payload(reply);
384 f.return_(out);
385 });
386 p.build()
387}
388
389pub fn monitor_down() -> Chunk {
391 let mut p = Program::new("monitor-down");
392 let child = p.function("child", 0, |f| {
393 let ms = f.load_i32(40);
394 f.sleep(ms);
395 let z = f.load_i32(0);
396 f.return_(z);
397 });
398 p.function("main", 0, |f| {
399 let cap = f.spawn(child, 0);
400 let mon = f.monitor(cap);
401 let msg = f.receive_match_imm(crate::TAG_SYS_DOWN);
402 let id = f.hop_request_id(msg);
403 let ok = f.eq(id, mon);
404 let trap = f.label();
405 f.branch_if_falsy(ok, trap);
406 let out = f.hop_payload(msg);
407 f.return_(out);
408 f.bind(trap);
409 f.trap(3);
410 });
411 p.build()
412}
413
414pub fn waiting_send() -> Chunk {
417 let mut p = Program::new("waiting-send");
418 p.function("server", 0, |f| {
419 let ms = f.load_i32(50);
420 f.sleep(ms);
421 let _first = f.receive();
422 let second = f.receive();
423 let out = f.hop_payload(second);
424 f.return_(out);
425 });
426 p.function("client", 1, |f| {
427 let server = f.reg(0);
428 let req_id = f.load_i32(1);
429 let one = f.load_i32(1);
430 let first = f.hop(req_id, TAG_REQ, one);
431 f.send(server, first);
432 let forty_two = f.load_i32(42);
433 let second = f.hop(req_id, TAG_REQ, forty_two);
434 f.send(server, second);
435 f.exit(second);
436 });
437 p.build()
438}
439
440pub fn boom() -> Chunk {
442 let mut p = Program::new("boom");
443 p.function("boom", 0, |f| f.trap(1));
444 p.build()
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::{
451 bytecode::CapId, decode, encode, std_native_table, verify, FlowOutcome, Message,
452 QuotaConfig, Runtime, RuntimeConfig, Value,
453 };
454
455 fn tiny(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
456 Runtime::with_config(
457 chunk,
458 RuntimeConfig {
459 workers: 1,
460 quantum: 10_000,
461 mailbox: crate::MailboxConfig::DEFAULT,
462 ..Default::default()
463 },
464 )
465 }
466
467 fn tiny_natives(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
468 Runtime::with_natives_and_config(
469 chunk,
470 std_native_table(),
471 RuntimeConfig {
472 workers: 1,
473 quantum: 10_000,
474 mailbox: crate::MailboxConfig::DEFAULT,
475 ..Default::default()
476 },
477 )
478 }
479
480 #[test]
481 fn add_forty_two_joins_42() -> Result<(), Box<dyn std::error::Error>> {
482 let rt = tiny(add_forty_two())?;
483 let idx = rt.function_index("main").ok_or("main")?;
484 let outcome = rt.spawn(idx, &[])?.join();
485 rt.shutdown();
486 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
487 Ok(())
488 }
489
490 #[test]
491 fn ping_pong_joins_2() -> Result<(), Box<dyn std::error::Error>> {
492 let chunk = ping_pong();
493 assert!(verify(&chunk).is_ok());
494 let bytes = encode(&chunk);
495 let chunk = decode(&bytes)?;
496 let rt = tiny_natives(chunk)?;
497 let idx = rt.function_index("main").ok_or("main")?;
498 let outcome = rt.spawn(idx, &[])?.join();
499 let sent = rt.metrics().messages_sent;
500 rt.shutdown();
501 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
502 assert!(sent >= 2);
503 Ok(())
504 }
505
506 #[test]
507 fn atomic_request_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
508 let chunk = atomic_request_reply();
509 assert!(verify(&chunk).is_ok());
510 let bytes = encode(&chunk);
511 let chunk = decode(&bytes)?;
512 let rt = tiny_natives(chunk)?;
513 let idx = rt.function_index("main").ok_or("main")?;
514 let outcome = rt.spawn(idx, &[])?.join();
515 let sent = rt.metrics().messages_sent;
516 rt.shutdown();
517 assert!(
518 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
519 "got {outcome:?}"
520 );
521 assert!(sent >= 1);
522 Ok(())
523 }
524
525 #[test]
526 fn atomic_actors_two_clients_sum_72() -> Result<(), Box<dyn std::error::Error>> {
527 let chunk = atomic_actors();
528 assert!(verify(&chunk).is_ok());
529 let bytes = encode(&chunk);
530 let chunk = decode(&bytes)?;
531 let rt = tiny_natives(chunk)?;
532 let idx = rt.function_index("main").ok_or("main")?;
533 let outcome = rt.spawn(idx, &[])?.join();
534 let sent = rt.metrics().messages_sent;
535 rt.shutdown();
536 assert!(
537 matches!(outcome, FlowOutcome::Completed(Value::Int(72))),
538 "got {outcome:?}"
539 );
540 assert!(sent >= 34);
542 Ok(())
543 }
544
545 #[test]
546 fn cap_in_payload_reissues_holder() -> Result<(), Box<dyn std::error::Error>> {
547 let chunk = cap_in_payload();
548 assert!(verify(&chunk).is_ok());
549 let rt = tiny_natives(chunk)?;
550 let idx = rt.function_index("main").ok_or("main")?;
551 let outcome = rt.spawn(idx, &[])?.join();
552 rt.shutdown();
553 assert!(
554 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
555 "got {outcome:?}"
556 );
557 Ok(())
558 }
559
560 #[test]
561 fn selective_receive_skips_junk_tag() -> Result<(), Box<dyn std::error::Error>> {
562 let chunk = selective_receive();
563 assert!(verify(&chunk).is_ok());
564 let bytes = encode(&chunk);
565 let chunk = decode(&bytes)?;
566 let rt = tiny_natives(chunk)?;
567 let idx = rt.function_index("main").ok_or("main")?;
568 let outcome = rt.spawn(idx, &[])?.join();
569 rt.shutdown();
570 assert!(
571 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
572 "got {outcome:?}"
573 );
574 Ok(())
575 }
576
577 #[test]
578 fn ask_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
579 let chunk = ask_reply();
580 assert!(verify(&chunk).is_ok());
581 let bytes = encode(&chunk);
582 let chunk = decode(&bytes)?;
583 let rt = tiny_natives(chunk)?;
584 let idx = rt.function_index("main").ok_or("main")?;
585 let outcome = rt.spawn(idx, &[])?.join();
586 let sent = rt.metrics().messages_sent;
587 rt.shutdown();
588 assert!(
589 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
590 "got {outcome:?}"
591 );
592 assert!(sent >= 2);
593 Ok(())
594 }
595
596 #[test]
597 fn ask_timeout_writes_unit() -> Result<(), Box<dyn std::error::Error>> {
598 let chunk = ask_timeout_expires();
599 assert!(verify(&chunk).is_ok());
600 let rt = tiny_natives(chunk)?;
601 let idx = rt.function_index("main").ok_or("main")?;
602 let outcome = rt.spawn(idx, &[])?.join();
603 rt.shutdown();
604 assert!(
605 matches!(outcome, FlowOutcome::Completed(Value::Unit)),
606 "got {outcome:?}"
607 );
608 Ok(())
609 }
610
611 #[test]
612 fn ask_target_exit_writes_sys_exit() -> Result<(), Box<dyn std::error::Error>> {
613 let chunk = ask_target_exits();
614 assert!(verify(&chunk).is_ok());
615 let rt = tiny_natives(chunk)?;
616 let idx = rt.function_index("main").ok_or("main")?;
617 let outcome = rt.spawn(idx, &[])?.join();
618 rt.shutdown();
619 assert!(
620 matches!(
621 outcome,
622 FlowOutcome::Completed(Value::Int(n)) if n == i64::from(crate::TAG_SYS_EXIT)
623 ),
624 "got {outcome:?}"
625 );
626 Ok(())
627 }
628
629 #[test]
630 fn server_loop_joins_42() -> Result<(), Box<dyn std::error::Error>> {
631 let chunk = server_loop();
632 assert!(verify(&chunk).is_ok());
633 let rt = tiny_natives(chunk)?;
634 let idx = rt.function_index("main").ok_or("main")?;
635 let outcome = rt.spawn(idx, &[])?.join();
636 rt.shutdown();
637 assert!(
638 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
639 "got {outcome:?}"
640 );
641 Ok(())
642 }
643
644 #[test]
645 fn send_overwrites_forged_sender() -> Result<(), Box<dyn std::error::Error>> {
646 let chunk = forged_sender_send();
647 assert!(verify(&chunk).is_ok());
648 let rt = tiny_natives(chunk)?;
649 let idx = rt.function_index("main").ok_or("main")?;
650 let outcome = rt.spawn(idx, &[])?.join();
651 rt.shutdown();
652 match outcome {
653 FlowOutcome::Completed(Value::Pid(n)) => {
654 assert_ne!(n, 999, "forged make_msg sender must not survive Send");
655 assert!(n >= 1, "authenticated sender must be a live flow id");
656 Ok(())
657 }
658 other => Err(format!("expected Completed(Pid), got {other:?}").into()),
659 }
660 }
661
662 #[test]
663 fn ask_overwrites_forged_request_sender() -> Result<(), Box<dyn std::error::Error>> {
664 let chunk = forged_sender_ask();
665 assert!(verify(&chunk).is_ok());
666 let rt = tiny_natives(chunk)?;
667 let idx = rt.function_index("main").ok_or("main")?;
668 let outcome = rt.spawn(idx, &[])?.join();
669 rt.shutdown();
670 match outcome {
671 FlowOutcome::Completed(Value::Pid(n)) => {
672 assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
673 assert!(n >= 1, "authenticated sender must be a live flow id");
674 Ok(())
675 }
676 other => Err(format!("expected Completed(Pid), got {other:?}").into()),
677 }
678 }
679
680 #[test]
681 fn send_scalar_target_traps() -> Result<(), Box<dyn std::error::Error>> {
682 let mut p = Program::new("bad-cap-target");
683 p.function("main", 0, |f| {
684 let bad_cap = f.load_i32(99);
685 let req_id = f.load_i32(1);
686 let payload = f.load_i32(1);
687 let msg = f.hop(req_id, TAG_PING, payload);
688 f.send(bad_cap, msg);
689 f.return_(msg);
690 });
691 let rt = tiny_natives(p.build())?;
692 let outcome = rt.spawn(0, &[])?.join();
693 rt.shutdown();
694 assert!(
695 matches!(outcome, FlowOutcome::Failed(_)),
696 "non-Cap Send target must fail, got {outcome:?}"
697 );
698 Ok(())
699 }
700
701 #[test]
702 fn send_scalar_is_not_an_atomic_hop() -> Result<(), Box<dyn std::error::Error>> {
703 let mut p = Program::new("bad-hop");
704 p.function("main", 0, |f| {
705 let cap = f.self_cap();
706 let scalar = f.load_i32(99);
707 f.send(cap, scalar);
708 f.return_(scalar);
709 });
710 let rt = tiny(p.build())?;
711 let outcome = rt.spawn(0, &[])?.join();
712 rt.shutdown();
713 assert!(
714 matches!(outcome, FlowOutcome::Failed(_)),
715 "scalar Send must trap, got {outcome:?}"
716 );
717 Ok(())
718 }
719
720 #[test]
721 fn monitor_down_joins_normal_reason() -> Result<(), Box<dyn std::error::Error>> {
722 let chunk = monitor_down();
723 assert!(verify(&chunk).is_ok());
724 let rt = tiny_natives(chunk)?;
725 let idx = rt.function_index("main").ok_or("main")?;
726 let outcome = rt.spawn(idx, &[])?.join();
727 rt.shutdown();
728 assert!(
729 matches!(outcome, FlowOutcome::Completed(Value::Int(0))),
730 "DOWN reason should be Normal (0), got {outcome:?}"
731 );
732 Ok(())
733 }
734
735 #[test]
736 fn waiting_send_second_hop_arrives() -> Result<(), Box<dyn std::error::Error>> {
737 let cap = crate::MailboxCapacity::new(1).ok_or("cap")?;
738 let rt = Runtime::with_natives_and_config(
739 waiting_send(),
740 std_native_table(),
741 RuntimeConfig {
742 workers: 1,
743 quantum: 10_000,
744 mailbox: crate::MailboxConfig::new(cap, crate::OverflowPolicy::Reject),
745 ..Default::default()
746 },
747 )?;
748 let server = rt.function_index("server").ok_or("server")?;
749 let client = rt.function_index("client").ok_or("client")?;
750 let server_h = rt.spawn(server, &[])?;
751 let server_cap = rt.mint_cap(server_h.id())?;
752 rt.spawn(client, &[Value::Cap(server_cap)])?;
753 let outcome = server_h.join();
754 rt.shutdown();
755 assert!(
756 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
757 "second hop should be admitted after the first pop, got {outcome:?}"
758 );
759 Ok(())
760 }
761
762 #[test]
763 fn link_kills_peer_on_fault() -> Result<(), Box<dyn std::error::Error>> {
764 let mut p = Program::new("link-kill");
765 p.function("park", 0, |f| {
766 let _ = f.receive();
767 f.trap(9);
768 });
769 p.function("boom", 0, |f| f.trap(1));
770 let rt = tiny(p.build())?;
771 let park = rt.function_index("park").ok_or("park")?;
772 let boom = rt.function_index("boom").ok_or("boom")?;
773 let parked = rt.spawn(park, &[])?;
774 let killer = rt.spawn(boom, &[])?;
775 rt.link(parked.id(), killer.id())?;
776 let boom_out = killer.join();
777 let park_out = parked.join();
778 rt.shutdown();
779 assert!(matches!(boom_out, FlowOutcome::Failed(_)), "{boom_out:?}");
780 assert!(
781 matches!(park_out, FlowOutcome::Failed(_)),
782 "linked peer must die on fault, got {park_out:?}"
783 );
784 Ok(())
785 }
786
787 #[test]
788 fn linked_exit_down_carries_link_reason() -> Result<(), Box<dyn std::error::Error>> {
789 let mut p = Program::new("link-down-reason");
790 p.function("watcher", 0, |f| {
791 let msg = f.receive_match_imm(crate::TAG_SYS_DOWN);
792 let out = f.hop_payload(msg);
793 f.return_(out);
794 });
795 p.function("park", 0, |f| {
796 let _ = f.receive();
797 f.trap(9);
798 });
799 p.function("boom", 0, |f| f.trap(1));
800 let rt = tiny_natives(p.build())?;
801 let watcher = rt.spawn(rt.function_index("watcher").ok_or("watcher")?, &[])?;
802 let parked = rt.spawn(rt.function_index("park").ok_or("park")?, &[])?;
803 let killer = rt.spawn(rt.function_index("boom").ok_or("boom")?, &[])?;
804 rt.monitor(watcher.id(), parked.id())?;
805 rt.link(parked.id(), killer.id())?;
806 let _ = killer.join();
807 let watched = watcher.join();
808 let _ = parked.join();
809 rt.shutdown();
810 assert!(
811 matches!(
812 watched,
813 FlowOutcome::Completed(Value::Int(n)) if n == crate::FlowExitReason::Link.as_u64() as i64
814 ),
815 "DOWN payload must be Link, got {watched:?}"
816 );
817 Ok(())
818 }
819
820 #[test]
821 fn monitor_dead_owner_is_rejected() -> Result<(), Box<dyn std::error::Error>> {
822 let rt = tiny(add_forty_two())?;
823 let first = rt.spawn(0, &[])?;
824 let dead = first.id();
825 let done = first.join();
826 assert!(matches!(done, FlowOutcome::Completed(_)));
827 let live = rt.spawn(0, &[])?;
828 let err = rt.monitor(dead, live.id());
829 live.join();
830 rt.shutdown();
831 assert!(
832 matches!(err, Err(crate::LifecycleError::NoSuchFlow(_))),
833 "{err:?}"
834 );
835 Ok(())
836 }
837
838 #[test]
839 fn forged_cap_cannot_be_registered() -> Result<(), Box<dyn std::error::Error>> {
840 let rt = tiny(add_forty_two())?;
841 let err = rt.register_name("svc", CapId::from_raw(99_999));
842 rt.shutdown();
843 assert_eq!(err, Err(crate::LifecycleError::InvalidCapability));
844 Ok(())
845 }
846
847 #[test]
848 fn bytecode_register_name_joins() -> Result<(), Box<dyn std::error::Error>> {
849 let mut p = Program::new("reg-self");
850 p.function("main", 0, |f| {
851 let name = f.load_str("svc");
852 f.register_name(name);
853 let z = f.load_i32(1);
854 f.return_(z);
855 });
856 let rt = tiny(p.build())?;
857 let outcome = rt.spawn(0, &[])?.join();
858 rt.shutdown();
859 assert!(
860 matches!(outcome, FlowOutcome::Completed(Value::Int(1))),
861 "host-spawned flow must be able to register_name, got {outcome:?}"
862 );
863 Ok(())
864 }
865
866 #[test]
867 fn named_service_whereis_returns_cap() -> Result<(), Box<dyn std::error::Error>> {
868 let chunk = named_service();
869 assert!(verify(&chunk).is_ok());
870 let rt = tiny_natives(chunk)?;
871 let idx = rt.function_index("main").ok_or("main")?;
872 let outcome = rt
873 .spawn(idx, &[])?
874 .join_timeout(std::time::Duration::from_secs(2))
875 .ok_or("named_service timed out")?;
876 rt.shutdown();
877 assert!(
878 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
879 "named whereis should yield a usable SEND Cap, got {outcome:?}"
880 );
881 Ok(())
882 }
883
884 #[test]
885 fn confined_spawn_cannot_register_name() -> Result<(), Box<dyn std::error::Error>> {
886 let mut p = Program::new("confined-reg");
887 let child = p.function("child", 0, |f| {
888 let name = f.load_str("stolen");
889 f.register_name(name);
890 let ms = f.load_i32(5_000);
893 f.sleep(ms);
894 let z = f.load_i32(1);
895 f.return_(z);
896 });
897 p.function("main", 0, |f| {
898 let _c = f.spawn_confined(child, 0);
899 let ms = f.load_i32(80);
900 f.sleep(ms);
901 let name = f.load_str("stolen");
902 let cap = f.whereis(name);
903 let ok = f.label();
904 f.branch_if_falsy(cap, ok);
905 let one = f.load_i32(1);
906 f.return_(one);
907 f.bind(ok);
908 let zero = f.load_i32(0);
909 f.return_(zero);
910 });
911 let rt = tiny(p.build())?;
912 let idx = rt.function_index("main").ok_or("main")?;
913 let outcome = rt.spawn(idx, &[])?.join();
914 rt.shutdown();
915 assert!(
916 matches!(outcome, FlowOutcome::Completed(Value::Int(0))),
917 "confined child must not publish a name, got {outcome:?}"
918 );
919 Ok(())
920 }
921
922 #[test]
923 fn heap_quota_charges_str_on_register_store() -> Result<(), Box<dyn std::error::Error>> {
924 let mut p = Program::new("heap-str");
925 p.function("main", 0, |f| {
926 let s = f.load_str("x".repeat(200));
927 f.return_(s);
928 });
929 let mut quota = QuotaConfig::permissive();
930 quota.mem_limit = 64;
931 let rt = Runtime::with_config(
932 p.build(),
933 RuntimeConfig {
934 workers: 1,
935 quantum: 10_000,
936 mailbox: crate::MailboxConfig::DEFAULT,
937 quota,
938 ..Default::default()
939 },
940 )?;
941 let outcome = rt.spawn(0, &[])?.join();
942 rt.shutdown();
943 assert!(
944 matches!(outcome, FlowOutcome::Failed(_)),
945 "200-byte Str must exceed 64-byte heap quota, got {outcome:?}"
946 );
947 Ok(())
948 }
949
950 #[test]
951 fn host_send_stamps_sender_zero_and_mints_request_id() -> Result<(), Box<dyn std::error::Error>>
952 {
953 let mut p = Program::new("host-send");
954 p.function("main", 0, |f| {
955 let msg = f.receive();
956 let sender = f.hop_sender(msg);
957 let rid = f.hop_request_id(msg);
958 let miss = f.label();
959 f.branch_if_falsy(rid, miss);
960 f.return_(sender);
961 f.bind(miss);
962 let neg = f.load_i32(-1);
963 f.return_(neg);
964 });
965 let rt = tiny_natives(p.build())?;
966 let h = rt.spawn(0, &[])?;
967 rt.send(h.id(), Value::Message(Message::request(0, 1, 0)))?;
968 let outcome = h.join();
969 rt.shutdown();
970 assert!(
971 matches!(outcome, FlowOutcome::Completed(Value::Pid(0))),
972 "host send must stamp sender=0 (Pid) and mint request_id, got {outcome:?}"
973 );
974 Ok(())
975 }
976
977 #[test]
978 fn registry_clears_on_exit() -> Result<(), Box<dyn std::error::Error>> {
979 let mut p = Program::new("reg");
980 p.function("main", 0, |f| {
981 let ms = f.load_i32(80);
982 f.sleep(ms);
983 let z = f.load_i32(1);
984 f.return_(z);
985 });
986 let rt = tiny(p.build())?;
987 let h = rt.spawn(0, &[])?;
988 let cap = rt.mint_cap(h.id())?;
989 rt.register_name("svc", cap)?;
990 assert_eq!(rt.whereis("svc")?, Some(cap));
991 let _ = h.join();
992 assert_eq!(rt.whereis("svc")?, None);
993 rt.shutdown();
994 Ok(())
995 }
996}