#![allow(clippy::unwrap_used, clippy::expect_used)] //! The Elixir paths through `eval` and `load_module`, against a real Elixir node. mod common; use beamdev::eval::{Language, eval, load_module}; use beamdev::rpc::rpc_call; use common::{Runtime, TestNode}; use eetf::Term; #[tokio::test] async fn eval_returns_the_value_of_an_elixir_expression() { let node = TestNode::start(Runtime::Elixir).await; let out = eval(node.manager(), "3 + 4", Language::Elixir, Some(5_000)) .await .unwrap(); assert!( matches!(&out, Term::FixInteger(i) if i.value == 7), "{:?}", out ); } #[tokio::test] async fn eval_sequences_elixir_expressions_and_reaches_the_node() { let node = TestNode::start(Runtime::Elixir).await; let out = eval( node.manager(), "x = :erlang.system_info(:process_count); is_integer(x)", Language::Elixir, Some(5_000), ) .await .unwrap(); assert!( matches!(&out, Term::Atom(a) if a.name == "true"), "{:?}", out ); } #[tokio::test] async fn an_elixir_exception_is_reported_not_swallowed() { let node = TestNode::start(Runtime::Elixir).await; let err = eval( node.manager(), "raise \"boom\"", Language::Elixir, Some(5_000), ) .await .expect_err("should raise"); assert!( err.to_string().contains("RuntimeError") && err.to_string().contains("boom"), "{err}" ); } #[tokio::test] async fn a_redacted_struct_field_stays_redacted_in_an_exception() { let node = TestNode::start(Runtime::Elixir).await; let define = r#" defmodule Secretive do @derive {Inspect, except: [:token]} defstruct [:token] end "#; load_module(node.manager(), define, Language::Elixir, Some(5_000)) .await .unwrap(); let err = eval( node.manager(), "Map.fetch!(%Secretive{token: \"s3cr3t\"}, :missing)", Language::Elixir, Some(5_000), ) .await .expect_err("should raise"); assert!(!err.to_string().contains("s3cr3t"), "{}", err); assert!(err.to_string().contains("KeyError"), "{}", err); } #[tokio::test] async fn a_runaway_elixir_expression_is_killed() { let node = TestNode::start(Runtime::Elixir).await; let err = eval( node.manager(), ":timer.sleep(:infinity)", Language::Elixir, Some(500), ) .await .expect_err("should be killed"); assert!(err.to_string().contains("timeout"), "{}", err); } #[tokio::test] async fn load_module_defines_an_elixir_module_and_names_it_correctly() { let node = TestNode::start(Runtime::Elixir).await; let loaded = load_module( node.manager(), "defmodule Greeter do\n def hello, do: :world\nend", Language::Elixir, Some(10_000), ) .await .unwrap(); assert_eq!(loaded.module, "Elixir.Greeter"); assert!(loaded.beam_bytes > 0, "should report the compiled size"); let out = rpc_call( node.manager(), "Elixir.Greeter", "hello", vec![], Some(5_000), ) .await .unwrap(); assert!( matches!(&out, Term::Atom(a) if a.name == "world"), "{:?}", out ); } /// A plain Erlang node has no Elixir standard library, so this must fail /// clearly rather than hang or report a confusing term. #[tokio::test] async fn elixir_eval_on_an_erlang_node_fails_clearly() { let node = TestNode::start(Runtime::Erlang).await; let err = eval(node.manager(), "3 + 4", Language::Elixir, Some(5_000)) .await .expect_err("Elixir is not available on a plain Erlang node"); assert!(err.to_string().contains("has no Elixir"), "{err}"); }