use std::path::{Path, PathBuf};
use assert_fs::TempDir;
use assert_fs::prelude::*;
mod common;
use common::*;
fn examples_root() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest_dir
.parent()
.and_then(Path::parent)
.expect("workspace root should be two levels above crates/cabin")
.join("examples")
}
fn copy_example(name: &str) -> TempDir {
let dir = TempDir::new().expect("temp dir");
dir.copy_from(examples_root().join(name), &["**"])
.unwrap_or_else(|err| panic!("failed to copy example `{name}`: {err}"));
dir
}
fn collect_files(root: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
if let Ok(entries) = std::fs::read_dir(root) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
out.extend(collect_files(&path));
} else {
out.push(path);
}
}
}
out
}
fn any_with_ext(files: &[PathBuf], ext: &str) -> bool {
files
.iter()
.any(|p| p.extension().is_some_and(|e| e == ext))
}
#[test]
fn check_produces_no_objects_archives_or_binaries() {
require_cxx_build_tools();
let dir = copy_example("library-and-app");
cabin()
.args(["check", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let files = collect_files(&dir.path().join("build"));
assert!(
!any_with_ext(&files, "o"),
"check must not produce object files: {files:?}"
);
assert!(
!any_with_ext(&files, "a"),
"check must not produce static archives: {files:?}"
);
assert!(
any_with_ext(&files, "check"),
"check must produce syntax-check stamps: {files:?}"
);
assert!(
!dir.path()
.join("build/dev/packages/library-and-app/app")
.exists(),
"check must not produce the `app` executable"
);
let ninja = std::fs::read_to_string(dir.path().join("build/dev/build.ninja"))
.expect("build.ninja written");
let syntax_only_flag = if cfg!(windows) {
"/Zs"
} else {
"-fsyntax-only"
};
assert!(
ninja.contains(syntax_only_flag),
"build.ninja must carry {syntax_only_flag}:\n{ninja}"
);
assert!(
ninja.contains(": cxx_check "),
"expected a cxx_check edge:\n{ninja}"
);
assert!(
!ninja.contains(": link_executable "),
"check must emit no link edge:\n{ninja}"
);
assert!(
!ninja.contains(": cxx_archive "),
"check must emit no archive edge:\n{ninja}"
);
assert!(
!ninja.contains(": cxx_compile "),
"check must emit no normal compile edge:\n{ninja}"
);
}
#[test]
fn check_fails_on_semantic_error() {
require_cxx_build_tools();
let dir = TempDir::new().expect("temp dir");
dir.child("cabin.toml")
.write_str(
"[package]\nname = \"broken\"\nversion = \"0.1.0\"\ncxx-standard = \"c++17\"\n\n\
[target.broken]\ntype = \"executable\"\nsources = [\"src/main.cc\"]\n",
)
.unwrap();
dir.child("src/main.cc")
.write_str("int main() { return undeclared_symbol(); }\n")
.unwrap();
let assert = cabin()
.args(["check", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.failure();
let out = assert.get_output();
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
combined.contains("undeclared"),
"expected the compiler's semantic diagnostic in the output, got:\n{combined}"
);
let files = collect_files(&dir.path().join("build"));
assert!(
!any_with_ext(&files, "o"),
"failed check must not leave object files: {files:?}"
);
}