use anyhow::bail;
use picotest_helpers::migration::{
find_migrations_directories, make_ddl_tier_overrides, parse_migrations,
};
use picotest_helpers::topology::{
parse_topology, PluginTopology, SingleNodeTopologyTransformer, TopologyTransformer,
DEFAULT_TIER,
};
use picotest_helpers::Cluster;
use std::collections::HashMap;
use std::{
env,
path::{Path, PathBuf},
sync::OnceLock,
time::Duration,
};
#[cfg(target_os = "linux")]
const LIB_EXT: &str = "so";
#[cfg(target_os = "macos")]
const LIB_EXT: &str = "dylib";
const PLUGIN_TOPOLOGY_FILENAME: &str = "topology.toml";
pub fn plugin_profile_build_path(plugin_path: &Path) -> PathBuf {
plugin_path.join("target").join("debug")
}
pub fn plugin_dylib_path(plugin_path: &Path, package_name: &str) -> PathBuf {
let plugin_dylib_filename = format!("lib{}.{LIB_EXT}", package_name.replace('-', "_"));
plugin_profile_build_path(plugin_path).join(plugin_dylib_filename)
}
pub fn plugin_topology_path(plugin_path: &Path) -> PathBuf {
plugin_path.join(PLUGIN_TOPOLOGY_FILENAME)
}
pub fn plugin_root_dir() -> PathBuf {
let plugin_topology_path = find_plugin_topology_path()
.expect("Error occurred while searching for plugin topology configuration")
.expect("Plugin topology configuration is not found");
let plugin_root_dir = plugin_topology_path
.parent()
.expect("Failed to obtain parent directory of plugin topology file");
assert!(
plugin_root_dir.join("Cargo.toml").exists(),
"broken plugin directory?"
);
plugin_root_dir.to_path_buf()
}
pub fn find_plugin_topology_path() -> anyhow::Result<Option<PathBuf>> {
let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into();
for path in manifest_dir.ancestors() {
let topology_path = path.join(PLUGIN_TOPOLOGY_FILENAME);
if topology_path.exists() {
return Ok(Some(topology_path));
}
}
Ok(None)
}
pub fn lua_ffi_call_unit_test(test_fn_name: &str, plugin_dylib_path: &str) -> String {
format!(
r#"
"[*] Running unit-test '{test_fn_name}'"
ffi = require("ffi")
ffi.cdef[[void {test_fn_name}();]]
dylib = "{plugin_dylib_path}"
ffi.load(dylib).{test_fn_name}()
"[*] Test '{test_fn_name}' has been finished"
true"#
)
}
pub fn verify_unit_test_output(output: &str) -> anyhow::Result<()> {
if output.contains("cannot open shared object file") {
bail!("failed to open plugin shared library")
} else if output.contains("missing declaration") || output.contains("undefined symbol") {
bail!("failed to call unit-test routine: missing symbol in plugin shared library")
} else if !output.contains("true") {
bail!("test has finished unexpectedly")
}
Ok(())
}
pub fn create_cluster(
plugin_path: Option<PathBuf>,
plugin_topology: Option<PluginTopology>,
timout: Duration,
) -> Cluster {
let plugin_path = plugin_path.unwrap_or_else(plugin_root_dir);
let plugin_topology = plugin_topology.map_or_else(
|| parse_topology(&plugin_topology_path(&plugin_path)),
Result::Ok,
);
Cluster::new(plugin_path, plugin_topology.unwrap(), timout)
.expect("Failed to create the cluster")
.run()
.expect("Failed to start the cluster")
}
pub fn get_or_create_unit_test_topology() -> &'static PluginTopology {
static TOPOLOGY: OnceLock<PluginTopology> = OnceLock::new();
TOPOLOGY.get_or_init(|| {
let plugin_root = plugin_root_dir();
let plugin_topology_path = plugin_topology_path(&plugin_root);
let plugin_topology = parse_topology(&plugin_topology_path).unwrap();
let profile_path = plugin_profile_build_path(&plugin_root);
let migrations_paths = find_migrations_directories(profile_path).unwrap();
let mut context_vars_map = HashMap::new();
for (plugin_name, migrations_path) in migrations_paths {
let plugin_migrations = parse_migrations(&migrations_path).unwrap();
let ctx_vars = make_ddl_tier_overrides(&plugin_migrations, DEFAULT_TIER);
context_vars_map.insert(plugin_name, ctx_vars);
}
let mut transformer = SingleNodeTopologyTransformer::default();
transformer.set_migration_context_provider(context_vars_map);
transformer.transform(&plugin_topology)
})
}