use std::collections::BTreeMap;
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{Duration, Instant};
use crate::bench::BenchError;
use crate::bench::client::{AuthKind, BenchClient, PreferReturn};
use crate::bench::compare::summarize;
use crate::bench::pack::BenchPack;
use crate::bench::posture::{
AuditSink, AuthnMode, CompressionMode, PostureDivergence, PostureItem, PostureProfile,
SigningScheme, Tenancy, ValidationDepth,
};
use crate::bench::result::{BaselineRecord, BaselineResources, RecipeReference};
use crate::bench::run::{self, BenchRun};
const SERVER_CPUS: &str = "4";
const SERVER_MEMORY: &str = "4G";
const DATABASE_CPUS: &str = "4";
const DATABASE_MEMORY: &str = "4G";
const DATABASE_SHM_SIZE: &str = "1gb";
const COMPOSE_WAIT_S: u64 = 300;
const READINESS_TIMEOUT: Duration = Duration::from_mins(5);
const READINESS_INTERVAL: Duration = Duration::from_secs(2);
const READINESS_PATH: &str = "/definition/template/adl1.4";
const BIND_HOST: &str = "127.0.0.1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ReferenceCdr {
EhrBase,
FerroEhr,
}
impl ReferenceCdr {
pub const ALL: &[ReferenceCdr] = &[ReferenceCdr::EhrBase, ReferenceCdr::FerroEhr];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
ReferenceCdr::EhrBase => "ehrbase",
ReferenceCdr::FerroEhr => "ferroehr",
}
}
#[must_use]
pub const fn display_name(self) -> &'static str {
match self {
ReferenceCdr::EhrBase => "EHRbase",
ReferenceCdr::FerroEhr => "FerroEHR",
}
}
pub fn parse(token: &str) -> Result<Self, BenchError> {
Self::ALL
.iter()
.copied()
.find(|cdr| cdr.as_str() == token)
.ok_or_else(|| BenchError::UnknownToken {
vocabulary: "reference CDR",
token: token.to_owned(),
accepted: Self::ALL
.iter()
.map(|cdr| cdr.as_str())
.collect::<Vec<_>>()
.join(", "),
})
}
#[must_use]
pub const fn pin(self) -> &'static ReferencePin {
match self {
ReferenceCdr::EhrBase => &EHRBASE_PIN,
ReferenceCdr::FerroEhr => &FERROEHR_PIN,
}
}
}
impl std::fmt::Display for ReferenceCdr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReferencePin {
pub cdr: ReferenceCdr,
pub server_image: &'static str,
pub database_image: &'static str,
pub recipe_repository: &'static str,
pub recipe_ref: &'static str,
pub recipe_file: &'static str,
pub host_port: u16,
pub database_port: u16,
pub rest_path: &'static str,
pub user: &'static str,
pub password: &'static str,
pub posture: PinPosture,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecipeSetting<T> {
pub value: T,
pub element: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PinPosture {
pub audit: RecipeSetting<AuditSink>,
pub signing: RecipeSetting<SigningScheme>,
pub validation: RecipeSetting<ValidationDepth>,
pub compression: RecipeSetting<CompressionMode>,
pub tenancy: RecipeSetting<Tenancy>,
pub authn: RecipeSetting<AuthnMode>,
}
impl PinPosture {
#[must_use]
pub const fn configured(&self, item: PostureItem) -> Option<(&'static str, &'static str)> {
match item {
PostureItem::Audit => Some((self.audit.value.as_str(), self.audit.element)),
PostureItem::VersionSigning => {
Some((self.signing.value.as_str(), self.signing.element))
}
PostureItem::CommitValidation => {
Some((self.validation.value.as_str(), self.validation.element))
}
PostureItem::Compression => {
Some((self.compression.value.as_str(), self.compression.element))
}
PostureItem::Tenancy => Some((self.tenancy.value.as_str(), self.tenancy.element)),
PostureItem::Authn | PostureItem::Tls => None,
}
}
}
const EHRBASE_PIN: ReferencePin = ReferencePin {
cdr: ReferenceCdr::EhrBase,
server_image: "ehrbase/ehrbase:2.35.1@sha256:a17cfdd7be7045a2abb75a37d33ae8c26c92f6e8acd832922cfc786d0791e8a8",
database_image: "ehrbase/ehrbase-v2-postgres:16.2@sha256:abe14e8f9ba33cabc9946c6c17c5aa95b64b35387f266cd20a894149203196d7",
recipe_repository: "https://github.com/ehrbase/ehrbase",
recipe_ref: "v2.35.1",
recipe_file: "docker-compose.yml",
host_port: 18091,
database_port: 15432,
rest_path: "/ehrbase/rest/openehr/v1",
user: "veredictum",
password: "veredictum",
posture: EHRBASE_POSTURE,
};
const EHRBASE_POSTURE: PinPosture = PinPosture {
audit: RecipeSetting {
value: AuditSink::Off,
element: "docker-compose.yml, .env.ehrbase and configuration/src/main/resources/application.yml carry no audit element, so the recipe writes no audit trail",
},
signing: RecipeSetting {
value: SigningScheme::None,
element: "docker-compose.yml, .env.ehrbase and configuration/src/main/resources/application.yml carry no version-signing element, so committed versions are unsigned",
},
validation: RecipeSetting {
value: ValidationDepth::Template,
element: "configuration/src/main/resources/application.yml `ehrbase.validation.check-for-extra-nodes: true`, `validate-rm-constraints: true` and `validate-folders: true`, with `server.disable-strict-validation` left commented out",
},
compression: RecipeSetting {
value: CompressionMode::Off,
element: "docker-compose.yml, .env.ehrbase and configuration/src/main/resources/application.yml carry no `server.compression` element, so nothing in the recipe switches response compression on",
},
tenancy: RecipeSetting {
value: Tenancy::Single,
element: "docker-compose.yml composes one server over one database and carries no tenancy element",
},
authn: RecipeSetting {
value: AuthnMode::Basic,
element: "configuration/src/main/resources/application.yml `security.authType: BASIC`, with .env.ehrbase supplying SECURITY_AUTHUSER and SECURITY_AUTHPASSWORD",
},
};
const FERROEHR_PIN: ReferencePin = ReferencePin {
cdr: ReferenceCdr::FerroEhr,
server_image: "ghcr.io/rubentalstra/ferroehr:4.0.11@sha256:06bb07a4a4788923f7f5005253afb3da989915ba4b5e91c5d483a6393672217d",
database_image: "ghcr.io/rubentalstra/ferroehr-postgres:4.0.11@sha256:0ed939f98c37e3dec8e2ad24010778040365b74fe677ce7bce1f7248effeed79",
recipe_repository: "https://github.com/rubentalstra/FerroEHR",
recipe_ref: "v4.0.11",
recipe_file: "docker/sut-ferroehr.yml",
host_port: 18080,
database_port: 15433,
rest_path: "/ferroehr/rest/openehr/v1",
user: "ferroehr",
password: "ferroehr",
posture: FERROEHR_POSTURE,
};
const FERROEHR_POSTURE: PinPosture = PinPosture {
audit: RecipeSetting {
value: AuditSink::Off,
element: "docker/sut-ferroehr.yml and the docker/ferroehr.dev.toml it mounts carry no audit element, so the recipe writes no audit trail",
},
signing: RecipeSetting {
value: SigningScheme::Digest,
element: "docker/sut-ferroehr.yml `FERROEHR__SIGNING__ENABLED: ${FERROEHR__SIGNING__ENABLED:-true}`, and docker/sut-signing-pgp.yml sets `FERROEHR__SIGNING__MODE: pgp` \"instead of the default digest\"",
},
validation: RecipeSetting {
value: ValidationDepth::Template,
element: "docker/sut-ferroehr.yml and docker/ferroehr.dev.toml carry no element relaxing commit validation",
},
compression: RecipeSetting {
value: CompressionMode::Off,
element: "docker/sut-ferroehr.yml and docker/ferroehr.dev.toml carry no compression element, so nothing in the recipe switches response compression on",
},
tenancy: RecipeSetting {
value: Tenancy::Single,
element: "docker/sut-ferroehr.yml composes one server over one database and carries no tenancy element",
},
authn: RecipeSetting {
value: AuthnMode::Basic,
element: "docker/ferroehr.dev.toml `[auth] enabled = true` with its `[[auth.basic.users]]` entries",
},
};
const FERROEHR_PASSWORD_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$ZmVycm9laHJEZXZTYWx0$be5nPwWjfUl1qvSrvkvqvdMOuCgM0VFcN/VN4MFLjT8";
const FERROEHR_CONFIG_FILE: &str = "ferroehr.toml";
const COMPOSE_FILE: &str = "compose.yaml";
impl ReferencePin {
#[must_use]
pub fn base_url(&self) -> String {
format!("http://{BIND_HOST}:{}{}", self.host_port, self.rest_path)
}
#[must_use]
pub fn project(&self) -> String {
format!("veredictum-baseline-{}", self.cdr.as_str())
}
#[must_use]
pub fn recipe(&self) -> RecipeReference {
RecipeReference {
repository: self.recipe_repository.to_owned(),
git_ref: self.recipe_ref.to_owned(),
file: self.recipe_file.to_owned(),
}
}
#[must_use]
pub fn declaration(&self, target: &PostureProfile) -> (PostureProfile, Vec<PostureDivergence>) {
let declared = PostureProfile {
name: target.name,
summary: target.summary,
audit: self.posture.audit.value,
signing: self.posture.signing.value,
validation: self.posture.validation.value,
compression: self.posture.compression.value,
tenancy: self.posture.tenancy.value,
};
let divergences = PostureItem::ALL
.iter()
.copied()
.filter_map(|item| {
let (configured, element) = self.posture.configured(item)?;
let profile_declares = target.declared(item)?;
(profile_declares != configured).then(|| PostureDivergence {
item,
profile_declares: profile_declares.to_owned(),
deployment_configures: configured.to_owned(),
source: format!(
"{} at {}: {element}",
self.recipe_repository, self.recipe_ref
),
})
})
.collect();
(declared, divergences)
}
#[must_use]
pub fn images(&self) -> BTreeMap<String, String> {
let mut images = BTreeMap::new();
let _replaced = images.insert("server".to_owned(), self.server_image.to_owned());
let _replaced = images.insert("database".to_owned(), self.database_image.to_owned());
images
}
#[must_use]
pub fn compose_document(&self) -> String {
match self.cdr {
ReferenceCdr::EhrBase => self.ehrbase_compose(),
ReferenceCdr::FerroEhr => self.ferroehr_compose(),
}
}
#[must_use]
pub fn side_files(&self) -> Vec<(&'static str, String)> {
match self.cdr {
ReferenceCdr::EhrBase => Vec::new(),
ReferenceCdr::FerroEhr => {
vec![(FERROEHR_CONFIG_FILE, ferroehr_configuration())]
}
}
}
fn ehrbase_compose(&self) -> String {
let (server, database) = (self.server_image, self.database_image);
let (port, db_port) = (self.host_port, self.database_port);
let (user, password) = (self.user, self.password);
format!(
"services:\n\
\x20 ehrbase-db:\n\
\x20 image: {database}\n\
\x20 shm_size: {DATABASE_SHM_SIZE}\n\
\x20 environment:\n\
\x20 POSTGRES_USER: postgres\n\
\x20 POSTGRES_PASSWORD: postgres\n\
\x20 EHRBASE_USER_ADMIN: ehrbase\n\
\x20 EHRBASE_PASSWORD_ADMIN: ehrbase\n\
\x20 EHRBASE_USER: ehrbase_restricted\n\
\x20 EHRBASE_PASSWORD: ehrbase_restricted\n\
\x20 healthcheck:\n\
\x20 test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n\
\x20 interval: 5s\n\
\x20 timeout: 5s\n\
\x20 retries: 24\n\
\x20 ports:\n\
\x20 - \"{BIND_HOST}:{db_port}:5432\"\n\
\x20 deploy:\n\
\x20 resources:\n\
\x20 limits:\n\
\x20 cpus: \"{DATABASE_CPUS}\"\n\
\x20 memory: {DATABASE_MEMORY}\n\
\x20 ehrbase:\n\
\x20 image: {server}\n\
\x20 depends_on:\n\
\x20 ehrbase-db:\n\
\x20 condition: service_healthy\n\
\x20 environment:\n\
\x20 DB_URL: jdbc:postgresql://ehrbase-db:5432/ehrbase\n\
\x20 DB_USER_ADMIN: ehrbase\n\
\x20 DB_PASS_ADMIN: ehrbase\n\
\x20 DB_USER: ehrbase_restricted\n\
\x20 DB_PASS: ehrbase_restricted\n\
\x20 SECURITY_AUTHTYPE: BASIC\n\
\x20 SECURITY_AUTHUSER: {user}\n\
\x20 SECURITY_AUTHPASSWORD: {password}\n\
\x20 SECURITY_AUTHADMINUSER: {user}-admin\n\
\x20 SECURITY_AUTHADMINPASSWORD: {password}\n\
\x20 SYSTEM_ALLOWTEMPLATEOVERWRITE: \"false\"\n\
\x20 ports:\n\
\x20 - \"{BIND_HOST}:{port}:8080\"\n\
\x20 deploy:\n\
\x20 resources:\n\
\x20 limits:\n\
\x20 cpus: \"{SERVER_CPUS}\"\n\
\x20 memory: {SERVER_MEMORY}\n"
)
}
fn ferroehr_compose(&self) -> String {
let (server, database) = (self.server_image, self.database_image);
let (port, db_port) = (self.host_port, self.database_port);
format!(
"services:\n\
\x20 ferroehr-postgres:\n\
\x20 image: {database}\n\
\x20 shm_size: {DATABASE_SHM_SIZE}\n\
\x20 environment:\n\
\x20 POSTGRES_PASSWORD: postgres\n\
\x20 PG_INIT_USER: ferroehr\n\
\x20 PG_INIT_PASSWORD: ferroehr\n\
\x20 PG_INIT_DB: ferroehr\n\
\x20 healthcheck:\n\
\x20 test: [\"CMD-SHELL\", \"pg_isready -U ferroehr -d ferroehr\"]\n\
\x20 interval: 5s\n\
\x20 timeout: 5s\n\
\x20 retries: 24\n\
\x20 ports:\n\
\x20 - \"{BIND_HOST}:{db_port}:5432\"\n\
\x20 deploy:\n\
\x20 resources:\n\
\x20 limits:\n\
\x20 cpus: \"{DATABASE_CPUS}\"\n\
\x20 memory: {DATABASE_MEMORY}\n\
\x20 ferroehr:\n\
\x20 image: {server}\n\
\x20 depends_on:\n\
\x20 ferroehr-postgres:\n\
\x20 condition: service_healthy\n\
\x20 environment:\n\
\x20 FERROEHR__DB__URL: postgres://ferroehr:ferroehr@ferroehr-postgres:5432/ferroehr\n\
\x20 FERROEHR__SERVER__RATE_LIMIT__ENABLED: \"false\"\n\
\x20 FERROEHR__SIGNING__ENABLED: \"true\"\n\
\x20 FERROEHR__SIGNING__MODE: digest\n\
\x20 FERROEHR__LOG__FILTER: warn\n\
\x20 volumes:\n\
\x20 - ./{FERROEHR_CONFIG_FILE}:/etc/ferroehr/ferroehr.toml:ro\n\
\x20 ports:\n\
\x20 - \"{BIND_HOST}:{port}:8080\"\n\
\x20 deploy:\n\
\x20 resources:\n\
\x20 limits:\n\
\x20 cpus: \"{SERVER_CPUS}\"\n\
\x20 memory: {SERVER_MEMORY}\n"
)
}
}
fn ferroehr_configuration() -> String {
format!(
"[auth]\n\
enabled = true\n\
\n\
[[auth.basic.users]]\n\
username = \"ferroehr\"\n\
password_hash = \"{FERROEHR_PASSWORD_HASH}\"\n\
roles = [\"ADMIN\", \"USER\"]\n"
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DockerCli {
binary: PathBuf,
}
impl Default for DockerCli {
fn default() -> Self {
Self {
binary: PathBuf::from("docker"),
}
}
}
impl DockerCli {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn at(binary: impl Into<PathBuf>) -> Self {
Self {
binary: binary.into(),
}
}
#[must_use]
pub fn binary(&self) -> &Path {
&self.binary
}
pub fn probe(&self) -> Result<String, BenchError> {
let output = Command::new(&self.binary)
.args(version_args())
.output()
.map_err(|source| BenchError::DockerUnavailable {
binary: self.binary.display().to_string(),
detail: source.to_string(),
})?;
if !output.status.success() {
return Err(BenchError::DockerUnavailable {
binary: self.binary.display().to_string(),
detail: format!(
"`docker version` exited {:?}: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr).trim()
),
});
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned())
}
fn run(&self, cdr: ReferenceCdr, args: &[String]) -> Result<String, BenchError> {
let output = Command::new(&self.binary)
.args(args)
.output()
.map_err(|source| BenchError::DockerUnavailable {
binary: self.binary.display().to_string(),
detail: source.to_string(),
})?;
if !output.status.success() {
return Err(BenchError::Baseline {
cdr: cdr.as_str().to_owned(),
detail: format!(
"`docker {}` exited {:?}: {}",
args.join(" "),
output.status.code(),
String::from_utf8_lossy(&output.stderr).trim()
),
});
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned())
}
}
#[must_use]
pub fn version_args() -> Vec<String> {
["version", "--format", "{{.Server.Version}}"]
.into_iter()
.map(str::to_owned)
.collect()
}
#[must_use]
pub fn compose_up_args(project: &str, compose_file: &Path) -> Vec<String> {
vec![
"compose".to_owned(),
"--project-name".to_owned(),
project.to_owned(),
"--file".to_owned(),
compose_file.display().to_string(),
"up".to_owned(),
"--detach".to_owned(),
"--wait".to_owned(),
"--wait-timeout".to_owned(),
COMPOSE_WAIT_S.to_string(),
]
}
#[must_use]
pub fn compose_down_args(project: &str, compose_file: &Path) -> Vec<String> {
vec![
"compose".to_owned(),
"--project-name".to_owned(),
project.to_owned(),
"--file".to_owned(),
compose_file.display().to_string(),
"down".to_owned(),
"--volumes".to_owned(),
"--remove-orphans".to_owned(),
]
}
#[derive(Debug)]
pub struct BaselineRun<'a> {
pub pack: &'a BenchPack,
pub profile: &'a PostureProfile,
pub repetitions: u32,
pub scale: f64,
pub seed_workers: Option<usize>,
pub docker: &'a DockerCli,
}
pub fn run_baselines(
run: &BaselineRun<'_>,
progress: &(dyn Fn(String) + Sync),
) -> Result<Vec<BaselineRecord>, BenchError> {
let version = run.docker.probe()?;
progress(format!(
"container runtime answers, server version {version}"
));
let mut records = Vec::with_capacity(ReferenceCdr::ALL.len());
for cdr in ReferenceCdr::ALL {
records.push(run_one_baseline(*cdr, run, progress)?);
}
Ok(records)
}
fn run_one_baseline(
cdr: ReferenceCdr,
run: &BaselineRun<'_>,
progress: &(dyn Fn(String) + Sync),
) -> Result<BaselineRecord, BenchError> {
let pin = cdr.pin();
let workspace = workspace_dir(pin);
let compose_file = workspace.join(COMPOSE_FILE);
write_workspace(pin, &workspace, &compose_file)?;
progress(format!(
"baseline {}: composing {} and {}",
cdr.as_str(),
pin.server_image,
pin.database_image
));
let measured = compose_and_measure(cdr, pin, run, &compose_file, progress);
progress(format!("baseline {}: tearing down", cdr.as_str()));
let torn_down = run
.docker
.run(cdr, &compose_down_args(&pin.project(), &compose_file))
.map(|_stdout| ());
let cleaned = std::fs::remove_dir_all(&workspace);
if let Err(error) = cleaned {
progress(format!(
"baseline {}: the workspace {} could not be removed: {error}",
cdr.as_str(),
workspace.display()
));
}
let record = measured?;
torn_down?;
Ok(record)
}
fn compose_and_measure(
cdr: ReferenceCdr,
pin: &ReferencePin,
run: &BaselineRun<'_>,
compose_file: &Path,
progress: &(dyn Fn(String) + Sync),
) -> Result<BaselineRecord, BenchError> {
let _stdout = run
.docker
.run(cdr, &compose_up_args(&pin.project(), compose_file))?;
wait_until_ready(pin, progress)?;
let (declared, divergences) = pin.declaration(run.profile);
for divergence in &divergences {
progress(format!(
"baseline {}: `{}` is declared `{}` by profile `{}` and configured `{}` by the pinned recipe, so this baseline runs and declares the recipe's value",
cdr.as_str(),
divergence.item,
divergence.profile_declares,
run.profile.name,
divergence.deployment_configures
));
}
progress(format!(
"baseline {}: driving pack {}@{} at seed {:#018x}",
cdr.as_str(),
run.pack.id.as_str(),
run.pack.version,
run.pack.seed
));
let mut result = run::execute(
&BenchRun {
pack: run.pack,
base_url: &pin.base_url(),
profile: &declared,
auth: AuthKind::Basic,
user: Some(pin.user),
credential: Some(pin.password),
repetitions: run.repetitions,
label: Some(pin.cdr.display_name()),
scale: run.scale,
seed_workers: run.seed_workers,
},
progress,
)?;
note_divergences(&mut result, cdr, divergences);
let cross = summarize(&result.repetitions);
Ok(BaselineRecord {
cdr: cdr.as_str().to_owned(),
display_name: cdr.display_name().to_owned(),
images: pin.images(),
recipe: pin.recipe(),
resources: pinned_resources(),
base_url: result.target.base_url.clone(),
sut_version: result.target.sut_version.clone(),
started_at: result.started_at.clone(),
finished_at: result.finished_at.clone(),
seed_phases: result.seed_phases.clone(),
repetitions: result.repetitions.clone(),
cross,
posture: result.posture.clone(),
})
}
fn note_divergences(
result: &mut crate::bench::result::BenchResult,
cdr: ReferenceCdr,
divergences: Vec<PostureDivergence>,
) {
if divergences.is_empty() {
return;
}
let items = divergences
.iter()
.map(|divergence| divergence.item.as_str())
.collect::<Vec<_>>()
.join(", ");
let _written = write!(
result.posture.summary,
" This baseline was composed from {}'s own pinned recipe, which configures {items} differently; it ran and declared the recipe's value, and the comparability block names each.",
cdr.display_name()
);
result.posture.comparability = divergences;
}
#[must_use]
pub fn pinned_resources() -> BaselineResources {
BaselineResources {
server_cpus: SERVER_CPUS.to_owned(),
server_memory: SERVER_MEMORY.to_owned(),
database_cpus: DATABASE_CPUS.to_owned(),
database_memory: DATABASE_MEMORY.to_owned(),
database_shm_size: DATABASE_SHM_SIZE.to_owned(),
}
}
fn workspace_dir(pin: &ReferencePin) -> PathBuf {
std::env::temp_dir().join(format!("{}-{}", pin.project(), std::process::id()))
}
fn write_workspace(
pin: &ReferencePin,
workspace: &Path,
compose_file: &Path,
) -> Result<(), BenchError> {
std::fs::create_dir_all(workspace).map_err(|source| BenchError::Write {
path: workspace.to_owned(),
source,
})?;
std::fs::write(compose_file, pin.compose_document()).map_err(|source| BenchError::Write {
path: compose_file.to_owned(),
source,
})?;
for (name, body) in pin.side_files() {
let path = workspace.join(name);
std::fs::write(&path, body).map_err(|source| BenchError::Write { path, source })?;
}
Ok(())
}
fn wait_until_ready(
pin: &ReferencePin,
progress: &(dyn Fn(String) + Sync),
) -> Result<(), BenchError> {
let base_url = pin.base_url();
let client = BenchClient::with_credential(
&base_url,
AuthKind::Basic,
Some(pin.user),
Some(pin.password),
)?;
let deadline = Instant::now() + READINESS_TIMEOUT;
let mut last = "no attempt completed".to_owned();
while Instant::now() < deadline {
let probe = client.send(
"baseline readiness",
reqwest::Method::GET,
READINESS_PATH,
None,
PreferReturn::Unstated,
);
last = match probe {
Ok(reply) if reply.status.is_success() => {
progress(format!(
"baseline {}: ready at {base_url}",
pin.cdr.as_str()
));
return Ok(());
}
Ok(reply) => format!("GET {READINESS_PATH} answered {}", reply.status),
Err(error) => error.to_string(),
};
std::thread::sleep(READINESS_INTERVAL);
}
Err(BenchError::Baseline {
cdr: pin.cdr.as_str().to_owned(),
detail: format!(
"the stack never became ready within {}s at {base_url}: {last}",
READINESS_TIMEOUT.as_secs()
),
})
}
#[cfg(test)]
#[expect(
clippy::panic_in_result_fn,
reason = "Result-returning tests in the Book ch11 shape that also assert; \
clippy offers no allow-in-tests knob for this lint"
)]
mod tests {
use super::*;
use crate::bench::posture::{CLINICAL_DEFAULT, MINIMAL};
#[test]
fn every_reference_token_round_trips() -> Result<(), BenchError> {
for cdr in ReferenceCdr::ALL {
assert_eq!(ReferenceCdr::parse(cdr.as_str())?, *cdr);
}
assert!(ReferenceCdr::parse("arcehr").is_err());
Ok(())
}
#[test]
fn every_reference_image_is_digest_pinned() {
for cdr in ReferenceCdr::ALL {
let pin = cdr.pin();
for image in [pin.server_image, pin.database_image] {
assert!(image.contains("@sha256:"), "{image}");
let digest = image.split_once("@sha256:").map(|(_, hex)| hex);
assert_eq!(digest.map(str::len), Some(64), "{image}");
assert!(
digest.is_some_and(|hex| hex.chars().all(|c| c.is_ascii_hexdigit())),
"{image}"
);
}
}
}
#[test]
fn the_baselines_publish_distinct_ports() {
let ports: Vec<u16> = ReferenceCdr::ALL
.iter()
.flat_map(|cdr| [cdr.pin().host_port, cdr.pin().database_port])
.collect();
let mut unique = ports.clone();
unique.sort_unstable();
unique.dedup();
assert_eq!(unique.len(), ports.len(), "{ports:?}");
}
#[test]
fn a_compose_document_carries_the_pins_and_the_ceilings() {
for cdr in ReferenceCdr::ALL {
let pin = cdr.pin();
let document = pin.compose_document();
assert!(
document.contains(pin.server_image),
"the compose document does not name the pinned server image"
);
assert!(
document.contains(pin.database_image),
"the compose document does not name the pinned database image"
);
assert!(
document.contains(SERVER_CPUS),
"the compose document does not state the server CPU ceiling"
);
assert!(
document.contains(SERVER_MEMORY),
"the compose document does not state the server memory ceiling"
);
assert!(
document.contains(DATABASE_SHM_SIZE),
"the compose document does not state the database shm size"
);
assert!(
document.contains(&format!("{BIND_HOST}:{}:8080", pin.host_port)),
"{document}"
);
}
}
#[test]
fn only_the_ferroehr_baseline_carries_a_side_file() {
let ferroehr = ReferenceCdr::FerroEhr.pin();
let names: Vec<&str> = ferroehr
.side_files()
.into_iter()
.map(|(name, _body)| name)
.collect();
assert_eq!(names, vec![FERROEHR_CONFIG_FILE]);
assert!(ferroehr.compose_document().contains(&format!(
"./{FERROEHR_CONFIG_FILE}:/etc/ferroehr/ferroehr.toml"
)),);
assert!(ReferenceCdr::EhrBase.pin().side_files().is_empty());
}
#[test]
fn the_compose_arguments_name_the_project_and_the_document() {
let file = Path::new("/tmp/veredictum/compose.yaml");
let up = compose_up_args("veredictum-baseline-ehrbase", file);
assert_eq!(
up,
vec![
"compose",
"--project-name",
"veredictum-baseline-ehrbase",
"--file",
"/tmp/veredictum/compose.yaml",
"up",
"--detach",
"--wait",
"--wait-timeout",
"300",
]
);
let down = compose_down_args("veredictum-baseline-ferroehr", file);
assert!(down.contains(&"--volumes".to_owned()), "{down:?}");
assert!(down.contains(&"--remove-orphans".to_owned()), "{down:?}");
assert_eq!(down.first().map(String::as_str), Some("compose"));
}
#[test]
fn an_absent_container_runtime_is_refused_by_name() {
let docker = DockerCli::at("/nonexistent/veredictum/docker");
let error = docker.probe().unwrap_err();
assert!(
matches!(&error, BenchError::DockerUnavailable { binary, .. }
if binary == "/nonexistent/veredictum/docker"),
"{error}"
);
assert!(error.to_string().contains("docker"), "{error}");
}
#[test]
fn both_baselines_run_under_the_same_ceilings() {
let resources = pinned_resources();
assert_eq!(resources.server_cpus, SERVER_CPUS);
assert_eq!(resources.database_memory, DATABASE_MEMORY);
for cdr in ReferenceCdr::ALL {
let document = cdr.pin().compose_document();
assert!(
document.contains(&format!("cpus: \"{SERVER_CPUS}\"")),
"{document}"
);
assert!(
document.contains(DATABASE_MEMORY),
"the compose document does not state the database memory ceiling"
);
}
}
#[test]
fn every_pin_discloses_its_upstream_recipe() {
for cdr in ReferenceCdr::ALL {
let recipe = cdr.pin().recipe();
assert!(recipe.repository.starts_with("https://"), "{recipe:?}");
assert!(recipe.git_ref.starts_with('v'), "{recipe:?}");
let extension = Path::new(&recipe.file)
.extension()
.and_then(std::ffi::OsStr::to_str);
assert!(matches!(extension, Some("yml" | "yaml")), "{recipe:?}");
}
}
#[test]
fn every_pin_records_the_authentication_its_baseline_presents() {
for cdr in ReferenceCdr::ALL {
assert_eq!(cdr.pin().posture.authn.value, AuthnMode::Basic);
}
}
#[test]
fn every_recorded_posture_element_names_its_file() {
for cdr in ReferenceCdr::ALL {
let posture = cdr.pin().posture;
for element in [
posture.audit.element,
posture.signing.element,
posture.validation.element,
posture.compression.element,
posture.tenancy.element,
posture.authn.element,
] {
assert!(
element.contains(".yml") || element.contains(".toml"),
"{element}"
);
}
}
}
#[test]
fn a_pin_that_contradicts_the_profile_runs_under_the_recipe() {
let (declared, divergences) = ReferenceCdr::FerroEhr.pin().declaration(&MINIMAL);
assert_eq!(declared.signing, SigningScheme::Digest);
assert_eq!(declared.name, MINIMAL.name);
assert_eq!(divergences.len(), 1, "{divergences:?}");
let divergence = divergences.first().map(|first| first.item);
assert_eq!(divergence, Some(PostureItem::VersionSigning));
assert!(
divergences
.iter()
.all(|line| line.source.contains("v4.0.11")),
"{divergences:?}"
);
}
#[test]
fn a_pin_that_agrees_with_the_profile_declares_it_unchanged() {
let (declared, divergences) = ReferenceCdr::EhrBase.pin().declaration(&MINIMAL);
assert_eq!(declared, MINIMAL);
assert!(divergences.is_empty(), "{divergences:?}");
}
#[test]
fn an_audit_profile_departs_from_both_recipes() {
for cdr in ReferenceCdr::ALL {
let (declared, divergences) = cdr.pin().declaration(&CLINICAL_DEFAULT);
assert_eq!(declared.audit, AuditSink::Off);
assert!(
divergences
.iter()
.any(|line| line.item == PostureItem::Audit
&& line.profile_declares == "internal"
&& line.deployment_configures == "off"),
"{divergences:?}"
);
}
}
#[test]
fn the_ferroehr_document_states_the_signing_posture_its_pin_records() {
let pin = ReferenceCdr::FerroEhr.pin();
let document = pin.compose_document();
assert_eq!(pin.posture.signing.value, SigningScheme::Digest);
assert!(
document.contains("FERROEHR__SIGNING__ENABLED: \"true\""),
"the compose document does not state FERROEHR__SIGNING__ENABLED"
);
assert!(
document.contains("FERROEHR__SIGNING__MODE: digest"),
"the compose document does not state FERROEHR__SIGNING__MODE: digest"
);
}
#[test]
fn the_base_url_and_the_project_follow_the_pin() {
let pin = ReferenceCdr::EhrBase.pin();
assert_eq!(
pin.base_url(),
"http://127.0.0.1:18091/ehrbase/rest/openehr/v1"
);
assert_eq!(pin.project(), "veredictum-baseline-ehrbase");
assert_eq!(
ReferenceCdr::FerroEhr.pin().project(),
"veredictum-baseline-ferroehr"
);
}
}