Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions c++/common_defs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*
*/

#include <green/h5pp/archive.h>
#include "green/grids/common_defs.h"

#include "green/grids/except.h"
Expand All @@ -16,4 +17,34 @@ namespace green::grids {
}
return std::filesystem::exists(path) ? path : (std::filesystem::exists(src_path) ? src_path : install_path);
}

void check_grids_version_in_hdf5(const std::string& results_file, const std::string& grid_file_version) {
// If results file does not exist, nothing to check
if (!std::filesystem::exists(results_file)) return;

h5pp::archive ar(results_file, "r");
if (ar.has_attribute("__grids_version__")) {
std::string grids_version_in_results;
grids_version_in_results = ar.get_attribute<std::string>("__grids_version__");
ar.close(); // safely close before throwing
if (compare_version_strings(grid_file_version, grids_version_in_results) < 0) {
throw outdated_grids_file_error("The current green-grids version (" + grid_file_version +
") is older than the green-grids version used to create the original results file ("
+ grids_version_in_results +
"). Please update green-grids to version " + grids_version_in_results);
} else if (compare_version_strings(grid_file_version, grids_version_in_results) > 0) {
throw outdated_results_file_error("The green-grids version used to create the results file (" + grids_version_in_results +
") is older than the current green-grids version (" + grid_file_version +
"). Please download the appropriate version from: " +
"https://github.com/Green-Phys/green-grids/releases/ or https://github.com/Green-Phys/green-grids/tags");
}
} else {
ar.close(); // safely close comparing version strings
if (compare_version_strings(grid_file_version, GRIDS_MIN_VERSION) > 0) {
throw outdated_results_file_error("The results file was created using un-versioned grid file (equiv. to " + GRIDS_MIN_VERSION +
") and the current green-grids version (" + grid_file_version + ") is newer.\n" +
"Please use old grid files from: https://github.com/Green-Phys/green-grids/releases/tag/v" + GRIDS_MIN_VERSION + ".");
}
}
}
} // namespace green::grids
74 changes: 44 additions & 30 deletions c++/green/grids/common_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,39 +54,53 @@ namespace green::grids {

std::string grid_path(const std::string& path);

/**
* @brief Check whether a version string satisfies the minimum required GRIDS version.
*
* Compares the given version string against the library's minimum supported
* GRIDS version specified by `GRIDS_MIN_VERSION`.
*
* @param v Version string to check (e.g. "0.2.4").
* @return true if v is greater than or equal to GRIDS_MIN_VERSION.
* @return false if v is less than GRIDS_MIN_VERSION.
* @throws outdated_grids_file_error if the format of v or GRIDS_MIN_VERSION is incorrect.
*/
inline bool CheckVersion(const std::string& v) {
int major_Vin = 0, minor_Vin = 0, patch_Vin = 0;
int major_Vref = 0, minor_Vref = 0, patch_Vref = 0;

char suffixV[32] = "";
char suffixM[32] = "";

int parsed_in = std::sscanf(v.c_str(), "%d.%d.%d%30s", &major_Vin, &minor_Vin, &patch_Vin, suffixV);
int parsed_ref = std::sscanf(GRIDS_MIN_VERSION.c_str(), "%d.%d.%d%30s", &major_Vref, &minor_Vref, &patch_Vref, suffixM);
inline int compare_version_strings(const std::string& v1, const std::string& v2) {
int major_V1 = 0, minor_V1 = 0, patch_V1 = 0;
int major_V2 = 0, minor_V2 = 0, patch_V2 = 0;

char suffix_V1[32] = "";
char suffix_V2[32] = "";

int parsed_1 = std::sscanf(v1.c_str(), "%d.%d.%d%30s", &major_V1, &minor_V1, &patch_V1, suffix_V1);
int parsed_2 = std::sscanf(v2.c_str(), "%d.%d.%d%30s", &major_V2, &minor_V2, &patch_V2, suffix_V2);

if (parsed_1 < 3) {
throw outdated_grids_file_error("First version string (v1) failed to parse: '" + v1 + "'. Expected format: major.minor.patch[suffix]");
}
if (parsed_2 < 3) {
throw outdated_grids_file_error("Second version string (v2) failed to parse: '" + v2 + "'. Expected format: major.minor.patch[suffix]");
}

if (parsed_in < 3 || parsed_ref < 3) {
throw outdated_grids_file_error("Version string format is incorrect. Expected format: major.minor.patch[suffix]");
if (major_V1 != major_V2) {
return major_V1 > major_V2 ? 1 : -1;
}

if (major_Vin != major_Vref) return major_Vin > major_Vref;
if (minor_Vin != minor_Vref) return minor_Vin > minor_Vref;
if (patch_Vin != patch_Vref) return patch_Vin > patch_Vref;

// If numeric parts in version are all equal, do not worry about suffix
// e.g., 0.2.4b10 has same integral format as 0.2.4
return true;
if (minor_V1 != minor_V2) {
return minor_V1 > minor_V2 ? 1 : -1;
}
if (patch_V1 != patch_V2) {
return patch_V1 > patch_V2 ? 1 : -1;
}

return 0;
}

/**
* @brief Checks consistency between grid-file version used in current run vs. the version
* used to generate the results file that is being (re-)started from.
* This is to prevent users from accidentally restarting from a results file that was generated with
* an older version of green-grids, which can lead to silent errors in the results.
*
* 1. If the results file does not have a green-grids version attribute, treat it as having been
* generated with the baseline grids version (GRIDS_MIN_VERSION, currently 0.2.4) and check
* that the current grid-file version is not newer; otherwise an error is raised.
* 2. If the results file has a green-grids version attribute, compare that version with the current
* grid-file version and distinguish older, equal, and newer cases, allowing only compatible
* combinations and throwing specific errors when there is a mismatch.
*
* @param results_file - path to the results file that is being restarted from
* @param grid_file_version - version of green-grids used in the current run (can be obtained from DysonSolver::get_grids_version())
*/
void check_grids_version_in_hdf5(const std::string& results_file, const std::string& grid_file_version);

} // namespace green::grids
#endif // GRIDS_COMMON_DEFS_H
5 changes: 5 additions & 0 deletions c++/green/grids/except.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ namespace green::grids {
public:
explicit outdated_grids_file_error(const std::string& string) : runtime_error(string) {}
};

class outdated_results_file_error : public std::runtime_error {
public:
explicit outdated_results_file_error(const std::string& what) : std::runtime_error(what) {}
};
} // namespace green::grids

#endif // GRIDS_EXCEPT_H
2 changes: 1 addition & 1 deletion c++/transformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace green::grids {
if (tnl_file.has_attribute("__grids_version__")) {
std::string v_str = tnl_file.get_attribute<std::string>("__grids_version__");
set_version(v_str);
if (!CheckVersion(v_str)) {
if (compare_version_strings(v_str, GRIDS_MIN_VERSION) < 0) {
throw outdated_grids_file_error("The grids file version " + v_str +
" is outdated. Minimum required version is " + GRIDS_MIN_VERSION + ".");
}
Expand Down
66 changes: 62 additions & 4 deletions test/grids_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,36 @@

#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <chrono>
#include <thread>
#include <random>

#include "tensor_test.h"

using namespace std::string_literals;

std::filesystem::path make_temp_h5_path() {
const auto temp_dir = std::filesystem::temp_directory_path();
// Use high-resolution timestamp and randomness to avoid filename collisions
const auto now = std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<std::uint64_t> dist;
const auto rand_val = dist(gen);
const auto filename = "grids_version_check_" + std::to_string(now) + "_" + std::to_string(rand_val) + ".h5";
return temp_dir / filename;
}

struct file_cleanup_guard {
explicit file_cleanup_guard(std::filesystem::path file_path) : path(std::move(file_path)) {}
~file_cleanup_guard() {
std::error_code ec;
std::filesystem::remove(path, ec);
}

std::filesystem::path path;
};

inline std::pair<int, char**> get_argc_argv(std::string& str) {
std::string key;
std::vector<char*> splits = {(char*)str.c_str()};
Expand Down Expand Up @@ -159,11 +182,46 @@ void check_transformer(green::grids::transformer_t& tr) {
double leakage = tr.check_chebyshev(X1t, 1);
REQUIRE(leakage < 1e-10);
}
SECTION("Check Version Info") {
SECTION("Check Comparison of Version Strings") {
std::string v = tr.get_version();
std::string v2 = "0.2.0"; // Older version
REQUIRE(green::grids::CheckVersion(v));
REQUIRE_FALSE(green::grids::CheckVersion(v2));
REQUIRE(green::grids::compare_version_strings(v, green::grids::GRIDS_MIN_VERSION) >= 0);
REQUIRE(green::grids::compare_version_strings(v2, green::grids::GRIDS_MIN_VERSION) < 0);
REQUIRE(green::grids::compare_version_strings(green::grids::GRIDS_MIN_VERSION, v2) > 0);
REQUIRE_THROWS_AS(green::grids::compare_version_strings("0.1", "0.2.4"), green::grids::outdated_grids_file_error);
REQUIRE_THROWS_AS(green::grids::compare_version_strings("0.2.4", "0.1"), green::grids::outdated_grids_file_error);
REQUIRE_NOTHROW(green::grids::compare_version_strings("0.2.4abce", "0.2.4.xyz"));
}
SECTION("Check Version Consistency in HDF5 File") {
// 1. Starting with new file (does not exist).
// Version check should pass because there's nothing to check / no inconsistency
std::filesystem::path res_file_path = make_temp_h5_path();
file_cleanup_guard cleanup(res_file_path);
std::string res_file = res_file_path.string();
REQUIRE_NOTHROW(green::grids::check_grids_version_in_hdf5(res_file, "0.2.4"));

// 2. Open file add some data but don't add __grids_version__
// This mimics the case when results file was created using old grids
// Checking consistency of grids versions should pass if old grids are used,
// otherwise throw an error for new grids
green::h5pp::archive ar_res_0(res_file, "w");
ar_res_0["One"] << 1.0;
ar_res_0.close();
REQUIRE_NOTHROW(green::grids::check_grids_version_in_hdf5(res_file, green::grids::GRIDS_MIN_VERSION));
REQUIRE_THROWS_AS(green::grids::check_grids_version_in_hdf5(res_file, "0.3.0"), green::grids::outdated_results_file_error);

// 3. Open the file and set the __grids_version__ attribute to 0.2.4
// Then comparing with 0.2.4 should pass;
// comparing with older version should throw outdated_grids_file_error;
// comparing with newer version should throw outdated_results_file_error
green::h5pp::archive ar_res_1(res_file, "a");
ar_res_1.set_attribute<std::string>("__grids_version__", "0.2.4");
ar_res_1.close();
REQUIRE_NOTHROW(green::grids::check_grids_version_in_hdf5(res_file, green::grids::GRIDS_MIN_VERSION));
REQUIRE_THROWS_AS(green::grids::check_grids_version_in_hdf5(res_file, "0.2.3"),
green::grids::outdated_grids_file_error);
REQUIRE_THROWS_AS(green::grids::check_grids_version_in_hdf5(res_file, "0.2.5"),
green::grids::outdated_results_file_error);
}
}

Expand Down