Skip to content

Commit

Permalink
refactor(tests): Refactor tests into integration tests. (#287)
Browse files Browse the repository at this point in the history
Move the test models into proper `tests` folder as proper
integration tests. Test functions are generated using a macro
in a consistent manner. These tests no also read JSON models
and CSV results from file rather than including them in the
test binary.

Where original unit tests had assertion recorders to test
outputs these have been converted to expected CSV output
files.
  • Loading branch information
jetuk authored Nov 14, 2024
1 parent d4badf6 commit f969d92
Show file tree
Hide file tree
Showing 73 changed files with 1,319 additions and 1,064 deletions.
21 changes: 11 additions & 10 deletions pywr-core/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,22 +169,26 @@ pub fn run_and_assert_parameter(

/// A struct to hold the expected outputs for a test.
pub struct ExpectedOutputs {
actual_path: PathBuf,
expected_str: &'static str,
output_path: PathBuf,
expected_str: String,
}

impl ExpectedOutputs {
pub fn new(actual_path: PathBuf, expected_str: &'static str) -> Self {
pub fn new(output_path: PathBuf, expected_str: String) -> Self {
Self {
actual_path,
output_path,
expected_str,
}
}

fn verify(&self) {
assert!(self.actual_path.exists());
let actual_str = std::fs::read_to_string(&self.actual_path).unwrap();
assert_eq!(actual_str, self.expected_str);
assert!(
self.output_path.exists(),
"Output file does not exist: {:?}",
self.output_path
);
let actual_str = std::fs::read_to_string(&self.output_path).unwrap();
assert_eq!(actual_str, self.expected_str, "Output file contents do not match");
}
}

Expand All @@ -193,18 +197,15 @@ impl ExpectedOutputs {
/// The model will only be run if the solver has the required solver features (and
/// is also enabled as a Cargo feature).
pub fn run_all_solvers(model: &Model, solvers_without_features: &[&str], expected_outputs: &[ExpectedOutputs]) {
println!("Running CLP");
check_features_and_run::<ClpSolver>(model, !solvers_without_features.contains(&"clp"), expected_outputs);

#[cfg(feature = "cbc")]
{
println!("Running CBC");
check_features_and_run::<CbcSolver>(model, !solvers_without_features.contains(&"cbc"), expected_outputs);
}

#[cfg(feature = "highs")]
{
println!("Running Highs");
check_features_and_run::<HighsSolver>(model, !solvers_without_features.contains(&"highs"), expected_outputs);
}

Expand Down
54 changes: 18 additions & 36 deletions pywr-schema/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ pub enum PywrNetworkRef {
/// The simplest model is given in the example below:
///
/// ```json
#[doc = include_str!("test_models/simple1.json")]
#[doc = include_str!("../tests/simple1.json")]
/// ```
///
///
Expand Down Expand Up @@ -733,11 +733,11 @@ pub struct PywrMultiNetworkEntry {
///
/// ```json5
/// // model.json
#[doc = include_str!("test_models/multi1/model.json")]
#[doc = include_str!("../tests/multi1/model.json")]
/// // network1.json
#[doc = include_str!("test_models/multi1/network1.json")]
#[doc = include_str!("../tests/multi1/network1.json")]
/// // network2.json
#[doc = include_str!("test_models/multi1/network2.json")]
#[doc = include_str!("../tests/multi1/network2.json")]
/// ```
///
///
Expand Down Expand Up @@ -892,16 +892,17 @@ mod tests {
use super::PywrModel;
use crate::model::Timestepper;
use crate::visit::VisitPaths;
use std::fs::read_to_string;
use std::path::PathBuf;

fn model_str() -> &'static str {
include_str!("./test_models/simple1.json")
fn model_str() -> String {
read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/simple1.json")).unwrap()
}

#[test]
fn test_simple1_schema() {
let data = model_str();
let schema: PywrModel = serde_json::from_str(data).unwrap();
let schema: PywrModel = serde_json::from_str(&data).unwrap();

assert_eq!(schema.network.nodes.len(), 3);
assert_eq!(schema.network.edges.len(), 2);
Expand Down Expand Up @@ -977,11 +978,11 @@ mod tests {
#[test]
fn test_visit_paths() {
let mut model_fn = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
model_fn.push("src/test_models/timeseries.json");
model_fn.push("tests/timeseries.json");

let mut schema = PywrModel::from_path(model_fn.as_path()).unwrap();

let expected_paths = vec![PathBuf::from("inflow.csv")];
let expected_paths = vec![PathBuf::from("inflow.csv"), PathBuf::from("timeseries-expected.csv")];

let mut paths: Vec<PathBuf> = Vec::new();

Expand All @@ -1002,26 +1003,6 @@ mod tests {
panic!("Expected an error due to missing file: {str}");
}
}

#[test]
fn test_v1_conversion() {
let v1_str = include_str!("./test_models/v1/timeseries.json");
let v1: pywr_v1_schema::PywrModel = serde_json::from_str(v1_str).unwrap();

let (v2, errors) = PywrModel::from_v1(v1);

assert_eq!(errors.len(), 0);

std::fs::write("tmp.json", serde_json::to_string_pretty(&v2).unwrap()).unwrap();

let v2_converted: serde_json::Value =
serde_json::from_str(&serde_json::to_string_pretty(&v2).unwrap()).unwrap();

let v2_expected: serde_json::Value =
serde_json::from_str(include_str!("./test_models/v1/timeseries-converted.json")).unwrap();

assert_eq!(v2_converted, v2_expected);
}
}

#[cfg(test)]
Expand All @@ -1032,16 +1013,17 @@ mod core_tests {
use crate::parameters::{AggFunc, AggregatedParameter, ConstantParameter, ConstantValue, Parameter, ParameterMeta};
use ndarray::{Array1, Array2, Axis};
use pywr_core::{metric::MetricF64, recorders::AssertionRecorder, solvers::ClpSolver, test_utils::run_all_solvers};
use std::fs::read_to_string;
use std::path::PathBuf;

fn model_str() -> &'static str {
include_str!("./test_models/simple1.json")
fn model_str() -> String {
read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/simple1.json")).unwrap()
}

#[test]
fn test_simple1_run() {
let data = model_str();
let schema: PywrModel = serde_json::from_str(data).unwrap();
let schema: PywrModel = serde_json::from_str(&data).unwrap();
let mut model = schema.build_model(None, None).unwrap();

let network = model.network_mut();
Expand Down Expand Up @@ -1070,7 +1052,7 @@ mod core_tests {
#[test]
fn test_cycle_error() {
let data = model_str();
let mut schema: PywrModel = serde_json::from_str(data).unwrap();
let mut schema: PywrModel = serde_json::from_str(&data).unwrap();

// Add additional parameters for the test
if let Some(parameters) = &mut schema.network.parameters {
Expand Down Expand Up @@ -1128,7 +1110,7 @@ mod core_tests {
#[test]
fn test_ordering() {
let data = model_str();
let mut schema: PywrModel = serde_json::from_str(data).unwrap();
let mut schema: PywrModel = serde_json::from_str(&data).unwrap();

if let Some(parameters) = &mut schema.network.parameters {
parameters.extend(vec![
Expand Down Expand Up @@ -1175,7 +1157,7 @@ mod core_tests {
#[test]
fn test_multi1_model() {
let mut model_fn = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
model_fn.push("src/test_models/multi1/model.json");
model_fn.push("tests/multi1/model.json");

let schema = PywrMultiNetworkModel::from_path(model_fn.as_path()).unwrap();
let mut model = schema.build_model(model_fn.parent(), None).unwrap();
Expand Down Expand Up @@ -1225,7 +1207,7 @@ mod core_tests {
#[test]
fn test_multi2_model() {
let mut model_fn = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
model_fn.push("src/test_models/multi2/model.json");
model_fn.push("tests/multi2/model.json");

let schema = PywrMultiNetworkModel::from_path(model_fn.as_path()).unwrap();
let mut model = schema.build_model(model_fn.parent(), None).unwrap();
Expand Down
Loading

0 comments on commit f969d92

Please sign in to comment.