Files
OrcaSlicer/tests/libslic3r/test_config.cpp
T
Clifford GarwoodandClaude Opus 4.7 ee757dac20 test: address self-review findings on [Variant] and [3mf][IMEX] coverage
Self-review found two weaknesses in the preceding test commits:

1) The equal-size [Variant] scenario claimed to distinguish the truncation
   guard's `cur > target` predicate from a regression to `cur >= target`,
   but both paths yield identical child values in practice: when
   extruder_variant names match, set_with_restore's variant_index is fully
   populated (no -1 slots) and the merge path restores every position from
   backup — producing the same {1.5, 2.5} output as the skip path. The
   test passes in both guard states.

   Rewritten to use mismatched variant names between child and parent.
   variant_index then has -1 slots, and set_with_restore overwrites those
   positions with parent values. Now the merge path yields {0.8, 0.8} and
   the skip path yields {1.5, 2.5} — observably different. Verified:
     - `cur >  target` (correct):  4 scenarios pass, 15 assertions
     - `cur >= target` (regressed): equal-size scenario fails with
       "1.5 is within 0.000000001 of 0.80000000000000004"
     - Guard removed entirely: child>parent + stride=2 both fail with
       truncation ("1 == 2" / "2 == 4")

2) The [3mf][IMEX] round-trip only covered a single plate. A plate-
   indexing regression (IMEX metadata landing on the wrong plate, or
   bleeding across plates on reload) would not have been caught.

   Added a multi-plate scenario: two plates with distinct mode and
   head-filament-map values. Asserts both land on their respective
   destination plates after reload. Load-bearing verified:
     - With IMEX serialization intact:    3 scenarios pass, 45 assertions
     - With IMEX serialization disabled:  positive + multi-plate fail
       (both "nullptr != nullptr"); primary-mode passes (expects nullptr)
     - With primary-mode short-circuit removed: primary-mode scenario
       fails ("0x... == nullptr") because primary modes now serialize

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:29:01 -04:00

493 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <catch2/catch_all.hpp>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PrintConfigConstants.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/archives/binary.hpp>
using namespace Slic3r;
SCENARIO("Generic config validation performs as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
WHEN( "outer_wall_line_width is set to 250%, a valid value") {
config.set_deserialize_strict("outer_wall_line_width", "250%");
THEN( "The config is read as valid.") {
REQUIRE(config.validate().empty());
}
}
WHEN( "outer_wall_line_width is set to -10, an invalid value") {
config.set("outer_wall_line_width", -10);
THEN( "Validate returns error") {
REQUIRE_FALSE(config.validate().empty());
}
}
WHEN( "wall_loops is set to -10, an invalid value") {
config.set("wall_loops", -10);
THEN( "Validate returns error") {
REQUIRE_FALSE(config.validate().empty());
}
}
}
}
SCENARIO("Config accessor functions perform as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
WHEN("A boolean option is set to a boolean value") {
REQUIRE_NOTHROW(config.set("gcode_comments", true));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing a 0 or 1") {
CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1"));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing something other than 0 or 1") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", "Z"), BadOptionTypeException);
}
AND_THEN("Value is unchanged.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == false);
}
}
WHEN("A boolean option is set to an int value") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", 1), BadOptionTypeException);
}
}
WHEN("A numeric option is set from serialized string") {
config.set_deserialize_strict("raft_layers", "20");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInt>("raft_layers")->getInt() == 20);
}
}
WHEN("An integer-based option is set through the integer interface") {
config.set("raft_layers", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInt>("raft_layers")->getInt() == 100);
}
}
WHEN("An floating-point option is set through the integer interface") {
config.set("default_acceleration", 10);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("default_acceleration")->getFloat() == 10.0);
}
}
WHEN("A floating-point option is set through the double interface") {
config.set("default_acceleration", 5.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("default_acceleration")->getFloat() == 5.5);
}
}
WHEN("An integer-based option is set through the double interface") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("top_shell_layers", 5.5), BadOptionTypeException);
}
}
WHEN("A numeric option is set to a non-numeric value.") {
auto prev_value = config.opt<ConfigOptionFloat>("default_acceleration")->getFloat();
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set_deserialize_strict("default_acceleration", "zzzz"), BadOptionValueException);
}
THEN("The value does not change.") {
REQUIRE(config.opt<ConfigOptionFloat>("default_acceleration")->getFloat() == prev_value);
}
}
WHEN("A string option is set through the string interface") {
config.set("machine_end_gcode", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("machine_end_gcode")->value == "100");
}
}
WHEN("A string option is set through the integer interface") {
config.set("machine_end_gcode", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("machine_end_gcode")->value == "100");
}
}
WHEN("A string option is set through the double interface") {
config.set("machine_end_gcode", 100.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("machine_end_gcode")->value == float_to_string_decimal_point(100.5));
}
}
WHEN("A float or percent is set as a percent through the string interface.") {
config.set_deserialize_strict("initial_layer_line_width", "100%");
THEN("Value and percent flag are 100/true") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("initial_layer_line_width");
REQUIRE(tmp->percent == true);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the string interface.") {
config.set_deserialize_strict("initial_layer_line_width", "100");
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("initial_layer_line_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the int interface.") {
config.set("initial_layer_line_width", 100);
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("initial_layer_line_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the double interface.") {
config.set("initial_layer_line_width", 100.5);
THEN("Value and percent flag are 100.5/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("initial_layer_line_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100.5);
}
}
WHEN("A numeric vector is set from serialized string") {
config.set_deserialize_strict("temperature_vitrification", "10,20");
THEN("The underlying value is set correctly.") {
CHECK(config.opt<ConfigOptionInts>("temperature_vitrification")->get_at(0) == 10);
CHECK(config.opt<ConfigOptionInts>("temperature_vitrification")->get_at(1) == 20);
}
}
// FIXME: Design better accessors for vector elements
// The following isn't supported and probably shouldn't be:
// WHEN("An integer-based vector option is set through the integer interface") {
// config.set("temperature_vitrification", 100);
// THEN("The underlying value is set correctly.") {
// REQUIRE(config.opt<ConfigOptionInts>("temperature_vitrification")->get_at(0) == 100);
// }
// }
WHEN("An integer-based vector option is set through the set_key_value interface") {
config.set_key_value("temperature_vitrification", new ConfigOptionInts{10,20});
THEN("The underlying value is set correctly.") {
CHECK(config.opt<ConfigOptionInts>("temperature_vitrification")->get_at(0) == 10);
CHECK(config.opt<ConfigOptionInts>("temperature_vitrification")->get_at(1) == 20);
}
}
WHEN("An invalid option is requested during set.") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1.0), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", "1"), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", true), UnknownOptionException);
}
}
WHEN("An invalid option is requested during get.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("An invalid option is requested during opt.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("getX called on an unset option.") {
THEN("The default is returned.") {
REQUIRE(config.opt_float("layer_height") == INITIAL_LAYER_HEIGHT);
REQUIRE(config.opt_int("raft_layers") == INITIAL_RAFT_LAYERS);
REQUIRE(config.opt_bool("reduce_crossing_wall") == INITIAL_REDUCE_CROSSING_WALL);
}
}
WHEN("opt_float called on an option that has been set.") {
config.set("layer_height", INITIAL_LAYER_HEIGHT*2);
THEN("The set value is returned.") {
REQUIRE(config.opt_float("layer_height") == INITIAL_LAYER_HEIGHT*2);
}
}
}
}
SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config;
std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini";
config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable);
THEN("Config object contains ini file options.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");
}
}
}
// TODO: https://github.com/SoftFever/OrcaSlicer/issues/11269 - Is this test still relevant? Delete if not.
// It was failing so at least "nozzle_type" and "extruder_printable_area" could not be serialized
// and an exception was thrown, but "nozzle_type" has been around for at least 3 months now.
// So maybe this test and the serialization logic in Config.?pp should be deleted if it doesn't get used.
SCENARIO("DynamicPrintConfig serialization", "[Config]") {
WHEN("DynamicPrintConfig is serialized and deserialized") {
FullPrintConfig full_print_config;
DynamicPrintConfig cfg;
cfg.apply(full_print_config, false);
std::string serialized;
// try {
std::ostringstream ss;
cereal::BinaryOutputArchive oarchive(ss);
oarchive(cfg);
serialized = ss.str();
// } catch (const std::runtime_error & /* e */) {
// // e.what();
// }
CAPTURE(serialized.length());
THEN("Config object contains ini file options.") {
DynamicPrintConfig cfg2;
// try {
std::stringstream ss(serialized);
cereal::BinaryInputArchive iarchive(ss);
iarchive(cfg2);
// } catch (const std::runtime_error & /* e */) {
// // e.what();
// }
CAPTURE(cfg.diff_report(cfg2));
REQUIRE(cfg == cfg2);
}
}
}
SCENARIO("update_non_diff_values_to_base_config does not truncate stride=2 child vectors when child has more extruders than parent",
"[Config][Variant]") {
GIVEN("A 2-extruder child with stride=2 machine limits inheriting from a 1-extruder parent") {
// Stride=2 keys store (normal, silent) pairs per variant: a 2-extruder child has size 4,
// a 1-extruder parent has size 2. The truncation guard must fire here too.
Slic3r::DynamicPrintConfig child;
Slic3r::DynamicPrintConfig parent;
child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
child.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard", "Direct Drive Standard"}));
child.set_key_value("machine_max_acceleration_x", new Slic3r::ConfigOptionFloats({500.0, 200.0, 600.0, 300.0}));
parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1}));
parent.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard"}));
parent.set_key_value("machine_max_acceleration_x", new Slic3r::ConfigOptionFloats({1000.0, 400.0}));
const Slic3r::t_config_option_keys keys = {
"machine_max_acceleration_x", "printer_extruder_id", "printer_extruder_variant"
};
const std::set<std::string> different_keys = {
"machine_max_acceleration_x", "printer_extruder_id", "printer_extruder_variant"
};
WHEN("update_non_diff_values_to_base_config is called") {
std::string id_name = "printer_extruder_id";
std::string var_name = "printer_extruder_variant";
child.update_non_diff_values_to_base_config(
parent, keys, different_keys, id_name, var_name,
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("machine_max_acceleration_x retains size 4 (2 variants × 2 silent modes)") {
REQUIRE(child.option<Slic3r::ConfigOptionFloats>("machine_max_acceleration_x")->values.size() == 4);
}
THEN("machine_max_acceleration_x preserves both extruders' normal and silent values") {
auto* v = child.option<Slic3r::ConfigOptionFloats>("machine_max_acceleration_x");
REQUIRE_THAT(v->values[0], Catch::Matchers::WithinAbs(500.0, 1e-9));
REQUIRE_THAT(v->values[1], Catch::Matchers::WithinAbs(200.0, 1e-9));
REQUIRE_THAT(v->values[2], Catch::Matchers::WithinAbs(600.0, 1e-9));
REQUIRE_THAT(v->values[3], Catch::Matchers::WithinAbs(300.0, 1e-9));
}
}
}
}
SCENARIO("update_non_diff_values_to_base_config runs the merge path in the equal-size case",
"[Config][Variant]") {
// Distinguishes the fix's `cur > target` guard from a stricter `cur >= target`.
// With `cur > target` (correct): equal-size does NOT fire the guard; merge runs via
// set_with_restore, which builds variant_index by matching (extruder_variant, extruder_id)
// pairs between child and parent. When the variants don't match, variant_index positions
// stay at -1, and set_with_restore overwrites those child positions with parent values.
// With `cur >= target` (regressed): guard fires; merge is skipped; child values stay intact.
// Using mismatched variants makes the two outcomes observably different.
GIVEN("A 2-extruder child and parent with matching extruder counts but mismatched variant names") {
Slic3r::DynamicPrintConfig child;
Slic3r::DynamicPrintConfig parent;
child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
child.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Bowden Standard", "Bowden Standard"}));
child.set_key_value("retraction_length", new Slic3r::ConfigOptionFloats({1.5, 2.5}));
parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
parent.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard", "Direct Drive Standard"}));
parent.set_key_value("retraction_length", new Slic3r::ConfigOptionFloats({0.8, 0.8}));
const Slic3r::t_config_option_keys keys = {
"retraction_length", "printer_extruder_id", "printer_extruder_variant"
};
const std::set<std::string> different_keys = {
"retraction_length", "printer_extruder_id", "printer_extruder_variant"
};
WHEN("update_non_diff_values_to_base_config is called") {
std::string id_name = "printer_extruder_id";
std::string var_name = "printer_extruder_variant";
child.update_non_diff_values_to_base_config(
parent, keys, different_keys, id_name, var_name,
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("retraction_length retains size 2") {
REQUIRE(child.option<Slic3r::ConfigOptionFloats>("retraction_length")->values.size() == 2);
}
THEN("retraction_length gets parent values — proves the merge ran (guard did not fire)") {
// If the guard regressed to `cur >= target`, this path would be skipped and
// retraction_length would remain {1.5, 2.5}. The correct `cur > target` guard
// does not fire for equal-size, the merge proceeds, and with mismatched
// variants the child positions receive parent values.
auto* rl = child.option<Slic3r::ConfigOptionFloats>("retraction_length");
REQUIRE_THAT(rl->values[0], Catch::Matchers::WithinAbs(0.8, 1e-9));
REQUIRE_THAT(rl->values[1], Catch::Matchers::WithinAbs(0.8, 1e-9));
}
}
}
}
SCENARIO("update_non_diff_values_to_base_config truncation guard does not affect non-variant scalar keys",
"[Config][Variant]") {
// The fix is scoped to options in printer_options_with_variant_1 / _2. A non-variant scalar
// listed in `keys` and `different_keys` should hit the "nothing to do" branch and remain
// untouched regardless of child/parent extruder count mismatch.
GIVEN("A 2-extruder child inheriting from a 1-extruder parent, with a non-variant scalar key in `keys`") {
Slic3r::DynamicPrintConfig child;
Slic3r::DynamicPrintConfig parent;
child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
child.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard", "Direct Drive Standard"}));
child.set_key_value("layer_height", new Slic3r::ConfigOptionFloat(0.20));
parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1}));
parent.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard"}));
parent.set_key_value("layer_height", new Slic3r::ConfigOptionFloat(0.28));
const Slic3r::t_config_option_keys keys = {
"layer_height", "printer_extruder_id", "printer_extruder_variant"
};
const std::set<std::string> different_keys = {
"layer_height", "printer_extruder_id", "printer_extruder_variant"
};
WHEN("update_non_diff_values_to_base_config is called") {
std::string id_name = "printer_extruder_id";
std::string var_name = "printer_extruder_variant";
child.update_non_diff_values_to_base_config(
parent, keys, different_keys, id_name, var_name,
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("the non-variant scalar layer_height is left unchanged on the child") {
REQUIRE_THAT(child.option<Slic3r::ConfigOptionFloat>("layer_height")->value,
Catch::Matchers::WithinAbs(0.20, 1e-9));
}
}
}
}
SCENARIO("update_non_diff_values_to_base_config preserves child vectors when child has more extruders than parent",
"[Config][Variant]") {
GIVEN("A 2-extruder child printer config inheriting from a 1-extruder parent") {
Slic3r::DynamicPrintConfig child;
Slic3r::DynamicPrintConfig parent;
child.set_key_value("nozzle_diameter", new Slic3r::ConfigOptionFloats({0.4, 0.4}));
child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
child.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard", "Direct Drive Standard"}));
child.set_key_value("retraction_length", new Slic3r::ConfigOptionFloats({1.5, 1.5}));
parent.set_key_value("nozzle_diameter", new Slic3r::ConfigOptionFloats({0.4}));
parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1}));
parent.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({"Direct Drive Standard"}));
parent.set_key_value("retraction_length", new Slic3r::ConfigOptionFloats({0.8}));
const Slic3r::t_config_option_keys keys = {
"retraction_length", "printer_extruder_id", "printer_extruder_variant"
};
const std::set<std::string> different_keys = {
"retraction_length", "printer_extruder_id", "printer_extruder_variant"
};
WHEN("update_non_diff_values_to_base_config is called") {
std::string id_name = "printer_extruder_id";
std::string var_name = "printer_extruder_variant";
child.update_non_diff_values_to_base_config(
parent, keys, different_keys, id_name, var_name,
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("printer_extruder_id retains size 2") {
REQUIRE(child.option<Slic3r::ConfigOptionInts>("printer_extruder_id")->values.size() == 2);
}
THEN("printer_extruder_variant retains size 2") {
REQUIRE(child.option<Slic3r::ConfigOptionStrings>("printer_extruder_variant")->values.size() == 2);
}
THEN("retraction_length retains size 2") {
REQUIRE(child.option<Slic3r::ConfigOptionFloats>("retraction_length")->values.size() == 2);
}
THEN("printer_extruder_id values are preserved for both extruders") {
auto* pe_id = child.option<Slic3r::ConfigOptionInts>("printer_extruder_id");
REQUIRE(pe_id->values.size() == 2);
REQUIRE(pe_id->values[0] == 1);
REQUIRE(pe_id->values[1] == 2);
}
}
}
}
// SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") {
// WHEN("DynamicPrintConfig is serialized and deserialized") {
// auto now = std::chrono::high_resolution_clock::now();
// auto timestamp = now.time_since_epoch().count();
// std::stringstream ss;
// ss << "catch_test_serialization_" << timestamp << ".json";
// std::string filename = (fs::temp_directory_path() / ss.str()).string();
// TODO: Finish making a unit test for JSON serialization
// FullPrintConfig full_print_config;
// DynamicPrintConfig cfg;
// cfg.apply(full_print_config, false);
// std::string serialized;
// try {
// std::ostringstream ss;
// cereal::BinaryOutputArchive oarchive(ss);
// oarchive(cfg);
// serialized = ss.str();
// } catch (const std::runtime_error & /* e */) {
// // e.what();
// }
// CAPTURE(serialized.length());
// THEN("Config object contains ini file options.") {
// DynamicPrintConfig cfg2;
// try {
// std::stringstream ss(serialized);
// cereal::BinaryInputArchive iarchive(ss);
// iarchive(cfg2);
// } catch (const std::runtime_error & /* e */) {
// // e.what();
// }
// CAPTURE(cfg.diff_report(cfg2));
// REQUIRE(cfg == cfg2);
// }
// }
// }