mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge pull request #4802 from roc-lang/valgrind-unit-tests
Valgrind unit tests
This commit is contained in:
commit
d8b2ff07f8
13 changed files with 547 additions and 64 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -5262,6 +5262,24 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valgrind"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"cli_utils",
|
||||||
|
"indoc",
|
||||||
|
"roc_build",
|
||||||
|
"roc_cli",
|
||||||
|
"roc_linker",
|
||||||
|
"roc_load",
|
||||||
|
"roc_mono",
|
||||||
|
"roc_packaging",
|
||||||
|
"roc_reporting",
|
||||||
|
"target-lexicon",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ members = [
|
||||||
"crates/repl_wasm",
|
"crates/repl_wasm",
|
||||||
"crates/repl_expect",
|
"crates/repl_expect",
|
||||||
"crates/test_utils",
|
"crates/test_utils",
|
||||||
|
"crates/valgrind",
|
||||||
"crates/tracing",
|
"crates/tracing",
|
||||||
"crates/utils",
|
"crates/utils",
|
||||||
"crates/docs",
|
"crates/docs",
|
||||||
|
@ -129,3 +130,7 @@ codegen-units = 1
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
|
[profile.release-with-debug]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|
|
@ -4,7 +4,7 @@ use roc_build::{
|
||||||
legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
|
legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
|
||||||
rebuild_host, LinkType, LinkingStrategy,
|
rebuild_host, LinkType, LinkingStrategy,
|
||||||
},
|
},
|
||||||
program::{self, CodeGenOptions},
|
program::{self, CodeGenBackend, CodeGenOptions},
|
||||||
};
|
};
|
||||||
use roc_builtins::bitcode;
|
use roc_builtins::bitcode;
|
||||||
use roc_load::{
|
use roc_load::{
|
||||||
|
@ -18,7 +18,10 @@ use roc_reporting::{
|
||||||
report::{RenderTarget, DEFAULT_PALETTE},
|
report::{RenderTarget, DEFAULT_PALETTE},
|
||||||
};
|
};
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use std::time::{Duration, Instant};
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
use std::{path::PathBuf, thread::JoinHandle};
|
use std::{path::PathBuf, thread::JoinHandle};
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
@ -570,3 +573,56 @@ pub fn check_file<'a>(
|
||||||
compilation_end,
|
compilation_end,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_str_test<'a>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
app_module_path: &Path,
|
||||||
|
app_module_source: &'a str,
|
||||||
|
assume_prebuild: bool,
|
||||||
|
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||||
|
let triple = target_lexicon::Triple::host();
|
||||||
|
|
||||||
|
let code_gen_options = CodeGenOptions {
|
||||||
|
backend: CodeGenBackend::Llvm,
|
||||||
|
opt_level: OptLevel::Normal,
|
||||||
|
emit_debug_info: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let emit_timings = false;
|
||||||
|
let link_type = LinkType::Executable;
|
||||||
|
let linking_strategy = LinkingStrategy::Surgical;
|
||||||
|
let wasm_dev_stack_bytes = None;
|
||||||
|
|
||||||
|
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
|
||||||
|
let build_ordering = BuildOrdering::AlwaysBuild;
|
||||||
|
let threading = Threading::AtMost(2);
|
||||||
|
|
||||||
|
let load_config = standard_load_config(&triple, build_ordering, threading);
|
||||||
|
|
||||||
|
let compilation_start = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Step 1: compile the app and generate the .o file
|
||||||
|
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||||
|
arena,
|
||||||
|
PathBuf::from("valgrind_test.roc"),
|
||||||
|
app_module_source,
|
||||||
|
app_module_path.to_path_buf(),
|
||||||
|
roc_cache_dir,
|
||||||
|
load_config,
|
||||||
|
)
|
||||||
|
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||||
|
|
||||||
|
build_loaded_file(
|
||||||
|
arena,
|
||||||
|
&triple,
|
||||||
|
app_module_path.to_path_buf(),
|
||||||
|
code_gen_options,
|
||||||
|
emit_timings,
|
||||||
|
link_type,
|
||||||
|
linking_strategy,
|
||||||
|
assume_prebuild,
|
||||||
|
wasm_dev_stack_bytes,
|
||||||
|
loaded,
|
||||||
|
compilation_start,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ use roc_load::{ExpectMetadata, LoadingProblem, Threading};
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_packaging::cache::RocCacheDir;
|
use roc_packaging::cache::RocCacheDir;
|
||||||
use roc_packaging::tarball::Compression;
|
use roc_packaging::tarball::Compression;
|
||||||
use roc_reporting::cli::Problems;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CString, OsStr};
|
use std::ffi::{CString, OsStr};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -463,7 +462,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||||
"if there were errors, we would have already exited."
|
"if there were errors, we would have already exited."
|
||||||
);
|
);
|
||||||
if problems.warnings > 0 {
|
if problems.warnings > 0 {
|
||||||
print_problems(problems, start_time.elapsed());
|
problems.print_to_stdout(start_time.elapsed());
|
||||||
println!(".\n\nRunning tests…\n\n\x1B[36m{}\x1B[39m", "─".repeat(80));
|
println!(".\n\nRunning tests…\n\n\x1B[36m{}\x1B[39m", "─".repeat(80));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -708,7 +707,7 @@ pub fn build(
|
||||||
// since the process is about to exit anyway.
|
// since the process is about to exit anyway.
|
||||||
// std::mem::forget(arena);
|
// std::mem::forget(arena);
|
||||||
|
|
||||||
print_problems(problems, total_time);
|
problems.print_to_stdout(total_time);
|
||||||
println!(" while successfully building:\n\n {generated_filename}");
|
println!(" while successfully building:\n\n {generated_filename}");
|
||||||
|
|
||||||
// Return a nonzero exit code if there were problems
|
// Return a nonzero exit code if there were problems
|
||||||
|
@ -716,7 +715,7 @@ pub fn build(
|
||||||
}
|
}
|
||||||
BuildAndRun => {
|
BuildAndRun => {
|
||||||
if problems.errors > 0 || problems.warnings > 0 {
|
if problems.errors > 0 || problems.warnings > 0 {
|
||||||
print_problems(problems, total_time);
|
problems.print_to_stdout(total_time);
|
||||||
println!(
|
println!(
|
||||||
".\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
".\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||||||
"─".repeat(80)
|
"─".repeat(80)
|
||||||
|
@ -737,7 +736,7 @@ pub fn build(
|
||||||
"if there are errors, they should have been returned as an error variant"
|
"if there are errors, they should have been returned as an error variant"
|
||||||
);
|
);
|
||||||
if problems.warnings > 0 {
|
if problems.warnings > 0 {
|
||||||
print_problems(problems, total_time);
|
problems.print_to_stdout(total_time);
|
||||||
println!(
|
println!(
|
||||||
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||||
"─".repeat(80)
|
"─".repeat(80)
|
||||||
|
@ -771,7 +770,7 @@ fn handle_error_module(
|
||||||
|
|
||||||
let problems = roc_build::program::report_problems_typechecked(&mut module);
|
let problems = roc_build::program::report_problems_typechecked(&mut module);
|
||||||
|
|
||||||
print_problems(problems, total_time);
|
problems.print_to_stdout(total_time);
|
||||||
|
|
||||||
if print_run_anyway_hint {
|
if print_run_anyway_hint {
|
||||||
// If you're running "main.roc" then you can just do `roc run`
|
// If you're running "main.roc" then you can just do `roc run`
|
||||||
|
@ -803,34 +802,6 @@ fn handle_loading_problem(problem: LoadingProblem) -> io::Result<i32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_problems(problems: Problems, total_time: std::time::Duration) {
|
|
||||||
const GREEN: usize = 32;
|
|
||||||
const YELLOW: usize = 33;
|
|
||||||
|
|
||||||
print!(
|
|
||||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms",
|
|
||||||
match problems.errors {
|
|
||||||
0 => GREEN,
|
|
||||||
_ => YELLOW,
|
|
||||||
},
|
|
||||||
problems.errors,
|
|
||||||
match problems.errors {
|
|
||||||
1 => "error",
|
|
||||||
_ => "errors",
|
|
||||||
},
|
|
||||||
match problems.warnings {
|
|
||||||
0 => GREEN,
|
|
||||||
_ => YELLOW,
|
|
||||||
},
|
|
||||||
problems.warnings,
|
|
||||||
match problems.warnings {
|
|
||||||
1 => "warning",
|
|
||||||
_ => "warnings",
|
|
||||||
},
|
|
||||||
total_time.as_millis(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
opt_level: OptLevel,
|
opt_level: OptLevel,
|
||||||
|
|
|
@ -251,10 +251,10 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
||||||
let mut cmd = Command::new("valgrind");
|
let mut cmd = Command::new("valgrind");
|
||||||
let named_tempfile =
|
let named_tempfile =
|
||||||
NamedTempFile::new().expect("Unable to create tempfile for valgrind results");
|
NamedTempFile::new().expect("Unable to create tempfile for valgrind results");
|
||||||
let filepath = named_tempfile.path().to_str().unwrap();
|
|
||||||
|
|
||||||
cmd.arg("--tool=memcheck");
|
cmd.arg("--tool=memcheck");
|
||||||
cmd.arg("--xml=yes");
|
cmd.arg("--xml=yes");
|
||||||
|
cmd.arg(format!("--xml-file={}", named_tempfile.path().display()));
|
||||||
|
|
||||||
// If you are having valgrind issues on MacOS, you may need to suppress some
|
// If you are having valgrind issues on MacOS, you may need to suppress some
|
||||||
// of the errors. Read more here: https://github.com/roc-lang/roc/issues/746
|
// of the errors. Read more here: https://github.com/roc-lang/roc/issues/746
|
||||||
|
@ -274,8 +274,6 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(format!("--xml-file={}", filepath));
|
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
cmd.arg(arg);
|
cmd.arg(arg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4468,23 +4468,7 @@ fn build_header<'a>(
|
||||||
//
|
//
|
||||||
// Also build a list of imported_values_to_expose (like `bar` above.)
|
// Also build a list of imported_values_to_expose (like `bar` above.)
|
||||||
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
|
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
|
||||||
let cloned_module_name = qualified_module_name.module.clone();
|
let pq_module_name = qualified_module_name.into_pq_module_name(opt_shorthand);
|
||||||
let pq_module_name = if qualified_module_name.is_builtin() {
|
|
||||||
// If this is a builtin, it must be unqualified, and we should *never* prefix it
|
|
||||||
// with the package shorthand! The user intended to import the module as-is here.
|
|
||||||
debug_assert!(qualified_module_name.opt_package.is_none());
|
|
||||||
PQModuleName::Unqualified(qualified_module_name.module)
|
|
||||||
} else {
|
|
||||||
match qualified_module_name.opt_package {
|
|
||||||
None => match opt_shorthand {
|
|
||||||
Some(shorthand) => {
|
|
||||||
PQModuleName::Qualified(shorthand, qualified_module_name.module)
|
|
||||||
}
|
|
||||||
None => PQModuleName::Unqualified(qualified_module_name.module),
|
|
||||||
},
|
|
||||||
Some(package) => PQModuleName::Qualified(package, cloned_module_name),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let module_id = module_ids.get_or_insert(&pq_module_name);
|
let module_id = module_ids.get_or_insert(&pq_module_name);
|
||||||
|
|
||||||
|
@ -4497,18 +4481,14 @@ fn build_header<'a>(
|
||||||
// to the same symbols as the ones we're using here.
|
// to the same symbols as the ones we're using here.
|
||||||
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
||||||
|
|
||||||
for Loc {
|
for loc_ident in exposed_idents {
|
||||||
region,
|
let ident_id = ident_ids.get_or_insert(loc_ident.value.as_str());
|
||||||
value: ident,
|
|
||||||
} in exposed_idents
|
|
||||||
{
|
|
||||||
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
|
||||||
let symbol = Symbol::new(module_id, ident_id);
|
let symbol = Symbol::new(module_id, ident_id);
|
||||||
|
|
||||||
// Since this value is exposed, add it to our module's default scope.
|
// Since this value is exposed, add it to our module's default scope.
|
||||||
debug_assert!(!scope.contains_key(&ident));
|
debug_assert!(!scope.contains_key(&loc_ident.value));
|
||||||
|
|
||||||
scope.insert(ident, (symbol, region));
|
scope.insert(loc_ident.value, (symbol, loc_ident.region));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
pub use roc_ident::IdentStr;
|
pub use roc_ident::IdentStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::symbol::PQModuleName;
|
||||||
|
|
||||||
/// This could be uppercase or lowercase, qualified or unqualified.
|
/// This could be uppercase or lowercase, qualified or unqualified.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||||
pub struct Ident(pub IdentStr);
|
pub struct Ident(pub IdentStr);
|
||||||
|
@ -21,6 +23,25 @@ pub struct QualifiedModuleName<'a> {
|
||||||
pub module: ModuleName,
|
pub module: ModuleName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> QualifiedModuleName<'a> {
|
||||||
|
pub fn into_pq_module_name(self, opt_shorthand: Option<&'a str>) -> PQModuleName<'a> {
|
||||||
|
if self.is_builtin() {
|
||||||
|
// If this is a builtin, it must be unqualified, and we should *never* prefix it
|
||||||
|
// with the package shorthand! The user intended to import the module as-is here.
|
||||||
|
debug_assert!(self.opt_package.is_none());
|
||||||
|
PQModuleName::Unqualified(self.module)
|
||||||
|
} else {
|
||||||
|
match self.opt_package {
|
||||||
|
None => match opt_shorthand {
|
||||||
|
Some(shorthand) => PQModuleName::Qualified(shorthand, self.module),
|
||||||
|
None => PQModuleName::Unqualified(self.module),
|
||||||
|
},
|
||||||
|
Some(package) => PQModuleName::Qualified(package, self.module),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ModuleName(IdentStr);
|
pub struct ModuleName(IdentStr);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,34 @@ impl Problems {
|
||||||
self.warnings.min(1) as i32
|
self.warnings.min(1) as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_to_stdout(&self, total_time: std::time::Duration) {
|
||||||
|
const GREEN: usize = 32;
|
||||||
|
const YELLOW: usize = 33;
|
||||||
|
|
||||||
|
print!(
|
||||||
|
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms",
|
||||||
|
match self.errors {
|
||||||
|
0 => GREEN,
|
||||||
|
_ => YELLOW,
|
||||||
|
},
|
||||||
|
self.errors,
|
||||||
|
match self.errors {
|
||||||
|
1 => "error",
|
||||||
|
_ => "errors",
|
||||||
|
},
|
||||||
|
match self.warnings {
|
||||||
|
0 => GREEN,
|
||||||
|
_ => YELLOW,
|
||||||
|
},
|
||||||
|
self.warnings,
|
||||||
|
match self.warnings {
|
||||||
|
1 => "warning",
|
||||||
|
_ => "warnings",
|
||||||
|
},
|
||||||
|
total_time.as_millis(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_problems(
|
pub fn report_problems(
|
||||||
|
|
20
crates/valgrind/Cargo.toml
Normal file
20
crates/valgrind/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "valgrind"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["The Roc Contributors"]
|
||||||
|
license = "UPL-1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
roc_cli = { path = "../cli" }
|
||||||
|
cli_utils = { path = "../cli_utils" }
|
||||||
|
roc_build = { path = "../compiler/build" }
|
||||||
|
roc_mono = { path = "../compiler/mono" }
|
||||||
|
roc_load = { path = "../compiler/load" }
|
||||||
|
roc_reporting = { path = "../reporting" }
|
||||||
|
roc_linker = { path = "../linker" }
|
||||||
|
roc_packaging = { path = "../packaging" }
|
||||||
|
bumpalo.workspace = true
|
||||||
|
target-lexicon.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
indoc.workspace = true
|
237
crates/valgrind/src/lib.rs
Normal file
237
crates/valgrind/src/lib.rs
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
static BUILD_ONCE: std::sync::Once = std::sync::Once::new();
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux"))]
|
||||||
|
fn build_host() {
|
||||||
|
use roc_build::link::preprocessed_host_filename;
|
||||||
|
use roc_linker::build_and_preprocess_host;
|
||||||
|
|
||||||
|
let platform_main_roc = std::env::current_dir()
|
||||||
|
.unwrap()
|
||||||
|
.join("zig-platform/main.roc");
|
||||||
|
|
||||||
|
// tests always run on the host
|
||||||
|
let target = target_lexicon::Triple::host();
|
||||||
|
|
||||||
|
// the preprocessed host is stored beside the platform's main.roc
|
||||||
|
let preprocessed_host_path =
|
||||||
|
platform_main_roc.with_file_name(preprocessed_host_filename(&target).unwrap());
|
||||||
|
|
||||||
|
build_and_preprocess_host(
|
||||||
|
roc_mono::ir::OptLevel::Normal,
|
||||||
|
&target,
|
||||||
|
&platform_main_roc,
|
||||||
|
&preprocessed_host_path,
|
||||||
|
vec![String::from("mainForHost")],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valgrind_test(source: &str) {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
valgrind_test_linux(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
let _ = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn valgrind_test_linux(source: &str) {
|
||||||
|
use roc_cli::build::BuiltFile;
|
||||||
|
|
||||||
|
// the host is identical for all tests so we only want to build it once
|
||||||
|
BUILD_ONCE.call_once(build_host);
|
||||||
|
|
||||||
|
let pf = std::env::current_dir()
|
||||||
|
.unwrap()
|
||||||
|
.join("zig-platform/main.roc");
|
||||||
|
|
||||||
|
assert!(pf.exists(), "cannot find platform {:?}", &pf);
|
||||||
|
|
||||||
|
let mut app_module_source = format!(
|
||||||
|
indoc::indoc!(
|
||||||
|
r#"
|
||||||
|
app "test"
|
||||||
|
packages {{ pf: "{}" }}
|
||||||
|
imports []
|
||||||
|
provides [main] to pf
|
||||||
|
|
||||||
|
main =
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
pf.to_str().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
for line in source.lines() {
|
||||||
|
app_module_source.push_str(" ");
|
||||||
|
app_module_source.push_str(line);
|
||||||
|
app_module_source.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let app_module_path = temp_dir.path().join("app.roc");
|
||||||
|
|
||||||
|
let arena = bumpalo::Bump::new();
|
||||||
|
let assume_prebuilt = true;
|
||||||
|
let res_binary_path = roc_cli::build::build_str_test(
|
||||||
|
&arena,
|
||||||
|
&app_module_path,
|
||||||
|
&app_module_source,
|
||||||
|
assume_prebuilt,
|
||||||
|
);
|
||||||
|
|
||||||
|
match res_binary_path {
|
||||||
|
Ok(BuiltFile {
|
||||||
|
binary_path,
|
||||||
|
problems,
|
||||||
|
total_time: _,
|
||||||
|
expect_metadata: _,
|
||||||
|
}) => {
|
||||||
|
if problems.exit_code() != 0 {
|
||||||
|
panic!("there are problems")
|
||||||
|
}
|
||||||
|
|
||||||
|
run_with_valgrind(&binary_path);
|
||||||
|
}
|
||||||
|
Err(roc_cli::build::BuildFileError::LoadingProblem(
|
||||||
|
roc_load::LoadingProblem::FormattedReport(report),
|
||||||
|
)) => {
|
||||||
|
eprintln!("{}", report);
|
||||||
|
panic!("");
|
||||||
|
}
|
||||||
|
Err(e) => panic!("{:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn run_with_valgrind(binary_path: &std::path::Path) {
|
||||||
|
use cli_utils::helpers::{extract_valgrind_errors, ValgrindError, ValgrindErrorXWhat};
|
||||||
|
|
||||||
|
// If possible, report the generated executable name relative to the current dir.
|
||||||
|
let generated_filename = binary_path
|
||||||
|
.strip_prefix(std::env::current_dir().unwrap())
|
||||||
|
.unwrap_or(binary_path)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (valgrind_out, raw_xml) =
|
||||||
|
cli_utils::helpers::run_with_valgrind([], &[generated_filename.to_string()]);
|
||||||
|
|
||||||
|
if valgrind_out.status.success() {
|
||||||
|
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||||
|
panic!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
failed to parse the `valgrind` xml output:
|
||||||
|
|
||||||
|
Error was:
|
||||||
|
|
||||||
|
{:?}
|
||||||
|
|
||||||
|
valgrind xml was:
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
valgrind stdout was:
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
valgrind stderr was:
|
||||||
|
|
||||||
|
{}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
err, raw_xml, valgrind_out.stdout, valgrind_out.stderr
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if !memory_errors.is_empty() {
|
||||||
|
for error in memory_errors {
|
||||||
|
let ValgrindError {
|
||||||
|
kind,
|
||||||
|
what: _,
|
||||||
|
xwhat,
|
||||||
|
} = error;
|
||||||
|
println!("Valgrind Error: {}\n", kind);
|
||||||
|
|
||||||
|
if let Some(ValgrindErrorXWhat {
|
||||||
|
text,
|
||||||
|
leakedbytes: _,
|
||||||
|
leakedblocks: _,
|
||||||
|
}) = xwhat
|
||||||
|
{
|
||||||
|
println!(" {}", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Valgrind found memory errors");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let exit_code = match valgrind_out.status.code() {
|
||||||
|
Some(code) => format!("exit code {}", code),
|
||||||
|
None => "no exit code".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"",
|
||||||
|
exit_code, valgrind_out.stdout, valgrind_out.stderr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat_consumes_first_argument() {
|
||||||
|
valgrind_test("List.concat (List.withCapacity 1024) [1,2,3] |> List.len |> Num.toStr");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_capacity_concat() {
|
||||||
|
valgrind_test(r#"Str.withCapacity 42 |> Str.concat "foobar""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_scalar() {
|
||||||
|
valgrind_test(indoc!(
|
||||||
|
r#"
|
||||||
|
Str.appendScalar "abcd" 'A'
|
||||||
|
|> Result.withDefault ""
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_not_present() {
|
||||||
|
valgrind_test(indoc!(
|
||||||
|
r#"
|
||||||
|
Str.split (Str.concat "a string that is stored on the heap" "!") "\n"
|
||||||
|
|> Str.joinWith ""
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_concat_first_argument_not_unique() {
|
||||||
|
valgrind_test(indoc!(
|
||||||
|
r#"
|
||||||
|
(
|
||||||
|
str1 = Str.reserve "" 48
|
||||||
|
str2 = "a"
|
||||||
|
|
||||||
|
out = Str.concat str1 str2
|
||||||
|
if Bool.false then
|
||||||
|
out
|
||||||
|
else
|
||||||
|
str1
|
||||||
|
)
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
2
crates/valgrind/zig-platform/.gitignore
vendored
Normal file
2
crates/valgrind/zig-platform/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
dynhost
|
||||||
|
libapp.so
|
138
crates/valgrind/zig-platform/host.zig
Normal file
138
crates/valgrind/zig-platform/host.zig
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const str = @import("str");
|
||||||
|
const RocStr = str.RocStr;
|
||||||
|
const testing = std.testing;
|
||||||
|
const expectEqual = testing.expectEqual;
|
||||||
|
const expect = testing.expect;
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
// This is a workaround for https://github.com/ziglang/zig/issues/8218
|
||||||
|
// which is only necessary on macOS.
|
||||||
|
//
|
||||||
|
// Once that issue is fixed, we can undo the changes in
|
||||||
|
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
|
||||||
|
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
||||||
|
// workaround is present in many host.zig files, so make sure to undo
|
||||||
|
// it everywhere!
|
||||||
|
if (builtin.os.tag == .macos) {
|
||||||
|
_ = @import("compiler_rt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Align = 2 * @alignOf(usize);
|
||||||
|
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
|
||||||
|
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
|
||||||
|
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
|
||||||
|
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||||
|
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||||
|
|
||||||
|
const DEBUG: bool = false;
|
||||||
|
|
||||||
|
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
|
||||||
|
if (DEBUG) {
|
||||||
|
var ptr = malloc(size);
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
|
||||||
|
return ptr;
|
||||||
|
} else {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
|
||||||
|
if (DEBUG) {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
|
||||||
|
if (DEBUG) {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
|
||||||
|
_ = tag_id;
|
||||||
|
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
const msg = @ptrCast([*:0]const u8, c_ptr);
|
||||||
|
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
|
||||||
|
return memcpy(dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
||||||
|
return memset(dst, value, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||||
|
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||||
|
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||||
|
extern fn getppid() c_int;
|
||||||
|
|
||||||
|
fn roc_getppid() callconv(.C) c_int {
|
||||||
|
return getppid();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roc_getppid_windows_stub() callconv(.C) c_int {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||||
|
return shm_open(name, oflag, mode);
|
||||||
|
}
|
||||||
|
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||||
|
return mmap(addr, length, prot, flags, fd, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||||
|
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||||
|
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||||
|
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mem = std.mem;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
|
||||||
|
|
||||||
|
const Unit = extern struct {};
|
||||||
|
|
||||||
|
pub fn main() u8 {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
|
||||||
|
var timer = std.time.Timer.start() catch unreachable;
|
||||||
|
|
||||||
|
// actually call roc to populate the callresult
|
||||||
|
var callresult = RocStr.empty();
|
||||||
|
roc__mainForHost_1_exposed_generic(&callresult);
|
||||||
|
|
||||||
|
const nanos = timer.read();
|
||||||
|
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
|
||||||
|
|
||||||
|
// stdout the result
|
||||||
|
stdout.print("{s}", .{callresult.asSlice()}) catch unreachable;
|
||||||
|
|
||||||
|
callresult.deinit();
|
||||||
|
|
||||||
|
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
9
crates/valgrind/zig-platform/main.roc
Normal file
9
crates/valgrind/zig-platform/main.roc
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
platform "echo-in-zig"
|
||||||
|
requires {} { main : Str }
|
||||||
|
exposes []
|
||||||
|
packages {}
|
||||||
|
imports []
|
||||||
|
provides [mainForHost]
|
||||||
|
|
||||||
|
mainForHost : Str
|
||||||
|
mainForHost = main
|
Loading…
Add table
Add a link
Reference in a new issue