roc/crates/cli/tests/cli_run.rs

1281 lines
45 KiB
Rust

extern crate pretty_assertions;
extern crate bumpalo;
extern crate indoc;
extern crate roc_collections;
extern crate roc_load;
extern crate roc_module;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{dir_from_root, file_from_root, Run};
use const_format::concatcp;
use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN, CMD_TEST};
#[cfg(all(unix, not(target_os = "macos")))]
const ALLOW_VALGRIND: bool = true;
// Disallow valgrind on macOS by default, because it reports a ton
// of false positives. For local development on macOS, feel free to
// change this to true!
#[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false;
#[cfg(windows)]
const ALLOW_VALGRIND: bool = false;
// use valgrind (if supported on the current platform)
#[derive(Debug, Clone, Copy)]
enum UseValgrind {
Yes,
No,
}
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER, "=", "legacy");
const BUILD_HOST_FLAG: &str = concatcp!("--", roc_cli::FLAG_BUILD_HOST);
const SUPPRESS_BUILD_HOST_WARNING_FLAG: &str =
concatcp!("--", roc_cli::FLAG_SUPPRESS_BUILD_HOST_WARNING);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
#[allow(dead_code)]
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const TEST_LEGACY_LINKER: bool = true;
// Surgical linker currently only supports linux x86_64,
// so we're always testing the legacy linker on other targets.
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
const TEST_LEGACY_LINKER: bool = false;
#[test]
#[cfg_attr(windows, ignore)]
fn platform_switching_rust() {
let expected_ending = "Roc <3 Rust!\n🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("examples/platform-switching", "rocLovesRust.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn platform_switching_zig() {
let expected_ending = "Roc <3 Zig!\n🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("examples/platform-switching", "rocLovesZig.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn platform_switching_wasm() {
// this is a web assembly example, but we don't test with JS at the moment
// so let's just check it for now
let runner = Run::new_roc().arg(CMD_CHECK).arg(
file_from_root("examples/platform-switching", "rocLovesWebAssembly.roc").as_path(),
);
let out = runner.run();
out.assert_clean_success();
}
#[test]
#[cfg_attr(windows, ignore)]
fn test_module_imports_pkg_w_flag() {
let expected_ending = indoc!(
r#"
0 failed and 1 passed in <ignored for test> ms.
"#
);
let runner = Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.add_args(["--main", "tests/module_imports_pkg/app.roc"])
.arg(file_from_root("crates/cli/tests/module_imports_pkg", "Module.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn test_module_imports_pkg_no_flag() {
let expected_ending = indoc!(
r#"
── UNRECOGNIZED PACKAGE in tests/module_imports_pkg/Module.roc ─────────────────
This module is trying to import from `pkg`:
3│ import pkg.Foo
^^^^^^^
A lowercase name indicates a package shorthand, but I don't know which
packages are available.
When checking a module directly, I look for a `main.roc` app or
package to resolve shorthands from.
You can create it, or specify an existing one with the --main flag."#
);
let runner = Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/module_imports_pkg", "Module.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn test_module_imports_unknown_pkg() {
let expected_ending = indoc!(
r#"
── UNRECOGNIZED PACKAGE in tests/module_imports_pkg/ImportsUnknownPkg.roc ──────
This module is trying to import from `cli`:
3│ import cli.Foo
^^^^^^^
A lowercase name indicates a package shorthand, but I don't recognize
this one. Did you mean one of these?
pkg
Note: I'm using the following module to resolve package shorthands:
tests/module_imports_pkg/app.roc
You can specify a different one with the --main flag."#
);
let runner = Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.add_args(["--main", "tests/module_imports_pkg/app.roc"])
.arg(
file_from_root(
"crates/cli/tests/module_imports_pkg",
"ImportsUnknownPkg.roc",
)
.as_path(),
);
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
/// this tests that a platform can correctly import a package
fn platform_requires_pkg() {
let expected_ending = "from app from package🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(file_from_root("crates/cli/tests/platform_requires_pkg", "app.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn transitive_expects() {
let expected_ending = indoc!(
r#"
0 failed and 3 passed in <ignored for test> ms.
"#
);
let runner = Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/expects_transitive", "main.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn transitive_expects_verbose() {
let expected_ending = indoc!(
r#"
Compiled in <ignored for test> ms.
Direct.roc:
0 failed and 2 passed in <ignored for test> ms.
Transitive.roc:
0 failed and 1 passed in <ignored for test> ms.
"#
);
let runner = Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.arg("--verbose")
.arg(file_from_root("crates/cli/tests/expects_transitive", "main.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(
windows,
ignore = "Flaky failure: Roc command failed with status ExitStatus(ExitStatus(3221225477))"
)]
fn fibonacci() {
let expected_ending = "";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/algorithms", "fibonacci.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn quicksort() {
let expected_ending =
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/algorithms", "quicksort.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
// TODO: write a new test once mono bugs are resolved in investigation
#[test]
#[cfg(not(debug_assertions))] // https://github.com/roc-lang/roc/issues/4806
fn check_virtual_dom_server() {
Run::new_roc()
.add_args([
CMD_CHECK,
file_from_root("examples/virtual-dom-wip", "example-server.roc")
.to_str()
.unwrap(),
])
.run()
.assert_clean_success();
}
// TODO: write a new test once mono bugs are resolved in investigation
#[test]
#[cfg(not(debug_assertions))] // https://github.com/roc-lang/roc/issues/4806
fn check_virtual_dom_client() {
Run::new_roc()
.add_args([
CMD_CHECK,
file_from_root("examples/virtual-dom-wip", "example-client.roc")
.to_str()
.unwrap(),
])
.run()
.assert_clean_success();
}
#[test]
#[cfg_attr(windows, ignore)]
// tea = The Elm Architecture
fn terminal_ui_tea() {
let expected_ending = "Hello Worldfoo!\n🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/tui", "main.roc").as_path())
.with_stdin_vals(vec!["foo\n"]);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(
any(target_os = "windows", target_os = "linux", target_os = "macos"),
ignore = "Segfault, likely broken because of alias analysis: https://github.com/roc-lang/roc/issues/6544"
)]
fn false_interpreter() {
// Test building
let build_runner = Run::new_roc()
.arg(CMD_BUILD)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(file_from_root("crates/cli/tests/false-interpreter", "False.roc").as_path())
.run();
build_runner.assert_clean_success();
// Test running
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/false-interpreter", "False.roc").as_path())
.add_args([
"--",
file_from_root("crates/cli/tests/false-interpreter/examples", "sqrt.false")
.as_path()
.to_str()
.unwrap(),
]);
let expected_ending = "1414";
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
mod test_platform_effects_zig {
use super::*;
use cli_utils::helpers::{file_from_root, Run};
use roc_cli::{CMD_BUILD, CMD_RUN};
static BUILD_PLATFORM_HOST: std::sync::Once = std::sync::Once::new();
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
let out = Run::new_roc()
.arg(CMD_BUILD)
.arg(BUILD_HOST_FLAG)
.arg(OPTIMIZE_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(
file_from_root("crates/cli/tests/effects/platform/", "app-stub.roc")
.as_path(),
)
.run();
out.assert_clean_success();
});
}
#[test]
#[cfg_attr(windows, ignore)]
fn interactive_effects() {
build_platform_host();
let expected_ending = "hi there!\nIt is known\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/effects", "print-line.roc").as_path())
.with_stdin_vals(vec!["hi there!"]);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn combine_tasks_with_record_builder() {
build_platform_host();
let expected_ending = "For multiple tasks: {a: 123, b: \"abc\", c: [123]}\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(file_from_root("crates/cli/tests/effects", "combine-tasks.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn inspect_logging() {
build_platform_host();
let expected_ending = "(@Community {friends: [{2}, {2}, {0, 1}], people: [(@Person {age: 27, favoriteColor: Blue, firstName: \"John\", hasBeard: Bool.true, lastName: \"Smith\"}), (@Person {age: 47, favoriteColor: Green, firstName: \"Debby\", hasBeard: Bool.false, lastName: \"Johnson\"}), (@Person {age: 33, favoriteColor: (RGB (255, 255, 0)), firstName: \"Jane\", hasBeard: Bool.false, lastName: \"Doe\"})]})\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/effects", "inspect-logging.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn module_params_pass_task() {
build_platform_host();
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/module_params", "pass_task.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with("Hi, Agus!\n");
}
}
mod test_platform_simple_zig {
use super::*;
use cli_utils::helpers::{file_from_root, Run};
use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_DEV, CMD_RUN, CMD_TEST};
static BUILD_PLATFORM_HOST: std::sync::Once = std::sync::Once::new();
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
let out = Run::new_roc()
.arg(CMD_BUILD)
.arg(BUILD_HOST_FLAG)
.arg(OPTIMIZE_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(
file_from_root("crates/cli/tests/test-platform-simple-zig", "app.roc")
.as_path(),
)
.run();
out.assert_clean_success();
});
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_str_unoptimized() {
build_platform_host();
let expected_ending = "I am Dep2.str2\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(roc_cli::CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(
file_from_root("crates/cli/tests/fixtures/multi-dep-str", "Main.roc").as_path(),
);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_str_optimized() {
build_platform_host();
let expected_ending = "I am Dep2.str2\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.arg(OPTIMIZE_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(
file_from_root("crates/cli/tests/fixtures/multi-dep-str", "Main.roc").as_path(),
);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_thunk_unoptimized() {
build_platform_host();
let expected_ending = "I am Dep2.value2\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(
file_from_root("crates/cli/tests/fixtures/multi-dep-thunk", "Main.roc")
.as_path(),
);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(
windows,
ignore = "Flaky failure: Roc command failed with status ExitStatus(ExitStatus(3221225477))"
)]
fn run_multi_dep_thunk_optimized() {
build_platform_host();
let expected_ending = "I am Dep2.value2\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.arg(OPTIMIZE_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(
file_from_root("crates/cli/tests/fixtures/multi-dep-thunk", "Main.roc")
.as_path(),
);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_packages_unoptimized() {
build_platform_host();
let expected_ending =
"Hello, World! This text came from a package! This text came from a CSV package!\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/fixtures/packages", "app.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_packages_optimized() {
build_platform_host();
let expected_ending =
"Hello, World! This text came from a package! This text came from a CSV package!\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.arg(OPTIMIZE_FLAG)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/fixtures/packages", "app.roc").as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_transitive_deps_app() {
build_platform_host();
let file_path = file_from_root(
"crates/cli/tests/fixtures/transitive-deps",
"direct-one.roc",
);
let expected_ending = "[One imports Two: From two]\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_path.as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_transitive_and_direct_dep_app() {
build_platform_host();
let file_path = file_from_root(
"crates/cli/tests/fixtures/transitive-deps",
"direct-one-and-two.roc",
);
let expected_ending = "[One imports Two: From two] | From two\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_path.as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn run_double_transitive_dep_app() {
build_platform_host();
let file_path = file_from_root(
"crates/cli/tests/fixtures/transitive-deps",
"direct-zero.roc",
);
let expected_ending = "[Zero imports One: [One imports Two: From two]]\n";
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_path.as_path());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn expects_dev() {
build_platform_host();
let expected_ending = indoc!(
r#"
── EXPECT FAILED in tests/expects/expects.roc ──────────────────────────────────
This expectation failed:
25│ expect words == []
^^^^^^^^^^^
When it failed, these variables had these values:
words : List Str
words = ["this", "will", "for", "sure", "be", "a", "large", "string", "so", "when", "we", "split", "it", "it", "will", "use", "seamless", "slices", "which", "affect", "printing"]
Program finished!
[<ignored for tests>:28] x = 42
[<ignored for tests>:30] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests>:32] "this is line 24" = "this is line 24"
[<ignored for tests>:18] x = "abc"
[<ignored for tests>:18] x = 10
[<ignored for tests>:18] x = (A (B C))
"#
);
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_DEV)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/expects", "expects.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn expects_test() {
build_platform_host();
let expected_ending = indoc!(
r#"
── EXPECT FAILED in tests/expects/expects.roc ──────────────────────────────────
This expectation failed:
6│ expect a == 2
^^^^^^
When it failed, these variables had these values:
a : Num *
a = 1
── EXPECT FAILED in tests/expects/expects.roc ──────────────────────────────────
This expectation failed:
7│ expect a == 3
^^^^^^
When it failed, these variables had these values:
a : Num *
a = 1
── EXPECT FAILED in tests/expects/expects.roc ──────────────────────────────────
This expectation failed:
11│> expect
12│> a = makeA
13│> b = 2i64
14│>
15│> a == b
When it failed, these variables had these values:
a : Int Signed64
a = 1
b : I64
b = 2
1 failed and 0 passed in <ignored for test> ms.
"#
);
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_TEST)
.with_valgrind(ALLOW_VALGRIND)
.arg(file_from_root("crates/cli/tests/expects", "expects.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn module_params() {
build_platform_host();
let expected_ending = indoc!(
r#"
App1.baseUrl: https://api.example.com/one
App2.baseUrl: http://api.example.com/two
App3.baseUrl: https://api.example.com/three
App1.getUser 1: https://api.example.com/one/users/1
App2.getUser 2: http://api.example.com/two/users/2
App3.getUser 3: https://api.example.com/three/users/3
App1.getPost 1: https://api.example.com/one/posts/1
App2.getPost 2: http://api.example.com/two/posts/2
App3.getPost 3: https://api.example.com/three/posts/3
App1.getPosts [1, 2]: ["https://api.example.com/one/posts/1", "https://api.example.com/one/posts/2"]
App2.getPosts [3, 4]: ["http://api.example.com/two/posts/3", "http://api.example.com/two/posts/4"]
App2.getPosts [5, 6]: ["http://api.example.com/two/posts/5", "http://api.example.com/two/posts/6"]
App1.getPostComments 1: https://api.example.com/one/posts/1/comments
App2.getPostComments 2: http://api.example.com/two/posts/2/comments
App2.getPostComments 3: http://api.example.com/two/posts/3/comments
App1.getCompanies [1, 2]: ["https://api.example.com/one/companies/1", "https://api.example.com/one/companies/2"]
App2.getCompanies [3, 4]: ["http://api.example.com/two/companies/3", "http://api.example.com/two/companies/4"]
App2.getCompanies [5, 6]: ["http://api.example.com/two/companies/5", "http://api.example.com/two/companies/6"]
App1.getPostAliased 1: https://api.example.com/one/posts/1
App2.getPostAliased 2: http://api.example.com/two/posts/2
App3.getPostAliased 3: https://api.example.com/three/posts/3
App1.baseUrlAliased: https://api.example.com/one
App2.baseUrlAliased: http://api.example.com/two
App3.baseUrlAliased: https://api.example.com/three
App1.getUserSafe 1: https://api.example.com/one/users/1
Prod.getUserSafe 2: http://api.example.com/prod_1/users/2?safe=true
usersApp1: ["https://api.example.com/one/users/1", "https://api.example.com/one/users/2", "https://api.example.com/one/users/3"]
getUserApp3Nested 3: https://api.example.com/three/users/3
usersApp3Passed: ["https://api.example.com/three/users/1", "https://api.example.com/three/users/2", "https://api.example.com/three/users/3"]
"#
);
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.arg(file_from_root("crates/cli/tests/module_params", "app.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn module_params_arity_mismatch() {
build_platform_host();
let runner = cli_utils::helpers::Run::new_roc().arg(CMD_DEV).arg(
file_from_root("crates/cli/tests/module_params", "arity_mismatch.roc").as_path(),
);
let out = runner.run();
insta::assert_snapshot!(out.normalize_stdout_and_stderr());
}
}
// TODO not sure if this cfg should still be here: #[cfg(not(debug_assertions))]
// this is for testing the benchmarks, to perform proper benchmarks see crates/cli/benches/README.md
mod test_benchmarks {
use super::{
UseValgrind, ALLOW_VALGRIND, BUILD_HOST_FLAG, OPTIMIZE_FLAG,
SUPPRESS_BUILD_HOST_WARNING_FLAG,
};
use cli_utils::helpers::{file_from_root, Run};
use roc_cli::CMD_BUILD;
// #[allow(unused_imports)]
use std::sync::Once;
static BUILD_PLATFORM_HOST: Once = Once::new();
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
let out = Run::new_roc()
.arg(CMD_BUILD)
.arg(BUILD_HOST_FLAG)
.arg(OPTIMIZE_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG)
.add_arg_if(super::LINKER_FLAG, super::TEST_LEGACY_LINKER)
.arg(
file_from_root("crates/cli/tests/benchmarks/platform", "app.roc").as_path(),
)
.run();
out.assert_clean_success();
});
}
fn test_benchmark(
roc_filename: &str,
stdin: Vec<&'static str>,
expected_ending: &str,
use_valgrind: UseValgrind,
) {
let dir_name = "crates/cli/tests/benchmarks";
let file_path = file_from_root(dir_name, roc_filename);
build_platform_host();
#[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))]
{
let runner = cli_utils::helpers::Run::new_roc()
.arg(roc_cli::CMD_RUN)
.add_arg_if(super::LINKER_FLAG, super::TEST_LEGACY_LINKER)
.arg(file_path.as_path())
.with_valgrind(matches!(use_valgrind, UseValgrind::Yes) && ALLOW_VALGRIND)
.with_stdin_vals(stdin);
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[cfg(feature = "wasm32-cli-run")]
check_output_wasm(file_path.as_path(), stdin, expected_ending);
#[cfg(feature = "i386-cli-run")]
check_output_i386(file_path.as_path(), stdin, expected_ending);
}
#[cfg(feature = "wasm32-cli-run")]
fn check_output_wasm(file_name: &std::path::Path, stdin: Vec<&str>, expected_ending: &str) {
// Check with and without optimizations
check_wasm_output_with_stdin(file_name, stdin.clone(), &[], expected_ending);
check_wasm_output_with_stdin(file_name, stdin, &[OPTIMIZE_FLAG], expected_ending);
}
#[cfg(feature = "wasm32-cli-run")]
fn check_wasm_output_with_stdin(
file: &std::path::Path,
stdin: Vec<&str>,
flags: &[&str],
expected_ending: &str,
) {
use super::{concatcp, TARGET_FLAG};
let mut flags = flags.to_vec();
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
let out = Run::new_roc()
.arg(CMD_BUILD)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(file)
.add_args(flags)
.run();
out.assert_clean_success();
let stdout = crate::run_wasm(&file.with_extension("wasm"), stdin);
if !stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, stdout
);
}
}
#[cfg(feature = "i386-cli-run")]
fn check_output_i386(
file_path: &std::path::Path,
stdin: Vec<&'static str>,
expected_ending: &str,
) {
use super::{concatcp, TARGET_FLAG};
let i386_target_arg = concatcp!(TARGET_FLAG, "=x86_32");
let runner = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(i386_target_arg)
.arg(file_path)
.with_stdin_vals(stdin.clone());
let out = runner.run();
out.assert_clean_success();
out.assert_stdout_and_stderr_ends_with(expected_ending);
let run_optimized = Run::new_roc()
.arg(CMD_RUN)
.add_arg_if(LINKER_FLAG, TEST_LEGACY_LINKER)
.arg(i386_target_arg)
.arg(OPTIMIZE_FLAG)
.arg(file_path)
.with_stdin_vals(stdin.clone());
let out_optimized = run_optimized.run();
out_optimized.assert_clean_success();
out_optimized.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn nqueens() {
test_benchmark("nQueens.roc", vec!["6"], "4\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn cfold() {
test_benchmark("cFold.roc", vec!["3"], "11 & 11\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn deriv() {
test_benchmark(
"deriv.roc",
vec!["2"],
"1 count: 6\n2 count: 22\n",
UseValgrind::Yes,
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_ck() {
test_benchmark("rBTreeCk.roc", vec!["100"], "10\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_insert() {
test_benchmark(
"rBTreeInsert.roc",
vec![],
"Node Black 0 {} Empty Empty\n",
UseValgrind::Yes,
)
}
/*
// rbtree_del does not work
#[test]
fn rbtree_del() {
test_benchmark(
"rBTreeDel.roc",
&["420"],
"30\n",
UseValgrind::Yes,
)
}
*/
#[test]
#[cfg_attr(windows, ignore)]
fn astar() {
if cfg!(feature = "wasm32-cli-run") {
eprintln!("WARNING: skipping testing benchmark testAStar.roc because it currently does not work on wasm32 due to dictionaries.");
} else {
test_benchmark("testAStar.roc", vec![], "True\n", UseValgrind::No)
}
}
#[test]
#[cfg_attr(windows, ignore)]
fn base64() {
test_benchmark(
"testBase64.roc",
vec![],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
UseValgrind::Yes,
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn closure() {
test_benchmark("closure.roc", vec![], "", UseValgrind::No)
}
#[test]
#[cfg_attr(windows, ignore)]
fn issue2279() {
test_benchmark("issue2279.roc", vec![], "Hello, world!\n", UseValgrind::Yes)
}
#[test]
fn quicksort_app() {
eprintln!("WARNING: skipping testing benchmark quicksortApp.roc because the test is broken right now!");
// test_benchmark(
// "quicksortApp.roc",
// vec![],
// "todo put the correct quicksort answer here",
// UseValgrind::Yes,
// )
}
}
#[test]
fn known_type_error() {
let expected_ending = indoc!(
r#"
── TYPE MISMATCH in tests/known_bad/TypeError.roc ──────────────────────────────
Something is off with the body of the main definition:
3│ main : Str -> Task {} []
4│ main = \_ ->
5│ "this is a string, not a Task {} [] function like the platform expects."
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is a string of type:
Str
But the type annotation on main says it should be:
Task {} []
Tip: Add type annotations to functions or values to help you figure
this out.
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warning found in <ignored for test> ms
"#
);
Run::new_roc()
.arg(CMD_CHECK)
.arg(file_from_root(
"crates/cli/tests/known_bad",
"TypeError.roc",
))
.run()
.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn known_type_error_with_long_path() {
let expected_ending = indoc!(
r#"
── UNUSED IMPORT in ...nown_bad/UnusedImportButWithALongFileNameForTesting.roc ─
Symbol is imported but not used.
3│ import Symbol exposing [Ident]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since Symbol isn't used, you don't need to import it.
────────────────────────────────────────────────────────────────────────────────
0 error and 1 warning found in <ignored for test> ms
"#
);
Run::new_roc()
.arg(CMD_CHECK)
.arg(file_from_root(
"crates/cli/tests/known_bad",
"UnusedImportButWithALongFileNameForTesting.roc",
))
.run()
.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn exposed_not_defined() {
let expected_ending = indoc!(
r#"
── MISSING DEFINITION in tests/known_bad/ExposedNotDefined.roc ─────────────────
bar is listed as exposed, but it isn't defined in this module.
You can fix this by adding a definition for bar, or by removing it
from exposes.
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warning found in <ignored for test> ms
"#
);
Run::new_roc()
.arg(CMD_CHECK)
.arg(file_from_root(
"crates/cli/tests/known_bad",
"ExposedNotDefined.roc",
))
.run()
.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn unused_import() {
let expected_ending = indoc!(
r#"
── UNUSED IMPORT in tests/known_bad/UnusedImport.roc ───────────────────────────
Symbol is imported but not used.
3│ import Symbol exposing [Ident]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since Symbol isn't used, you don't need to import it.
────────────────────────────────────────────────────────────────────────────────
0 error and 1 warning found in <ignored for test> ms
"#
);
Run::new_roc()
.arg(CMD_CHECK)
.arg(file_from_root(
"crates/cli/tests/known_bad",
"UnusedImport.roc",
))
.run()
.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
fn format_check_good() {
Run::new_roc()
.arg(CMD_FORMAT)
.arg(CHECK_FLAG)
.arg(file_from_root("crates/cli/tests/fixtures/format", "Formatted.roc").as_path())
.run()
.assert_clean_success();
}
#[test]
fn format_check_reformatting_needed() {
Run::new_roc()
.arg(CMD_FORMAT)
.arg(CHECK_FLAG)
.arg(file_from_root("crates/cli/tests/fixtures/format", "NotFormatted.roc").as_path())
.run()
.assert_nonzero_exit();
}
#[test]
fn format_check_folders() {
// This fails, because "NotFormatted.roc" is present in this folder
Run::new_roc()
.arg(CMD_FORMAT)
.arg(CHECK_FLAG)
.arg(dir_from_root("crates/cli/tests/fixtures/format").as_path())
.run()
.assert_nonzero_exit();
// This doesn't fail, since only "Formatted.roc" and non-roc files are present in this folder
Run::new_roc()
.arg(CMD_FORMAT)
.arg(CHECK_FLAG)
.arg(dir_from_root("crates/cli/tests/fixtures/format/formatted_directory").as_path())
.run()
.assert_clean_success();
}
}
#[cfg(feature = "wasm32-cli-run")]
fn run_wasm(wasm_path: &std::path::Path, stdin: Vec<&str>) -> String {
use bumpalo::Bump;
use roc_wasm_interp::{DefaultImportDispatcher, Instance, Value, WasiFile};
let wasm_bytes = std::fs::read(wasm_path).unwrap();
let arena = Bump::new();
let mut instance = {
let mut fake_stdin = vec![];
let fake_stdout = vec![];
let fake_stderr = vec![];
for s in stdin {
fake_stdin.extend_from_slice(s.as_bytes())
}
let mut dispatcher = DefaultImportDispatcher::default();
dispatcher.wasi.files = vec![
WasiFile::ReadOnly(fake_stdin),
WasiFile::WriteOnly(fake_stdout),
WasiFile::WriteOnly(fake_stderr),
];
Instance::from_bytes(&arena, &wasm_bytes, dispatcher, false).unwrap()
};
let result = instance.call_export("_start", []);
match result {
Ok(Some(Value::I32(0))) => match &instance.import_dispatcher.wasi.files[1] {
WasiFile::WriteOnly(fake_stdout) => String::from_utf8(fake_stdout.clone())
.unwrap_or_else(|_| "Wasm test printed invalid UTF-8".into()),
_ => unreachable!(),
},
Ok(Some(Value::I32(exit_code))) => {
format!("WASI app exit code {}", exit_code)
}
Ok(Some(val)) => {
format!("WASI _start returned an unexpected number type {:?}", val)
}
Ok(None) => "WASI _start returned no value".into(),
Err(e) => {
format!("WASI error {}", e)
}
}
}