mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Add test_compile crate
This commit is contained in:
parent
98535bfbce
commit
67bca80921
9 changed files with 418 additions and 121 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -3803,6 +3803,25 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test_compile"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"pretty_assertions",
|
||||||
|
"roc_builtins",
|
||||||
|
"roc_can",
|
||||||
|
"roc_derive",
|
||||||
|
"roc_load",
|
||||||
|
"roc_parse",
|
||||||
|
"roc_problem",
|
||||||
|
"roc_region",
|
||||||
|
"roc_reporting",
|
||||||
|
"roc_solve",
|
||||||
|
"roc_target",
|
||||||
|
"test_solve_helpers",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test_derive"
|
name = "test_derive"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
|
@ -17,6 +17,7 @@ members = [
|
||||||
"crates/repl_wasm",
|
"crates/repl_wasm",
|
||||||
"crates/repl_expect",
|
"crates/repl_expect",
|
||||||
"crates/roc_std",
|
"crates/roc_std",
|
||||||
|
"crates/test_compile",
|
||||||
"crates/test_utils",
|
"crates/test_utils",
|
||||||
"crates/test_utils_dir",
|
"crates/test_utils_dir",
|
||||||
"crates/valgrind",
|
"crates/valgrind",
|
||||||
|
|
|
@ -35,7 +35,7 @@ use roc_region::all::{Loc, Position, Region};
|
||||||
|
|
||||||
use crate::parser::Progress::{self, *};
|
use crate::parser::Progress::{self, *};
|
||||||
|
|
||||||
fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
pub fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
||||||
|_arena, state: State<'a>, _min_indent: u32| {
|
|_arena, state: State<'a>, _min_indent: u32| {
|
||||||
if state.has_reached_end() {
|
if state.has_reached_end() {
|
||||||
Ok((NoProgress, (), state))
|
Ok((NoProgress, (), state))
|
||||||
|
|
|
@ -7,14 +7,22 @@ edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_builtins = { path = "../compiler/builtins" }
|
||||||
|
roc_derive = { path = "../compiler/derive", features = [
|
||||||
|
"debug-derived-symbols",
|
||||||
|
] }
|
||||||
|
roc_region = { path = "../compiler/region" }
|
||||||
|
roc_load = { path = "../compiler/load" }
|
||||||
|
roc_parse = { path = "../compiler/parse" }
|
||||||
|
roc_can = { path = "../compiler/can" }
|
||||||
|
roc_problem = { path = "../compiler/problem" }
|
||||||
|
roc_reporting = { path = "../reporting" }
|
||||||
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
roc_solve = { path = "../compiler/solve" }
|
||||||
|
test_solve_helpers = { path = "../compiler/test_solve_helpers" }
|
||||||
|
|
||||||
|
bumpalo.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_builtins = { path = "../builtins" }
|
|
||||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
|
||||||
roc_load = { path = "../load" }
|
|
||||||
roc_parse = { path = "../parse" }
|
|
||||||
roc_problem = { path = "../problem" }
|
|
||||||
roc_reporting = { path = "../../reporting" }
|
|
||||||
roc_target = { path = "../roc_target" }
|
|
||||||
roc_solve = { path = "../solve" }
|
|
||||||
test_solve_helpers = { path = "../test_solve_helpers" }
|
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
|
222
crates/test_compile/src/deindent.rs
Normal file
222
crates/test_compile/src/deindent.rs
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// The purpose of this function is to let us run tests like this:
|
||||||
|
///
|
||||||
|
/// run_some_test(r#"
|
||||||
|
/// x = 1
|
||||||
|
///
|
||||||
|
/// x
|
||||||
|
/// ")
|
||||||
|
///
|
||||||
|
/// ...without needing to call a macro like `indoc!` to deal with the fact that
|
||||||
|
/// multiline Rust string literals preserve all the indented spaces.
|
||||||
|
///
|
||||||
|
/// This function removes the indentation by removing leading newlines (e.g. after
|
||||||
|
/// the `(r#"` opening) and then counting how many spaces precede the first line
|
||||||
|
/// (e.g. `" x = 1"` here) and trimming that many spaces from the beginning
|
||||||
|
/// of each subsequent line. The end of the string is then trimmed normally, and
|
||||||
|
/// any remaining empty lines are left empty.
|
||||||
|
///
|
||||||
|
/// This function is a no-op on single-line strings.
|
||||||
|
pub fn trim_and_deindent<'a>(arena: &'a Bump, input: &'a str) -> &'a str {
|
||||||
|
let newline_count = input.chars().filter(|&ch| ch == '\n').count();
|
||||||
|
|
||||||
|
// If it's a single-line string, return it without allocating anything.
|
||||||
|
if newline_count == 0 {
|
||||||
|
return input.trim(); // Trim to remove spaces
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim leading blank lines - we expect at least one, because the opening line will be `(r#"`
|
||||||
|
// (Also, there may be stray blank lines at the start, which this will trim off too.)
|
||||||
|
let mut lines = bumpalo::collections::Vec::with_capacity_in(newline_count + 1, arena);
|
||||||
|
|
||||||
|
for line in input
|
||||||
|
.lines()
|
||||||
|
// Keep skipping until we hit a line that is neither empty nor all spaces.
|
||||||
|
.skip_while(|line| line.chars().all(|ch| ch == ' '))
|
||||||
|
{
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop trailing blank lines
|
||||||
|
while lines
|
||||||
|
.last()
|
||||||
|
.map_or(false, |line| line.chars().all(|ch| ch == ' '))
|
||||||
|
{
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we've trimmed leading and trailing blank lines,
|
||||||
|
// Find the smallest indent of the remaining lines. That's our indentation amount.
|
||||||
|
let smallest_indent = lines
|
||||||
|
.iter()
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.map(|line| line.chars().take_while(|&ch| ch == ' ').count())
|
||||||
|
.min()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
// Remove this amount of indentation from each line.
|
||||||
|
let mut final_str_len = 0;
|
||||||
|
|
||||||
|
lines.iter_mut().for_each(|line| {
|
||||||
|
if line.starts_with(" ") {
|
||||||
|
*line = line.get(smallest_indent..).unwrap_or("");
|
||||||
|
}
|
||||||
|
final_str_len += line.len() + 1; // +1 for the newline that will be added to the end of this line.
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert lines into a bumpalo::String
|
||||||
|
let mut answer = bumpalo::collections::String::with_capacity_in(final_str_len, arena);
|
||||||
|
|
||||||
|
// Unconditionally push a newline after each line we add. We'll trim off the last one before we return.
|
||||||
|
for line in lines {
|
||||||
|
answer.push_str(line);
|
||||||
|
answer.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim off the extra newline we added at the end. (Saturate to 0 if we ended up with no lines.)
|
||||||
|
&answer.into_bump_str()[..final_str_len.saturating_sub(1)]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_line_input() {
|
||||||
|
let input = "single line";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), "single line");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_with_indentation() {
|
||||||
|
let input = r#"
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
x
|
||||||
|
"#;
|
||||||
|
let expected = "x = 1\n\nx";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_with_varying_indentation() {
|
||||||
|
let input = r#"
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
z = 3
|
||||||
|
"#;
|
||||||
|
let expected = "x = 1\n y = 2\nz = 3";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_with_empty_lines() {
|
||||||
|
let input = r#"
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
y = 2
|
||||||
|
|
||||||
|
z = 3
|
||||||
|
"#;
|
||||||
|
let expected = "x = 1\n\ny = 2\n\nz = 3";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_without_leading_newline() {
|
||||||
|
let input = " x = 1\n y = 2";
|
||||||
|
let expected = "x = 1\ny = 2";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_multiple_leading_newlines() {
|
||||||
|
let input = "\n\n\n x = 1\n y = 2";
|
||||||
|
let expected = "x = 1\ny = 2";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_mixed_indentation() {
|
||||||
|
let input = r#"
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
z = 3
|
||||||
|
"#;
|
||||||
|
let expected = " x = 1\ny = 2\n z = 3";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_only_spaces() {
|
||||||
|
let input = " ";
|
||||||
|
let expected = "";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_only_newlines() {
|
||||||
|
let input = "\n\n\n";
|
||||||
|
let expected = "";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_tabs() {
|
||||||
|
let input = "\t\tx = 1\n\t\ty = 2";
|
||||||
|
let expected = "\t\tx = 1\n\t\ty = 2";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_mixed_spaces_and_tabs() {
|
||||||
|
let input = " \tx = 1\n \ty = 2";
|
||||||
|
let expected = "\tx = 1\n\ty = 2";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_trailing_spaces() {
|
||||||
|
let input = " x = 1 \n y = 2 ";
|
||||||
|
let expected = "x = 1 \ny = 2 ";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_empty_lines_and_spaces() {
|
||||||
|
let input = " x = 1\n \n y = 2";
|
||||||
|
let expected = "x = 1\n\ny = 2";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_different_indentation_levels() {
|
||||||
|
let input = " x = 1\n y = 2\n z = 3";
|
||||||
|
let expected = " x = 1\n y = 2\nz = 3";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_non_space_characters_at_start() {
|
||||||
|
let input = "x = 1\n y = 2\n z = 3";
|
||||||
|
let expected = "x = 1\n y = 2\n z = 3";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_input() {
|
||||||
|
let input = "";
|
||||||
|
let expected = "";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_with_only_one_indented_line() {
|
||||||
|
let input = " x = 1";
|
||||||
|
let expected = "x = 1";
|
||||||
|
assert_eq!(trim_and_deindent(&Bump::new(), input), expected);
|
||||||
|
}
|
||||||
|
}
|
106
crates/test_compile/src/help_can.rs
Normal file
106
crates/test_compile/src/help_can.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use crate::help_parse::ParseExpr;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_can::expr::Expr;
|
||||||
|
|
||||||
|
pub struct CanExpr {
|
||||||
|
parse_expr: ParseExpr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CanExpr {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
parse_expr: ParseExpr::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanExpr {
|
||||||
|
pub fn can_expr<'a>(&'a self, input: &'a str) -> Result<Expr, CanExprProblem> {
|
||||||
|
match self.parse_expr.parse_expr(input) {
|
||||||
|
Ok(ast) => {
|
||||||
|
// todo canonicalize AST and return that result.
|
||||||
|
let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{expr_str:?} {e:?}"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut var_store = VarStore::default();
|
||||||
|
let var = var_store.fresh();
|
||||||
|
let qualified_module_ids = PackageModuleIds::default();
|
||||||
|
|
||||||
|
let mut scope = Scope::new(
|
||||||
|
home,
|
||||||
|
"TestPath".into(),
|
||||||
|
IdentIds::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let dep_idents = IdentIds::exposed_builtins(0);
|
||||||
|
let mut env = Env::new(
|
||||||
|
arena,
|
||||||
|
expr_str,
|
||||||
|
home,
|
||||||
|
Path::new("Test.roc"),
|
||||||
|
&dep_idents,
|
||||||
|
&qualified_module_ids,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Desugar operators (convert them to Apply calls, taking into account
|
||||||
|
// operator precedence and associativity rules), before doing other canonicalization.
|
||||||
|
//
|
||||||
|
// If we did this *during* canonicalization, then each time we
|
||||||
|
// visited a BinOp node we'd recursively try to apply this to each of its nested
|
||||||
|
// operators, and then again on *their* nested operators, ultimately applying the
|
||||||
|
// rules multiple times unnecessarily.
|
||||||
|
let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr);
|
||||||
|
|
||||||
|
scope.add_alias(
|
||||||
|
Symbol::NUM_INT,
|
||||||
|
Region::zero(),
|
||||||
|
vec![Loc::at_zero(AliasVar::unbound(
|
||||||
|
"a".into(),
|
||||||
|
Variable::EMPTY_RECORD,
|
||||||
|
))],
|
||||||
|
vec![],
|
||||||
|
Type::EmptyRec,
|
||||||
|
roc_types::types::AliasKind::Structural,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (loc_expr, output) = canonicalize_expr(
|
||||||
|
&mut env,
|
||||||
|
&mut var_store,
|
||||||
|
&mut scope,
|
||||||
|
Region::zero(),
|
||||||
|
&loc_expr.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut all_ident_ids = IdentIds::exposed_builtins(1);
|
||||||
|
all_ident_ids.insert(home, scope.locals.ident_ids);
|
||||||
|
|
||||||
|
let interns = Interns {
|
||||||
|
module_ids: env.qualified_module_ids.clone().into_module_ids(),
|
||||||
|
all_ident_ids,
|
||||||
|
};
|
||||||
|
|
||||||
|
CanExprOut {
|
||||||
|
loc_expr,
|
||||||
|
output,
|
||||||
|
problems: env.problems,
|
||||||
|
home: env.home,
|
||||||
|
var_store,
|
||||||
|
interns,
|
||||||
|
var,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(syntax_error) => {
|
||||||
|
// todo panic due to unexpected syntax error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_arena(self) -> Bump {
|
||||||
|
self.parse_expr.into_arena()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,60 @@
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_parse::{
|
||||||
|
ast,
|
||||||
|
blankspace::space0_before_optional_after,
|
||||||
|
expr::{expr_end, loc_expr_block},
|
||||||
|
parser::{skip_second, EExpr, Parser, SourceError, SyntaxError},
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
|
use roc_region::all::{Loc, Position};
|
||||||
|
|
||||||
|
use crate::deindent::trim_and_deindent;
|
||||||
|
|
||||||
pub struct ParseExpr {
|
pub struct ParseExpr {
|
||||||
arena: Box<Bump>,
|
arena: Bump,
|
||||||
ast: Box<Ast>,
|
}
|
||||||
|
|
||||||
|
impl Default for ParseExpr {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
arena: Bump::with_capacity(4096),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseExpr {
|
impl ParseExpr {
|
||||||
pub fn parse(&str) -> Self {
|
pub fn parse_expr<'a>(&'a self, input: &'a str) -> Result<ast::Expr<'a>, SyntaxError<'a>> {
|
||||||
let mut arena = Bump::new();
|
self.parse_loc_expr(input)
|
||||||
let ast = parse(arena, without_indent(str));
|
.map(|loc_expr| loc_expr.value)
|
||||||
|
.map_err(|e| e.problem)
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
pub fn parse_loc_expr<'a>(
|
||||||
arena,
|
&'a self,
|
||||||
ast,
|
input: &'a str,
|
||||||
|
) -> Result<Loc<ast::Expr<'a>>, SourceError<'a, SyntaxError<'a>>> {
|
||||||
|
let original_bytes = trim_and_deindent(&self.arena, input).as_bytes();
|
||||||
|
let state = State::new(original_bytes);
|
||||||
|
|
||||||
|
let parser = skip_second(
|
||||||
|
space0_before_optional_after(
|
||||||
|
loc_expr_block(true),
|
||||||
|
EExpr::IndentStart,
|
||||||
|
EExpr::IndentEnd,
|
||||||
|
),
|
||||||
|
expr_end(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match parser.parse(&self.arena, state, 0) {
|
||||||
|
Ok((_, loc_expr, _)) => Ok(loc_expr),
|
||||||
|
Err((_, fail)) => Err(SourceError {
|
||||||
|
problem: SyntaxError::Expr(fail, Position::default()),
|
||||||
|
bytes: original_bytes,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast(&self) -> &Ast {
|
pub fn into_arena(self) -> Bump {
|
||||||
self.ast
|
self.arena
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
mod deindent;
|
||||||
|
mod help_can;
|
||||||
mod help_parse;
|
mod help_parse;
|
||||||
mod without_indent;
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
/// The purpose of this function is to let us run tests like this:
|
|
||||||
///
|
|
||||||
/// run_some_test(r#"
|
|
||||||
/// x = 1
|
|
||||||
///
|
|
||||||
/// x
|
|
||||||
/// ")
|
|
||||||
///
|
|
||||||
/// ...without needing to call a macro like `indoc!` to deal with the fact that
|
|
||||||
/// multiline Rust string literals preserve all the indented spaces. This takes out
|
|
||||||
/// the indentation as well as the leading newline in examples like the above, and it's
|
|
||||||
/// a no-op on single-line strings.
|
|
||||||
pub fn without_indent(input: &str) -> &str {
|
|
||||||
// Ignore any leading newlines, which we expect because the opening line will be `(r#"`
|
|
||||||
let input = input.trim_start_matches('\n');
|
|
||||||
let leading_spaces = input.chars().take_while(|&ch| ch == ' ').count();
|
|
||||||
|
|
||||||
input
|
|
||||||
.lines()
|
|
||||||
.map(|line| {
|
|
||||||
if line.starts_with(" ") {
|
|
||||||
line.get(leading_spaces..).unwrap_or("")
|
|
||||||
} else {
|
|
||||||
line
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single_line_input() {
|
|
||||||
let input = "single line";
|
|
||||||
assert_eq!(without_indent(input), "single line");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiline_with_indentation() {
|
|
||||||
let input = r#"
|
|
||||||
x = 1
|
|
||||||
|
|
||||||
x
|
|
||||||
"#;
|
|
||||||
let expected = "x = 1\n\nx";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiline_with_varying_indentation() {
|
|
||||||
let input = r#"
|
|
||||||
x = 1
|
|
||||||
y = 2
|
|
||||||
z = 3
|
|
||||||
"#;
|
|
||||||
let expected = "x = 1\n y = 2\nz = 3";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiline_with_empty_lines() {
|
|
||||||
let input = r#"
|
|
||||||
x = 1
|
|
||||||
|
|
||||||
y = 2
|
|
||||||
|
|
||||||
z = 3
|
|
||||||
"#;
|
|
||||||
let expected = "x = 1\n\ny = 2\n\nz = 3";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_input_without_leading_newline() {
|
|
||||||
let input = " x = 1\n y = 2";
|
|
||||||
let expected = "x = 1\ny = 2";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_input_with_multiple_leading_newlines() {
|
|
||||||
let input = "\n\n\n x = 1\n y = 2";
|
|
||||||
let expected = "x = 1\ny = 2";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_input_with_mixed_indentation() {
|
|
||||||
let input = r#"
|
|
||||||
x = 1
|
|
||||||
y = 2
|
|
||||||
z = 3
|
|
||||||
"#;
|
|
||||||
let expected = "x = 1\ny = 2\n z = 3";
|
|
||||||
assert_eq!(without_indent(input), expected);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue