diff --git a/src/nodes/graph.cpp b/src/nodes/graph.cpp index 0c10f14966..d29fb5058b 100644 --- a/src/nodes/graph.cpp +++ b/src/nodes/graph.cpp @@ -283,7 +283,7 @@ void Graph::deserialise(const SerialisedValue &node) } /* - *Mermaid processing code + * Mermaid processing code */ // Node types that represent data sources diff --git a/src/nodes/node.cpp b/src/nodes/node.cpp index a566c7abbd..2058afdcc5 100644 --- a/src/nodes/node.cpp +++ b/src/nodes/node.cpp @@ -356,7 +356,14 @@ void Node::deserialise(const SerialisedValue &node) [this](const auto &k, const auto &v) { if (inputs_.contains(k)) - inputs_[k]->deserialise(v); + try + { + inputs_[k]->deserialise(v); + } + catch (std::exception &ex) + { + Messenger::exception("Error reading input {} in node {} ({}).", k, name(), ex.what()); + } else Messenger::exception("Node {} does not contain a parameter {}", name(), k); }); @@ -364,7 +371,14 @@ void Node::deserialise(const SerialisedValue &node) [this](const auto &k, const auto &v) { if (options_.contains(k)) - options_[k]->deserialise(v); + try + { + options_[k]->deserialise(v); + } + catch (std::exception &ex) + { + Messenger::exception("Error reading option {} in node {} ({}).", k, name(), ex.what()); + } else Messenger::exception("Node {} does not contain an option {}", name(), k); }); diff --git a/src/nodes/parameter.h b/src/nodes/parameter.h index a925056362..1ee9451f18 100644 --- a/src/nodes/parameter.h +++ b/src/nodes/parameter.h @@ -606,23 +606,23 @@ template class SerialisableParameter : public Parameter) { DataClass proxy; // Fake T value to get the correct overload - Parameter::data_ = getEnumOptions(proxy).deserialise(node); + Parameter::data_ = getEnumOptions(proxy).enumeration(toml::find(node, "data")); } - else if constexpr (std::is_convertible>::value) + else if constexpr (std::is_same_v>) { if (node.contains("data")) Parameter::data_ = toml::find(node, "data"); else Parameter::data_ = {}; } - else if constexpr (std::is_convertible>::value) + else if constexpr (std::is_same_v>) { if (node.contains("data")) Parameter::data_ = toml::find(node, "data"); else Parameter::data_ = {}; } - else if constexpr (std::is_convertible>::value) + else if constexpr (std::is_same_v>) { if (node.contains("data")) Parameter::data_ = toml::find(node, "data"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad9e4babe4..610a03c74c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,7 +62,7 @@ function(dissolve_add_test) endfunction() -add_library(testing testing.cpp testGraph.cpp testing.h testGraph.h) +add_library(testing testing.cpp testGraph.cpp testing.h testGraph.h testGraphFixture.h) target_link_libraries(testing PRIVATE GTest::gtest_main) target_include_directories( testing PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src ${PROJECT_SOURCE_DIR} ${CONAN_INCLUDE_DIRS_GTEST} diff --git a/tests/nodes/sdf.cpp b/tests/nodes/sdf.cpp index 3344e5d1c0..8367b9bcc8 100644 --- a/tests/nodes/sdf.cpp +++ b/tests/nodes/sdf.cpp @@ -5,69 +5,122 @@ #include "nodes/importDLPUtilsPDens.h" #include "nodes/iterableGraph.h" #include "nodes/species.h" -#include "tests/testGraph.h" +#include "tests/testGraphFixture.h" namespace UnitTest { -TEST(SDFNodeTest, Water) +class SDFNodeWaterTest : public TestGraphFixture { - // Set up the test graph - TestGraph testGraph; - testGraph.createConfiguration("Box", {{"species/water-dlpoly.toml", 267}}, 0.1); + private: + Data3D referenceData_; - // Create trajectory iterator - auto iterator = testGraph.appendTrajectoryIterator("ImportXYZTrajectory", "dlpoly/water267-analysis/water-267-298K.xyz"); - EXPECT_TRUE(iterator); + protected: + // Prepare any necessary test data + void prepareTestData() override + { + ASSERT_TRUE(ImportDLPUtilsPDensNode::read(referenceData_, "dlpoly/water267-analysis/water-267-298K.11.pdens")); + } + // Perform graph construction + void constructGraph() override + { + testGraph_.createConfiguration("Box", {{"species/water-dlpoly.toml", 267}}, 0.1); - // Add the analysis module to the iterator - auto sdf = dynamic_cast(iterator->createNode("SDF")); - ASSERT_TRUE(sdf); - auto *water = testGraph.findNode("Water")->getOutputValue("Species"); - ASSERT_TRUE(water); - ASSERT_TRUE(sdf->setOption("SiteA", {{water->findSite("HMidpoint")}})); - ASSERT_TRUE(sdf->setOption("SiteB", {{water->findSite("HMidpoint")}})); - ASSERT_TRUE(sdf->setOption("RangeX", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(sdf->setOption("RangeY", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(sdf->setOption("RangeZ", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(iterator->addEdge({testGraph.fetchHeadName(), "Configuration", "SDF", "Configuration"})); + // Create trajectory iterator + auto iterator = + testGraph_.appendTrajectoryIterator("ImportXYZTrajectory", "dlpoly/water267-analysis/water-267-298K.xyz"); + ASSERT_TRUE(iterator); + ASSERT_TRUE(iterator->setOption("N", 95)); - // Run from the iterator node explicitly - ASSERT_TRUE(iterator->setOption("N", 95)); - ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); + // Add the analysis module to the iterator + auto sdf = dynamic_cast(iterator->createNode("SDF")); + ASSERT_TRUE(sdf); + auto *water = testGraph_.findNode("Water")->getOutputValue("Species"); + ASSERT_TRUE(water); + ASSERT_TRUE(sdf->setOption("SiteA", {{water->findSite("HMidpoint")}})); + ASSERT_TRUE(sdf->setOption("SiteB", {{water->findSite("HMidpoint")}})); + ASSERT_TRUE(sdf->setOption("RangeX", {-10.25, 10.25, 0.5})); + ASSERT_TRUE(sdf->setOption("RangeY", {-10.25, 10.25, 0.5})); + ASSERT_TRUE(sdf->setOption("RangeZ", {-10.25, 10.25, 0.5})); + ASSERT_TRUE(iterator->addEdge({testGraph_.fetchHeadName(), "Configuration", "SDF", "Configuration"})); + } + // Run the graph + void runGraph() override + { + auto *iterator = findNode("Iterator"); + ASSERT_TRUE(iterator); + ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); + } + // Perform tests on generated data + void performTests() override + { + auto *iterator = findNode("Iterator"); + ASSERT_TRUE(iterator); + auto *sdf = dynamic_cast(iterator->findNode("SDF")); + ASSERT_TRUE(sdf); + EXPECT_TRUE(testData3D(sdf->sdf(), "SDF", referenceData_, "dlpoly/water267-analysis/water-267-298K.11.pdens", 0.13)); + } +}; - Data3D referenceData; - EXPECT_TRUE(ImportDLPUtilsPDensNode::read(referenceData, "dlpoly/water267-analysis/water-267-298K.11.pdens")); - EXPECT_TRUE(testData3D(sdf->sdf(), "SDF", referenceData, "dlpoly/water267-analysis/water-267-298K.11.pdens", 0.13)); -} - -TEST(SDFNodeTest, Benzene) -{ - // Set up the test graph - TestGraph testGraph; - testGraph.createConfiguration("Box", {{"species/benzene.toml", 181}}, {29.925089931000, 29.925089931000, 29.925089931000}); - - // Create trajectory iterator - auto iterator = testGraph.appendTrajectoryIterator("ImportXYZTrajectory", "dlpoly/benzene181/benzene181.xyz"); - EXPECT_TRUE(iterator); - - // Add the analysis module to the iterator - auto sdf = dynamic_cast(iterator->createNode("SDF")); - ASSERT_TRUE(sdf); - auto *benzene = testGraph.findNode("Benzene")->getOutputValue("Species"); - ASSERT_TRUE(benzene); - ASSERT_TRUE(sdf->setOption("SiteA", {{benzene->findSite("Ring")}})); - ASSERT_TRUE(sdf->setOption("SiteB", {{benzene->findSite("Ring")}})); - ASSERT_TRUE(sdf->setOption("RangeX", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(sdf->setOption("RangeY", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(sdf->setOption("RangeZ", {-10.25, 10.25, 0.5})); - ASSERT_TRUE(iterator->addEdge({testGraph.fetchHeadName(), "Configuration", "SDF", "Configuration"})); - - // Run from the iterator node explicitly - ASSERT_TRUE(iterator->setOption("N", 80)); - ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); - - Data3D referenceData; - EXPECT_TRUE(ImportDLPUtilsPDensNode::read(referenceData, "dlpoly/benzene181/benzene181.11.pdens")); - EXPECT_TRUE(testData3D(sdf->sdf(), "SDF", referenceData, "dlpoly/benzene181/benzene181.11.pdens", 0.3)); -} +TEST_F(SDFNodeWaterTest, Water) { go(); } +// { +// // Set up the test graph +// TestGraph testGraph; +// testGraph.createConfiguration("Box", {{"species/water-dlpoly.toml", 267}}, 0.1); +// +// // Create trajectory iterator +// auto iterator = testGraph.appendTrajectoryIterator("ImportXYZTrajectory", "dlpoly/water267-analysis/water-267-298K.xyz"); +// EXPECT_TRUE(iterator); +// +// // Add the analysis module to the iterator +// auto sdf = dynamic_cast(iterator->createNode("SDF")); +// ASSERT_TRUE(sdf); +// auto *water = testGraph.findNode("Water")->getOutputValue("Species"); +// ASSERT_TRUE(water); +// ASSERT_TRUE(sdf->setOption("SiteA", {{water->findSite("HMidpoint")}})); +// ASSERT_TRUE(sdf->setOption("SiteB", {{water->findSite("HMidpoint")}})); +// ASSERT_TRUE(sdf->setOption("RangeX", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(sdf->setOption("RangeY", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(sdf->setOption("RangeZ", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(iterator->addEdge({testGraph.fetchHeadName(), "Configuration", "SDF", "Configuration"})); +// +// // Run from the iterator node explicitly +// ASSERT_TRUE(iterator->setOption("N", 95)); +// ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); +// +// Data3D referenceData; +// EXPECT_TRUE(ImportDLPUtilsPDensNode::read(referenceData, "dlpoly/water267-analysis/water-267-298K.11.pdens")); +// EXPECT_TRUE(testData3D(sdf->sdf(), "SDF", referenceData, "dlpoly/water267-analysis/water-267-298K.11.pdens", 0.13)); +// } +// +// TEST(SDFNodeTest, Benzene) +// { +// // Set up the test graph +// TestGraph testGraph; +// testGraph.createConfiguration("Box", {{"species/benzene.toml", 181}}, +// {29.925089931000, 29.925089931000, 29.925089931000}); +// +// // Create trajectory iterator +// auto iterator = testGraph.appendTrajectoryIterator("ImportXYZTrajectory", "dlpoly/benzene181/benzene181.xyz"); +// EXPECT_TRUE(iterator); +// +// // Add the analysis module to the iterator +// auto sdf = dynamic_cast(iterator->createNode("SDF")); +// ASSERT_TRUE(sdf); +// auto *benzene = testGraph.findNode("Benzene")->getOutputValue("Species"); +// ASSERT_TRUE(benzene); +// ASSERT_TRUE(sdf->setOption("SiteA", {{benzene->findSite("Ring")}})); +// ASSERT_TRUE(sdf->setOption("SiteB", {{benzene->findSite("Ring")}})); +// ASSERT_TRUE(sdf->setOption("RangeX", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(sdf->setOption("RangeY", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(sdf->setOption("RangeZ", {-10.25, 10.25, 0.5})); +// ASSERT_TRUE(iterator->addEdge({testGraph.fetchHeadName(), "Configuration", "SDF", "Configuration"})); +// +// // Run from the iterator node explicitly +// ASSERT_TRUE(iterator->setOption("N", 80)); +// ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); +// +// Data3D referenceData; +// EXPECT_TRUE(ImportDLPUtilsPDensNode::read(referenceData, "dlpoly/benzene181/benzene181.11.pdens")); +// EXPECT_TRUE(testData3D(sdf->sdf(), "SDF", referenceData, "dlpoly/benzene181/benzene181.11.pdens", 0.3)); +// } } // namespace UnitTest \ No newline at end of file diff --git a/tests/testGraph.cpp b/tests/testGraph.cpp index 57f0d3a5ca..09d867ec44 100644 --- a/tests/testGraph.cpp +++ b/tests/testGraph.cpp @@ -198,7 +198,7 @@ IterableGraph *TestGraph::appendTrajectoryIterator(std::string trajectoryImportN auto oldGraph = currentGraph_; // Add iterator node and make it the current graph - currentGraph_ = dynamic_cast(appendNode("Iterator", "Iterator")); + currentGraph_ = dynamic_cast(appendNode("Iterator")); EXPECT_TRUE(currentGraph_); head_ = nullptr; diff --git a/tests/testGraphFixture.h b/tests/testGraphFixture.h new file mode 100644 index 0000000000..9ef6f707dd --- /dev/null +++ b/tests/testGraphFixture.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 Team Dissolve and contributors + +#pragma once + +#include "tests/testGraph.h" + +namespace UnitTest +{ +class TestGraphFixture : public testing::Test +{ + public: + TestGraphFixture() = default; + ~TestGraphFixture() override = default; + + private: + // Serialised graph TOML + SerialisedValue graphTOML_; + + protected: + // Test graph + TestGraph testGraph_; + // Deserialised graph + DissolveGraph deserialisedGraph_; + // Current graph target + OptionalReferenceWrapper currentGraph_; + + private: + // Serialise the current graph to a TOML + bool serialiseGraphToTOML() + { + try + { + testGraph_.serialise("graph", graphTOML_); + } + catch (std::exception &ex) + { + std::cout << std::format("Failed to serialise graph TOML for test {}:\n", "TODO"); + std::cout << ex.what() << std::endl; + return false; + } + + return true; + } + + protected: + // Prepare any necessary test data + virtual void prepareTestData() = 0; + // Perform graph construction + virtual void constructGraph() = 0; + // Run graph + virtual void runGraph() = 0; + // Perform tests on generated data + virtual void performTests() = 0; + // Find specified node + template NodeClass *findNode(std::string nodeName) + { + EXPECT_TRUE(currentGraph_.has_value()); + auto *node = currentGraph_.value().get().findNode(nodeName); + EXPECT_TRUE(node); + return dynamic_cast(node); + } + + // Go + void go() + { + // Prepare test data + ASSERT_NO_THROW(prepareTestData()); + + // Set the initial graph target to the test graph + currentGraph_ = testGraph_; + + // Construct the graph + ASSERT_NO_THROW(constructGraph()); + // Run the graph + ASSERT_NO_THROW(runGraph()); + // Run the tests + ASSERT_NO_THROW(performTests()); + // Serialise graph to TOML + ASSERT_TRUE(serialiseGraphToTOML()); + + // Switch to the deserialised graph target + currentGraph_ = deserialisedGraph_; + + // Deserialise from the stored TOML + ASSERT_NO_THROW(deserialisedGraph_.deserialise(graphTOML_["graph"])); + // Run the graph + ASSERT_NO_THROW(runGraph()); + // Run the tests + ASSERT_NO_THROW(performTests()); + } +}; +}; // namespace UnitTest