230 lines
7.1 KiB
Rust
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)
|
|
)
|
|
}
|