use std::str::FromStr;
use backhopper_core::compat::source_attributes::{Presence, Unreadable, extract_exports};
use backhopper_core::model::names::{Arity, FunctionName};
fn fa(name: &str, arity: u8) -> (FunctionName, Arity) {
(FunctionName::from_str(name).unwrap(), Arity::new(arity))
}
#[test]
fn a_plain_export_list_is_known() {
let surface = extract_exports("-module(m).\n-export([f/0, g/1]).\n");
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Present);
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
assert_eq!(surface.lookup(&fa("h", 0)), Presence::Absent);
}
#[test]
fn several_export_attributes_union() {
let surface = extract_exports("-export([f/0]).\n-export([g/1]).\n");
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Present);
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
}
#[test]
fn export_all_makes_the_surface_unknowable() {
let surface = extract_exports("-compile(export_all).\n");
assert!(!surface.is_complete());
assert_eq!(
surface.lookup(&fa("f", 0)),
Presence::Unknowable(Unreadable::ExportAll)
);
let surface = extract_exports("-compile([export_all, nowarn_export_all]).\n");
assert!(!surface.is_complete());
}
#[test]
fn a_parse_transform_makes_the_surface_unknowable() {
let surface = extract_exports("-compile({parse_transform, lager_transform}).\n");
assert!(!surface.is_complete());
assert_eq!(
surface.lookup(&fa("f", 0)),
Presence::Unknowable(Unreadable::ParseTransform)
);
}
#[test]
fn a_macro_in_the_export_list_names_itself_as_the_ground() {
let surface = extract_exports("-export([f/0, ?API]).\n");
assert!(!surface.is_complete());
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Present);
assert_eq!(
surface.lookup(&fa("g", 0)),
Presence::Unknowable(Unreadable::MacroInExportList)
);
}
#[test]
fn no_export_attribute_is_known_and_empty() {
let surface = extract_exports("-module(m).\nf() -> ok.\n");
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Absent);
}
#[test]
fn export_type_is_not_a_function_export() {
let surface = extract_exports("-export_type([t/0]).\n-export([f/1]).\n");
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("f", 1)), Presence::Present);
assert_eq!(surface.lookup(&fa("t", 0)), Presence::Absent);
}
#[test]
fn merging_an_unreadable_surface_makes_the_union_unreadable() {
let mut surface = extract_exports("-export([f/0]).\n");
surface.merge(extract_exports("-compile(export_all).\n-export([g/1]).\n"));
assert!(!surface.is_complete());
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Present);
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
assert_eq!(
surface.lookup(&fa("h", 2)),
Presence::Unknowable(Unreadable::ExportAll)
);
}
#[test]
fn merging_two_readable_surfaces_stays_readable() {
let mut surface = extract_exports("-export([f/0]).\n");
surface.merge(extract_exports("-export([g/1]).\n"));
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
assert_eq!(surface.lookup(&fa("h", 2)), Presence::Absent);
}
#[test]
fn merging_two_unreadable_surfaces_keeps_the_ground_already_held() {
let mut surface = extract_exports("-export([f/0, ?API]).\n");
surface.merge(extract_exports("-compile(export_all).\n"));
assert_eq!(
surface.lookup(&fa("h", 2)),
Presence::Unknowable(Unreadable::MacroInExportList)
);
}
#[test]
fn a_parse_transform_outranks_export_all_in_either_merge_order() {
let mut surface = extract_exports("-compile(export_all).\n-export([f/0]).\n");
surface.merge(extract_exports(
"-compile({parse_transform, lager_transform}).\n",
));
assert_eq!(
surface.lookup(&fa("f", 0)),
Presence::Unknowable(Unreadable::ParseTransform)
);
let mut surface = extract_exports("-compile({parse_transform, lager_transform}).\n");
surface.merge(extract_exports("-compile(export_all).\n-export([f/0]).\n"));
assert_eq!(
surface.lookup(&fa("f", 0)),
Presence::Unknowable(Unreadable::ParseTransform)
);
}
#[test]
fn a_parse_transform_outranks_export_all_within_one_module() {
let surface = extract_exports(
"-compile(export_all).\n-compile({parse_transform, lager_transform}).\n-export([f/0]).\n",
);
assert_eq!(
surface.lookup(&fa("f", 0)),
Presence::Unknowable(Unreadable::ParseTransform)
);
}
#[test]
fn an_export_after_a_comment_line_is_read() {
let src = "-export([\n init/1,\n %% protocol helpers\n make_enqueue/3,\n make_checkout/3\n ]).\n";
let surface = extract_exports(src);
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("init", 1)), Presence::Present);
assert_eq!(surface.lookup(&fa("make_enqueue", 3)), Presence::Present);
assert_eq!(surface.lookup(&fa("make_checkout", 3)), Presence::Present);
}
#[test]
fn an_export_after_a_single_percent_comment_is_read() {
let src = "-export([new/8,\n % exclusive_owner\n get_exclusive_owner/1,\n % name (#resource)\n get_name/1,\n % pid\n get_pid/1,\n set_pid/2]).\n";
let surface = extract_exports(src);
for key in [
fa("get_exclusive_owner", 1),
fa("get_name", 1),
fa("get_pid", 1),
fa("set_pid", 2),
] {
assert_eq!(surface.lookup(&key), Presence::Present, "{key:?}");
}
}
#[test]
fn a_question_mark_in_an_export_list_comment_is_not_a_macro() {
let surface = extract_exports("-export([f/0, % see ?MODULE\n g/1]).\n");
assert!(surface.is_complete());
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
}
#[test]
fn a_closing_bracket_in_an_export_list_comment_does_not_end_the_list() {
let surface = extract_exports("-export([f/0, % [deprecated]\n g/1, h/2]).\n");
assert_eq!(surface.lookup(&fa("g", 1)), Presence::Present);
assert_eq!(surface.lookup(&fa("h", 2)), Presence::Present);
}
#[test]
fn a_real_macro_in_an_export_list_still_withholds() {
let surface = extract_exports("-export([f/0, % a note\n ?EXPORTS]).\n");
assert_eq!(
surface.lookup(&fa("g", 1)),
Presence::Unknowable(Unreadable::MacroInExportList)
);
assert_eq!(surface.lookup(&fa("f", 0)), Presence::Present);
}