From 136ed55cc5218fd41548307ae5e79abd39f36874 Mon Sep 17 00:00:00 2001 From: wwuoneway Date: Tue, 3 Nov 2026 16:18:11 -0600 Subject: [PATCH 1/2] form: declare configuration names and on-disk tokens independently A technology's on-disk token was derived by lowercasing its configuration spelling, tying a persistent file-format identifier to a user-interface string: renaming a configuration name would silently have changed what FORM writes to disk. There was no parser in the other direction either, so the descriptor read back in #1033 could not be turned into a technology::id. Declare both spellings in one constexpr registry in core/technology.hpp, holding each technology's id, configuration name, on-disk token and whether FORM implements it. Every lookup and both parsers derive from that table. Add a separate exact-match parsing path for on-disk tokens: on_disk_token(), find_on_disk_token() and from_on_disk_token(). --- form/core/container_naming.hpp | 15 +- form/core/technology.hpp | 177 ++++++++++++++++++--- form/persistence/navigation_naming.hpp | 4 +- form/persistence/persistence_writer.cpp | 4 +- test/form/dune_example_navigation_dump.cpp | 2 +- test/form/form_basics_test.cpp | 61 ++++++- test/form/form_navigation_test.cpp | 5 +- test/form/form_storage_test.cpp | 14 +- test/form/navigation_check.hpp | 20 +-- 9 files changed, 235 insertions(+), 67 deletions(-) diff --git a/form/core/container_naming.hpp b/form/core/container_naming.hpp index 62a0a83ff..2f8b66c3d 100644 --- a/form/core/container_naming.hpp +++ b/form/core/container_naming.hpp @@ -61,19 +61,6 @@ namespace form::detail::experimental { return result; } - /// Returns the technology token used in physical names. - inline std::string technology_name(form::technology::id tech) - { - if (tech.major == form::technology::major::generic) { - return "generic"; - } - auto name = form::technology::to_string(tech); - for (char& c : name) { - c = static_cast(std::tolower(static_cast(c))); - } - return sanitize_name(name); - } - /// Throws if a creator or stage cannot be part of a physical row-space name. inline void check_row_space_parts(std::string_view creator, std::string_view stage) { @@ -99,7 +86,7 @@ namespace form::detail::experimental { std::string_view creator, std::string_view stage) { - std::string result = technology_name(tech); + std::string result{form::technology::on_disk_token(tech)}; result += row_space_field_separator; result += build_stream_name(creator, stage); return result; diff --git a/form/core/technology.hpp b/form/core/technology.hpp index 63e1ab55e..07c80bf6c 100644 --- a/form/core/technology.hpp +++ b/form/core/technology.hpp @@ -1,13 +1,23 @@ #ifndef FORM_CORE_TECHNOLOGY_HPP #define FORM_CORE_TECHNOLOGY_HPP +#include +#include #include +#include #include +#include #include #include #include -/* A storage technology, identified by a (major, minor) pair */ +/* @file technology.hpp + * @brief Storage technology identifiers and their configuration and on-disk names. + * - A storage technology, identified by a (major, minor) pair. + * - Configuration names and on-disk tokens are independent: configuration names are user-facing; + * on-disk tokens are part of the FORM persistent format. Neither is derived from the other. + * - Add technologies to detail::registry. Compile-time checks enforce the registry invariants. + */ namespace form::technology { @@ -32,40 +42,165 @@ namespace form::technology { constexpr auto operator<=>(id const&) const = default; }; - // Backends: valid (major, minor) pairs, stable numeric values as a future token may persist them + // Backends: valid (major, minor) pairs. The numeric values are stable, but nothing writes + // them to a file: a file names a technology by its on-disk token. inline constexpr id root_ttree{.major = major::root, .minor = 1}; inline constexpr id root_rntuple{.major = major::root, .minor = 2}; inline constexpr id hdf5{.major = major::hdf5, .minor = 1}; - // Canonical string -> technology mapping: the single place a technology string is parsed, replacing the copies that used to live in each module/source/test - inline id from_string(std::string_view name) + namespace detail { + + struct registration { + id technology; + std::string_view configuration_name; + std::string_view on_disk_token; + bool is_implemented; + }; + + /// The single place a technology's spellings are declared. Nothing else may invent one. + inline constexpr std::array registry{ + // Generic is not configurable but has an on-disk token. + registration{.technology = id{}, + .configuration_name = {}, + .on_disk_token = "generic", + .is_implemented = true}, + registration{.technology = root_ttree, + .configuration_name = "ROOT_TTREE", + .on_disk_token = "root_ttree", + .is_implemented = true}, + registration{.technology = root_rntuple, + .configuration_name = "ROOT_RNTUPLE", + .on_disk_token = "root_rntuple", + .is_implemented = true}, + // Reserved for a future HDF5 backend. + registration{.technology = hdf5, + .configuration_name = "HDF5", + .on_disk_token = "hdf5", + .is_implemented = false}, + }; + + /// Every technology must be nameable on disk: a row without a token could not be written. + consteval bool tokens_are_present() + { + return std::ranges::all_of( + registry, [](registration const& entry) { return !entry.on_disk_token.empty(); }); + } + + /// Checks that technology IDs, tokens and non-empty configuration names are unique. + consteval bool registry_is_unambiguous() + { + for (std::size_t i = 0; i < registry.size(); ++i) { + for (std::size_t j = i + 1; j < registry.size(); ++j) { + auto const& left = registry[i]; + auto const& right = registry[j]; + if (left.technology == right.technology) { + return false; + } + if (left.on_disk_token == right.on_disk_token) { + return false; + } + if (!left.configuration_name.empty() && + left.configuration_name == right.configuration_name) { + return false; + } + } + } + return true; + } + + static_assert(tokens_are_present(), + "every technology needs an on-disk token: it names the technology in a file"); + static_assert(registry_is_unambiguous(), + "two technologies claim the same id or the same spelling"); + + constexpr registration const* lookup_by_id(id technology) + { + auto const found = std::ranges::find_if(registry, [technology](registration const& entry) { + return entry.technology == technology; + }); + return found == registry.end() ? nullptr : &*found; + } + + constexpr registration const* lookup_by_configuration_name(std::string_view name) + { + if (name.empty()) { + return nullptr; + } + auto const found = std::ranges::find_if( + registry, [name](registration const& entry) { return entry.configuration_name == name; }); + return found == registry.end() ? nullptr : &*found; + } + + // No empty guard is needed, unlike configuration names: every token is non-empty. + constexpr registration const* lookup_by_on_disk_token(std::string_view token) + { + auto const found = std::ranges::find_if( + registry, [token](registration const& entry) { return entry.on_disk_token == token; }); + return found == registry.end() ? nullptr : &*found; + } + + } // namespace detail + + // ---- Configuration names: user-facing spellings, independent of on-disk tokens. ---- + + inline std::string to_string(id tech) { - if (name == "ROOT_TTREE") { - return root_ttree; + auto const* const found = detail::lookup_by_id(tech); + if (found == nullptr || found->configuration_name.empty()) { + return "UNKNOWN"; } - if (name == "ROOT_RNTUPLE") { - return root_rntuple; + return std::string{found->configuration_name}; + } + + /// Parses an exact configuration name; throws if unknown or unavailable. + inline id from_string(std::string_view name) + { + auto const* const found = detail::lookup_by_configuration_name(name); + if (found == nullptr) { + if (detail::lookup_by_on_disk_token(name) != nullptr) { + throw std::runtime_error("Unknown technology: " + std::string{name} + + " (that is an on-disk token, not a configuration name)"); + } + throw std::runtime_error("Unknown technology: " + std::string{name}); } - if (name == "HDF5") { - // HDF5 is a reserved technology but has no backend yet: reject it at parse time - throw std::runtime_error("Technology 'HDF5' is recognized but not yet implemented"); + if (!found->is_implemented) { + throw std::runtime_error("Technology '" + std::string{name} + + "' is recognized but not yet implemented"); } - throw std::runtime_error("Unknown technology: " + std::string(name)); + return found->technology; } - // Canonical technology -> string mapping - inline std::string to_string(id tech) + // ---- On-disk tokens: persistent identifiers written into FORM files. ---- + + constexpr std::string_view on_disk_token(id tech) { - if (tech == root_ttree) { - return "ROOT_TTREE"; + auto const* const found = detail::lookup_by_id(tech); + if (found == nullptr) { + throw std::runtime_error("FORM: an unregistered technology has no on-disk token and " + "cannot be named in a file"); } - if (tech == root_rntuple) { - return "ROOT_RNTUPLE"; + return found->on_disk_token; + } + + constexpr std::optional find_on_disk_token(std::string_view token) + { + auto const* const found = detail::lookup_by_on_disk_token(token); + return found == nullptr ? std::nullopt : std::optional{found->technology}; + } + + /// Parses an exact on-disk token; throws if unknown or unavailable. + constexpr id from_on_disk_token(std::string_view token) + { + auto const* const found = detail::lookup_by_on_disk_token(token); + if (found == nullptr) { + throw std::runtime_error("Unknown FORM on-disk technology token: '" + std::string{token} + + "'"); } - if (tech == hdf5) { - return "HDF5"; + if (!found->is_implemented) { + throw std::runtime_error("FORM technology '" + std::string{token} + + "' is recorded in the file but is not implemented"); } - return "UNKNOWN"; + return found->technology; } } // namespace form::technology diff --git a/form/persistence/navigation_naming.hpp b/form/persistence/navigation_naming.hpp index daeb7919b..6a737f9ad 100644 --- a/form/persistence/navigation_naming.hpp +++ b/form/persistence/navigation_naming.hpp @@ -59,7 +59,7 @@ namespace form::detail::experimental { form::technology::id tech) { std::string name{navigation_prefix}; - name += technology_name(tech); + name += form::technology::on_disk_token(tech); name += "_cells_"; name += hierarchy_key(hierarchy); return name; @@ -69,7 +69,7 @@ namespace form::detail::experimental { inline std::string navigation_dictionary_name(form::technology::id tech) { std::string name{navigation_prefix}; - name += technology_name(tech); + name += form::technology::on_disk_token(tech); name += "_products"; return name; } diff --git a/form/persistence/persistence_writer.cpp b/form/persistence/persistence_writer.cpp index 30b7cb304..29bbbbe25 100644 --- a/form/persistence/persistence_writer.cpp +++ b/form/persistence/persistence_writer.cpp @@ -50,7 +50,7 @@ namespace { for (auto const& tech : actual->second) { if (!planned.contains(tech)) { throw std::runtime_error("persistence_writer: file '" + file_name + "' holds '" + - technology_name(tech) + + std::string{form::technology::on_disk_token(tech)} + "' products that were never planned; its descriptor would be " "inconsistent with its contents"); } @@ -65,7 +65,7 @@ namespace { if (!technology_names.empty()) { technology_names += ','; } - technology_names += technology_name(tech); + technology_names += form::technology::on_disk_token(tech); } return {{std::string{descriptor_layout_version_key}, std::string{form_layout_version}}, diff --git a/test/form/dune_example_navigation_dump.cpp b/test/form/dune_example_navigation_dump.cpp index 3a3d73498..4eb32d7af 100644 --- a/test/form/dune_example_navigation_dump.cpp +++ b/test/form/dune_example_navigation_dump.cpp @@ -174,7 +174,7 @@ int main(int const argc, char* argv[]) return 1; } - auto const prefix = "nav_" + technology_token(tech_string); + auto const prefix = "nav_" + std::string{form::technology::on_disk_token(technology)}; checker checks; column_reader reader{file_name, technology}; diff --git a/test/form/form_basics_test.cpp b/test/form/form_basics_test.cpp index 536f54290..75f1f193a 100644 --- a/test/form/form_basics_test.cpp +++ b/test/form/form_basics_test.cpp @@ -36,11 +36,14 @@ #include "root_storage/root_rntuple_write_container.hpp" #endif #include +#include +#include #include #include #include #include +#include #include #include #include @@ -245,7 +248,7 @@ TEST_CASE("token basics", "[form]") CHECK(t.id() == 42u); } -TEST_CASE("technology::id string conversions", "[form]") +TEST_CASE("technology::id configuration names", "[form]") { using namespace form::technology; @@ -264,6 +267,56 @@ TEST_CASE("technology::id string conversions", "[form]") // An unknown name throws; an unknown id stringifies to the sentinel CHECK_THROWS_AS(from_string("NOT_A_TECH"), std::runtime_error); CHECK(to_string(id{}) == "UNKNOWN"); + + // An on-disk token is not a configuration name. The job configuration takes "ROOT_TTREE"; + // "root_ttree" is what a file carries, and naming the confusion beats "Unknown technology". + CHECK_THROWS_AS(from_string("root_ttree"), std::runtime_error); + CHECK_THROWS_WITH(from_string("root_ttree"), + Catch::Matchers::ContainsSubstring("on-disk token, not a configuration name")); + CHECK_THROWS_AS(from_string("ROOT_Ttree"), std::runtime_error); + CHECK_THROWS_AS(from_string(""), std::runtime_error); + + // Generic: not selectable from a configuration + CHECK_THROWS_AS(from_string("GENERIC"), std::runtime_error); + CHECK_THROWS_AS(from_string("generic"), std::runtime_error); +} + +TEST_CASE("technology::id on-disk tokens", "[form]") +{ + using namespace form::technology; + + static_assert(on_disk_token(root_ttree) == "root_ttree"); + static_assert(on_disk_token(root_rntuple) == "root_rntuple"); + static_assert(on_disk_token(id{}) == "generic"); + // hdf5 has no backend, but fixing its spelling now stops a future implementation from + // picking a different one. Every registered token is pinned here. + static_assert(on_disk_token(hdf5) == "hdf5"); + + CHECK(on_disk_token(root_ttree) == "root_ttree"); + CHECK(on_disk_token(root_rntuple) == "root_rntuple"); + CHECK(on_disk_token(id{}) == "generic"); + + for (auto const tech : {root_ttree, root_rntuple, id{}}) { + CHECK(find_on_disk_token(on_disk_token(tech)) == std::optional{tech}); + CHECK(from_on_disk_token(on_disk_token(tech)) == tech); + } + + static_assert(find_on_disk_token("ROOT_TTREE") == std::nullopt); + CHECK(find_on_disk_token("ROOT_TTREE") == std::nullopt); + CHECK(find_on_disk_token("Root_TTree") == std::nullopt); + CHECK(find_on_disk_token("GENERIC") == std::nullopt); + CHECK_THROWS_AS(from_on_disk_token("ROOT_TTREE"), std::runtime_error); + + CHECK(find_on_disk_token("not_a_token") == std::nullopt); + CHECK(find_on_disk_token("") == std::nullopt); + CHECK_THROWS_AS(from_on_disk_token("not_a_token"), std::runtime_error); + CHECK_THROWS_AS(from_on_disk_token(""), std::runtime_error); + + CHECK(find_on_disk_token("hdf5") == std::optional{hdf5}); + CHECK_THROWS_AS(from_on_disk_token("hdf5"), std::runtime_error); + + CHECK_THROWS_AS(on_disk_token(id{.major = static_cast(99), .minor = 0}), + std::runtime_error); } TEST_CASE("technology::id members and ordering", "[form]") @@ -1024,9 +1077,9 @@ TEST_CASE("hierarchy keys and navigation column names", "[form]") SECTION("a navigation name carries the technology that wrote it") { - CHECK(technology_name(form::technology::root_ttree) == "root_ttree"); - CHECK(technology_name(form::technology::root_rntuple) == "root_rntuple"); - CHECK(technology_name(form::technology::id{}) == "generic"); + CHECK(form::technology::on_disk_token(form::technology::root_ttree) == "root_ttree"); + CHECK(form::technology::on_disk_token(form::technology::root_rntuple) == "root_rntuple"); + CHECK(form::technology::on_disk_token(form::technology::id{}) == "generic"); CHECK(navigation_table_name(cell_hierarchy{{"event"}}, form::technology::root_ttree) == "nav_root_ttree_cells_event"); diff --git a/test/form/form_navigation_test.cpp b/test/form/form_navigation_test.cpp index 4b95d6d9b..0e4d15722 100644 --- a/test/form/form_navigation_test.cpp +++ b/test/form/form_navigation_test.cpp @@ -3,7 +3,6 @@ // // Generic navigation checks are in navigation_check.hpp; this file checks the toy fixture. -#include "core/container_naming.hpp" #include "core/technology.hpp" #include "navigation_check.hpp" #include "test_helpers.hpp" @@ -79,7 +78,7 @@ namespace { } checks.check(track_start->creator == "Toy_Tracker", "trackStart names its creator"); checks.check(track_start->stage == test_stage, "trackStart names its stage"); - auto const tech = form::detail::experimental::technology_name(found.technology); + std::string const tech{form::technology::on_disk_token(found.technology)}; checks.check(track_start->container_name == tech + "__Toy_Tracker__test_stage/trackStart", "trackStart names its product container in its (creator, stage) row space"); checks.check(track_start->hierarchy_key == "event_segment", @@ -110,7 +109,7 @@ int main(int const argc, char* argv[]) return 1; } - auto const prefix = "nav_" + technology_token(tech_string); + auto const prefix = "nav_" + std::string{form::technology::on_disk_token(technology)}; auto const segment_table = prefix + "_cells_event_segment"; auto const event_table = prefix + "_cells_event"; diff --git a/test/form/form_storage_test.cpp b/test/form/form_storage_test.cpp index 5b96a1325..f41421646 100644 --- a/test/form/form_storage_test.cpp +++ b/test/form/form_storage_test.cpp @@ -688,7 +688,12 @@ TEST_CASE("storage_reader read_descriptor: a written file identifies its layout std::string{form_layout_version}); // A single-technology file names just that technology, with the token FORM writes to disk. - CHECK(descriptor_value(descriptor, descriptor_technologies_key) == technology_name(technology)); + auto const technologies = descriptor_value(descriptor, descriptor_technologies_key); + REQUIRE(technologies.has_value()); + CHECK(*technologies == form::technology::on_disk_token(technology)); + + // The on-disk token identifies the technology independently of its configuration name. + CHECK(form::technology::from_on_disk_token(*technologies) == technology); // The file opened for the descriptor is the one the reader goes on to read products from. for (auto const& cell : cells) { @@ -1265,7 +1270,7 @@ TEST_CASE("Stage round-trip: a reader locates a product by creator and stage", " } // Writer and reader use technology__creator__stage for physical row-space names. - auto const tech = technology_name(technology); + std::string const tech{form::technology::on_disk_token(technology)}; storage_reader storage; tech_setting_config const settings{}; auto const index_rows = [&](std::string const& container) { @@ -1285,9 +1290,8 @@ TEST_CASE("Stage round-trip: a reader locates a product by creator and stage", " form::test::check_layout(checks, columns, layout); CHECK(checks.failures() == 0); - auto const table = - layout.table("nav_" + form::test::technology_token(form::technology::to_string(technology)) + - "_cells_event_segment"); + auto const table = layout.table( + "nav_" + std::string{form::technology::on_disk_token(technology)} + "_cells_event_segment"); CHECK(table.transform(&form::test::navigation_table::stream_ids) == std::vector>{ {"shower", "stage1"}, {"tracker", ""}, {"tracker", "stage1"}, {"tracker", "stage2"}}); diff --git a/test/form/navigation_check.hpp b/test/form/navigation_check.hpp index 1a67bfdfa..3c3a8743f 100644 --- a/test/form/navigation_check.hpp +++ b/test/form/navigation_check.hpp @@ -10,7 +10,6 @@ #include "storage/istorage.hpp" #include -#include #include #include #include @@ -96,15 +95,6 @@ namespace form::test { return values; } - inline std::string technology_token(std::string const& tech_string) - { - std::string name = tech_string; - for (char& c : name) { - c = static_cast(std::tolower(static_cast(c))); - } - return name; - } - inline std::string column_of(std::string const& table, std::string const& column) { return build_full_label(table, column); @@ -240,7 +230,7 @@ namespace form::test { struct layout { form::technology::id technology{}; - std::string technology_token; + std::string on_disk_token; std::string dictionary; std::vector products; /// Ordered by table name, so the printout is stable. @@ -441,7 +431,7 @@ namespace form::test { product.product_name), what + " uses its creator-stage row space"); checks.check(product.navigation_container == - "nav_" + found.technology_token + "_cells_" + product.hierarchy_key, + "nav_" + found.on_disk_token + "_cells_" + product.hierarchy_key, what + " names the navigation table of its hierarchy"); checks.check(product.navigation_column == navigation_row_column(product.creator, product.stage), @@ -475,8 +465,8 @@ namespace form::test { { layout found; found.technology = form::technology::from_string(tech_string); - found.technology_token = technology_token(tech_string); - found.dictionary = "nav_" + found.technology_token + "_products"; + found.on_disk_token = form::technology::on_disk_token(found.technology); + found.dictionary = "nav_" + found.on_disk_token + "_products"; auto const names = reader.column(column_of(found.dictionary, "product_name")); if (names.empty()) { @@ -587,7 +577,7 @@ namespace form::test { { std::cout << "\nFORM navigation layout: " << found.tables.size() << " hierarchy table(s)" - << " for technology '" << found.technology_token << "'.\n" + << " for technology '" << found.on_disk_token << "'.\n" << "A table with more than one stream column is a wide table: those (creator, stage)\n" << "streams share a hierarchy, so they share its table.\n"; for (auto const& table : found.tables) { From b48707247703f9e1734a8119103b9acd2a58e333 Mon Sep 17 00:00:00 2001 From: wwuoneway Date: Wed, 4 Nov 2026 17:12:12 -0600 Subject: [PATCH 2/2] addressed clang-tidy issues which were not shown in local check --- form/core/technology.hpp | 32 ++++++++++++++++++++------------ test/form/form_basics_test.cpp | 2 +- test/form/form_storage_test.cpp | 5 +++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/form/core/technology.hpp b/form/core/technology.hpp index 07c80bf6c..329f7c561 100644 --- a/form/core/technology.hpp +++ b/form/core/technology.hpp @@ -91,8 +91,8 @@ namespace form::technology { { for (std::size_t i = 0; i < registry.size(); ++i) { for (std::size_t j = i + 1; j < registry.size(); ++j) { - auto const& left = registry[i]; - auto const& right = registry[j]; + auto const& left = registry.at(i); + auto const& right = registry.at(j); if (left.technology == right.technology) { return false; } @@ -115,10 +115,12 @@ namespace form::technology { constexpr registration const* lookup_by_id(id technology) { - auto const found = std::ranges::find_if(registry, [technology](registration const& entry) { - return entry.technology == technology; - }); - return found == registry.end() ? nullptr : &*found; + for (auto const& entry : registry) { + if (entry.technology == technology) { + return &entry; + } + } + return nullptr; } constexpr registration const* lookup_by_configuration_name(std::string_view name) @@ -126,17 +128,23 @@ namespace form::technology { if (name.empty()) { return nullptr; } - auto const found = std::ranges::find_if( - registry, [name](registration const& entry) { return entry.configuration_name == name; }); - return found == registry.end() ? nullptr : &*found; + for (auto const& entry : registry) { + if (entry.configuration_name == name) { + return &entry; + } + } + return nullptr; } // No empty guard is needed, unlike configuration names: every token is non-empty. constexpr registration const* lookup_by_on_disk_token(std::string_view token) { - auto const found = std::ranges::find_if( - registry, [token](registration const& entry) { return entry.on_disk_token == token; }); - return found == registry.end() ? nullptr : &*found; + for (auto const& entry : registry) { + if (entry.on_disk_token == token) { + return &entry; + } + } + return nullptr; } } // namespace detail diff --git a/test/form/form_basics_test.cpp b/test/form/form_basics_test.cpp index 75f1f193a..c52a1c5f6 100644 --- a/test/form/form_basics_test.cpp +++ b/test/form/form_basics_test.cpp @@ -315,7 +315,7 @@ TEST_CASE("technology::id on-disk tokens", "[form]") CHECK(find_on_disk_token("hdf5") == std::optional{hdf5}); CHECK_THROWS_AS(from_on_disk_token("hdf5"), std::runtime_error); - CHECK_THROWS_AS(on_disk_token(id{.major = static_cast(99), .minor = 0}), + CHECK_THROWS_AS(on_disk_token(id{.major = form::technology::major::root, .minor = 99}), std::runtime_error); } diff --git a/test/form/form_storage_test.cpp b/test/form/form_storage_test.cpp index f41421646..965a6becd 100644 --- a/test/form/form_storage_test.cpp +++ b/test/form/form_storage_test.cpp @@ -690,10 +690,11 @@ TEST_CASE("storage_reader read_descriptor: a written file identifies its layout // A single-technology file names just that technology, with the token FORM writes to disk. auto const technologies = descriptor_value(descriptor, descriptor_technologies_key); REQUIRE(technologies.has_value()); - CHECK(*technologies == form::technology::on_disk_token(technology)); + auto const token = technologies.value_or(std::string{}); + CHECK(token == form::technology::on_disk_token(technology)); // The on-disk token identifies the technology independently of its configuration name. - CHECK(form::technology::from_on_disk_token(*technologies) == technology); + CHECK(form::technology::from_on_disk_token(token) == technology); // The file opened for the descriptor is the one the reader goes on to read products from. for (auto const& cell : cells) {