roc/crates/repl_expect/src/lib.rs
Folkert de Vries c7f9a39625
Merge pull request #3702 from rtfeldman/test-str-builtins
`roc test` on `Str` builtins
2022-08-07 13:14:17 +02:00

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" []]
"#
),
);
}
}