591 lines
14 KiB
Rust
591 lines
14 KiB
Rust
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[test]
|
|
fn local_canonical_fixture_is_formatter_stable() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = Path::new("../tests/canonical.fmt");
|
|
let expected = fs::read_to_string("../tests/canonical.fmt").expect("read canonical.fmt");
|
|
|
|
let output = run_formatter(compiler, fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
assert!(
|
|
output.stderr.is_empty(),
|
|
"formatter wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn local_top_level_test_fixture_is_formatter_stable() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = Path::new("../tests/top-level-test.fmt");
|
|
let expected =
|
|
fs::read_to_string("../tests/top-level-test.fmt").expect("read top-level-test.fmt");
|
|
|
|
let output = run_formatter(compiler, fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
assert!(
|
|
output.stderr.is_empty(),
|
|
"formatter wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn local_v1_formatter_stability_fixture_is_formatter_stable() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = Path::new("../tests/formatter-stability-v1.fmt");
|
|
let expected = fs::read_to_string("../tests/formatter-stability-v1.fmt")
|
|
.expect("read formatter-stability-v1.fmt");
|
|
|
|
let output = run_formatter(compiler, fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
assert!(
|
|
output.stderr.is_empty(),
|
|
"formatter wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn local_comments_fixture_is_formatter_stable() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = Path::new("../tests/comments.slo");
|
|
let expected = fs::read_to_string("../tests/comments.slo").expect("read comments.slo");
|
|
|
|
let output = run_formatter(compiler, fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
assert!(
|
|
output.stderr.is_empty(),
|
|
"formatter wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_canonicalizes_supported_syntax() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"supported-messy",
|
|
r#"; status: formatter-canonical
|
|
; Scope: current strict supported syntax only.
|
|
(module main)
|
|
(fn add (
|
|
(a i32)
|
|
(b i32)) -> i32 (+ a b))
|
|
|
|
(fn main() -> i32
|
|
(print_i32(add 20 22))
|
|
0)
|
|
"#,
|
|
);
|
|
let expected = fs::read_to_string("../tests/canonical.fmt").expect("read canonical.fmt");
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_canonicalizes_struct_forms() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"struct",
|
|
r#"
|
|
(module main)
|
|
|
|
(struct Point (x i32) (y i32))
|
|
|
|
(fn point_sum () -> i32
|
|
(+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y)))
|
|
|
|
(fn main () -> i32
|
|
(point_sum))
|
|
"#,
|
|
);
|
|
let expected = concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(struct Point\n",
|
|
" (x i32)\n",
|
|
" (y i32))\n",
|
|
"\n",
|
|
"(fn point_sum () -> i32\n",
|
|
" (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y)))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (point_sum))\n",
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_canonicalizes_top_level_tests() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"top-level-test",
|
|
r#"; status: formatter-canonical
|
|
; Scope: promoted top-level test formatter contract.
|
|
(module tests)
|
|
|
|
(fn add (
|
|
(a i32)
|
|
(b i32)) -> i32 (+ a b))
|
|
|
|
(test "add works" (= (add 2 3) 5))
|
|
|
|
(fn main() -> i32 0)
|
|
"#,
|
|
);
|
|
let expected =
|
|
fs::read_to_string("../tests/top-level-test.fmt").expect("read top-level-test.fmt");
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_keeps_long_inline_forms_inline() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"long-inline",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn accept_many (
|
|
(a i32)
|
|
(b i32)
|
|
(c i32)
|
|
(d i32)
|
|
(e i32)
|
|
(f i32)
|
|
(g i32)
|
|
(h i32)
|
|
(i i32)
|
|
(j i32)) -> i32
|
|
(+ a j))
|
|
|
|
(fn main () -> i32
|
|
(accept_many
|
|
100000001
|
|
100000002
|
|
100000003
|
|
100000004
|
|
100000005
|
|
100000006
|
|
100000007
|
|
100000008
|
|
100000009
|
|
100000010))
|
|
"#,
|
|
);
|
|
let expected = concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn accept_many ((a i32) (b i32) (c i32) (d i32) (e i32) (f i32) (g i32) (h i32) (i i32) (j i32)) -> i32\n",
|
|
" (+ a j))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (accept_many 100000001 100000002 100000003 100000004 100000005 100000006 100000007 100000008 100000009 100000010))\n",
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
expected,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_rejects_test_names_outside_v0_subset() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"invalid-test-name",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "bad\nname"
|
|
true)
|
|
"#,
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"formatter unexpectedly accepted invalid test name\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"formatter emitted stdout for rejected test name\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stderr.contains("error[InvalidTestName]") || stderr.contains("(code InvalidTestName)"),
|
|
"stderr did not contain InvalidTestName\nstderr:\n{}",
|
|
stderr,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_reports_unsupported_standard_library_calls() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let cases = [
|
|
(
|
|
"unsupported-std-call",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_unit 0)
|
|
0)
|
|
"#,
|
|
),
|
|
(
|
|
"unsupported-std-user-call",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn std.io.print_unit ((value i32)) -> i32
|
|
value)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_unit 0))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_formatter(compiler, &fixture);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"formatter unexpectedly accepted unsupported std call `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"formatter emitted stdout for unsupported std call `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedStandardLibraryCall"),
|
|
"formatter stderr did not contain UnsupportedStandardLibraryCall for `{}`\nstderr:\n{}",
|
|
name,
|
|
stderr,
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_preserves_full_line_comments_inside_function_bodies() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"body-comments",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn add ((a i32) (b i32)) -> i32
|
|
; keep this in add
|
|
(+ a b))
|
|
|
|
(fn main () -> i32
|
|
; before effect
|
|
(print_i32 (add 20 21))
|
|
; before result
|
|
(+ 20 22)
|
|
; after result
|
|
)
|
|
"#,
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn add ((a i32) (b i32)) -> i32\n",
|
|
" ; keep this in add\n",
|
|
" (+ a b))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" ; before effect\n",
|
|
" (print_i32 (add 20 21))\n",
|
|
" ; before result\n",
|
|
" (+ 20 22)\n",
|
|
" ; after result\n",
|
|
")\n",
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_preserves_full_line_comments_after_last_form() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"trailing-comments",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
|
|
; keep this file trailer
|
|
; and normalize indentation
|
|
"#,
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
|
|
assert_success(&output);
|
|
assert_eq!(
|
|
String::from_utf8(output.stdout).expect("formatter output is UTF-8"),
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" 0)\n",
|
|
"\n",
|
|
"; keep this file trailer\n",
|
|
"; and normalize indentation\n",
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_rejects_comments_inside_inline_expression_forms() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let fixture = write_fixture(
|
|
"expression-comment",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(+ 20
|
|
; cannot keep this while preserving inline calls
|
|
22))
|
|
"#,
|
|
);
|
|
|
|
let output = run_formatter(compiler, &fixture);
|
|
assert_formatter_comment_rejection(&output, "inline expression form");
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_rejects_same_line_and_trailing_comments() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let cases = [
|
|
(
|
|
"same-line-module-comment",
|
|
r#"
|
|
(module main) ; unsupported trailing module comment
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
"same-line module comment",
|
|
),
|
|
(
|
|
"same-line-expression-comment",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(+ 20 22) ; unsupported trailing expression comment
|
|
)
|
|
"#,
|
|
"same-line expression comment",
|
|
),
|
|
];
|
|
|
|
for (name, source, label) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_formatter(compiler, &fixture);
|
|
assert_formatter_comment_rejection(&output, label);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_rejects_comments_in_headers_and_signatures() {
|
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
let cases = [
|
|
(
|
|
"module-header-comment",
|
|
r#"
|
|
(module
|
|
; unsupported module header comment
|
|
main)
|
|
"#,
|
|
"module header",
|
|
),
|
|
(
|
|
"struct-signature-comment",
|
|
r#"
|
|
(module main)
|
|
|
|
(struct
|
|
; unsupported struct signature comment
|
|
Pair
|
|
(left i32)
|
|
(right i32))
|
|
"#,
|
|
"struct signature",
|
|
),
|
|
(
|
|
"function-signature-comment",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn add ((a i32)
|
|
; unsupported function signature comment
|
|
(b i32)) -> i32
|
|
(+ a b))
|
|
"#,
|
|
"function signature",
|
|
),
|
|
(
|
|
"test-header-comment",
|
|
r#"
|
|
(module main)
|
|
|
|
(test
|
|
; unsupported test header comment
|
|
"addition works"
|
|
true)
|
|
"#,
|
|
"test header",
|
|
),
|
|
];
|
|
|
|
for (name, source, label) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_formatter(compiler, &fixture);
|
|
assert_formatter_comment_rejection(&output, label);
|
|
}
|
|
}
|
|
|
|
fn run_formatter(compiler: &str, fixture: &Path) -> Output {
|
|
Command::new(compiler)
|
|
.arg("--format")
|
|
.arg(fixture)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run glagol --format on `{}`: {}", fixture.display(), err))
|
|
}
|
|
|
|
fn assert_success(output: &Output) {
|
|
assert!(
|
|
output.status.success(),
|
|
"formatter failed\nstdout:\n{}\nstderr:\n{}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr),
|
|
);
|
|
}
|
|
|
|
fn assert_formatter_comment_rejection(output: &Output, label: &str) {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"formatter unexpectedly accepted unsupported comment position in {}\nstdout:\n{}\nstderr:\n{}",
|
|
label,
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"formatter emitted stdout for rejected {}\nstdout:\n{}\nstderr:\n{}",
|
|
label,
|
|
stdout,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stderr.contains("error[UnsupportedFormatterComment]"),
|
|
"stderr did not contain human UnsupportedFormatterComment for {}\nstderr:\n{}",
|
|
label,
|
|
stderr,
|
|
);
|
|
assert!(
|
|
stderr.contains(" (schema slovo.diagnostic)\n")
|
|
&& stderr.contains(" (version 1)\n")
|
|
&& stderr.contains(" (code UnsupportedFormatterComment)\n")
|
|
&& stderr.contains(" (span\n"),
|
|
"stderr did not contain structured UnsupportedFormatterComment diagnostic for {}\nstderr:\n{}",
|
|
label,
|
|
stderr,
|
|
);
|
|
}
|
|
|
|
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
let mut path = std::env::temp_dir();
|
|
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
path.push(format!(
|
|
"glagol-formatter-{}-{}-{}.slo",
|
|
std::process::id(),
|
|
id,
|
|
name
|
|
));
|
|
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
path
|
|
}
|