Merge remote-tracking branch 'remote/main' into upgrade-llvm-zig

This commit is contained in:
Luke Boswell 2024-11-29 08:58:47 +11:00
commit 2feb5d3c2e
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
426 changed files with 8889 additions and 4190 deletions

View file

@ -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());
}
_ => {}
}
}
}

View file

@ -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

View 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"

View 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

View 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

View file

@ -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!

View file

@ -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! {})"

View file

@ -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