slovo/compiler/tests/symbols_beta10.rs

185 lines
6.1 KiB
Rust

use std::{
fs,
path::PathBuf,
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
};
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn symbols_single_file_reports_editor_facing_source_metadata() {
let source = r#"(module docs (export main))
(type Count i32)
(struct Point
(x i32)
(y i32))
(enum Status Ready (Blocked i32))
(fn main () -> i32
0)
(test "main returns zero"
(= (main) 0))
"#;
let file = write_file("symbols-file", source);
let output = run_glagol(["symbols".as_ref(), file.as_os_str()]);
assert_success("symbols file", &output);
assert!(output.stderr.is_empty(), "symbols wrote stderr");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.starts_with("(symbols\n"));
assert!(stdout.contains(" (schema slovo.symbols)\n"));
assert!(stdout.contains(" (version \"1.0.0-beta.10\")\n"));
assert!(stdout.contains(&format!(" (path \"{}\")\n", file.display())));
assert!(stdout.contains(" (name \"docs\")\n"));
assert!(stdout.contains(" (module_span (span "));
assert!(stdout.contains("(range (start (line 1) (column 1))"));
assert!(stdout.contains(" (name \"main\")\n"));
assert!(stdout.contains(" (name \"Count\")\n"));
assert!(stdout.contains(" (name \"Point\")\n"));
assert!(stdout.contains(" (name \"Status\")\n"));
assert!(stdout.contains(" (name \"Blocked\")\n"));
assert!(stdout.contains(" (name \"main returns zero\")\n"));
}
#[test]
fn symbols_project_writes_output_and_manifest_without_stdout() {
let project = write_project(
"symbols-project",
&[(
"math",
"(module math (export one))\n\n(fn one () -> i32\n 1)\n",
)],
"(module main)\n\n(import math (one))\n\n(fn main () -> i32\n (one))\n",
);
let output_path = unique_path("symbols-project-out").with_extension("sexpr");
let manifest_path = unique_path("symbols-project-manifest").with_extension("slo");
let output = run_glagol([
"symbols".as_ref(),
project.as_os_str(),
"-o".as_ref(),
output_path.as_os_str(),
"--manifest".as_ref(),
manifest_path.as_os_str(),
]);
assert_success("symbols project", &output);
assert!(output.stdout.is_empty(), "symbols -o wrote stdout");
assert!(output.stderr.is_empty(), "symbols project wrote stderr");
let symbols = fs::read_to_string(&output_path).expect("read symbols output");
assert!(symbols.contains(" (name \"main\")\n"));
assert!(symbols.contains(" (name \"math\")\n"));
assert!(symbols.contains(" (module \"math\")\n"));
assert!(symbols.contains(" (name \"one\")\n"));
assert!(symbols.contains(" (imports\n"));
assert!(symbols.contains(" (functions\n"));
let manifest = fs::read_to_string(&manifest_path).expect("read symbols manifest");
assert!(manifest.contains(" (mode symbols)\n"));
assert!(manifest.contains(" (kind symbols)\n"));
assert!(manifest.contains(&format!(" (path \"{}\")\n", output_path.display())));
}
#[test]
fn symbols_workspace_includes_package_names_deterministically() {
let workspace = unique_path("symbols-workspace");
let scaffold = run_glagol([
"new".as_ref(),
workspace.as_os_str(),
"--template".as_ref(),
"workspace".as_ref(),
]);
assert_success("workspace scaffold", &scaffold);
let first = run_glagol(["symbols".as_ref(), workspace.as_os_str()]);
let second = run_glagol(["symbols".as_ref(), workspace.as_os_str()]);
assert_success("symbols workspace first", &first);
assert_success("symbols workspace second", &second);
assert_eq!(
first.stdout, second.stdout,
"workspace symbols output was not deterministic"
);
let stdout = String::from_utf8_lossy(&first.stdout);
assert!(stdout.contains(" (package \"app\")\n"));
assert!(stdout.contains(" (package \"libutil\")\n"));
assert!(stdout.contains(" (module \"libutil.libutil\")\n"));
}
#[test]
fn symbols_usage_is_part_of_the_public_cli_surface() {
let output = run_glagol([std::ffi::OsStr::new("--help")]);
assert_success("glagol help", &output);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("glagol symbols <file.slo|project|workspace>"));
}
fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf {
let project = unique_path(name);
fs::create_dir_all(project.join("src")).expect("create project src");
fs::write(
project.join("slovo.toml"),
format!(
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
name
),
)
.expect("write manifest");
for (module, source) in modules {
fs::write(project.join("src").join(format!("{}.slo", module)), source)
.expect("write module");
}
fs::write(project.join("src/main.slo"), main).expect("write main");
project
}
fn write_file(name: &str, source: &str) -> PathBuf {
let path = unique_path(name).with_extension("slo");
fs::write(&path, source).expect("write fixture");
path
}
fn unique_path(name: &str) -> PathBuf {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock before UNIX_EPOCH")
.as_nanos();
std::env::temp_dir().join(format!(
"glagol-symbols-beta10-{}-{}-{}-{}",
std::process::id(),
nanos,
id,
name
))
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.output()
.expect("run glagol")
}
fn assert_success(context: &str, output: &Output) {
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}