From cbee2162a6312189958c05dcc0f326abe51267dc Mon Sep 17 00:00:00 2001 From: mmd-osm Date: Wed, 5 Mar 2025 19:13:03 +0000 Subject: [PATCH] Simplified unit test code with c++20 --- include/cgimap/output_formatter.hpp | 21 +++--- test/test_core.cpp | 48 ++++++------- test/test_database.cpp | 10 +-- test/test_database.hpp | 8 ++- test/test_formatter.cpp | 101 ++++++---------------------- 5 files changed, 63 insertions(+), 125 deletions(-) diff --git a/include/cgimap/output_formatter.hpp b/include/cgimap/output_formatter.hpp index d7116272..59fbfa87 100644 --- a/include/cgimap/output_formatter.hpp +++ b/include/cgimap/output_formatter.hpp @@ -70,6 +70,8 @@ struct element_info { bool visible_, std::optional redaction_ = {}); + bool operator==(const element_info &other) const = default; + // Standard meanings osm_nwr_id_t id = 0; osm_nwr_id_t version = 0; @@ -98,6 +100,8 @@ struct changeset_info { size_t num_changes_, size_t comments_count_); + bool operator==(const changeset_info &other) const = default; + // returns true if the changeset is "open" at a particular // point in time. // @@ -111,7 +115,8 @@ struct changeset_info { // closed explicitly with a closing time, or close implicitly // an hour after the last update to the changeset. closed_at // should have an ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ - std::string created_at, closed_at; + std::string created_at; + std::string closed_at; // anonymous objects don't have UIDs or display names std::optional uid; std::optional display_name; @@ -141,13 +146,7 @@ struct changeset_comment_info { std::string created_at, std::string author_display_name); - constexpr bool operator==(const changeset_comment_info &other) const { - return ((id == other.id) && - (author_id == other.author_id) && - (body == other.body) && - (created_at == other.created_at) && - (author_display_name == other.author_display_name)); - } + bool operator==(const changeset_comment_info &other) const = default; }; struct member_info { @@ -158,11 +157,7 @@ struct member_info { member_info() = default; member_info(element_type type_, osm_nwr_id_t ref_, std::string role_); - constexpr bool operator==(const member_info &other) const { - return ((type == other.type) && - (ref == other.ref) && - (role == other.role)); - } + bool operator==(const member_info &other) const = default; }; using nodes_t = std::vector; diff --git a/test/test_core.cpp b/test/test_core.cpp index 87832706..85e799af 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -11,6 +11,7 @@ #include "cgimap/output_buffer.hpp" #include "cgimap/request_helpers.hpp" #include "cgimap/time.hpp" +#include "cgimap/util.hpp" #include "staticxml.hpp" #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #include "test_request.hpp" @@ -43,11 +45,12 @@ std::map read_headers(std::istream &in, // allow comments in lines which begin immediately with #. this shouldn't // conflict with any headers, as although http headers technically can start // with #, i'm pretty sure we're not using any which do. - if ((line.size() > 0) && (line[0] == '#')) { + if (line.starts_with('#')) { continue; } - al::erase_all(line, "\r"); + std::erase(line, '\r'); + if (!in.good()) { throw std::runtime_error("Test file ends before separator."); } @@ -55,20 +58,19 @@ std::map read_headers(std::istream &in, break; } - boost::iterator_range result = - al::find_first(line, ":"); - if (!result) { - throw std::runtime_error( - "Test file header doesn't match expected format."); - } - - std::string key(line.begin(), result.begin()); - std::string val(result.end(), line.end()); + // Split HTTP header "Request-Method: GET" into "Request-Method" and value "GET" + std::string_view line_view(line); - al::trim(key); - al::trim(val); + auto pos = line_view.find(':'); - headers.insert(std::make_pair(key, val)); + if (pos != std::string_view::npos) { + auto key = std::string(trim(line_view.substr(0, pos))); + auto value = std::string(trim(line_view.substr(pos + 1))); + headers.try_emplace(key, value); + } + else { + throw std::runtime_error("Test file header doesn't match expected format."); + } } return headers; @@ -78,20 +80,18 @@ std::map read_headers(std::istream &in, * take the test file and use it to set up the request headers. */ void setup_request_headers(test_request &req, std::istream &in) { - using dict = std::map; - dict headers = read_headers(in, "---"); + auto headers = read_headers(in, "---"); - for (const dict::value_type &val : headers) { - std::string key(val.first); - - al::to_upper(key); - al::replace_all(key, "-", "_"); + for (auto const & [k, v] : headers) { + auto key = k; + std::ranges::transform(key, key.begin(), [](unsigned char c) { + return (c == '-' ? '_' : std::toupper(c)); + }); if (key == "DATE") { - req.set_current_time(parse_time(val.second)); - + req.set_current_time(parse_time(v)); } else { - req.set_header(key, val.second); + req.set_header(key, v); } } diff --git a/test/test_database.cpp b/test/test_database.cpp index 88241b93..4c7b8a08 100644 --- a/test/test_database.cpp +++ b/test/test_database.cpp @@ -155,8 +155,8 @@ void test_database::testcase_ended() { txn_owner_readwrite.reset(); } -void test_database::run( - const std::function &func) { +template +void test_database::run(Func func) { try { // clear out database before using it! @@ -170,8 +170,8 @@ void test_database::run( testcase_ended(); } -void test_database::run_update( - const std::function &func) { +template +void test_database::run_update(Func func) { try { // clear out database before using it! @@ -184,7 +184,7 @@ void test_database::run_update( testcase_ended(); } -test_database::setup_error::setup_error(std::string str) +test_database::setup_error::setup_error(const std::string& str) : m_str(str) { } diff --git a/test/test_database.hpp b/test/test_database.hpp index 1f9da6dc..351b88ae 100644 --- a/test/test_database.hpp +++ b/test/test_database.hpp @@ -33,7 +33,7 @@ struct test_database { // allow the test to be skipped, as people might not have or want an // apidb database set up on their local machines. struct setup_error : public std::exception { - explicit setup_error(std::string fmt); + explicit setup_error(const std::string& fmt); ~setup_error() noexcept override = default; const char *what() const noexcept override; @@ -59,11 +59,13 @@ struct test_database { // writeable and readonly data selection available from the // test_database's get_data_selection() call. the func should // do its own testing - the run method here is just plumbing. - void run(const std::function &func); + template + void run(Func func); // run a database update test in write mode. test will be // executed exactly once only. - void run_update(const std::function &func); + template + void run_update(Func func); // return a data selection factory pointing at the current database [[nodiscard]] std::shared_ptr get_data_selection_factory() const; diff --git a/test/test_formatter.cpp b/test/test_formatter.cpp index 39bc2749..8d34cfe5 100644 --- a/test/test_formatter.cpp +++ b/test/test_formatter.cpp @@ -14,40 +14,18 @@ #include #include #include +#include namespace { bool equal_tags(const tags_t &a, const tags_t &b) { - using vec_tags_t = std::vector >; if (a.size() != b.size()) { return false; } - vec_tags_t sorted_a(a.begin(), a.end()); - vec_tags_t sorted_b(b.begin(), b.end()); - std::sort(sorted_a.begin(), sorted_a.end()); - std::sort(sorted_b.begin(), sorted_b.end()); - return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin()); -} - -bool equal_nodes(const nodes_t &a, const nodes_t &b) { - if (a.size() != b.size()) { return false; } - std::vector a_vec(a.begin(), a.end()); - std::vector b_vec(b.begin(), b.end()); - return std::equal(a_vec.begin(), a_vec.end(), b_vec.begin()); -} - -bool equal_members(const members_t &a, const members_t &b) { - if (a.size() != b.size()) { return false; } - std::vector a_vec(a.begin(), a.end()); - std::vector b_vec(b.begin(), b.end()); - const size_t n = a_vec.size(); - for (size_t i = 0; i < n; ++i) { - if ((a_vec[i].type != b_vec[i].type) - || (a_vec[i].ref != b_vec[i].ref) - || (a_vec[i].role != b_vec[i].role)) { - return false; - } - } - return true; + auto sorted_a = a; + auto sorted_b = b; + std::ranges::sort(sorted_a); + std::ranges::sort(sorted_b); + return (sorted_a == sorted_b); } } // anonymous namespace @@ -57,18 +35,10 @@ test_formatter::node_t::node_t(const element_info &elem_, double lon_, double la : elem(elem_), lon(lon_), lat(lat_), tags(tags_) {} bool test_formatter::node_t::operator==(const node_t &other) const { -#define CMP(sym) { if ((sym) != other. sym) { return false; } } - CMP(elem.id) - CMP(elem.version) - CMP(elem.changeset) - CMP(elem.timestamp) - CMP(elem.uid) - CMP(elem.display_name) - CMP(elem.visible) - CMP(lon) - CMP(lat) -#undef CMP - return equal_tags(tags, other.tags); + return elem == other.elem && + lon == other.lon && + lat == other.lat && + equal_tags(tags, other.tags); } test_formatter::way_t::way_t(const element_info &elem_, const nodes_t &nodes_, @@ -76,16 +46,9 @@ test_formatter::way_t::way_t(const element_info &elem_, const nodes_t &nodes_, : elem(elem_), nodes(nodes_), tags(tags_) {} bool test_formatter::way_t::operator==(const way_t &other) const { -#define CMP(sym) { if ((sym) != other. sym) { return false; } } - CMP(elem.id) - CMP(elem.version) - CMP(elem.changeset) - CMP(elem.timestamp) - CMP(elem.uid) - CMP(elem.display_name) - CMP(elem.visible) -#undef CMP - return equal_tags(tags, other.tags) && equal_nodes(nodes, other.nodes); + return elem == other.elem && + nodes == other.nodes && + equal_tags(tags, other.tags); } test_formatter::relation_t::relation_t(const element_info &elem_, @@ -94,16 +57,9 @@ test_formatter::relation_t::relation_t(const element_info &elem_, : elem(elem_), members(members_), tags(tags_) {} bool test_formatter::relation_t::operator==(const relation_t &other) const { -#define CMP(sym) { if ((sym) != other. sym) { return false; } } - CMP(elem.id) - CMP(elem.version) - CMP(elem.changeset) - CMP(elem.timestamp) - CMP(elem.uid) - CMP(elem.display_name) - CMP(elem.visible) -#undef CMP - return equal_tags(tags, other.tags) && equal_members(members, other.members); + return elem == other.elem && + members == other.members && + equal_tags(tags, other.tags); } test_formatter::changeset_t::changeset_t(const changeset_info &info, @@ -119,26 +75,11 @@ test_formatter::changeset_t::changeset_t(const changeset_info &info, } bool test_formatter::changeset_t::operator==(const changeset_t &other) const { -#define CMP(sym) { if (!(sym == other.sym)) { return false; } } - CMP(m_info.id) - CMP(m_info.created_at) - CMP(m_info.closed_at) - CMP(m_info.uid) - CMP(m_info.display_name) - CMP(m_info.bounding_box) - CMP(m_info.num_changes) - CMP(m_info.comments_count) - CMP(m_tags.size()) - CMP(m_include_comments) - if (m_include_comments) { - CMP(m_comments.size()) - if (!std::equal(m_comments.begin(), m_comments.end(), other.m_comments.begin())) { - return false; - } - } - CMP(m_time) -#undef CMP - return std::equal(m_tags.begin(), m_tags.end(), other.m_tags.begin()); + return equal_tags(other.m_tags, m_tags) && + m_include_comments == other.m_include_comments && + m_info == other.m_info && + m_time == other.m_time && + (!m_include_comments || m_comments == other.m_comments); } mime::type test_formatter::mime_type() const {