Support multiple modules in uitest

This commit is contained in:
Ayaz Hafiz 2023-04-12 11:35:33 -05:00
parent 0ec0568ef9
commit 014ec8c092
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
5 changed files with 192 additions and 30 deletions

View file

@ -31,7 +31,7 @@ mod solve_expr {
.. ..
}, },
src, src,
) = run_load_and_infer(src, false)?; ) = run_load_and_infer(src, [], false)?;
let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default();
@ -103,7 +103,7 @@ mod solve_expr {
interns, interns,
abilities_store, abilities_store,
.. ..
} = run_load_and_infer(src, false).unwrap().0; } = run_load_and_infer(src, [], false).unwrap().0;
let can_problems = can_problems.remove(&home).unwrap_or_default(); let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default();

View file

@ -44,8 +44,9 @@ fn promote_expr_to_module(src: &str) -> String {
buffer buffer
} }
pub fn run_load_and_infer( pub fn run_load_and_infer<'a>(
src: &str, src: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
no_promote: bool, no_promote: bool,
) -> Result<(LoadedModule, String), std::io::Error> { ) -> Result<(LoadedModule, String), std::io::Error> {
use tempfile::tempdir; use tempfile::tempdir;
@ -65,6 +66,11 @@ pub fn run_load_and_infer(
let loaded = { let loaded = {
let dir = tempdir()?; let dir = tempdir()?;
for (file, source) in dependencies {
std::fs::write(dir.path().join(format!("{file}.roc")), source)?;
}
let filename = PathBuf::from("Test.roc"); let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename); let file_path = dir.path().join(filename);
let result = roc_load::load_and_typecheck_str( let result = roc_load::load_and_typecheck_str(
@ -338,7 +344,11 @@ impl InferredProgram {
} }
} }
pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram, Box<dyn Error>> { pub fn infer_queries<'a>(
src: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
options: InferOptions,
) -> Result<InferredProgram, Box<dyn Error>> {
let ( let (
LoadedModule { LoadedModule {
module_id: home, module_id: home,
@ -351,7 +361,7 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram
.. ..
}, },
src, src,
) = run_load_and_infer(src, options.no_promote)?; ) = run_load_and_infer(src, dependencies, options.no_promote)?;
let declarations = declarations_by_id.remove(&home).unwrap(); let declarations = declarations_by_id.remove(&home).unwrap();
let subs = solved.inner_mut(); let subs = solved.inner_mut();

View file

@ -8,8 +8,19 @@ use roc_mono::{
ir::{Proc, ProcLayout}, ir::{Proc, ProcLayout},
layout::STLayoutInterner, layout::STLayoutInterner,
}; };
use tempfile::tempdir;
pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io::Result<()> { #[derive(Default)]
pub struct MonoOptions {
pub no_check: bool,
}
pub fn write_compiled_ir<'a>(
writer: &mut impl io::Write,
test_module: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
options: MonoOptions,
) -> io::Result<()> {
use roc_packaging::cache::RocCacheDir; use roc_packaging::cache::RocCacheDir;
use std::path::PathBuf; use std::path::PathBuf;
@ -17,8 +28,14 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io
let arena = &Bump::new(); let arena = &Bump::new();
let dir = tempdir()?;
for (file, source) in dependencies {
std::fs::write(dir.path().join(format!("{file}.roc")), source)?;
}
let filename = PathBuf::from("Test.roc"); let filename = PathBuf::from("Test.roc");
let src_dir = PathBuf::from("fake/test/path"); let file_path = dir.path().join(filename);
let load_config = LoadConfig { let load_config = LoadConfig {
target_info: roc_target::TargetInfo::default_x86_64(), target_info: roc_target::TargetInfo::default_x86_64(),
@ -29,9 +46,9 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io
}; };
let loaded = roc_load::load_and_monomorphize_from_str( let loaded = roc_load::load_and_monomorphize_from_str(
arena, arena,
filename, file_path,
module_source, test_module,
src_dir, dir.path().to_path_buf(),
RocCacheDir::Disallowed, RocCacheDir::Disallowed,
load_config, load_config,
); );
@ -58,7 +75,9 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io
let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next(); let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next();
check_procedures(arena, &interns, &mut layout_interner, &procedures); if !options.no_check {
check_procedures(arena, &interns, &mut layout_interner, &procedures);
}
write_procedures(writer, layout_interner, procedures, main_fn_symbol) write_procedures(writer, layout_interner, procedures, main_fn_symbol)
} }

View file

@ -8,7 +8,9 @@ use std::{
use lazy_static::lazy_static; use lazy_static::lazy_static;
use libtest_mimic::{run, Arguments, Failed, Trial}; use libtest_mimic::{run, Arguments, Failed, Trial};
use mono::MonoOptions;
use regex::Regex; use regex::Regex;
use roc_collections::VecMap;
use test_solve_helpers::{ use test_solve_helpers::{
infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER, infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER,
}; };
@ -38,9 +40,17 @@ lazy_static! {
static ref RE_OPT_INFER: Regex = static ref RE_OPT_INFER: Regex =
Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap(); Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap();
/// # +opt mono:<opt>
static ref RE_OPT_MONO: Regex =
Regex::new(r#"# \+opt mono:(?P<opt>.*)"#).unwrap();
/// # +emit:<opt> /// # +emit:<opt>
static ref RE_EMIT: Regex = static ref RE_EMIT: Regex =
Regex::new(r#"# \+emit:(?P<opt>.*)"#).unwrap(); Regex::new(r#"# \+emit:(?P<opt>.*)"#).unwrap();
/// ## module <name>
static ref RE_MODULE: Regex =
Regex::new(r#"## module (?P<name>.*)"#).unwrap();
} }
fn collect_uitest_files() -> io::Result<Vec<PathBuf>> { fn collect_uitest_files() -> io::Result<Vec<PathBuf>> {
@ -82,10 +92,18 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
let TestCase { let TestCase {
infer_options, infer_options,
emit_options, emit_options,
source, mono_options,
} = TestCase::parse(data)?; program,
} = TestCase::parse(&data)?;
let inferred_program = infer_queries(&source, infer_options)?; let inferred_program = infer_queries(
program.test_module,
program
.other_modules
.iter()
.map(|(md, src)| (&**md, &**src)),
infer_options,
)?;
{ {
let mut fd = fs::OpenOptions::new() let mut fd = fs::OpenOptions::new()
@ -93,7 +111,13 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
.truncate(true) .truncate(true)
.open(&path)?; .open(&path)?;
assemble_query_output(&mut fd, &source, inferred_program, emit_options)?; assemble_query_output(
&mut fd,
program,
inferred_program,
mono_options,
emit_options,
)?;
} }
check_for_changes(&path)?; check_for_changes(&path)?;
@ -103,10 +127,17 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
const EMIT_HEADER: &str = "# -emit:"; const EMIT_HEADER: &str = "# -emit:";
struct TestCase { struct Modules<'a> {
before_any: &'a str,
other_modules: VecMap<&'a str, &'a str>,
test_module: &'a str,
}
struct TestCase<'a> {
infer_options: InferOptions, infer_options: InferOptions,
mono_options: MonoOptions,
emit_options: EmitOptions, emit_options: EmitOptions,
source: String, program: Modules<'a>,
} }
#[derive(Default)] #[derive(Default)]
@ -115,21 +146,74 @@ struct EmitOptions {
mono: bool, mono: bool,
} }
impl TestCase { impl<'a> TestCase<'a> {
fn parse(mut data: String) -> Result<Self, Failed> { fn parse(mut data: &'a str) -> Result<Self, Failed> {
// Drop anything following `# -emit:` header lines; that's the output. // Drop anything following `# -emit:` header lines; that's the output.
if let Some(drop_at) = data.find(EMIT_HEADER) { if let Some(drop_at) = data.find(EMIT_HEADER) {
data.truncate(drop_at); data = data[..drop_at].trim_end();
data.truncate(data.trim_end().len());
} }
let infer_options = Self::parse_infer_options(&data)?;
let mono_options = Self::parse_mono_options(&data)?;
let emit_options = Self::parse_emit_options(&data)?;
let program = Self::parse_modules(data);
Ok(TestCase { Ok(TestCase {
infer_options: Self::parse_infer_options(&data)?, infer_options,
emit_options: Self::parse_emit_options(&data)?, mono_options,
source: data, emit_options,
program,
}) })
} }
fn parse_modules(data: &'a str) -> Modules<'a> {
let mut module_starts = RE_MODULE.captures_iter(&data).peekable();
let first_module_start = match module_starts.peek() {
None => {
// This is just a single module with no name; it is the test module.
return Modules {
before_any: Default::default(),
other_modules: Default::default(),
test_module: data,
};
}
Some(p) => p.get(0).unwrap().start(),
};
let before_any = data[..first_module_start].trim();
let mut test_module = None;
let mut other_modules = VecMap::new();
while let Some(module_start) = module_starts.next() {
let module_name = module_start.name("name").unwrap().as_str();
let module_start = module_start.get(0).unwrap().end();
let module = match module_starts.peek() {
None => &data[module_start..],
Some(next_module_start) => {
let module_end = next_module_start.get(0).unwrap().start();
&data[module_start..module_end]
}
}
.trim();
if module_name == "Test" {
test_module = Some(module);
} else {
other_modules.insert(module_name, module);
}
}
let test_module = test_module.expect("no Test module found");
Modules {
before_any,
other_modules,
test_module,
}
}
fn parse_infer_options(data: &str) -> Result<InferOptions, Failed> { fn parse_infer_options(data: &str) -> Result<InferOptions, Failed> {
let mut infer_opts = InferOptions { let mut infer_opts = InferOptions {
no_promote: true, no_promote: true,
@ -149,6 +233,21 @@ impl TestCase {
Ok(infer_opts) Ok(infer_opts)
} }
fn parse_mono_options(data: &str) -> Result<MonoOptions, Failed> {
let mut mono_opts = MonoOptions::default();
let found_infer_opts = RE_OPT_MONO.captures_iter(data);
for infer_opt in found_infer_opts {
let opt = infer_opt.name("opt").unwrap().as_str();
match opt.trim() {
"no_check" => mono_opts.no_check = true,
other => return Err(format!("unknown mono option: {other:?}").into()),
}
}
Ok(mono_opts)
}
fn parse_emit_options(data: &str) -> Result<EmitOptions, Failed> { fn parse_emit_options(data: &str) -> Result<EmitOptions, Failed> {
let mut emit_opts = EmitOptions::default(); let mut emit_opts = EmitOptions::default();
@ -188,17 +287,37 @@ fn check_for_changes(path: &Path) -> Result<(), Failed> {
/// Assemble the output for a test, with queries elaborated in-line. /// Assemble the output for a test, with queries elaborated in-line.
fn assemble_query_output( fn assemble_query_output(
writer: &mut impl io::Write, writer: &mut impl io::Write,
source: &str, program: Modules<'_>,
inferred_program: InferredProgram, inferred_program: InferredProgram,
mono_options: MonoOptions,
emit_options: EmitOptions, emit_options: EmitOptions,
) -> io::Result<()> { ) -> io::Result<()> {
let Modules {
before_any,
other_modules,
test_module,
} = program;
if !before_any.is_empty() {
writeln!(writer, "{before_any}\n")?;
}
for (module, source) in other_modules.iter() {
writeln!(writer, "## module {module}")?;
writeln!(writer, "{}\n", source)?;
}
if !other_modules.is_empty() {
writeln!(writer, "## module Test")?;
}
// Reverse the queries so that we can pop them off the end as we pass through the lines. // Reverse the queries so that we can pop them off the end as we pass through the lines.
let (queries, program) = inferred_program.decompose(); let (queries, program) = inferred_program.decompose();
let mut sorted_queries = queries.into_sorted(); let mut sorted_queries = queries.into_sorted();
sorted_queries.reverse(); sorted_queries.reverse();
let mut reflow = Reflow::new_unindented(writer); let mut reflow = Reflow::new_unindented(writer);
write_source_with_answers(&mut reflow, source, sorted_queries, 0)?; write_source_with_answers(&mut reflow, test_module, sorted_queries, 0)?;
// Finish up with any remaining emit options we were asked to provide. // Finish up with any remaining emit options we were asked to provide.
let EmitOptions { can_decls, mono } = emit_options; let EmitOptions { can_decls, mono } = emit_options;
@ -212,7 +331,7 @@ fn assemble_query_output(
// Unfortunately, with the current setup we must now recompile into the IR. // Unfortunately, with the current setup we must now recompile into the IR.
// TODO: extend the data returned by a monomorphized module to include // TODO: extend the data returned by a monomorphized module to include
// that of a solved module. // that of a solved module.
mono::write_compiled_ir(writer, source)?; mono::write_compiled_ir(writer, test_module, other_modules, mono_options)?;
} }
Ok(()) Ok(())

View file

@ -1,9 +1,23 @@
# +emit:mono # +emit:mono
app "test" provides [main] to "./platform" # +opt mono:no_check
main = "" ## module Dep
interface Dep exposes [defaultRequest] imports []
defaultRequest = {
url: "",
body: "",
}
## module Test
app "test" imports [Dep.{ defaultRequest }] provides [main] to "./platform"
main =
{ defaultRequest & url: "http://www.example.com" }
# -emit:mono # -emit:mono
procedure Test.0 (): procedure Test.0 ():
let Test.1 : Str = ""; let Test.3 : Str = "http://www.example.com";
let Test.2 : Str = StructAtIndex 0 Dep.0;
let Test.1 : {Str, Str} = Struct {Test.2, Test.3};
ret Test.1; ret Test.1;