From acdb57459091c1ecb37a33e61666dc6d27c0d891 Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Fri, 26 Jun 2026 16:17:23 +0100 Subject: [PATCH 1/6] Start tinkering with a fixture for node testing with round-trip TOML plus data deserialisation. --- tests/testGraphFixture.h | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/testGraphFixture.h diff --git a/tests/testGraphFixture.h b/tests/testGraphFixture.h new file mode 100644 index 0000000000..78ceba98e4 --- /dev/null +++ b/tests/testGraphFixture.h @@ -0,0 +1,79 @@ +// 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; + // 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() + { + currentGraph_ = testGraph_; + + // Construct the graph + ASSERT_NO_THROW(constructGraph()); + // Run the tests + ASSERT_NO_THROW(performTests()); + // Serialise graph to TOML + ASSERT_TRUE(serialiseGraphToTOML()); + + // + currentGraph_ = deserialisedGraph_; + } + // +}; +}; // namespace UnitTest From d6f6ccfddddff1c005cb234c4f9910f1d73cd340 Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Mon, 29 Jun 2026 09:42:32 +0100 Subject: [PATCH 2/6] Fix comment. --- src/nodes/graph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d82b2c39bcac1d5fde865f4b068af825febd21d8 Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Mon, 29 Jun 2026 09:42:59 +0100 Subject: [PATCH 3/6] Test fixture with SDF node. --- tests/CMakeLists.txt | 2 +- tests/nodes/sdf.cpp | 167 ++++++++++++++++++++++++++------------- tests/testGraph.cpp | 2 +- tests/testGraphFixture.h | 18 ++++- 4 files changed, 128 insertions(+), 61 deletions(-) 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..f90f5a94ad 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() + { + auto *iterator = findNode("Iterator"); + ASSERT_TRUE(iterator); + ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); + } + // Perform tests on generated data + void performTests() + { + 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 index 78ceba98e4..9ef6f707dd 100644 --- a/tests/testGraphFixture.h +++ b/tests/testGraphFixture.h @@ -48,6 +48,8 @@ class TestGraphFixture : public testing::Test 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 @@ -62,18 +64,30 @@ class TestGraphFixture : public testing::Test // 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 From b8e6f65b11f65a93a784a119cd1d6ce7cec2ca6f Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Tue, 30 Jun 2026 08:54:48 +0100 Subject: [PATCH 4/6] Exception handling in Node::deserialise(). --- src/nodes/node.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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); }); From 1dfc635c5391062965ee8dbbe0ac25989e158406 Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Tue, 30 Jun 2026 08:55:04 +0100 Subject: [PATCH 5/6] Direct type comparison in Parameter::deserialise(). --- src/nodes/parameter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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"); From 150687328c20c9a8da4ef7f2ca406ba900fd9c2a Mon Sep 17 00:00:00 2001 From: Tristan Youngs Date: Tue, 30 Jun 2026 08:55:13 +0100 Subject: [PATCH 6/6] Missed overrides. --- tests/nodes/sdf.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/nodes/sdf.cpp b/tests/nodes/sdf.cpp index f90f5a94ad..8367b9bcc8 100644 --- a/tests/nodes/sdf.cpp +++ b/tests/nodes/sdf.cpp @@ -44,14 +44,14 @@ class SDFNodeWaterTest : public TestGraphFixture ASSERT_TRUE(iterator->addEdge({testGraph_.fetchHeadName(), "Configuration", "SDF", "Configuration"})); } // Run the graph - void runGraph() + void runGraph() override { auto *iterator = findNode("Iterator"); ASSERT_TRUE(iterator); ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success); } // Perform tests on generated data - void performTests() + void performTests() override { auto *iterator = findNode("Iterator"); ASSERT_TRUE(iterator);