#![allow(
clippy::needless_raw_string_hashes,
clippy::uninlined_format_args,
clippy::too_many_lines,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::doc_markdown,
clippy::items_after_statements
)]
use std::path::{Path, PathBuf};
use std::process::Command as StdCommand;
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 run_artifact(path: &Path, label: &str) -> String {
let output = StdCommand::new(path)
.output()
.unwrap_or_else(|err| panic!("{label}: failed to spawn `{}`: {err}", path.display()));
assert!(
output.status.success(),
"{label}: `{}` exited with {:?}; stderr = {}",
path.display(),
output.status,
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout)
.unwrap_or_else(|err| panic!("{label}: artifact stdout is not utf-8: {err}"))
}
#[test]
fn hello_c_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("hello-c");
cabin()
.args(["build", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let artifact = dir.path().join(format!(
"build/dev/packages/hello-c/hello-c{}",
std::env::consts::EXE_SUFFIX
));
let stdout = run_artifact(&artifact, "hello-c");
assert!(
stdout.contains("Hello from Cabin (C)"),
"hello-c artifact: stdout = {stdout}"
);
}
#[test]
fn hello_cpp_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("hello-cpp");
cabin()
.args(["build", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("Hello from Cabin (C++)"),
"hello-cpp run: stdout = {stdout}"
);
}
#[test]
fn platform_cfg_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("platform-cfg");
cabin()
.args(["build", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
let expected = if cfg!(windows) {
"Hello from Cabin on Windows"
} else {
"Hello from Cabin on Unix"
};
assert!(
stdout.contains(expected),
"platform-cfg run: stdout = {stdout}, expected to contain {expected:?}"
);
}
#[test]
fn library_and_app_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("library-and-app");
cabin()
.args(["build", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("Hello, Cabin!"),
"library-and-app run: stdout = {stdout}"
);
}
#[test]
fn header_only_lib_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("header-only-lib");
cabin()
.args(["build", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
for expected in [
"circle area (r = 2): 12.57",
"rectangle area (3 x 4): 12.00",
] {
assert!(
stdout.contains(expected),
"header-only-lib run: missing `{expected}`; stdout = {stdout}"
);
}
}
#[test]
fn workspace_basic_builds_workspace() {
require_cxx_build_tools();
let dir = copy_example("workspace-basic");
cabin()
.args(["build", "--workspace", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
}
#[test]
fn workspace_basic_builds_single_package() {
require_cxx_build_tools();
let dir = copy_example("workspace-basic");
cabin()
.args(["build", "-p", "cli", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
}
#[test]
fn workspace_basic_runs_selected_package() {
require_cxx_build_tools();
let dir = copy_example("workspace-basic");
let output = cabin()
.args(["run", "-p", "cli", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("doubled(21) = 42"),
"workspace-basic run -p cli: stdout = {stdout}"
);
}
#[test]
#[ignore = "requires external network"]
fn zlib_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("zlib-usage");
run_port_build_then_run(&PortBuildRun {
label: "zlib-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["zlib version: 1.3"],
});
}
#[test]
#[ignore = "requires external network"]
fn cjson_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("cjson-usage");
run_port_build_then_run(&PortBuildRun {
label: "cjson-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["cJSON parsed name: Cabin", "cJSON version: 1.7"],
});
}
#[test]
#[ignore = "requires external network"]
fn xxhash_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("xxhash-usage");
run_port_build_then_run(&PortBuildRun {
label: "xxhash-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["xxHash version: 803", "XXH64(\"Cabin\") = 002d85a6f376e171"],
});
}
#[test]
#[ignore = "requires external network"]
fn tinyxml2_usage_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("tinyxml2-usage");
run_port_build_then_run(&PortBuildRun {
label: "tinyxml2-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["tinyxml2 parsed to: Cabin", "tinyxml2 version: 11.0.0"],
});
}
#[test]
#[ignore = "requires external network"]
fn sqlite3_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("sqlite3-usage");
run_port_build_then_run(&PortBuildRun {
label: "sqlite3-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"sqlite version: 3.53",
"sqlite threadsafe: 1",
"sqlite query result: 42",
],
});
}
#[test]
#[ignore = "requires external network"]
fn sqlite3_single_threaded_feature_disables_threadsafety() {
require_c_and_cxx_build_tools();
let dir = copy_example("sqlite3-usage");
dir.child("cabin.toml")
.write_str(
r#"[package]
name = "sqlite3-usage"
version = "0.1.0"
c-standard = "c11"
[dependencies]
sqlite3 = { port = true, version = "^3", features = ["single-threaded"] }
[target.sqlite3-usage]
type = "executable"
sources = ["src/main.c"]
deps = ["sqlite3"]
"#,
)
.unwrap();
run_port_build_then_run(&PortBuildRun {
label: "sqlite3-usage single-threaded",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["sqlite threadsafe: 0"],
});
}
#[test]
#[ignore = "requires external network"]
fn libpng_usage_cache_lifecycle_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("libpng-usage");
let manifest = dir.path().join("cabin.toml");
let warm_cache = dir.path().join("cache");
let frozen_cache = dir.path().join("cache-frozen");
run_port_cache_lifecycle(&PortCacheLifecycle {
label: "libpng-usage",
manifest,
build_root: dir.path().join("build"),
warm_cache,
pristine_cache: frozen_cache,
expected_stdout: &[
"libpng version: 1.6.50",
"zlib version (via libpng port edge): 1.3",
],
expected_downloads: &["libpng", "zlib"],
frozen_port: "libpng",
});
}
#[test]
#[ignore = "requires external network"]
fn fmt_usage_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("fmt-usage");
run_port_build_then_run(&PortBuildRun {
label: "fmt-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["fmt version: 120200", "Hello, Cabin!"],
});
}
#[test]
#[ignore = "requires external network"]
fn spdlog_usage_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("spdlog-usage");
run_port_build_then_run(&PortBuildRun {
label: "spdlog-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["[info] Hello from spdlog!", "spdlog version: 1.17.0"],
});
}
#[test]
#[ignore = "requires external network"]
fn googletest_usage_runs_tests() {
require_cxx_build_tools();
let dir = copy_example("googletest-usage");
let output = cabin()
.args(["test", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
for expected in [
"test googletest-usage:calc_gtest ... ok",
"test result: ok. 1 passed; 0 failed;",
] {
assert!(
stdout.contains(expected),
"googletest-usage test: missing `{expected}`; stdout = {stdout}"
);
}
}
#[test]
#[ignore = "requires external network"]
fn catch2_usage_runs_tests() {
require_cxx_build_tools();
let dir = copy_example("catch2-usage");
let output = cabin()
.args(["test", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
for expected in [
"test catch2-usage:calc_catch2 ... ok",
"test result: ok. 1 passed; 0 failed;",
] {
assert!(
stdout.contains(expected),
"catch2-usage test: missing `{expected}`; stdout = {stdout}"
);
}
}
#[test]
#[ignore = "requires external network"]
fn nlohmann_json_usage_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("nlohmann-json-usage");
run_port_build_then_run(&PortBuildRun {
label: "nlohmann-json-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"json parsed name: Cabin",
"json parsed answer: 42",
"nlohmann_json version: 3.12.0",
],
});
}
#[test]
#[ignore = "requires external network"]
fn cli11_usage_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("cli11-usage");
run_port_build_then_run(&PortBuildRun {
label: "cli11-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["CLI11 parsed count: 3", "CLI11 version: 2.6.2"],
});
}
#[test]
#[ignore = "requires external network"]
fn miniz_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("miniz-usage");
run_port_build_then_run(&PortBuildRun {
label: "miniz-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"miniz roundtrip: Cabin compresses with miniz",
"miniz version: 11.3.2",
],
});
}
#[test]
#[ignore = "requires external network"]
fn stb_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("stb-usage");
run_port_build_then_run(&PortBuildRun {
label: "stb-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["stb_sprintf formatted: Cabin scores 42"],
});
}
#[test]
#[ignore = "requires external network"]
fn uthash_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("uthash-usage");
run_port_build_then_run(&PortBuildRun {
label: "uthash-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["uthash lookup: cabin = 42", "uthash version: 2.4.0"],
});
}
#[test]
#[ignore = "requires external network"]
fn inih_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("inih-usage");
run_port_build_then_run(&PortBuildRun {
label: "inih-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &["inih parsed name: Cabin", "inih parsed port: 8080"],
});
}
#[test]
#[ignore = "requires external network"]
fn picohttpparser_usage_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("picohttpparser-usage");
run_port_build_then_run(&PortBuildRun {
label: "picohttpparser-usage",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"picohttpparser method: GET",
"picohttpparser path: /hello",
"picohttpparser headers: 1",
],
});
}
#[test]
#[ignore = "requires external network"]
fn catch2_custom_main_feature_links_consumer_main() {
require_cxx_build_tools();
let dir = copy_example("catch2-usage");
dir.child("cabin.toml")
.write_str(
r#"[package]
name = "catch2-usage"
version = "0.1.0"
cxx-standard = "c++17"
[dependencies]
catch2 = { port = true, version = "^3.15", features = ["custom-main"] }
[target.calc]
type = "library"
sources = ["src/calc.cc"]
include-dirs = ["include"]
[target.calc_catch2]
type = "test"
sources = ["tests/calc_catch2.cc"]
deps = ["calc", "catch2"]
"#,
)
.unwrap();
dir.child("tests/calc_catch2.cc")
.write_str(
r#"#include <catch_amalgamated.hpp>
#include "calc.h"
TEST_CASE("triple scales integers") { REQUIRE(triple(2) == 6); }
int main(int argc, char* argv[]) {
return Catch::Session().run(argc, argv);
}
"#,
)
.unwrap();
let output = cabin()
.args(["test", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("test result: ok. 1 passed; 0 failed;"),
"catch2-usage custom-main test: stdout = {stdout}"
);
}
#[test]
#[ignore = "requires external network"]
fn cli_with_spdlog_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("cli-with-spdlog");
run_port_build_then_run(&PortBuildRun {
label: "cli-with-spdlog",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"[info] preparing 2 greeting(s) for Cabin",
"1/2: Hello, Cabin!",
"2/2: Hello, Cabin!",
"spdlog version: 1.17.0",
"fmt version (external): 120200",
],
});
}
#[test]
#[ignore = "requires external network"]
fn unit_test_gtest_runs_tests() {
require_cxx_build_tools();
let dir = copy_example("unit-test-gtest");
let output = cabin()
.args(["test", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
for expected in [
"test unit-test-gtest:stats_gtest ... ok",
"test result: ok. 1 passed; 0 failed;",
] {
assert!(
stdout.contains(expected),
"unit-test-gtest test: missing `{expected}`; stdout = {stdout}"
);
}
}
#[test]
#[ignore = "requires external network"]
fn json_cli_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("json-cli");
run_port_build_then_run(&PortBuildRun {
label: "json-cli",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"package: json-cli v0.1.0",
"dependency count: 3",
r#"summary: {"deps":["fmt","spdlog","sqlite3"],"name":"json-cli"}"#,
],
});
}
#[test]
#[ignore = "requires external network"]
fn sqlite_todo_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("sqlite-todo");
run_port_build_then_run(&PortBuildRun {
label: "sqlite-todo",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"[x] #1 write the manifest",
"[ ] #2 add a lockfile",
"[ ] #3 ship v0.1.0",
"open todos: 2",
],
});
}
#[test]
#[ignore = "requires external network"]
fn png_info_builds_and_runs() {
require_c_and_cxx_build_tools();
let dir = copy_example("png-info");
run_port_build_then_run(&PortBuildRun {
label: "png-info",
manifest: dir.path().join("cabin.toml"),
build_dir: dir.path().join("build"),
cache_dir: dir.path().join("cache"),
expected_stdout: &[
"png-info: 2x2, 4 channel(s)",
"roundtrip pixels match: yes",
"libpng version: 1.6.50",
"zlib version (transitive port edge): 1.3",
],
});
}
#[test]
#[ignore = "requires external network"]
fn workspace_app_and_lib_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("workspace-app-and-lib");
cabin()
.args(["build", "--workspace", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("Hello, Cabin! (formatted by fmt 120200)"),
"workspace-app-and-lib run: stdout = {stdout}"
);
}
#[test]
fn feature_gated_targets_builds_and_runs() {
require_cxx_build_tools();
let dir = copy_example("feature-gated-targets");
cabin()
.args(["build", "--workspace", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success();
let output = cabin()
.args(["run", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
assert!(
stdout.contains("TLS GET example.org"),
"feature-gated-targets run: stdout = {stdout}"
);
cabin()
.args(["build", "-p", "netlib", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build2"))
.assert()
.success();
for name in ["libtls.a", "tls.lib"] {
let gated_lib = dir.path().join("build2/dev/packages/netlib").join(name);
assert!(
!gated_lib.exists(),
"gated target must not build without its required feature: {}",
gated_lib.display()
);
}
}
#[test]
fn library_with_tests_runs_tests() {
require_cxx_build_tools();
let dir = copy_example("library-with-tests");
let output = cabin()
.args(["test", "--manifest-path"])
.arg(dir.path().join("cabin.toml"))
.arg("--build-dir")
.arg(dir.path().join("build"))
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8(output.stdout).expect("stdout is utf-8");
for expected in [
"test library-with-tests:calc_test ... ok",
"test library-with-tests:parity_test ... ok",
"test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in",
] {
assert!(
stdout.contains(expected),
"library-with-tests test: missing `{expected}`; stdout = {stdout}"
);
}
}