From ee153a56b12e3508e5fa733ca031cc5bd0da7e6f Mon Sep 17 00:00:00 2001 From: Adam Washington Date: Mon, 4 Dec 2023 08:52:19 +0000 Subject: [PATCH] feat: (##1076) enable backward compatibility (#1740) --- src/main/CMakeLists.txt | 2 ++ src/main/compatibility.cpp | 47 +++++++++++++++++++++++++++++++ src/main/compatibility.h | 14 ++++++++++ src/main/io.cpp | 14 +++++++++- src/main/version.cpp | 30 ++++++++++++++++++++ src/main/version.h | 17 ++++++++++++ tests/io/CMakeLists.txt | 1 + tests/io/version.cpp | 57 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/main/compatibility.cpp create mode 100644 src/main/compatibility.h create mode 100644 tests/io/version.cpp diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index bc101d7daf..9c7e338c0b 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -1,6 +1,7 @@ add_library( main comms.cpp + compatibility.cpp cli.cpp dissolve.cpp io.cpp @@ -14,6 +15,7 @@ add_library( simulation.cpp version.cpp cli.h + compatibility.h dissolve.h keywords.h version.h diff --git a/src/main/compatibility.cpp b/src/main/compatibility.cpp new file mode 100644 index 0000000000..16bc208dfe --- /dev/null +++ b/src/main/compatibility.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2023 Team Dissolve and contributors + +#include "main/compatibility.h" +#include +#include + +namespace dissolve +{ + +// Upgrade from version zero, which never supported TOML in the first +// place. This exists purely to allow testing the backward +// compatibility code before there is any pass worth checking. +SerialisedValue unrealUpgrade(SerialisedValue contents) +{ + if (contents["pairPotentials"].contains("coulombTruncation")) + contents["pairPotentials"]["coulombTruncation"] = + toml::find(contents["pairPotentials"], "coulombTruncation") + "ed"; + + return contents; +} + +// Upgrade a file to the current version. +SerialisedValue backwardsUpgrade(SerialisedValue contents) +{ + using Version::DissolveVersion; + + // A map of version numbers and the corresponding function to + // upgrade the file. + std::map> breakingChanges = {{"1.2.0", unrealUpgrade}}; + + DissolveVersion current(toml::find(contents, "version")); + + for (auto &[key, value] : breakingChanges) + { + DissolveVersion next(key); + if (current < next) + { + contents = value(contents); + current = next; + } + } + + return contents; +} + +} // namespace dissolve diff --git a/src/main/compatibility.h b/src/main/compatibility.h new file mode 100644 index 0000000000..a91980d8c4 --- /dev/null +++ b/src/main/compatibility.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2023 Team Dissolve and contributors + +#pragma once + +#include "base/serialiser.h" +#include "main/version.h" + +namespace dissolve +{ + +SerialisedValue backwardsUpgrade(SerialisedValue contents); + +} diff --git a/src/main/io.cpp b/src/main/io.cpp index 29d2af7bca..a64a541401 100644 --- a/src/main/io.cpp +++ b/src/main/io.cpp @@ -2,14 +2,19 @@ // Copyright (c) 2023 Team Dissolve and contributors #include "base/lineParser.h" +#include "base/messenger.h" +#include "base/serialiser.h" #include "base/sysFunc.h" #include "classes/atomType.h" #include "classes/species.h" #include "data/isotopes.h" +#include "main/compatibility.h" #include "main/dissolve.h" #include "main/keywords.h" #include "main/version.h" #include +#include +#include // Load input file through supplied parser bool Dissolve::loadInput(LineParser &parser) @@ -144,6 +149,8 @@ SerialisedValue Dissolve::serialise() const { SerialisedValue root; + root["version"] = Version::semantic(); + if (!coreData_.masterBonds().empty() || !coreData_.masterAngles().empty() || !coreData_.masterTorsions().empty() || !coreData_.masterImpropers().empty()) root["master"] = coreData_.serialiseMaster(); @@ -179,8 +186,13 @@ void Dissolve::deserialisePairPotentials(const SerialisedValue &node) } // Read values from a serialisable value -void Dissolve::deserialise(const SerialisedValue &node) +void Dissolve::deserialise(const SerialisedValue &originalNode) { + // Default to current version if no version info is given. + auto hasVersion = originalNode.contains("version"); + if (!hasVersion) + Messenger::warn("File does not contain version information. Assuming the current version: {}", Version::semantic()); + const SerialisedValue node = hasVersion ? dissolve::backwardsUpgrade(originalNode) : originalNode; Serialisable::optionalOn(node, "pairPotentials", [this](const auto node) { deserialisePairPotentials(node); }); Serialisable::optionalOn(node, "master", [this](const auto node) { coreData_.deserialiseMaster(node); }); diff --git a/src/main/version.cpp b/src/main/version.cpp index 3dded1c079..eff5d2f23d 100644 --- a/src/main/version.cpp +++ b/src/main/version.cpp @@ -3,6 +3,7 @@ #include "main/version.h" #include +#include #define DISSOLVEVERSION "1.4.0" #define DISSOLVESHORTHASH "" @@ -50,4 +51,33 @@ std::string_view appType() #endif } +DissolveVersion::DissolveVersion(std::string_view version) +{ + auto dot = version.find("."); + major_ = std::stoi(std::string(version.substr(0, dot))); + auto rest = version.substr(dot + 1); + dot = rest.find("."); + minor_ = std::stoi(std::string(rest.substr(0, dot))); + patch_ = std::stoi(std::string(rest.substr(dot + 1))); +} + +bool DissolveVersion::operator<(const DissolveVersion &other) const +{ + if (major_ != other.major_) + return major_ < other.major_; + if (minor_ != other.minor_) + return minor_ < other.minor_; + return patch_ < other.patch_; +} + +bool DissolveVersion::operator==(const DissolveVersion &other) const +{ + return major_ == other.major_ && minor_ == other.minor_ && patch_ == other.patch_; +} + +std::ostream &operator<<(std::ostream &os, const DissolveVersion &version) +{ + os << fmt::format("{}.{}.{}", version.major_, version.minor_, version.patch_); + return os; +} }; // namespace Version diff --git a/src/main/version.h b/src/main/version.h index ad73744f86..45c5766e98 100644 --- a/src/main/version.h +++ b/src/main/version.h @@ -15,4 +15,21 @@ std::string_view info(); std::string_view repoUrl(); // Return app type std::string_view appType(); + +// This class handles the version numbers of the app in a structured +// manner. +class DissolveVersion +{ + private: + int major_; + int minor_; + int patch_; + + public: + DissolveVersion(std::string_view version); + bool operator<(const DissolveVersion &other) const; + bool operator==(const DissolveVersion &other) const; + friend std::ostream &operator<<(std::ostream &os, const DissolveVersion &version); +}; + }; // namespace Version diff --git a/tests/io/CMakeLists.txt b/tests/io/CMakeLists.txt index ac8d300818..5dd16ef725 100644 --- a/tests/io/CMakeLists.txt +++ b/tests/io/CMakeLists.txt @@ -1,2 +1,3 @@ dissolve_add_test(SRC cif.cpp) dissolve_add_test(SRC intraParameterParse.cpp) +dissolve_add_test(SRC version.cpp) diff --git a/tests/io/version.cpp b/tests/io/version.cpp new file mode 100644 index 0000000000..dd9e4d158a --- /dev/null +++ b/tests/io/version.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2023 Team Dissolve and contributors + +#include "main/version.h" +#include "base/serialiser.h" +#include "main/dissolve.h" +#include + +namespace UnitTest +{ + +TEST(VersionTest, VersionClass) +{ + using Version::DissolveVersion; + DissolveVersion first{"1.10.3"}, second{"2.1.12"}, minor{"2.2.1"}, patch{"2.2.2"}, third{"3.0.0"}; + + // Main sequence + EXPECT_LT(first, second); + EXPECT_LT(second, minor); + EXPECT_LT(minor, patch); + EXPECT_LT(patch, third); + + // Full combinatorial + EXPECT_LT(first, minor); + EXPECT_LT(first, patch); + EXPECT_LT(first, third); + EXPECT_LT(second, minor); + EXPECT_LT(second, patch); + EXPECT_LT(second, third); + EXPECT_LT(minor, third); +} + +TEST(VersionTest, VersionInfo) +{ + CoreData coreData; + Dissolve dissolve(coreData); + dissolve.loadInput("dissolve/input/rdfMethod.txt"); + auto serialised = dissolve.serialise(); + + Version::DissolveVersion fileVersion(serialised["version"].as_string().str), actual(Version::semantic()); + + EXPECT_EQ(fileVersion, actual); +} + +TEST(VersionTest, VersionUpgrade) +{ + CoreData coreData; + Dissolve dissolve(coreData); + + SerialisedValue old; + old["version"] = "0.0.0"; + old["pairPotentials"] = {{"coulombTruncation", "Shift"}}; + + dissolve.deserialise(old); +} + +} // namespace UnitTest