OpenMS
Loading...
Searching...
No Matches
ClassTest.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: Marc Sturm, Clemens Groepl, Chris Bielow, Timo Sachsenberg $
7// --------------------------------------------------------------------------
8
9#pragma once
10
11// Std-only: no libOpenMS or third-party headers, so tests of every OpenMS library
12// (incl. ones that don't link libOpenMS, like OpenSwathAlgo) can use it and
13// recompiles stay cheap. Utilities are its own (ClassTestUtils.h); OpenMS behavior
14// is registered by the OpenMSTestSupport target (src/tests/class_tests), which
15// every class test of a libOpenMS-linking project links.
18
19#include <fstream>
20#include <iostream>
21#include <string>
22#include <vector>
23#include <type_traits>
24
25// Empty declaration to avoid problems in case the namespace is not
26// yet defined (e.g. TEST/ClassTest_test.cpp)
27
29#ifndef stdcout
30#define stdcout std::cout
31#endif
32
33namespace OpenMS
34{
35 namespace Internal
36 {
38 namespace ClassTest
39 {
40
42 std::string
43 createTmpFileName(const std::string& file, int line, const std::string& extension = "");
44
45 // isRealType() and writtenDigits() -- used by #TEST_REAL_SIMILAR -- are provided
46 // by ClassTestUtils.h. They classify by convertibility to double, so the framework
47 // does not need to know library value types (DataValue, ParamValue, ...) by name.
48
54 void
55 testRealSimilar(const char* file, int line, long double number_1,
56 const char* number_1_stringified,
57 bool number_1_is_realtype, int number_1_written_digits,
58 long double number_2, const char* number_2_stringified,
59 bool /* number_2_is_realtype */, int number_2_written_digits);
60
62 bool
63 isRealSimilar(long double number_1, long double number_2);
64
72 void
73 testStringSimilar(const char* file, int line,
74 const std::string& string_1,
75 const char* string_1_stringified,
76 const std::string& string_2,
77 const char* string_2_stringified);
78
80 void
81 testStringEqual(const char* file, int line,
82 const std::string& string_1,
83 const char* string_1_stringified,
84 const std::string& string_2,
85 const char* string_2_stringified);
86
92 bool
93 isFileSimilar(const std::string& filename_1,
94 const std::string& filename_2);
95
97 void
99
101 void
102 printWithPrefix(const std::string& text, const int marked = -1);
103
113 void mainInit(const char* version, const char* class_name, int argc, const char* argv0);
114
124 void filesEqual(int line, const char* filename, const char* templatename, const char* filename_stringified, const char* templatename_stringified);
125
128
130 void
131 setWhitelist(const char* const /* file */, const int line,
132 const std::string& whitelist);
133
135 extern double ratio_max_allowed;
136
138 extern double ratio_max;
139
141 extern double ratio;
142
144 extern double absdiff_max_allowed;
145
147 extern double absdiff_max;
148
150 extern double absdiff;
151
152 extern int line_num_1_max;
153 extern int line_num_2_max;
154
156 extern int verbose;
157
159 extern bool all_tests;
160
162 extern bool test;
163
165 extern bool this_test;
166
168 extern int exception;
169
171 extern std::string exception_name;
172
174 extern std::string exception_message;
175
177 extern std::string test_name;
178
180 extern int start_section_line;
181
183 extern int test_line;
184
186 extern const char* version_string;
187
189 extern std::vector<std::string> tmp_file_list;
190
192 extern std::vector<unsigned int> failed_lines_list;
193
195 extern std::ifstream infile;
196
198 extern std::ifstream templatefile;
199
201 extern bool equal_files;
202
204 extern char line_buffer[65536];
205
207 extern int test_count;
208
210 extern std::string add_message;
211
216 extern std::string fuzzy_message;
217
219 extern bool newline;
220
221 template <typename T1, typename T2>
222 void
223 testEqual(const char* /*file*/, int line, const T1& expression_1,
224 const char* expression_1_stringified,
225 const T2& expression_2,
226 const char* expression_2_stringified)
227 {
228 ++test_count;
229 test_line = line;
230 // For std::string targets, stringify the comparison value rather than
231 // constructing std::string(expression_2): the old OpenMS::String had
232 // numeric/char converting constructors (e.g. String(114) == "114") that
233 // std::string lacks. detail::toString reproduces that behavior (and must
234 // format numbers exactly like StringUtils::toStr, see ClassTestUtils.h).
235 if constexpr (std::is_same_v<T1, std::string>)
236 {
237 this_test = bool(expression_1 == detail::toString(expression_2));
238 }
239 else
240 {
241 this_test = bool(expression_1 == T1(expression_2));
242 }
243 test &= this_test;
244 {
246 if (!this_test || verbose > 1)
247 {
248 stdcout << ' ' << (this_test ? '+' : '-') << " line " << line << " : TEST_EQUAL(" << expression_1_stringified << ','
249 << expression_2_stringified << "): got '";
250
251 // we can't print wide chars directly using operator<< so we need to test for it
252 if constexpr (std::is_same_v<std::remove_cv_t<T1>, char16_t*> || std::is_same_v<std::remove_cv_t<T2>, char16_t*>)
253 {
254 stdcout << (expression_1 == nullptr ? "(null)" : "(char16_t*)") << "', expected '"
255 << (expression_2 == nullptr ? "(null)" : "(char16_t*)") << "'\n";
256 }
257 else if constexpr (std::is_enum_v<T1> && std::is_enum_v<T2>)
258 {
259 stdcout << static_cast<int>(expression_1) << "', expected '" << static_cast<int>(expression_2) << "'\n";
260 }
261 else
262 {
263 detail::printValue(stdcout, expression_1);
264 stdcout << "', expected '";
265 detail::printValue(stdcout, expression_2);
266 stdcout << "'\n";
267 }
268 }
269 if (!this_test)
270 {
271 failed_lines_list.push_back(line);
272 }
273 }
274 }
275
276 inline void testTrue(const char* /*file*/, int line, const bool expression_1, const char* expression_1_stringified)
277 {
278 ++test_count;
279 test_line = line;
280 this_test = expression_1;
281 test &= this_test;
282 {
284 if (this_test)
285 {
286 if (verbose > 1)
287 {
288 stdcout << " + line " << line << ": TEST_TRUE(" << expression_1_stringified << "): ok\n";
289 }
290 }
291 else
292 {
293 stdcout << " - line " << line << ": TEST_TRUE(" << expression_1_stringified << "): failed\n";
294 failed_lines_list.push_back(line);
295 }
296 }
297 }
298
299 inline void testFalse(const char* /*file*/, int line, const bool expression_1, const char* expression_1_stringified)
300 {
301 ++test_count;
302 test_line = line;
303 this_test = !expression_1;
304 test &= this_test;
305 {
307 if (this_test)
308 {
309 if (verbose > 1)
310 {
311 stdcout << " + line " << line << ": TEST_FALSE(" << expression_1_stringified << "): ok\n";
312 }
313 }
314 else
315 {
316 stdcout << " - line " << line << ": TEST_FALSE(" << expression_1_stringified << "): failed\n";
317 failed_lines_list.push_back(line);
318 }
319 }
320 }
321
322 template <typename T1, typename T2>
323 void
324 testNotEqual(const char* /*file*/, int line, const T1& expression_1,
325 const char* expression_1_stringified,
326 const T2& expression_2,
327 const char* expression_2_stringified)
328 {
329 ++test_count;
330 test_line = line;
331 this_test = !(expression_1 == T1(expression_2));
332 test &= this_test;
333 {
335 if (!this_test || verbose > 1)
336 {
337 stdcout << ' ' << (this_test ? '+' : '-') << " line " << line << " : TEST_NOT_EQUAL(" << expression_1_stringified << ','
338 << expression_2_stringified << "): got '";
339 if constexpr (std::is_enum_v<T1> && std::is_enum_v<T2>)
340 {
341 stdcout << static_cast<int>(expression_1) << "', forbidden is '" << static_cast<int>(expression_2) << "'\n";
342 }
343 else
344 {
345 detail::printValue(stdcout, expression_1);
346 stdcout << "', forbidden is '";
347 detail::printValue(stdcout, expression_2);
348 stdcout << "'\n";
349 }
350 }
351 if (!this_test)
352 {
353 failed_lines_list.push_back(line);
354 }
355 }
356 }
357
358
373 using ExceptionTranslator = bool (*)(std::string& description);
374
377
383
385 void setPreconditionTestsEnabled(bool enabled);
386
390
391 void printLastException(std::ostream& out);
392
393 int endTestPostProcess(std::ostream& out);
394
395 void endSectionPostProcess(std::ostream& out, const int line);
396 }
397 }
398}
399
400// A namespace alias - apparently these cannot be documented using doxygen (?)
402
486#define START_TEST(class_name, version) \
487 int main(int argc, char** argv) \
488 { \
489 TEST::mainInit(version, #class_name, argc, argv[0]); \
490 try {
491
503#define END_TEST \
504 /* global try block */ \
505 } \
506 catch (...) \
507 { \
508 TEST::printLastException(stdcout); \
509 } \
510 return TEST::endTestPostProcess(stdcout); \
511 }
512
534#define START_SECTION(name_of_test) \
535 TEST::test = true; \
536 TEST::newline = false; \
537 TEST::test_name = # name_of_test; \
538 TEST::test_count = 0; \
539 TEST::start_section_line = __LINE__; \
540 stdcout << "checking " << TEST::test_name << " ... " << std::flush; \
541 try \
542 { \
543 while (true) \
544 {
545
571#define END_SECTION \
572 break; \
573 } \
574 } \
575 catch (...) \
576 { \
577 TEST::printLastException(stdcout);\
578 } \
579 TEST::endSectionPostProcess(stdcout, __LINE__);
580
581
601#define TEST_EQUAL(a, b) TEST::testEqual(__FILE__, __LINE__, (a), (# a), (b), (# b));
602
612#define TEST_TRUE(a) TEST::testTrue(__FILE__, __LINE__, (a), (#a));
613
623#define TEST_FALSE(a) TEST::testFalse(__FILE__, __LINE__, (a), (#a));
624
625
637#define TEST_NOT_EQUAL(a, b) TEST::testNotEqual(__FILE__, __LINE__, (a), (# a), (b), (# b));
638
651#define TEST_STRING_EQUAL(a, b) TEST::testStringEqual(__FILE__, __LINE__, (a), (# a), (b), (# b));
652
667#define TEST_FILE_EQUAL(filename, templatename) \
668 { \
669 TEST::filesEqual(__LINE__, filename, templatename, #filename, #templatename); \
670 }
671
687#define TEST_REAL_SIMILAR(a, b) TEST::testRealSimilar(__FILE__, __LINE__, (a), (# a), TEST::isRealType(a), TEST::writtenDigits(a), (b), (# b), TEST::isRealType(b), TEST::writtenDigits(b));
688
704#define TEST_STRING_SIMILAR(a, b) TEST::testStringSimilar(__FILE__, __LINE__, (a), (# a), (b), (# b));
705
720#define TEST_FILE_SIMILAR(a, b) \
721 { \
722 ++TEST::test_count; \
723 TEST::test_line = __LINE__; \
724 TEST::this_test = TEST::isFileSimilar((a), (b)); \
725 TEST::test = TEST::test && TEST::this_test; \
726 { \
727 TEST::initialNewline(); \
728 if (TEST::this_test) \
729 { \
730 if (TEST::verbose > 1) \
731 { \
732 stdcout << " + line " << __LINE__ \
733 << ": TEST_FILE_SIMILAR(" # a "," # b "): absolute: " \
734 << TEST::precisionWrapper(TEST::absdiff) \
735 << " (" \
736 << TEST::precisionWrapper(TEST::absdiff_max_allowed) \
737 << "), relative: " \
738 << TEST::precisionWrapper(TEST::ratio) \
739 << " (" \
740 << TEST::precisionWrapper(TEST::ratio_max_allowed) \
741 << ")\n"; \
742 stdcout << "message: \n"; \
743 stdcout << TEST::fuzzy_message; \
744 } \
745 } \
746 else \
747 { \
748 stdcout << " - line " << TEST::test_line << \
749 ": TEST_FILE_SIMILAR(" # a "," # b ") ... -\n"; \
750 stdcout << "message: \n"; \
751 stdcout << TEST::fuzzy_message; \
752 TEST::failed_lines_list.push_back(TEST::test_line); \
753 } \
754 } \
755 }
756
769#define TOLERANCE_RELATIVE(a) \
770 TEST::ratio_max_allowed = (a); \
771 { \
772 TEST::initialNewline(); \
773 if (TEST::verbose > 1) \
774 { \
775 stdcout << " + line " << __LINE__ << \
776 ": TOLERANCE_RELATIVE(" << TEST::ratio_max_allowed << \
777 ") (\"" # a "\")\n"; \
778 } \
779 }
780
792#define TOLERANCE_ABSOLUTE(a) \
793 TEST::absdiff_max_allowed = (a); \
794 { \
795 TEST::initialNewline(); \
796 if (TEST::verbose > 1) \
797 { \
798 stdcout << " + line " << __LINE__ << \
799 ": TOLERANCE_ABSOLUTE(" << TEST::absdiff_max_allowed << \
800 ") (\"" # a "\")\n"; \
801 } \
802 }
803
809#define WHITELIST(a) TEST::setWhitelist(__FILE__, __LINE__, (a));
810
823#define TEST_EXCEPTION(exception_type, command) \
824 { \
825 ++TEST::test_count; \
826 TEST::test_line = __LINE__; \
827 TEST::exception = 0; \
828 try \
829 { \
830 command; \
831 } \
832 catch (exception_type&) \
833 { \
834 TEST::exception = 1; \
835 } \
836 catch (...) \
837 { \
838 /* the framework knows only std::exception; registered translators (see */ \
839 /* OpenMSTestSupport.cpp) describe library exception types by name */ \
840 TEST::exception = 2; \
841 TEST::exception_name = TEST::describeCaughtException(); \
842 } \
843 TEST::this_test = (TEST::exception == 1); \
844 TEST::test = TEST::test && TEST::this_test; \
845 { \
846 TEST::initialNewline(); \
847 switch (TEST::exception) \
848 { \
849 case 0: \
850 stdcout << " - line " << TEST::test_line << \
851 ": TEST_EXCEPTION(" # exception_type "," # command \
852 "): no exception thrown!\n"; \
853 TEST::failed_lines_list.push_back(TEST::test_line); \
854 break; \
855 case 1: \
856 if (TEST::verbose > 1) \
857 { \
858 stdcout << " + line " << TEST::test_line << \
859 ": TEST_EXCEPTION(" # exception_type "," # command \
860 "): OK\n"; \
861 } \
862 break; \
863 case 2: \
864 stdcout << " - line " << TEST::test_line << \
865 ": TEST_EXCEPTION(" # exception_type "," # command \
866 "): wrong exception thrown! \"" \
867 << TEST::exception_name << "\"\n"; \
868 TEST::failed_lines_list.push_back(TEST::test_line); \
869 break; \
870 } \
871 } \
872 }
873
887#define TEST_PRECONDITION_VIOLATED(command) \
888 { \
889 if (TEST::preconditionTestsEnabled()) \
890 { \
891 TEST_EXCEPTION(Exception::Precondition, command); \
892 } \
893 else \
894 { \
895 STATUS("TEST_PRECONDITION_VIOLATED(" # command ") - skipped"); \
896 } \
897 }
898
910#define TEST_POSTCONDITION_VIOLATED(command) \
911 { \
912 if (TEST::preconditionTestsEnabled()) \
913 { \
914 TEST_EXCEPTION(Exception::Postcondition, command); \
915 } \
916 else \
917 { \
918 STATUS("TEST_POSTCONDITION_VIOLATED(" # command ") - skipped"); \
919 } \
920 }
921
922
939#define TEST_EXCEPTION_WITH_MESSAGE(exception_type, command, message) \
940 { \
941 ++TEST::test_count; \
942 TEST::test_line = __LINE__; \
943 TEST::exception = 0; \
944 try \
945 { \
946 command; \
947 } \
948 catch (exception_type& et) \
949 { \
950 if (std::string(et.what()) != std::string(message)) \
951 { \
952 TEST::exception = 4; \
953 TEST::exception_message = et.what(); \
954 } \
955 else TEST::exception = 1; \
956 } \
957 catch (...) \
958 { \
959 TEST::exception = 2; \
960 TEST::exception_name = TEST::describeCaughtException(); \
961 } \
962 TEST::this_test = (TEST::exception == 1); \
963 TEST::test = TEST::test && TEST::this_test; \
964 \
965 { \
966 TEST::initialNewline(); \
967 switch (TEST::exception) \
968 { \
969 case 0: \
970 stdcout << " - line " << TEST::test_line << \
971 ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \
972 "): no exception thrown!\n"; \
973 TEST::failed_lines_list.push_back(TEST::test_line); \
974 break; \
975 case 1: \
976 if (TEST::verbose > 1) \
977 { \
978 /* this is actually what we want to get: */ \
979 stdcout << " + line " << TEST::test_line << \
980 ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \
981 "): OK\n"; \
982 } \
983 break; \
984 case 2: \
985 stdcout << " - line " << TEST::test_line << \
986 ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \
987 "): wrong exception thrown! \"" << \
988 TEST::exception_name << "\"\n"; \
989 TEST::failed_lines_list.push_back(TEST::test_line); \
990 break; \
991 case 4: \
992 stdcout << " - line " << TEST::test_line << \
993 ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \
994 "): exception has wrong message: got '" << \
995 TEST::exception_message << \
996 "', expected '" << \
997 (message) << "'\n"; \
998 TEST::failed_lines_list.push_back(TEST::test_line); \
999 break; \
1000 } \
1001 } \
1002 }
1003
1017#define NEW_TMP_FILE_EXT(filename, extension) filename = TEST::createTmpFileName(__FILE__, __LINE__, extension);
1018
1019
1020#define NEW_TMP_FILE(filename) filename = TEST::createTmpFileName(__FILE__, __LINE__);
1021
1022
1030#define ABORT_IF(condition) \
1031 if (condition) \
1032 { \
1033 { \
1034 TEST::test_line = __LINE__; \
1035 TEST::this_test = false; \
1036 TEST::test = TEST::test && TEST::this_test; \
1037 TEST::failed_lines_list.push_back(TEST::test_line); \
1038 TEST::initialNewline(); \
1039 stdcout << " - line " << TEST::test_line << \
1040 ": ABORT_IF(" # condition "): TEST ABORTED\n"; \
1041 } \
1042 break; \
1043 }
1044
1062#define STATUS(message) \
1063 { \
1064 TEST::initialNewline(); \
1065 stdcout << " line " \
1066 << __LINE__ \
1067 << ": status: " \
1068 << message \
1069 << "\n"; \
1070 }
1071
1081#define ADD_MESSAGE(message) \
1082 TEST::add_message = message;
1083
1093#define NOT_TESTABLE \
1094 TEST::test_count = 1;
1095
// end of defgroup ClassTest
#define stdcout
Provide a point of redirection for testing the test macros, see ClassTest_test.cpp.
Definition ClassTest.h:30
std::string toString(const std::string &s)
Definition ClassTestUtils.h:222
void printValue(std::ostream &os, const T &v)
Definition ClassTestUtils.h:268
Namespace for class tests.
Definition ClassTest.h:39
double ratio_max
Maximum ratio of numbers observed so far, see TOLERANCE_RELATIVE.
int verbose
Verbosity level ( "-v" is 1 and "-V" is 2 )
void setPreconditionTestsEnabled(bool enabled)
See preconditionTestsEnabled().
void testEqual(const char *, int line, const T1 &expression_1, const char *expression_1_stringified, const T2 &expression_2, const char *expression_2_stringified)
Definition ClassTest.h:223
int test_count
Counter for the number of elementary tests within the current subsection.
void testStringSimilar(const char *file, int line, const std::string &string_1, const char *string_1_stringified, const std::string &string_2, const char *string_2_stringified)
Compare strings using absdiff_max_allowed and ratio_max_allowed.
void testFalse(const char *, int line, const bool expression_1, const char *expression_1_stringified)
Definition ClassTest.h:299
bool isRealSimilar(long double number_1, long double number_2)
used by testRealSimilar()
bool(*)(std::string &description) ExceptionTranslator
Translator producing a description of the currently handled exception.
Definition ClassTest.h:373
int test_line
Line of current elementary test.
double absdiff
Recent absolute difference of numbers, see TOLERANCE_ABSOLUTE.
char line_buffer[65536]
(A buffer for one line from a file. Used by TEST_FILE_EQUAL.)
std::string add_message
See ADD_MESSAGE.
void endSectionPostProcess(std::ostream &out, const int line)
std::string fuzzy_message
Last message from a fuzzy comparison. Written by isRealSimilar(), testStringSimilar(),...
void initialNewline()
make sure we have a newline before results from first subtest
bool all_tests
Status of the whole test.
std::string exception_message
(Used by various macros. Stores the "message" of the exception, if applicable.)
std::string test_name
Name of current subsection.
void registerExceptionTranslator(ExceptionTranslator translator)
Register an exception translator (idempotent; bounded, first-come first-served).
void testRealSimilar(const char *file, int line, long double number_1, const char *number_1_stringified, bool number_1_is_realtype, int number_1_written_digits, long double number_2, const char *number_2_stringified, bool, int number_2_written_digits)
Compare floating point numbers using absdiff_max_allowed and ratio_max_allowed.
void testNotEqual(const char *, int line, const T1 &expression_1, const char *expression_1_stringified, const T2 &expression_2, const char *expression_2_stringified)
Definition ClassTest.h:324
double absdiff_max
Maximum difference of numbers observed so far, see TOLERANCE_ABSOLUTE.
bool newline
(Flags whether a new line is in place, depending on context and verbosity setting....
void filesEqual(int line, const char *filename, const char *templatename, const char *filename_stringified, const char *templatename_stringified)
Test if two files are exactly equal (used in TEST_FILE_EQUAL macro)
bool test
Status of the current subsection.
double absdiff_max_allowed
Maximum absolute difference of numbers allowed, see TOLERANCE_ABSOLUTE.
std::string describeCaughtException()
void testStringEqual(const char *file, int line, const std::string &string_1, const char *string_1_stringified, const std::string &string_2, const char *string_2_stringified)
used by TEST_STRING_EQUAL
const char * version_string
Version string supplied with START_TEST.
std::ifstream infile
Questionable file tested by TEST_FILE_EQUAL.
int endTestPostProcess(std::ostream &out)
std::string exception_name
(Used by various macros. Stores the "name" of the exception, if applicable.)
void mainInit(const char *version, const char *class_name, int argc, const char *argv0)
Set up some classtest variables as obtained from the 'START_TEST' macro and check that no additional ...
double ratio
Recent ratio of numbers, see TOLERANCE_RELATIVE.
void removeTempFiles()
removed all temporary files created with the NEW_TMP_FILE macro
std::vector< std::string > tmp_file_list
List of tmp file names (these will be cleaned up, see NEW_TMP_FILE)
bool isFileSimilar(const std::string &filename_1, const std::string &filename_2)
Compare files using absdiff_max_allowed and ratio_max_allowed.
bool this_test
Status of last elementary test.
void printLastException(std::ostream &out)
std::string createTmpFileName(const std::string &file, int line, const std::string &extension="")
Creates a temporary file name from the test name and the line with the specified extension.
std::ifstream templatefile
Template (correct) file used by TEST_FILE_EQUAL.
void testTrue(const char *, int line, const bool expression_1, const char *expression_1_stringified)
Definition ClassTest.h:276
double ratio_max_allowed
Maximum ratio of numbers allowed, see TOLERANCE_RELATIVE.
void printWithPrefix(const std::string &text, const int marked=-1)
print the text, each line gets a prefix, the marked line number gets a special prefix
bool equal_files
(A variable used by TEST_FILE_EQUAL)
std::vector< unsigned int > failed_lines_list
List of all failed lines for summary at the end of the test.
int start_section_line
Line where current subsection started.
int exception
(Used by various macros. Indicates a rough category of the exception being caught....
void setWhitelist(const char *const, const int line, const std::string &whitelist)
set the whitelist_
Main OpenMS namespace.
Definition openswathalgo/include/OpenMS/OPENSWATHALGO/DATAACCESS/ISpectrumAccess.h:19