Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221#![allow(clippy::unwrap_used, clippy::expect_used)]
//! Evaluating source and loading modules on a stock Erlang node.
mod common;
use beamdev::connection::ConnectionManager;use beamdev::eval::{Language, eval, load_module};use beamdev::rpc::rpc_call;use common::{Runtime, TestNode};use eetf::Term;
async fn process_count(cm: &ConnectionManager) -> i32 { let out = eval(cm, "length(processes()).", Language::Erlang, Some(5_000)) .await .expect("count processes"); match out { Term::FixInteger(i) => i.value, other => panic!("expected an integer, got {:?}", other), }}
#[tokio::test]async fn eval_reaches_functions_the_old_sandbox_forbade() { let node = TestNode::start(Runtime::Erlang).await; let out = eval( node.manager(), "erlang:process_info(self(), [message_queue_len]).", Language::Erlang, Some(5_000), ) .await .expect("eval"); assert!(matches!(&out, Term::List(_)));}
#[tokio::test]async fn eval_sequences_multiple_expressions() { let node = TestNode::start(Runtime::Erlang).await; let out = eval( node.manager(), "X = 2, Y = 3, X * Y.", Language::Erlang, Some(5_000), ) .await .expect("eval"); assert!(matches!(&out, Term::FixInteger(i) if i.value == 6));}
#[tokio::test]async fn eval_reports_a_parse_error() { let node = TestNode::start(Runtime::Erlang).await; let err = eval( node.manager(), "this is not erlang.", Language::Erlang, Some(5_000), ) .await .expect_err("should not parse");
assert!(err.to_string().contains("parse failed"), "{err}");}
#[tokio::test]async fn load_module_compiles_and_loads_erlang_source() { let node = TestNode::start(Runtime::Erlang).await; let source = "-module(proto_mod).\n\ -export([hi/0]).\n\ hi() -> lists:map(fun(X) -> X * 2 end, [1, 2, 3]).";
let loaded = load_module(node.manager(), source, Language::Erlang, Some(10_000)) .await .expect("load"); assert_eq!(loaded.module, "proto_mod"); assert!(loaded.beam_bytes > 0);
// Erlang encodes small-integer lists as STRING_EXT, so [2,4,6] is a ByteList. let out = rpc_call(node.manager(), "proto_mod", "hi", vec![], Some(5_000)) .await .expect("call loaded module"); match &out { Term::ByteList(b) => assert_eq!(b.bytes, vec![2, 4, 6]), other => panic!("expected [2,4,6], got {:?}", other), }}
/// rex addresses its reply to the pid we send as `From`, so that pid must live/// on the node we actually handshook as -- not a hardcoded name. Every test/// here connects under a generated local name, so reaching the node at all/// proves the reply came back to the right place.#[tokio::test]async fn rpc_works_with_a_generated_local_node_name() { let node = TestNode::start(Runtime::Erlang).await; let out = rpc_call(node.manager(), "erlang", "node", vec![], Some(5_000)) .await .expect("rpc should reach us at our real local node name"); assert!(matches!(&out, Term::Atom(a) if a.name == node.name));}
/// The RPC timeout bounds how long we wait, not how long the node works, so the/// runner has to kill the process itself.#[tokio::test]async fn a_runaway_expression_is_killed_and_leaves_nothing_behind() { let node = TestNode::start(Runtime::Erlang).await; let before = process_count(node.manager()).await;
let err = eval( node.manager(), "timer:sleep(infinity).", Language::Erlang, Some(500), ) .await .expect_err("should be killed"); assert!(err.to_string().contains("timeout"), "{}", err);
// The counting eval is itself a process, so allow for the one in flight. let after = process_count(node.manager()).await; assert!( after <= before, "leaked processes: {} before, {} after", before, after );}
#[tokio::test]async fn an_allocation_bomb_is_killed_and_leaves_nothing_behind() { let node = TestNode::start(Runtime::Erlang).await; let before = process_count(node.manager()).await;
let err = eval( node.manager(), "lists:seq(1, 100000000).", Language::Erlang, Some(20_000), ) .await .expect_err("should be killed"); assert!(err.to_string().contains("killed"), "{}", err);
let after = process_count(node.manager()).await; assert!( after <= before, "leaked processes: {} before, {} after", before, after );}
#[tokio::test]async fn an_exception_reports_its_class_and_reason() { let node = TestNode::start(Runtime::Erlang).await; let err = eval(node.manager(), "1 / 0.", Language::Erlang, Some(5_000)) .await .expect_err("should raise"); assert!(err.to_string().contains("badarith"), "{}", err);}
/// The runner is not deployed on connect, so the first eval has to notice it is/// missing and load it.#[tokio::test]async fn the_first_eval_deploys_the_runner_itself() { let node = TestNode::start(Runtime::Erlang).await;
let loaded = eval( node.manager(), "code:which(beamdev_runner) =/= non_existing.", Language::Erlang, Some(5_000), ) .await .expect("eval should deploy the runner and succeed");
assert!( matches!(&loaded, Term::Atom(a) if a.name == "true"), "{:?}", loaded );}
/// If the node loses the runner -- a restart, a purge -- the next eval puts it/// back rather than failing.#[tokio::test]async fn a_purged_runner_is_redeployed() { let node = TestNode::start(Runtime::Erlang).await; eval(node.manager(), "ok.", Language::Erlang, Some(5_000)) .await .expect("first eval");
rpc_call( node.manager(), "code", "purge", vec![eetf::Term::from(eetf::Atom::from("beamdev_runner"))], Some(5_000), ) .await .expect("purge"); rpc_call( node.manager(), "code", "delete", vec![eetf::Term::from(eetf::Atom::from("beamdev_runner"))], Some(5_000), ) .await .expect("delete");
let out = eval(node.manager(), "2 + 2.", Language::Erlang, Some(5_000)) .await .expect("eval should redeploy the runner"); assert!( matches!(&out, Term::FixInteger(i) if i.value == 4), "{:?}", out );}