mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Support multiple modules in uitest
This commit is contained in:
parent
0ec0568ef9
commit
014ec8c092
5 changed files with 192 additions and 30 deletions
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue