use super::*;
fn write_pkg(root: &Path, manifest: &str, files: &[(&str, &str)]) {
assert_fs::fixture::ChildPath::new(root.join("cabin.toml"))
.write_str(manifest)
.unwrap();
for (rel, content) in files {
assert_fs::fixture::ChildPath::new(root.join(rel))
.write_str(content)
.unwrap();
}
}
fn header_only_cxx(version: &str, cxx: &str, interface_cxx: &str) -> String {
format!(
r#"[package]
name = "hdr"
version = "{version}"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
cxx-standard = "{cxx}"
interface-cxx-standard = "{interface_cxx}"
"#
)
}
fn compiled_cxx(version: &str, interface_cxx: &str) -> String {
format!(
r#"[package]
name = "demo"
version = "{version}"
[target.demo]
type = "library"
sources = ["src/demo.cc"]
include-dirs = ["include"]
cxx-standard = "c++20"
interface-cxx-standard = "{interface_cxx}"
"#
)
}
fn compiled_c(version: &str, interface_c: &str) -> String {
format!(
r#"[package]
name = "demo"
version = "{version}"
[target.demo]
type = "library"
sources = ["src/demo.c"]
include-dirs = ["include"]
c-standard = "c17"
interface-c-standard = "{interface_c}"
"#
)
}
const CXX_SOURCES: &[(&str, &str)] = &[
("include/demo.h", "#pragma once\nint demo_value();\n"),
(
"src/demo.cc",
"#include \"demo.h\"\nint demo_value() { return 1; }\n",
),
];
const C_SOURCES: &[(&str, &str)] = &[
("include/demo.h", "#pragma once\nint demo_value(void);\n"),
(
"src/demo.c",
"#include \"demo.h\"\nint demo_value(void) { return 1; }\n",
),
];
const HDR_SOURCES: &[(&str, &str)] = &[("include/hdr.h", "#pragma once\n#define HDR 1\n")];
fn publish(pkg_root: &Path, registry: &Path) -> assert_cmd::assert::Assert {
cabin()
.args(["publish", "--manifest-path"])
.arg(pkg_root.join("cabin.toml"))
.arg("--registry-dir")
.arg(registry)
.assert()
}
#[test]
fn pl1_rejects_and_leaves_registry_unwritten() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
write_pkg(
&pkg_root,
&header_only_cxx("1.0.0", "c++17", "c++20"),
HDR_SOURCES,
);
let registry = dir.path().join("registry");
let assertion = publish(&pkg_root, ®istry).failure().code(1);
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("interface-cxx-standard") && stderr.contains("c++20"),
"expected the PL1 message in: {stderr}"
);
assert!(
stderr.contains("rejected this publish"),
"expected the rejection preamble in: {stderr}"
);
assert!(!registry.join("config.json").exists());
assert!(!registry.join("packages").exists());
}
#[test]
fn pl1_rejects_for_c() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
write_pkg(
&pkg_root,
r#"[package]
name = "hdr"
version = "1.0.0"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
c-standard = "c11"
interface-c-standard = "c17"
"#,
HDR_SOURCES,
);
let registry = dir.path().join("registry");
let assertion = publish(&pkg_root, ®istry).failure().code(1);
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("interface-c-standard") && stderr.contains("c17") && stderr.contains("c11"),
"expected the PL1 C message in: {stderr}"
);
assert!(!registry.join("config.json").exists());
}
#[test]
fn pl2_warns_on_inferred_cxx_interface() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
write_pkg(
&pkg_root,
r#"[package]
name = "hdr"
version = "1.0.0"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
c-standard = "c11"
interface-c-standard = "c11"
cxx-standard = "c++20"
"#,
HDR_SOURCES,
);
let registry = dir.path().join("registry");
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("warning:")
&& stderr.contains("interface-cxx-standard")
&& stderr.contains("c++20")
&& stderr.contains("inferred"),
"expected the PL2 warning in: {stderr}"
);
assert!(registry.join("packages/hdr.json").is_file());
}
#[test]
fn pl2_warns_on_inferred_c_interface() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
write_pkg(
&pkg_root,
r#"[package]
name = "hdr"
version = "1.0.0"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
cxx-standard = "c++20"
interface-cxx-standard = "c++20"
c-standard = "c11"
"#,
HDR_SOURCES,
);
let registry = dir.path().join("registry");
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("warning:")
&& stderr.contains("interface-c-standard")
&& stderr.contains("inferred"),
"expected the PL2 C warning in: {stderr}"
);
}
#[test]
fn pl2_quiet_when_all_interfaces_declared() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
write_pkg(
&pkg_root,
&header_only_cxx("1.0.0", "c++20", "c++17"),
HDR_SOURCES,
);
let registry = dir.path().join("registry");
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
!stderr.contains("warning:"),
"no lint warning expected in: {stderr}"
);
}
#[test]
fn pl3_warns_on_patch_raise_cxx() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let registry = dir.path().join("registry");
write_pkg(&pkg_root, &compiled_cxx("1.0.0", "c++17"), CXX_SOURCES);
publish(&pkg_root, ®istry).success();
write_pkg(&pkg_root, &compiled_cxx("1.0.1", "c++20"), CXX_SOURCES);
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("warning:")
&& stderr.contains("raised from")
&& stderr.contains("c++17")
&& stderr.contains("c++20")
&& stderr.contains("discouraged in patches"),
"expected the PL3 warning in: {stderr}"
);
}
#[test]
fn pl3_warns_on_patch_raise_c() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let registry = dir.path().join("registry");
write_pkg(&pkg_root, &compiled_c("1.0.0", "c11"), C_SOURCES);
publish(&pkg_root, ®istry).success();
write_pkg(&pkg_root, &compiled_c("1.0.1", "c17"), C_SOURCES);
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
stderr.contains("warning:")
&& stderr.contains("raised from")
&& stderr.contains("c11")
&& stderr.contains("c17"),
"expected the PL3 C warning in: {stderr}"
);
}
#[test]
fn pl3_quiet_on_first_publish() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let registry = dir.path().join("registry");
write_pkg(&pkg_root, &compiled_cxx("1.0.0", "c++20"), CXX_SOURCES);
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
!stderr.contains("raised from"),
"a first publish must not fire PL3: {stderr}"
);
}
#[test]
fn pl3_quiet_on_minor_release() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let registry = dir.path().join("registry");
write_pkg(&pkg_root, &compiled_cxx("1.0.0", "c++17"), CXX_SOURCES);
publish(&pkg_root, ®istry).success();
write_pkg(&pkg_root, &compiled_cxx("1.1.0", "c++20"), CXX_SOURCES);
let assertion = publish(&pkg_root, ®istry).success();
let stderr = String::from_utf8_lossy(&assertion.get_output().stderr).to_string();
assert!(
!stderr.contains("raised from"),
"a minor release must not fire PL3: {stderr}"
);
}
#[test]
fn dry_run_staging_only_skips_pl3_and_still_warns() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let out = dir.path().join("dist");
write_pkg(
&pkg_root,
r#"[package]
name = "hdr"
version = "1.0.0"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
c-standard = "c11"
interface-c-standard = "c11"
cxx-standard = "c++20"
"#,
HDR_SOURCES,
);
let assertion = cabin()
.args(["publish", "--dry-run", "--manifest-path"])
.arg(pkg_root.join("cabin.toml"))
.arg("--output-dir")
.arg(&out)
.assert()
.success();
let output = assertion.get_output();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
assert!(
stdout.contains("Patch-release requirement check (PL3) skipped"),
"expected the PL3-skip note in stdout: {stdout}"
);
assert!(
stderr.contains("warning:") && stderr.contains("interface-cxx-standard"),
"expected the PL2 warning on the dry-run path: {stderr}"
);
}
#[test]
fn publish_json_reports_warnings() {
let dir = TempDir::new().unwrap();
let pkg_root = dir.path().join("pkg");
let registry = dir.path().join("registry");
write_pkg(
&pkg_root,
r#"[package]
name = "hdr"
version = "1.0.0"
[target.hdr]
type = "header-only"
include-dirs = ["include"]
c-standard = "c11"
interface-c-standard = "c11"
cxx-standard = "c++20"
"#,
HDR_SOURCES,
);
let assertion = cabin()
.args(["publish", "--manifest-path"])
.arg(pkg_root.join("cabin.toml"))
.arg("--registry-dir")
.arg(®istry)
.args(["--format", "json"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&assertion.get_output().stdout).to_string();
let value: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let warnings = value["warnings"].as_array().expect("warnings array");
assert_eq!(warnings.len(), 1, "expected one warning in: {stdout}");
assert!(
warnings[0]
.as_str()
.unwrap()
.contains("interface-cxx-standard"),
"expected the PL2 warning text in JSON: {stdout}"
);
}