OpenMS
Loading...
Searching...
No Matches
ClassTestUtils.h
Go to the documentation of this file.
1// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin
2// SPDX-License-Identifier: BSD-3-Clause
3//
4// --------------------------------------------------------------------------
5// $Maintainer: Timo Sachsenberg $
6// $Authors: Timo Sachsenberg $
7// --------------------------------------------------------------------------
8
9#pragma once
10
11// Standard-library-only utilities owned by the class-test framework (no libOpenMS
12// dependency). Two mirror libOpenMS behavior and must not drift:
13// * detail::appendNumeric <-> StringUtils' appendNumeric (StringUtils.cpp): so
14// TEST_EQUAL(std::string, <number>) formats exactly like StringUtils::toStr().
15// * writtenDigits <-> OpenMS::writtenDigits (OpenMS/CONCEPT/Types.h).
16
17#include <charconv>
18#include <cmath>
19#include <cstring>
20#include <filesystem>
21#include <limits>
22#include <ostream>
23#include <ranges>
24#include <string>
25#include <string_view>
26#include <type_traits>
27#include <vector>
28
29namespace OpenMS
30{
31 namespace Internal
32 {
33 namespace ClassTest
34 {
43 template <typename T>
44 constexpr int writtenDigits(const T& = T())
45 {
46 using C = std::remove_cvref_t<T>;
47 if constexpr (std::is_floating_point_v<C>)
48 {
49 return std::numeric_limits<C>::digits10;
50 }
51 else if constexpr (std::is_integral_v<C>)
52 {
53 return std::numeric_limits<C>::digits10;
54 }
55 else if constexpr (std::is_class_v<C> && std::is_convertible_v<const C&, double>)
56 {
57 return std::numeric_limits<double>::digits10;
58 }
59 else
60 {
61 return 6; // default precision of an ostream (27.4.4.1), like OpenMS::writtenDigits
62 }
63 }
64
71 template <typename T>
72 constexpr bool isRealType(const T&)
73 {
74 using C = std::remove_cvref_t<T>;
75 return std::is_floating_point_v<C> || (std::is_class_v<C> && std::is_convertible_v<const C&, double>);
76 }
77
78 namespace detail
79 {
81 inline void trim(std::string& s)
82 {
83 const char* ws = " \t\n\r";
84 const size_t b = s.find_first_not_of(ws);
85 if (b == std::string::npos)
86 {
87 s.clear();
88 return;
89 }
90 const size_t e = s.find_last_not_of(ws);
91 s = s.substr(b, e - b + 1);
92 }
93
97 inline std::vector<std::string> split(const std::string& s, char splitter)
98 {
99 std::vector<std::string> out;
100 if (s.empty()) return out;
101 size_t begin = 0;
102 for (size_t i = 0; i < s.size(); ++i)
103 {
104 if (s[i] == splitter)
105 {
106 out.emplace_back(s, begin, i - begin);
107 begin = i + 1;
108 }
109 }
110 out.emplace_back(s, begin, s.size() - begin);
111 return out;
112 }
113
115 inline std::string join(const std::vector<std::string>& v, const char* sep)
116 {
117 std::string out;
118 for (size_t i = 0; i < v.size(); ++i)
119 {
120 if (i) out += sep;
121 out += v[i];
122 }
123 return out;
124 }
125
130 template <typename T>
131 inline void appendNumeric(T value, std::string& target, int precision, bool fixed_format)
132 {
133 if (std::isnan(value)) { target += "NaN"; return; }
134 if (std::isinf(value)) { target += (value < T(0)) ? "-inf" : "inf"; return; }
135 char buf[64];
136 std::to_chars_result fc;
137
138 // Determine format: use scientific for extreme values or when fixed_format is requested
139 // but the value is too small/large for fixed notation
140 T abs_val = (value < 0) ? -value : value;
141 bool use_scientific = fixed_format
142 ? (abs_val != T(0) && (abs_val >= T(1e4) || abs_val < T(1e-2)))
143 : (abs_val != T(0) && (abs_val >= T(1e4) || abs_val < T(1e-2)));
144
145 if (use_scientific)
146 {
147 if (fixed_format)
148 {
149 fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::scientific, 3);
150 }
151 else
152 {
153 // Shortest round-trip representation (no explicit precision): the unique shortest
154 // decimal that round-trips back to the same double, hence platform-independent.
155 fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::scientific);
156 }
157 }
158 else if (fixed_format)
159 {
160 fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::fixed, precision);
161 }
162 else
163 {
164 fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::fixed, precision);
165 }
166
167 if (fc.ec == std::errc{})
168 {
169 const char* end = fc.ptr;
170
171 // For scientific notation: post-process "1.234e+04" into the historical "1.234e04"
172 const char* e_pos = reinterpret_cast<const char*>(std::memchr(buf, 'e', end - buf));
173 if (e_pos)
174 {
175 const char* e_orig = e_pos; // save before mantissa trimming
176
177 // Trim trailing zeros from mantissa (before 'e')
178 const char* dot = reinterpret_cast<const char*>(std::memchr(buf, '.', e_orig - buf));
179 if (dot)
180 {
181 while (e_pos > dot + 1 && *(e_pos - 1) == '0') --e_pos;
182 if (e_pos == dot + 1) e_pos = dot + 2; // keep at least one digit after dot
183 }
184 target.append(buf, static_cast<size_t>(e_pos - buf));
185 if (!dot) target += ".0";
186 target += 'e';
187
188 // Fix exponent format: "+04" -> "04" (remove '+', keep '-' and zero-padding).
189 const char* exp_start = e_orig + 1; // skip 'e'
190 if (exp_start < end && *exp_start == '+')
191 ++exp_start; // skip '+'
192 size_t exp_len = static_cast<size_t>(end - exp_start);
193 target.append(exp_start, exp_len);
194 return;
195 }
196
197 // For fixed notation: trim trailing zeros after decimal point
198 const char* dot = reinterpret_cast<const char*>(std::memchr(buf, '.', end - buf));
199 if (dot)
200 {
201 while (end > dot + 1 && *(end - 1) == '0') --end;
202 if (end == dot + 1) end = dot + 2; // keep at least one digit after dot
203 }
204 else if constexpr (std::is_floating_point_v<T>)
205 {
206 target.append(buf, static_cast<size_t>(end - buf));
207 target += ".0";
208 return;
209 }
210 target.append(buf, static_cast<size_t>(end - buf));
211 }
212 else
213 {
214 target += std::to_string(static_cast<double>(value));
215 }
216 }
217
221
222 inline std::string toString(const std::string& s) { return s; }
223 inline std::string toString(std::string_view sv) { return std::string(sv); }
224 inline std::string toString(const char* s) { return std::string(s); }
225 inline std::string toString(char c) { return std::string(1, c); }
226 template <typename T>
227 inline std::string toString(const T& v)
228 {
229 static_assert(std::is_arithmetic_v<T>,
230 "TEST_EQUAL(std::string, x): x must be a string, character or number. "
231 "For other types, stringify explicitly in the test.");
232 std::string r;
233 if constexpr (std::is_same_v<T, bool>)
234 {
235 r = v ? "1" : "0";
236 }
237 else if constexpr (std::is_floating_point_v<T>)
238 {
239 appendNumeric(v, r, std::numeric_limits<T>::digits10, false); // full precision, like toStr(v, true)
240 }
241 else
242 {
243 char buf[32];
244 auto [p, ec] = std::to_chars(buf, buf + sizeof(buf), v);
245 if (ec == std::errc{}) r.append(buf, p);
246 }
247 return r;
248 }
250
253 template <typename T>
254 concept Streamable = requires(std::ostream& os, const T& t) { os << t; };
255
258 template <typename T>
259 void printValue(std::ostream& os, const T& v)
260 {
261 using C = std::remove_cvref_t<T>;
262 if constexpr (Streamable<C>)
263 {
264 os << v;
265 }
266 else if constexpr (std::ranges::input_range<C>)
267 {
268 os << '[';
269 bool first = true;
270 for (const auto& e : v)
271 {
272 if (!first) os << ", ";
273 first = false;
274 using E = std::remove_cvref_t<decltype(e)>;
275 if constexpr (std::is_arithmetic_v<E>)
276 {
277 os << toString(e); // numbers like ListUtilsIO (via toStr)
278 }
279 else if constexpr (Streamable<E>)
280 {
281 os << e;
282 }
283 else
284 {
285 os << "<?>";
286 }
287 }
288 os << ']';
289 }
290 else
291 {
292 os << "<value of unprintable type - provide operator<< or compare via TEST_TRUE>";
293 }
294 }
295
299 inline std::filesystem::path to_path(const std::string& s)
300 {
301#ifdef _WIN32
302 try
303 {
304 return std::filesystem::path(std::u8string(reinterpret_cast<const char8_t*>(s.data()), s.size()));
305 }
306 catch (const std::system_error&)
307 {
308 return std::filesystem::path(s);
309 }
310#else
311 return std::filesystem::path(std::u8string(reinterpret_cast<const char8_t*>(s.data()), s.size()));
312#endif
313 }
314 } // namespace detail
315
323 template <typename FloatingPointType>
325 {
326 FloatingPointType value;
327 };
328
329 template <typename FloatingPointType>
330 inline PrecisionWrapped<FloatingPointType> precisionWrapper(const FloatingPointType value)
331 {
333 }
334
335 template <typename FloatingPointType>
336 inline std::ostream& operator<<(std::ostream& os, const PrecisionWrapped<FloatingPointType>& p)
337 {
338 os << detail::toString(p.value);
339 return os;
340 }
341 } // namespace ClassTest
342 } // namespace Internal
343} // namespace OpenMS
OpenMS looks in the< code > OPENMS_THERMO_MANAGED_DIR</code > environment variable first
Definition common-cmake-parameters.doxygen:179
@ C
c-ion (N-terminal, ETD)
std::string join(const std::vector< std::string > &v, const char *sep)
Join with a separator (display purposes only).
Definition ClassTestUtils.h:115
std::string toString(const std::string &s)
Definition ClassTestUtils.h:222
std::filesystem::path to_path(const std::string &s)
Definition ClassTestUtils.h:299
void trim(std::string &s)
Remove leading and trailing whitespace (' ', '\t', '\n', '\r'), like StringUtils::trim.
Definition ClassTestUtils.h:81
void appendNumeric(T value, std::string &target, int precision, bool fixed_format)
Definition ClassTestUtils.h:131
std::vector< std::string > split(const std::string &s, char splitter)
Definition ClassTestUtils.h:97
void printValue(std::ostream &os, const T &v)
Definition ClassTestUtils.h:259
constexpr bool isRealType(const T &)
Whether TEST_REAL_SIMILAR may convert a value of this type to double.
Definition ClassTestUtils.h:72
constexpr int writtenDigits(const T &=T())
Decimal digits to print for a value in TEST_REAL_SIMILAR reports.
Definition ClassTestUtils.h:44
FloatingPointType value
Definition ClassTestUtils.h:326
PrecisionWrapped< FloatingPointType > precisionWrapper(const FloatingPointType value)
Definition ClassTestUtils.h:330
std::ostream & operator<<(std::ostream &os, const PrecisionWrapped< FloatingPointType > &p)
Definition ClassTestUtils.h:336
Stream wrapper printing a floating point value with full precision.
Definition ClassTestUtils.h:325
Main OpenMS namespace.
Definition openswathalgo/include/OpenMS/OPENSWATHALGO/DATAACCESS/ISpectrumAccess.h:19