slovo/compiler/tests/package_workspace_discipline_beta24.rs

230 lines
7.1 KiB
Rust

use std::{
fs,
path::PathBuf,
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_WORKSPACE_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn duplicate_package_keys_report_package_manifest_invalid() {
let workspace = write_workspace(
"duplicate-package-key",
"[workspace]\nmembers = [\"packages/app\"]\n",
&[WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nname = \"other\"\nversion = \"0.1.0\"\n",
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
}],
);
let output = run_glagol([
"--json-diagnostics".as_ref(),
"check".as_ref(),
workspace.as_os_str(),
]);
assert_exit_code("duplicate package key", &output, 1);
assert_json_diagnostic_code("duplicate package key", &output, "PackageManifestInvalid");
assert_json_diagnostic_code_absent("duplicate package key", &output, "ProjectManifestInvalid");
}
#[test]
fn invalid_dependency_key_reports_invalid_package_dependency_name() {
let workspace = write_workspace(
"invalid-dependency-key",
"[workspace]\nmembers = [\"packages/app\"]\n",
&[WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nBad_Name = { path = \"../util\" }\n",
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
}],
);
let output = run_glagol([
"--json-diagnostics".as_ref(),
"check".as_ref(),
workspace.as_os_str(),
]);
assert_exit_code("invalid dependency key", &output, 1);
assert_json_diagnostic_code(
"invalid dependency key",
&output,
"InvalidPackageDependencyName",
);
}
#[test]
fn duplicate_dependency_keys_report_duplicate_package_dependency_name() {
let workspace = write_workspace(
"duplicate-dependency-key",
"[workspace]\nmembers = [\"packages/app\"]\n",
&[WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\nutil = { path = \"../util-again\" }\n",
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
}],
);
let output = run_glagol([
"--json-diagnostics".as_ref(),
"check".as_ref(),
workspace.as_os_str(),
]);
assert_exit_code("duplicate dependency key", &output, 1);
assert_json_diagnostic_code(
"duplicate dependency key",
&output,
"DuplicatePackageDependencyName",
);
}
#[test]
fn valid_dependency_identity_checks_cleanly() {
let workspace = write_workspace(
"valid-dependency-identity",
"[workspace]\nmembers = [\"packages/app\", \"packages/util\"]\n",
&[
WorkspacePackageSpec {
member: "packages/util",
manifest: "[package]\nname = \"util\"\nversion = \"0.1.0\"\n",
modules: &[(
"util",
"(module util (export answer))\n\n(fn answer () -> i32\n 42)\n",
)],
},
WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\n",
modules: &[(
"main",
"(module main)\n\n(import util.util (answer))\n\n(fn main () -> i32\n (answer))\n",
)],
},
],
);
let output = run_glagol(["check".as_ref(), workspace.as_os_str()]);
assert_success_stdout("valid dependency identity", output, "");
}
struct WorkspacePackageSpec<'a> {
member: &'a str,
manifest: &'a str,
modules: &'a [(&'a str, &'a str)],
}
fn write_workspace(
name: &str,
workspace_manifest: &str,
packages: &[WorkspacePackageSpec<'_>],
) -> PathBuf {
let root = unique_path(name);
fs::create_dir_all(&root).expect("create workspace root");
fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest");
for package in packages {
let package_root = root.join(package.member);
let src = package_root.join("src");
fs::create_dir_all(&src).expect("create workspace package src");
fs::write(package_root.join("slovo.toml"), package.manifest)
.expect("write workspace package manifest");
for (module, source) in package.modules {
fs::write(src.join(format!("{}.slo", module)), source)
.expect("write workspace package module");
}
}
root
}
fn unique_path(name: &str) -> PathBuf {
let id = NEXT_WORKSPACE_ID.fetch_add(1, Ordering::SeqCst);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!(
"glagol-package-workspace-discipline-beta24-{}-{}-{}-{}",
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_stdout(context: &str, output: Output, expected: &str) {
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
expected,
"{} stdout mismatch",
context
);
assert!(
output.stderr.is_empty(),
"{} wrote stderr:\n{}",
context,
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_exit_code(context: &str, output: &Output, expected: i32) {
assert_eq!(
output.status.code(),
Some(expected),
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_json_diagnostic_code(context: &str, output: &Output, expected: &str) {
let diagnostics = diagnostic_text(output);
assert!(
diagnostics.contains(&format!(r#""code":"{}""#, expected)),
"{} did not report `{}`:\n{}",
context,
expected,
diagnostics
);
}
fn assert_json_diagnostic_code_absent(context: &str, output: &Output, unexpected: &str) {
let diagnostics = diagnostic_text(output);
assert!(
!diagnostics.contains(&format!(r#""code":"{}""#, unexpected)),
"{} unexpectedly reported `{}`:\n{}",
context,
unexpected,
diagnostics
);
}
fn diagnostic_text(output: &Output) -> String {
format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}