mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-15 21:23:57 +00:00
Merge remote-tracking branch 'remote/main' into upgrade-llvm-zig
This commit is contained in:
commit
2feb5d3c2e
426 changed files with 8889 additions and 4190 deletions
|
|
@ -274,6 +274,7 @@ pub fn build_app() -> Command {
|
|||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file to test")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.num_args(0..)
|
||||
.required(false)
|
||||
.default_value(DEFAULT_ROC_FILENAME)
|
||||
)
|
||||
|
|
@ -516,18 +517,21 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
|
|||
Some(n) => Threading::AtMost(*n),
|
||||
};
|
||||
|
||||
let path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
||||
let paths: Vec<_> = matches.get_many::<PathBuf>(ROC_FILE).unwrap().collect();
|
||||
|
||||
// Spawn the root task
|
||||
if !path.exists() {
|
||||
let current_dir = env::current_dir().unwrap();
|
||||
let expected_file_path = current_dir.join(path);
|
||||
let paths: Vec<_> = {
|
||||
let mut flatten_paths: Vec<_> = vec![];
|
||||
for path in paths.into_iter() {
|
||||
// Spawn the root task
|
||||
if !path.exists() {
|
||||
let current_dir = env::current_dir().unwrap();
|
||||
let expected_file_path = current_dir.join(path);
|
||||
|
||||
let current_dir_string = current_dir.display();
|
||||
let expected_file_path_string = expected_file_path.display();
|
||||
let current_dir_string = current_dir.display();
|
||||
let expected_file_path_string = expected_file_path.display();
|
||||
|
||||
// TODO these should use roc_reporting to display nicer error messages.
|
||||
match matches.value_source(ROC_FILE) {
|
||||
// TODO these should use roc_reporting to display nicer error messages.
|
||||
match matches.value_source(ROC_FILE) {
|
||||
Some(ValueSource::DefaultValue) => {
|
||||
eprintln!(
|
||||
"\nThe current directory ({current_dir_string}) does not contain a {DEFAULT_ROC_FILENAME} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n"
|
||||
|
|
@ -535,116 +539,141 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
|
|||
}
|
||||
_ => eprintln!("\nThis file was not found: {expected_file_path_string}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n"),
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let arena = &arena;
|
||||
let function_kind = FunctionKind::from_env();
|
||||
|
||||
let opt_main_path = matches.get_one::<PathBuf>(FLAG_MAIN);
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let load_config = LoadConfig {
|
||||
target,
|
||||
function_kind,
|
||||
// TODO: expose this from CLI?
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
palette: roc_reporting::report::DEFAULT_PALETTE,
|
||||
threading,
|
||||
exec_mode: ExecutionMode::Test,
|
||||
};
|
||||
let load_result = roc_load::load_and_monomorphize(
|
||||
arena,
|
||||
path.to_path_buf(),
|
||||
opt_main_path.cloned(),
|
||||
RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = match load_result {
|
||||
Ok(loaded) => loaded,
|
||||
Err(LoadMonomorphizedError::LoadingProblem(problem)) => {
|
||||
return handle_loading_problem(problem);
|
||||
}
|
||||
Err(LoadMonomorphizedError::ErrorModule(module)) => {
|
||||
return handle_error_module(module, start_time.elapsed(), path.as_os_str(), false);
|
||||
process::exit(1);
|
||||
} else if path.is_dir() {
|
||||
find_all_roc_files(path, &mut flatten_paths);
|
||||
} else {
|
||||
flatten_paths.push(path.clone());
|
||||
}
|
||||
}
|
||||
flatten_paths
|
||||
};
|
||||
let problems = report_problems_monomorphized(&mut loaded);
|
||||
|
||||
let mut expectations = std::mem::take(&mut loaded.expectations);
|
||||
let mut all_files_total_failed_count = 0;
|
||||
let mut all_files_total_passed_count = 0;
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
let sources = loaded.sources.clone();
|
||||
for path in paths.iter() {
|
||||
let arena = &arena;
|
||||
let function_kind = FunctionKind::from_env();
|
||||
|
||||
let (dyn_lib, expects_by_module, layout_interner) =
|
||||
roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
arena,
|
||||
let opt_main_path = matches.get_one::<PathBuf>(FLAG_MAIN);
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let load_config = LoadConfig {
|
||||
target,
|
||||
loaded,
|
||||
opt_level,
|
||||
LlvmBackendMode::CliTest,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Print warnings before running tests.
|
||||
{
|
||||
debug_assert_eq!(
|
||||
problems.errors, 0,
|
||||
"if there were errors, we would have already exited."
|
||||
function_kind,
|
||||
// TODO: expose this from CLI?
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
palette: roc_reporting::report::DEFAULT_PALETTE,
|
||||
threading,
|
||||
exec_mode: ExecutionMode::Test,
|
||||
};
|
||||
let load_result = roc_load::load_and_monomorphize(
|
||||
arena,
|
||||
path.to_path_buf(),
|
||||
opt_main_path.cloned(),
|
||||
RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
|
||||
load_config,
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
problems.print_error_warning_count(start_time.elapsed());
|
||||
println!(".\n\nRunning tests…\n\n\x1B[36m{}\x1B[39m", "─".repeat(80));
|
||||
|
||||
let mut loaded = match load_result {
|
||||
Ok(loaded) => loaded,
|
||||
Err(LoadMonomorphizedError::LoadingProblem(problem)) => {
|
||||
return handle_loading_problem(problem);
|
||||
}
|
||||
Err(LoadMonomorphizedError::ErrorModule(module)) => {
|
||||
return handle_error_module(module, start_time.elapsed(), path.as_os_str(), false);
|
||||
}
|
||||
};
|
||||
let problems = report_problems_monomorphized(&mut loaded);
|
||||
|
||||
let mut expectations = std::mem::take(&mut loaded.expectations);
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
let sources = loaded.sources.clone();
|
||||
|
||||
let (dyn_lib, expects_by_module, layout_interner) =
|
||||
roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
arena,
|
||||
target,
|
||||
loaded,
|
||||
opt_level,
|
||||
LlvmBackendMode::CliTest,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Print warnings before running tests.
|
||||
{
|
||||
debug_assert_eq!(
|
||||
problems.errors, 0,
|
||||
"if there were errors, we would have already exited."
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
problems.print_error_warning_count(start_time.elapsed());
|
||||
println!(".\n\nRunning tests…\n\n\x1B[36m{}\x1B[39m", "─".repeat(80));
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
let arena = &bumpalo::Bump::new();
|
||||
let interns = arena.alloc(interns);
|
||||
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
let mut total_failed_count = 0;
|
||||
let mut total_passed_count = 0;
|
||||
|
||||
let mut results_by_module = Vec::new();
|
||||
let global_layout_interner = layout_interner.into_global();
|
||||
|
||||
let compilation_duration = start_time.elapsed();
|
||||
|
||||
for (module_id, expects) in expects_by_module.into_iter() {
|
||||
let test_start_time = Instant::now();
|
||||
|
||||
let (failed_count, passed_count) = roc_repl_expect::run::run_toplevel_expects(
|
||||
&mut writer,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&global_layout_interner,
|
||||
&dyn_lib,
|
||||
&mut expectations,
|
||||
expects,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tests_duration = test_start_time.elapsed();
|
||||
|
||||
results_by_module.push(ModuleTestResults {
|
||||
module_id,
|
||||
failed_count,
|
||||
passed_count,
|
||||
tests_duration,
|
||||
});
|
||||
|
||||
total_failed_count += failed_count;
|
||||
total_passed_count += passed_count;
|
||||
}
|
||||
|
||||
let total_duration = start_time.elapsed();
|
||||
all_files_total_failed_count += total_failed_count;
|
||||
all_files_total_passed_count += total_passed_count;
|
||||
if total_failed_count == 0 && total_passed_count == 0 {
|
||||
// Only report no expectations found once.
|
||||
continue;
|
||||
} else if matches.get_flag(FLAG_VERBOSE) {
|
||||
println!("Compiled in {} ms.", compilation_duration.as_millis());
|
||||
for module_test_results in results_by_module {
|
||||
print_test_results(module_test_results, &sources);
|
||||
}
|
||||
} else {
|
||||
let test_summary_str =
|
||||
test_summary(total_failed_count, total_passed_count, total_duration);
|
||||
println!("{test_summary_str}");
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
let arena = &bumpalo::Bump::new();
|
||||
let interns = arena.alloc(interns);
|
||||
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
let mut total_failed_count = 0;
|
||||
let mut total_passed_count = 0;
|
||||
|
||||
let mut results_by_module = Vec::new();
|
||||
let global_layout_interner = layout_interner.into_global();
|
||||
|
||||
let compilation_duration = start_time.elapsed();
|
||||
|
||||
for (module_id, expects) in expects_by_module.into_iter() {
|
||||
let test_start_time = Instant::now();
|
||||
|
||||
let (failed_count, passed_count) = roc_repl_expect::run::run_toplevel_expects(
|
||||
&mut writer,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&global_layout_interner,
|
||||
&dyn_lib,
|
||||
&mut expectations,
|
||||
expects,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tests_duration = test_start_time.elapsed();
|
||||
|
||||
results_by_module.push(ModuleTestResults {
|
||||
module_id,
|
||||
failed_count,
|
||||
passed_count,
|
||||
tests_duration,
|
||||
});
|
||||
|
||||
total_failed_count += failed_count;
|
||||
total_passed_count += passed_count;
|
||||
}
|
||||
|
||||
let total_duration = start_time.elapsed();
|
||||
|
||||
if total_failed_count == 0 && total_passed_count == 0 {
|
||||
if all_files_total_failed_count == 0 && all_files_total_passed_count == 0 {
|
||||
// TODO print this in a more nicely formatted way!
|
||||
println!("No expectations were found.");
|
||||
|
||||
|
|
@ -655,18 +684,32 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
|
|||
// running tests altogether!
|
||||
Ok(2)
|
||||
} else {
|
||||
if matches.get_flag(FLAG_VERBOSE) {
|
||||
println!("Compiled in {} ms.", compilation_duration.as_millis());
|
||||
for module_test_results in results_by_module {
|
||||
print_test_results(module_test_results, &sources);
|
||||
}
|
||||
} else {
|
||||
let test_summary_str =
|
||||
test_summary(total_failed_count, total_passed_count, total_duration);
|
||||
println!("{test_summary_str}");
|
||||
}
|
||||
Ok((all_files_total_failed_count > 0) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
Ok((total_failed_count > 0) as i32)
|
||||
fn find_all_roc_files(path: &PathBuf, flatten_paths: &mut Vec<PathBuf>) {
|
||||
if path.is_dir() {
|
||||
if let Ok(entries) = std::fs::read_dir(path) {
|
||||
entries.for_each(|entry| {
|
||||
if let Ok(entry) = entry {
|
||||
let entry_path = entry.path();
|
||||
find_all_roc_files(&entry_path, flatten_paths);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
eprintln!(
|
||||
"\nSomething went wrong opening the directory {}\n",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
} else if path.is_file() {
|
||||
match path.extension() {
|
||||
Some(extension) if extension == "roc" => {
|
||||
flatten_paths.push(path.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ mod cli_tests {
|
|||
.unwrap();
|
||||
|
||||
let cli_build = ExecCli::new(
|
||||
roc_cli::CMD_RUN,
|
||||
roc_cli::CMD_DEV,
|
||||
file_from_root("examples/platform-switching", "rocLovesRust.roc"),
|
||||
);
|
||||
|
||||
|
|
@ -371,7 +371,7 @@ mod cli_tests {
|
|||
mod test_platform_basic_cli {
|
||||
|
||||
use super::*;
|
||||
use roc_cli::CMD_RUN;
|
||||
use roc_cli::CMD_DEV;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
|
|
@ -430,7 +430,7 @@ mod cli_tests {
|
|||
)]
|
||||
fn module_params_issue_7116() {
|
||||
let cli_build = ExecCli::new(
|
||||
CMD_RUN,
|
||||
CMD_DEV,
|
||||
file_from_root(
|
||||
"crates/cli/tests/test-projects/module_params",
|
||||
"issue_7116.roc",
|
||||
|
|
@ -451,7 +451,7 @@ mod cli_tests {
|
|||
)]
|
||||
fn module_params_pass_task() {
|
||||
let cli_build = ExecCli::new(
|
||||
CMD_RUN,
|
||||
CMD_DEV,
|
||||
file_from_root(
|
||||
"crates/cli/tests/test-projects/module_params",
|
||||
"pass_task.roc",
|
||||
|
|
@ -971,6 +971,62 @@ mod cli_tests {
|
|||
|
||||
cli_build.run().assert_clean_stdout(expected_out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn effectful_suffixed_record_field() {
|
||||
build_platform_host();
|
||||
|
||||
let cli_build = ExecCli::new(
|
||||
roc_cli::CMD_DEV,
|
||||
file_from_root(
|
||||
"crates/cli/tests/test-projects/effectful",
|
||||
"suffixed_record_field.roc",
|
||||
),
|
||||
);
|
||||
|
||||
let expected_output = "notEffectful: hardcoded\neffectful: from stdin\n";
|
||||
|
||||
cli_build.check_build_and_run(
|
||||
expected_output,
|
||||
ALLOW_VALGRIND,
|
||||
Some("from stdin"),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn effectful_on_err() {
|
||||
build_platform_host();
|
||||
|
||||
let cli_build = ExecCli::new(
|
||||
roc_cli::CMD_DEV,
|
||||
file_from_root("crates/cli/tests/test-projects/effectful", "on_err.roc"),
|
||||
);
|
||||
|
||||
let expected_output = "Enter your password:\nLOG: Failed login attempt\n";
|
||||
|
||||
cli_build.check_build_and_run(expected_output, ALLOW_VALGRIND, Some("42"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn effectful_for_each_try() {
|
||||
build_platform_host();
|
||||
|
||||
let cli_build = ExecCli::new(
|
||||
roc_cli::CMD_DEV,
|
||||
file_from_root(
|
||||
"crates/cli/tests/test-projects/effectful",
|
||||
"for_each_try.roc",
|
||||
),
|
||||
);
|
||||
|
||||
let expected_output = "✅ 0\n✅ 2\n✅ 4\n✅ 6\n✅ 8\n9 is not even! ABORT!\n";
|
||||
|
||||
cli_build.check_build_and_run(expected_output, ALLOW_VALGRIND, None, None);
|
||||
}
|
||||
}
|
||||
|
||||
// this is for testing the benchmarks (on small inputs), to perform proper benchmarks see crates/cli/benches/README.md
|
||||
|
|
|
|||
35
crates/cli/tests/test-projects/effectful/echo.roc
Normal file
35
crates/cli/tests/test-projects/effectful/echo.roc
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! : {} => {}
|
||||
main! = \{} -> tick! {}
|
||||
|
||||
tick! = \{} ->
|
||||
line = Effect.getLine! {}
|
||||
|
||||
if !(Str.isEmpty line) then
|
||||
Effect.putLine! (echo line)
|
||||
else
|
||||
Effect.putLine! "Received no input."
|
||||
|
||||
echo : Str -> Str
|
||||
echo = \shout ->
|
||||
silence = \length -> List.repeat ' ' length
|
||||
|
||||
shout
|
||||
|> Str.toUtf8
|
||||
|> List.mapWithIndex \_, i ->
|
||||
length = (List.len (Str.toUtf8 shout) - i)
|
||||
phrase = (List.splitAt (Str.toUtf8 shout) length).before
|
||||
|
||||
List.concat (silence (if i == 0 then 2 * length else length)) phrase
|
||||
|> List.join
|
||||
|> Str.fromUtf8
|
||||
|> Result.withDefault ""
|
||||
|
||||
expect
|
||||
message = "hello!"
|
||||
echoedMessage = echo message
|
||||
|
||||
echoedMessage == " hello! hello hell hel he h"
|
||||
23
crates/cli/tests/test-projects/effectful/for_each_try.roc
Normal file
23
crates/cli/tests/test-projects/effectful/for_each_try.roc
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! : {} => {}
|
||||
main! = \{} ->
|
||||
good = [0, 2, 4] |> List.forEachTry! validate!
|
||||
expect good == Ok {}
|
||||
|
||||
bad = [6, 8, 9, 10] |> List.forEachTry! validate!
|
||||
expect bad == Err 9
|
||||
|
||||
{}
|
||||
|
||||
validate! : U32 => Result {} U32
|
||||
validate! = \x ->
|
||||
if Num.isEven x then
|
||||
Effect.putLine! "✅ $(Num.toStr x)"
|
||||
Ok {}
|
||||
|
||||
else
|
||||
Effect.putLine! "$(Num.toStr x) is not even! ABORT!"
|
||||
Err x
|
||||
24
crates/cli/tests/test-projects/effectful/on_err.roc
Normal file
24
crates/cli/tests/test-projects/effectful/on_err.roc
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! : {} => {}
|
||||
main! = \{} ->
|
||||
_ =
|
||||
authenticate! {}
|
||||
|> Result.onErr! \BadPass ->
|
||||
Effect.putLine! "LOG: Failed login attempt"
|
||||
Ok "Bad password"
|
||||
|
||||
{}
|
||||
|
||||
authenticate! : {} => Result Str [BadPass]
|
||||
authenticate! = \{} ->
|
||||
Effect.putLine! "Enter your password:"
|
||||
|
||||
password = Effect.getLine! {}
|
||||
|
||||
if password == "password" then
|
||||
Ok "You are in"
|
||||
else
|
||||
Err BadPass
|
||||
|
|
@ -5,7 +5,7 @@ import pf.Effect
|
|||
main! : {} => {}
|
||||
main! = \{} ->
|
||||
["Welcome!", "What's your name?"]
|
||||
|> forEach! Effect.putLine!
|
||||
|> List.forEach! Effect.putLine!
|
||||
|
||||
line = Effect.getLine! {}
|
||||
|
||||
|
|
@ -17,11 +17,3 @@ main! = \{} ->
|
|||
|
||||
Effect.putLine! "You entered: $(line)"
|
||||
Effect.putLine! "It is known"
|
||||
|
||||
forEach! : List a, (a => {}) => {}
|
||||
forEach! = \l, f! ->
|
||||
when l is
|
||||
[] -> {}
|
||||
[x, .. as xs] ->
|
||||
f! x
|
||||
forEach! xs f!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
Fx : {
|
||||
getLine!: {} => Str,
|
||||
}
|
||||
|
||||
main! : {} => {}
|
||||
main! = \{} ->
|
||||
notEffectful : Fx
|
||||
notEffectful = {
|
||||
getLine!: \{} -> "hardcoded"
|
||||
}
|
||||
|
||||
effectful : Fx
|
||||
effectful = {
|
||||
getLine!: Effect.getLine!
|
||||
}
|
||||
|
||||
Effect.putLine! "notEffectful: $(notEffectful.getLine! {})"
|
||||
Effect.putLine! "effectful: $(effectful.getLine! {})"
|
||||
|
|
@ -21,7 +21,7 @@ polyDbg = \x ->
|
|||
|
||||
main =
|
||||
str = "this will for sure be a large string so when we split it it will use seamless slices which affect printing"
|
||||
words = Str.split str " "
|
||||
words = Str.splitOn str " "
|
||||
expect words == []
|
||||
|
||||
x = 42
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue