use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use scientific_workflow::configuration::{
ConfigurationError, ConfigurationSpace, ProjectPaths, ResolvedConfiguration,
};
static NEXT_DIRECTORY: AtomicU64 = AtomicU64::new(0);
fn configuration_directory(name: &str, fixed: &str, sweep: &str) -> PathBuf {
let sequence = NEXT_DIRECTORY.fetch_add(1, Ordering::Relaxed);
let directory = std::env::temp_dir().join(format!(
"scientific-workflow-{name}-{}-{sequence}",
std::process::id()
));
fs::create_dir_all(&directory).unwrap();
fs::write(directory.join("fixed.json"), fixed).unwrap();
fs::write(directory.join("sweep.json"), sweep).unwrap();
directory
}
#[test]
fn resolves_every_fixed_and_cartesian_sweep_combination() {
let directory = configuration_directory(
"cartesian",
r#"{"solver":{"step":0.01},"seed":7}"#,
r#"{"mode":"cartesian","axes":{"temperature":{"values":[280.0,300.0]},"size":{"values":[4,8]}}}"#,
);
let space = ConfigurationSpace::load(&directory).unwrap();
assert_eq!(space.combination_count(), 4);
let resolved = space
.combinations()
.map(|configuration| {
let values: (f64, usize, u64, f64) = configuration
.decode_values(("/temperature", "/size", "/seed", "/solver/step"))
.unwrap();
(configuration.ordinal(), values)
})
.collect::<Vec<_>>();
assert_eq!(
resolved,
vec![
(0, (280.0, 4, 7, 0.01)),
(1, (280.0, 8, 7, 0.01)),
(2, (300.0, 4, 7, 0.01)),
(3, (300.0, 8, 7, 0.01)),
]
);
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn resolves_explicit_cases_without_creating_tasks() {
let directory = configuration_directory(
"cases",
r#"{"shared":true}"#,
r#"{"mode":"cases","cases":[{"temperature":280.0,"seed":1},{"temperature":300.0,"seed":9}]}"#,
);
let combinations = ConfigurationSpace::load(&directory)
.unwrap()
.combinations()
.collect::<Vec<ResolvedConfiguration>>();
assert_eq!(combinations.len(), 2);
assert_eq!(combinations[0].decode_value::<u64>("/seed").unwrap(), 1);
assert_eq!(combinations[1].decode_value::<u64>("/seed").unwrap(), 9);
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn indexed_access_and_value_errors_retain_the_combination_ordinal() {
let directory = configuration_directory(
"indexed",
r#"{"seed":7}"#,
r#"{"mode":"cartesian","axes":{}}"#,
);
let space = ConfigurationSpace::load(&directory).unwrap();
let configuration = space.combination(0).unwrap();
assert!(matches!(
configuration.decode_value::<String>("/seed"),
Err(ConfigurationError::DecodeConfigurationValue { ordinal: 0, .. })
));
assert!(matches!(
space.combination(1),
Err(ConfigurationError::CombinationOrdinalOutOfBounds {
ordinal: 1,
combination_count: 1
})
));
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn project_paths_are_strict_ordered_and_resolve_lexically() {
let root = std::env::temp_dir().join(format!(
"scientific-workflow-paths-{}-{}",
std::process::id(),
NEXT_DIRECTORY.fetch_add(1, Ordering::Relaxed)
));
let configuration = root.join("config");
fs::create_dir_all(&configuration).unwrap();
fs::write(
configuration.join("paths.json"),
r#"{"recordings":"results","input":"data/input.json"}"#,
)
.unwrap();
let paths = ProjectPaths::load(&root).unwrap();
assert_eq!(paths.keys().collect::<Vec<_>>(), ["recordings", "input"]);
assert_eq!(
paths.resolve_path("recordings").unwrap(),
root.join("results")
);
assert_eq!(paths.to_json_value()["input"], "data/input.json");
fs::write(
configuration.join("paths.json"),
r#"{"recordings":"first","recordings":"second"}"#,
)
.unwrap();
assert!(matches!(
ProjectPaths::load(&root),
Err(ConfigurationError::DuplicateConfigurationKey { key, .. })
if key == "recordings"
));
fs::write(configuration.join("paths.json"), r#"{" ":"results"}"#).unwrap();
assert!(matches!(
ProjectPaths::load(&root),
Err(ConfigurationError::InvalidConfigurationDocument { .. })
));
fs::remove_dir_all(root).unwrap();
}