mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
837 lines
21 KiB
Rust
837 lines
21 KiB
Rust
use roc_module::symbol::Interns;
|
|
use roc_mono::{
|
|
ir::ProcLayout,
|
|
layout::{CapturesNiche, LayoutCache},
|
|
};
|
|
use roc_parse::ast::Expr;
|
|
use roc_repl_eval::{
|
|
eval::{jit_to_ast, ToAstProblem},
|
|
ReplAppMemory,
|
|
};
|
|
use roc_target::TargetInfo;
|
|
use roc_types::subs::{Subs, Variable};
|
|
|
|
mod app;
|
|
pub mod run;
|
|
|
|
use app::{ExpectMemory, ExpectReplApp};
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn get_values<'a>(
|
|
target_info: TargetInfo,
|
|
arena: &'a bumpalo::Bump,
|
|
subs: &Subs,
|
|
interns: &'a Interns,
|
|
start: *const u8,
|
|
start_offset: usize,
|
|
variables: &[Variable],
|
|
) -> Result<(usize, Vec<Expr<'a>>), ToAstProblem> {
|
|
let mut result = Vec::with_capacity(variables.len());
|
|
|
|
let memory = ExpectMemory { start };
|
|
|
|
let app = ExpectReplApp {
|
|
memory: arena.alloc(memory),
|
|
offset: start_offset,
|
|
};
|
|
|
|
let app = arena.alloc(app);
|
|
|
|
for (i, variable) in variables.iter().enumerate() {
|
|
let start = app.memory.deref_usize(start_offset + i * 8);
|
|
app.offset = start;
|
|
|
|
let expr = {
|
|
let variable = *variable;
|
|
|
|
let content = subs.get_content_without_compacting(variable);
|
|
|
|
let mut layout_cache = LayoutCache::new(target_info);
|
|
let layout = layout_cache.from_var(arena, variable, subs).unwrap();
|
|
|
|
let proc_layout = ProcLayout {
|
|
arguments: &[],
|
|
result: layout,
|
|
captures_niche: CapturesNiche::no_niche(),
|
|
};
|
|
|
|
let element = jit_to_ast(
|
|
arena,
|
|
app,
|
|
"expect_repl_main_fn",
|
|
proc_layout,
|
|
content,
|
|
subs,
|
|
interns,
|
|
target_info,
|
|
)?;
|
|
|
|
element
|
|
};
|
|
|
|
result.push(expr);
|
|
}
|
|
|
|
Ok((app.offset, result))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use indoc::indoc;
|
|
use pretty_assertions::assert_eq;
|
|
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult, run_roc_dylib};
|
|
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
|
use roc_reporting::report::RenderTarget;
|
|
use target_lexicon::Triple;
|
|
|
|
use crate::run::expect_mono_module_to_dylib;
|
|
|
|
use super::*;
|
|
|
|
fn run_expect_test(source: &str, expected: &str) {
|
|
let arena = bumpalo::Bump::new();
|
|
let arena = &arena;
|
|
|
|
let triple = Triple::host();
|
|
let target = &triple;
|
|
|
|
let opt_level = roc_mono::ir::OptLevel::Normal;
|
|
let target_info = TargetInfo::from(target);
|
|
|
|
// Step 1: compile the app and generate the .o file
|
|
let src_dir = tempfile::tempdir().unwrap();
|
|
let filename = src_dir.path().join("Test.roc");
|
|
|
|
std::fs::write(&filename, source).unwrap();
|
|
|
|
let load_config = LoadConfig {
|
|
target_info,
|
|
render: RenderTarget::ColorTerminal,
|
|
threading: Threading::Single,
|
|
exec_mode: ExecutionMode::Test,
|
|
};
|
|
let loaded = roc_load::load_and_monomorphize_from_str(
|
|
arena,
|
|
filename,
|
|
source,
|
|
src_dir.path().to_path_buf(),
|
|
Default::default(),
|
|
load_config,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut loaded = loaded;
|
|
let mut expectations = std::mem::take(&mut loaded.expectations);
|
|
let loaded = loaded;
|
|
|
|
let interns = loaded.interns.clone();
|
|
|
|
let (lib, expects) = expect_mono_module_to_dylib(
|
|
arena,
|
|
target.clone(),
|
|
loaded,
|
|
opt_level,
|
|
LlvmBackendMode::CliTest,
|
|
)
|
|
.unwrap();
|
|
|
|
let arena = &bumpalo::Bump::new();
|
|
let interns = arena.alloc(interns);
|
|
|
|
const BUFFER_SIZE: usize = 1024;
|
|
|
|
let mut shared_buffer = [0u8; BUFFER_SIZE];
|
|
|
|
// communicate the mmapped name to zig/roc
|
|
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
|
let mut result = RocCallResult::default();
|
|
unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) };
|
|
|
|
let mut writer = Vec::with_capacity(1024);
|
|
let (_failed, _passed) = crate::run::run_expects(
|
|
&mut writer,
|
|
RenderTarget::ColorTerminal,
|
|
arena,
|
|
interns,
|
|
&lib,
|
|
&mut expectations,
|
|
shared_buffer.as_mut_ptr(),
|
|
expects,
|
|
)
|
|
.unwrap();
|
|
|
|
// Remove ANSI escape codes from the answer - for example:
|
|
//
|
|
// Before: "42 \u{1b}[35m:\u{1b}[0m Num *"
|
|
// After: "42 : Num *"
|
|
let bytes = strip_ansi_escapes::strip(writer).unwrap();
|
|
let actual = String::from_utf8(bytes).unwrap();
|
|
|
|
if !actual.is_empty() {
|
|
// trim off the first line; it contains a path in a tempdir that
|
|
// changes between test runs
|
|
let p = actual.bytes().position(|c| c == b'\n').unwrap();
|
|
let (_, x) = actual.split_at(p);
|
|
let x = x.trim_start();
|
|
|
|
if x != expected {
|
|
println!("{}", x);
|
|
}
|
|
|
|
assert_eq!(x, expected);
|
|
} else {
|
|
assert_eq!(actual, expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn equals_pass() {
|
|
run_expect_test(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect 1 == 1
|
|
"#,
|
|
"",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn equals_fail() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect 1 == 2
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│ expect 1 == 2
|
|
^^^^^^^^^^^^^
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_integer() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = 1
|
|
b = 2
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = 1
|
|
7│> b = 2
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Num a
|
|
a = 1
|
|
|
|
b : Num a
|
|
b = 2
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_list_of_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = ["foo"]
|
|
b = ["a string so long that it cannot be short"]
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = ["foo"]
|
|
7│> b = ["a string so long that it cannot be short"]
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : List Str
|
|
a = ["foo"]
|
|
|
|
b : List Str
|
|
b = ["a string so long that it cannot be short"]
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_list_of_list_of_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = [["foo"], []]
|
|
b = [["a string so long that it cannot be short", "bar"]]
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = [["foo"], []]
|
|
7│> b = [["a string so long that it cannot be short", "bar"]]
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : List (List Str)
|
|
a = [[""], []]
|
|
|
|
b : List (List Str)
|
|
b = [["a string so long that it cannot be short", "bar"]]
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_copy_result() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
items = [0, 1]
|
|
expected : Result I64 [OutOfBounds]*
|
|
expected = Ok 42
|
|
|
|
List.get items 0 == expected
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> items = [0, 1]
|
|
7│> expected : Result I64 [OutOfBounds]*
|
|
8│> expected = Ok 42
|
|
9│>
|
|
10│> List.get items 0 == expected
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
items : List (Num a)
|
|
items = [0, 1]
|
|
|
|
expected : Result I64 [OutOfBounds]*
|
|
expected = Ok 42
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_clone_result() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a : Result Str Str
|
|
a = Ok "foo"
|
|
|
|
b : Result Str Str
|
|
b = Err "bar"
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a : Result Str Str
|
|
7│> a = Ok "foo"
|
|
8│>
|
|
9│> b : Result Str Str
|
|
10│> b = Err "bar"
|
|
11│>
|
|
12│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Result Str Str
|
|
a = Ok "foo"
|
|
|
|
b : Result Str Str
|
|
b = Err "bar"
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_copy_record() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
vec1 = { x: 1.0, y: 2.0 }
|
|
vec2 = { x: 4.0, y: 8.0 }
|
|
|
|
vec1 == vec2
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> vec1 = { x: 1.0, y: 2.0 }
|
|
7│> vec2 = { x: 4.0, y: 8.0 }
|
|
8│>
|
|
9│> vec1 == vec2
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
vec1 : { x : Frac a, y : Frac b }
|
|
vec1 = { x: 1, y: 2 }
|
|
|
|
vec2 : { x : Frac a, y : Frac b }
|
|
vec2 = { x: 4, y: 8 }
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn two_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
strings = ["Astra mortemque praestare gradatim", "Profundum et fundamentum"]
|
|
|
|
strings == []
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> strings = ["Astra mortemque praestare gradatim", "Profundum et fundamentum"]
|
|
7│>
|
|
8│> strings == []
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
strings : List Str
|
|
strings = ["Astra mortemque praestare gradatim", "Profundum et fundamentum"]
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn compare_long_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = "Astra mortemque praestare gradatim"
|
|
b = "Profundum et fundamentum"
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = "Astra mortemque praestare gradatim"
|
|
7│> b = "Profundum et fundamentum"
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Str
|
|
a = "Astra mortemque praestare gradatim"
|
|
|
|
b : Str
|
|
b = "Profundum et fundamentum"
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn struct_with_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = {
|
|
utopia: "Astra mortemque praestare gradatim",
|
|
brillist: "Profundum et fundamentum",
|
|
}
|
|
|
|
a != a
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = {
|
|
7│> utopia: "Astra mortemque praestare gradatim",
|
|
8│> brillist: "Profundum et fundamentum",
|
|
9│> }
|
|
10│>
|
|
11│> a != a
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : { brillist : Str, utopia : Str }
|
|
a = { brillist: "Profundum et fundamentum", utopia: "Astra mortemque praestare gradatim" }
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn box_with_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = Box.box "Astra mortemque praestare gradatim"
|
|
b = Box.box "Profundum et fundamentum"
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = Box.box "Astra mortemque praestare gradatim"
|
|
7│> b = Box.box "Profundum et fundamentum"
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Box Str
|
|
a = Box.box "Astra mortemque praestare gradatim"
|
|
|
|
b : Box Str
|
|
b = Box.box "Profundum et fundamentum"
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn result_with_strings() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
expect
|
|
a = Ok "Astra mortemque praestare gradatim"
|
|
b = Err "Profundum et fundamentum"
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
5│> expect
|
|
6│> a = Ok "Astra mortemque praestare gradatim"
|
|
7│> b = Err "Profundum et fundamentum"
|
|
8│>
|
|
9│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : [Ok Str]a
|
|
a = Ok "Astra mortemque praestare gradatim"
|
|
|
|
b : [Err Str]a
|
|
b = Err "Profundum et fundamentum"
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn linked_list() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
ConsList a : [ Nil, Cons a (ConsList a) ]
|
|
|
|
cons = \list, x -> Cons x list
|
|
|
|
expect
|
|
a : ConsList Str
|
|
a = Nil
|
|
|
|
b : ConsList Str
|
|
b = Nil
|
|
|> cons "Astra mortemque praestare gradatim"
|
|
|> cons "Profundum et fundamentum"
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
9│> expect
|
|
10│> a : ConsList Str
|
|
11│> a = Nil
|
|
12│>
|
|
13│> b : ConsList Str
|
|
14│> b = Nil
|
|
15│> |> cons "Astra mortemque praestare gradatim"
|
|
16│> |> cons "Profundum et fundamentum"
|
|
17│>
|
|
18│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : ConsList Str
|
|
a = Nil
|
|
|
|
b : ConsList Str
|
|
b = Cons "Profundum et fundamentum" (Cons "Astra mortemque praestare gradatim" Nil)
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nullable_tree() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
Tree a : [ Empty, Leaf a, Node (Tree a) (Tree a) ]
|
|
|
|
cons = \list, x -> Cons x list
|
|
|
|
expect
|
|
a : Tree Str
|
|
a = Leaf "Astra mortemque praestare gradatim"
|
|
|
|
b : Tree Str
|
|
b = Node Empty Empty
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
9│> expect
|
|
10│> a : Tree Str
|
|
11│> a = Leaf "Astra mortemque praestare gradatim"
|
|
12│>
|
|
13│> b : Tree Str
|
|
14│> b = Node Empty Empty
|
|
15│>
|
|
16│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Tree Str
|
|
a = Leaf "Astra mortemque praestare gradatim"
|
|
|
|
b : Tree Str
|
|
b = Node Empty Empty
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn recursive_tree() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
Tree a : [ Leaf a, Node (Tree a) (Tree a) ]
|
|
|
|
expect
|
|
a : Tree Str
|
|
a = Leaf "Astra mortemque praestare gradatim"
|
|
|
|
b : Tree Str
|
|
b = Node (Leaf "a") (Leaf "b")
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
7│> expect
|
|
8│> a : Tree Str
|
|
9│> a = Leaf "Astra mortemque praestare gradatim"
|
|
10│>
|
|
11│> b : Tree Str
|
|
12│> b = Node (Leaf "a") (Leaf "b")
|
|
13│>
|
|
14│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : Tree Str
|
|
a = Leaf "Astra mortemque praestare gradatim"
|
|
|
|
b : Tree Str
|
|
b = Node (Leaf "a") (Leaf "b")
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rose_tree() {
|
|
run_expect_test(
|
|
indoc!(
|
|
r#"
|
|
app "test" provides [main] to "./platform"
|
|
|
|
main = 0
|
|
|
|
RoseTree a : [Tree a (List (RoseTree a))]
|
|
|
|
expect
|
|
a : RoseTree Str
|
|
a = Tree "Astra mortemque praestare gradatim" []
|
|
|
|
b : RoseTree Str
|
|
b = Tree "foo" [ Tree "bar" [] ]
|
|
|
|
a == b
|
|
"#
|
|
),
|
|
indoc!(
|
|
r#"
|
|
This expectation failed:
|
|
|
|
7│> expect
|
|
8│> a : RoseTree Str
|
|
9│> a = Tree "Astra mortemque praestare gradatim" []
|
|
10│>
|
|
11│> b : RoseTree Str
|
|
12│> b = Tree "foo" [ Tree "bar" [] ]
|
|
13│>
|
|
14│> a == b
|
|
|
|
When it failed, these variables had these values:
|
|
|
|
a : RoseTree Str
|
|
a = Tree "Astra mortemque praestare gradatim" []
|
|
|
|
b : RoseTree Str
|
|
b = Tree "foo" [Tree "bar" []]
|
|
"#
|
|
),
|
|
);
|
|
}
|
|
}
|