7#if defined(ENABLE_CATCH)
9# include <catch2/catch_all.hpp>
27 explicit TempJSONFile(
const std::string& contents) {
28 path_ = std::filesystem::temp_directory_path()
30 std::ofstream(path_) << contents;
34 std::filesystem::remove(path_, ec);
36 TempJSONFile(
const TempJSONFile&) =
delete;
37 TempJSONFile& operator=(
const TempJSONFile&) =
delete;
38 TempJSONFile(TempJSONFile&&) =
delete;
39 TempJSONFile& operator=(TempJSONFile&&) =
delete;
41 [[nodiscard]] std::string path()
const {
42 return path_.string();
46 std::filesystem::path path_;
47 static inline int counter_ = 0;
52TEST_CASE(
"parse_factory_options: no '?' suffix returns whole string + empty options",
"[FactoryOptions]") {
54 REQUIRE(r.clean_string ==
"HEOS::Water");
55 REQUIRE(r.options_json.empty());
58TEST_CASE(
"parse_factory_options: bare '?' yields empty options",
"[FactoryOptions]") {
60 REQUIRE(r.clean_string ==
"HEOS::Water");
61 REQUIRE(r.options_json.empty());
64TEST_CASE(
"parse_factory_options: whitespace-only tail is treated as empty",
"[FactoryOptions]") {
66 REQUIRE(r.clean_string ==
"HEOS::Water");
67 REQUIRE(r.options_json.empty());
70TEST_CASE(
"parse_factory_options: inline JSON tail is returned verbatim",
"[FactoryOptions]") {
72 REQUIRE(r.clean_string == "SVDSBTL&HEOS::Water");
73 REQUIRE(r.options_json == R
"({"critical_patch":"off"})");
76TEST_CASE("parse_factory_options: split is on the FIRST '?' only",
"[FactoryOptions]") {
77 SECTION(
"'?' inside a JSON string value") {
79 REQUIRE(r.clean_string == "HEOS::Water");
80 REQUIRE(r.options_json == R
"({"hint":"what?"})");
82 SECTION("'?' is the entire string value") {
84 REQUIRE(r.clean_string == "HEOS::Water");
85 REQUIRE(r.options_json == R
"({"q":"?"})");
87 SECTION("URL-style with both '?' and '&' inside the value") {
89 REQUIRE(r.clean_string == "HEOS::Water");
90 REQUIRE(r.options_json == R
"({"meta":{"url":"http://x.com/path?id=1&q=2"}})");
92 SECTION("regex string with escaped '?'") {
94 REQUIRE(r.clean_string == "HEOS::Water");
95 REQUIRE(r.options_json == R
"({"regex":"^[A-Z]+\\?$"})");
97 SECTION("multiple '?' chained inside one string value") {
99 REQUIRE(r.clean_string == "HEOS::Water");
100 REQUIRE(r.options_json == R
"({"chain":"first?second?third"})");
104TEST_CASE("parse_factory_options: '@path' reads file contents verbatim",
"[FactoryOptions]") {
105 SECTION(
"simple file path") {
106 TempJSONFile f(R
"({"critical_patch":"auto","grid":{"NT":200}})");
107 const std::string s =
"SVDSBTL&HEOS::Water?@" + f.path();
109 REQUIRE(r.clean_string ==
"SVDSBTL&HEOS::Water");
110 REQUIRE(r.options_json == R
"({"critical_patch":"auto","grid":{"NT":200}})");
112 SECTION("file path with internal characters ('-', '_', '.', digits)") {
113 TempJSONFile f(R
"({"x":1})");
114 const std::string s =
"HEOS::Water?@" + f.path();
116 REQUIRE(r.options_json == R
"({"x":1})");
118 SECTION("missing '@path' file throws") {
121 SECTION(
"bare '@' with no path throws ValueError") {
126TEST_CASE(
"parse_factory_options: clean_string preserves the full backend+fluid grammar",
"[FactoryOptions]") {
127 SECTION(
"simple backend") {
129 REQUIRE(r.clean_string ==
"HEOS::Water");
131 SECTION(
"source-backend split (& inside the cleaned half)") {
133 REQUIRE(r.clean_string ==
"SVDSBTL&HEOS::Water");
135 SECTION(
"no fluid name") {
137 REQUIRE(r.clean_string ==
"HEOS");
138 REQUIRE(r.options_json ==
"{}");
151TEST_CASE(
"AbstractState::factory: '?<options>' is stripped before backend dispatch",
"[FactoryOptions]") {
152 SECTION(
"no options — existing behaviour unchanged") {
154 REQUIRE(AS !=
nullptr);
157 REQUIRE(AS->backend_name() ==
"HelmholtzEOSBackend");
159 SECTION(
"empty options ('?{}') accepted by default overload") {
161 REQUIRE(AS !=
nullptr);
163 SECTION(
"bare '?' accepted by default overload") {
165 REQUIRE(AS !=
nullptr);
169TEST_CASE(
"AbstractState::factory: non-empty options on an opted-out backend throw",
"[FactoryOptions]") {
173TEST_CASE(
"AbstractState::build_options_json: default returns empty string",
"[FactoryOptions]") {
175 REQUIRE(AS->build_options_json().empty());