#![allow(clippy::unwrap_used, clippy::expect_used)] //! A fake Erlang node that serves the `$gen_call` requests our RPC chains make. //! //! Test-only: it answers `rex` from a handler the test supplies, so a chain of //! calls can be exercised without a running BEAM. use crate::connection::TestPeer; use eetf::{Pid, Term, Tuple}; use erl_dist::message::Message; use std::sync::{Arc, Mutex}; /// Records the MFAs a fake node was asked to run, in order. #[derive(Clone, Default)] pub(crate) struct CallLog(Arc>>); impl CallLog { pub(crate) fn mfas(&self) -> Vec<(String, String)> { self.0.lock().unwrap().clone() } } /// Serves `$gen_call` requests to `rex` from `handler` until the /// connection closes. pub(crate) fn spawn( peer: TestPeer, handler: impl Fn(&str, &str, &[Term]) -> Term + Send + 'static, ) -> CallLog { let log = CallLog::default(); let recorded = log.clone(); tokio::spawn(async move { let (mut peer_tx, mut peer_rx) = peer; while let Ok(message) = peer_rx.recv().await { let Message::RegSend(reg_send) = message else { continue; }; let Some((reply_to, tag, module, function, args)) = decode_gen_call(reg_send.message) else { continue; }; recorded .0 .lock() .unwrap() .push((module.clone(), function.clone())); let result = handler(&module, &function, &args); let reply = Message::send(reply_to, Term::from(Tuple::from(vec![tag, result]))); if peer_tx.send(reply).await.is_err() { break; } } }); log } /// Unpacks `{'$gen_call', {Pid, Tag}, {call, M, F, Args, GL}}`. fn decode_gen_call(term: Term) -> Option<(Pid, Term, String, String, Vec)> { let Term::Tuple(outer) = term else { return None; }; let [_, from, call] = outer.elements.as_slice() else { return None; }; let Term::Tuple(from) = from else { return None; }; let [Term::Pid(reply_to), tag] = from.elements.as_slice() else { return None; }; let Term::Tuple(call) = call else { return None; }; let [ _, Term::Atom(module), Term::Atom(function), Term::List(args), _, ] = call.elements.as_slice() else { return None; }; Some(( reply_to.clone(), tag.clone(), module.name.clone(), function.name.clone(), args.elements.clone(), )) }