├── .gitignore ├── .travis.yml ├── LICENSE_1_0.txt ├── README.md ├── examples ├── example1.cpp ├── example2.cpp ├── example3.cpp ├── example3a.cpp ├── example4-file1.cpp ├── example4-file2.cpp └── example5.cpp ├── include └── clue.hpp └── tests ├── Makefile ├── lest_cpp03.hpp ├── test_clue.cpp └── test_clue_part2.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | script: cd tests && make test_clue 5 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clue – Collect and learn from uncovering evidence 2 | 3 | A tiny header-only C++ logging framework. 4 | 5 | If your logging requirements are modest or you like to begin _light_, then `clue` may be for you. 6 | 7 | `clue` is a C++03 header-only library to log messages with a severity and optional module identifier. Provided logging destinations are the console, a text file, the Windows debugger, the Windows event log, an in-memory log (string) and Unix syslog. You can also define your own logging destination. `clue` is based on an idea by Mark Nelson, presented in DrDobbs [1] and on ideas found in the CATCH test framework by Phil Nash [2]. 8 | 9 | **Contents** 10 | 11 | - [Example usage](#example-usage) 12 | - [Compile and run](#compile-and-run) 13 | - [Synopsis](#synopsis) 14 | - [Dependencies](#dependencies) 15 | - [Reported to work with](#reported-to-work-with) 16 | - [Other logging libraries](#other-logging-libraries) 17 | - [Notes and References](#notes-and-references) 18 | 19 | 20 | 21 | ## Example usage 22 | 23 | ```Cpp 24 | #include "clue.hpp" 25 | int main() 26 | { 27 | LOG_EMERGENCY( "design by contract violation: " << "irrecoverable, terminating..." ); 28 | LOG_ALERT ( "practically-unrecoverable condition: " << "need more memory; trying hard, likely failing..." ); 29 | LOG_CRITICAL ( "normative behaviour cannot be achieved: " << "severe error" ); 30 | LOG_ERROR ( "normative behaviour cannot be achieved: " << "error" ); 31 | LOG_WARNING ( "you should be aware of: " << "e.g. disk 90% full, please free some" ); 32 | LOG_NOTICE ( "operating normal: " << "database connection achieved" ); 33 | LOG_INFO ( "monitoring health of the system: " << "tracking more actions, not significantly degrading performance" ); 34 | LOG_DEBUG ( "tracking detailed information: " << "speed: " << 3.14 << " m/s" ); 35 | } 36 | ``` 37 | 38 | The terminology in above messages is taken from the article by Matthew Wilson [Choosing severity levels](http://blog.pantheios.org/2010/10/choosing-severity-levels.html) on the blog of the Pantheios logging framework [3]. A description of these terms can be found in [4]. 39 | 40 | ## Compile and run 41 | 42 | ```Text 43 | prompt> g++ -Wall -o example1.exe example1.cpp && example1 44 | 2014-03-14T23:01:33 Emergency: design by contract violation: irrecoverable, terminating... 45 | 2014-03-14T23:01:33 Alert: practically-unrecoverable condition: need more memory; trying hard, likely failing... 46 | 2014-03-14T23:01:33 Critical: normative behaviour cannot be achieved: severe error 47 | 2014-03-14T23:01:33 Error: normative behaviour cannot be achieved: error 48 | 2014-03-14T23:01:33 Warning: you should be aware of: e.g. disk 90% full, please free some 49 | 2014-03-14T23:01:33 Notice: operating normal: database connection achieved 50 | 2014-03-14T23:01:33 Info: monitoring health of the system: tracking more actions, not significantly degrading performance 51 | 2014-03-14T23:01:33 Debug: tracking detailed information: speed: 3.14 m/s 52 | ``` 53 | 54 | ## Synopsis 55 | 56 | ### Unique macro names 57 | 58 | -Dclue_NO_SHORT_NAMES 59 | All macros of `clue` start with `clue_` to hopefully make them unique. Note however, that at default also macros without the leading `clue_` are available. Define `clue_NO_SHORT_NAMES` to omit the short variants. 60 | 61 | ### Logging macros 62 | 63 | `clue` defines the following logging macros (see note below). 64 | **LOG_EMERGENCY(** _expr_ **)** 65 | **LOG_ALERT (** _expr_ **)** 66 | **LOG_CRITICAL (** _expr_ **)** 67 | **LOG_ERROR (** _expr_ **)** 68 | **LOG_WARNING (** _expr_ **)** 69 | **LOG_NOTICE (** _expr_ **)** 70 | **LOG_INFO (** _expr_ **)** 71 | **LOG_DEBUG (** _expr_ **)** 72 | 73 | Use these like `LOG_NOTICE( "Gear switched to: " << gear );` 74 | 75 | Note: if `clue_LOG_TO_SYSLOG` or `LOG_TO_SYSLOG` is defined, the above short variants are not available as these would clash with the syslog severities. 76 | 77 | ### Log severities 78 | 79 | `clue` defines the following severities. 80 | **LOG_SEV_NONE** 81 | **LOG_SEV_EMERGENCY** 82 | **LOG_SEV_ALERT** 83 | **LOG_SEV_CRITICAL** 84 | **LOG_SEV_ERROR** 85 | **LOG_SEV_WARNING** 86 | **LOG_SEV_NOTICE** 87 | **LOG_SEV_INFO** 88 | **LOG_SEV_DEBUG** 89 | **LOG_SEV_MAX** 90 | 91 | See also section _Logging level_ below. 92 | 93 | ### Loggging level 94 | 95 | -DLOG_LEVEL=LOG_SEV_NOTICE 96 | Note: work in progress. Define the logging statements that are active via `LOG_LEVEL` before inclusion of `clue.hpp`. The idea is to make this runtime configurable. 97 | 98 | -DLOG_LEVEL_BUILD=LOG_SEV_DEBUG 99 | Define which logging statements will be included in the code via `LOG_LEVEL_BUILD` before inclusion of `clue.hpp`. If you do not define `LOG_LEVEL_BUILD`, all logging statements are included. See also section Other macros, `clue_OMIT_UNUSED_LOG_EXPRESSIONS` 100 | 101 | ### Module name 102 | 103 | -DLOG_MODULE_NAME=*name* 104 | Log messages may include a _module_ or _feature_ identifier right after the severity. You add such an identifer by defining `LOG_MODULE_NAME` before inclusion of `clue.hpp`. For example, a .cpp file may contain: 105 | 106 | ```Cpp 107 | #define LOG_MODULE_NAME "Gearbox" 108 | #include "clue.hpp" 109 | 110 | int main() 111 | { 112 | const int gear = 5; 113 | LOG_NOTICE( "Gear switched to: " << gear ); 114 | } 115 | ``` 116 | 117 | Compile and run: 118 | 119 | ```Text 120 | prompt>g++ -Wall -o example2.exe example2.cpp && example2 121 | 2014-03-15T17:29:11 Notice: Gearbox: Gear switched to: 5 122 | ``` 123 | 124 | ### Logging destination control 125 | 126 | -DLOG\_TO\_... 127 | To select a specific logging destination, define one of the following before inclusion of `clue.hpp`. 128 | 129 | -DLOG_TO_CONSOLE 130 | Log to `std::clog`. 131 | 132 | -DLOG_TO_FILE=\\"path/to/logfile.txt\\" 133 | Log to a text files pecified by the given path. 134 | 135 | -DLOG_TO_STRING 136 | 137 | Log to memory. This makes function `strlog & the_log()` available in namespace clue. Type `strlog` provides: 138 | - void **clear()** - reset the severity to clue_LOG_SEV_NONE and clear text (as after construction), 139 | - int **severity()** - the latest logged severity, 140 | - std::string **text()** - the logged text since the latest clear(). 141 | 142 | Note: it's not safe to use `the_log()` from different threads. 143 | See also: [Define your own string logging object](#own_string_log_object). 144 | 145 | -DLOG_TO_DEBUGGER 146 | On Windows, log via `OutputDebugString()`. On Unix: TBD. 147 | 148 | -DLOG_TO_EVENTLOG 149 | Windows only. Log via `ReportEvent()`. See source code in `to_eventlog_severity()` for mapping from clue (syslog) severity to event log severity. Note: you must link to `Advapi32.lib`. 150 | 151 | -DLOG_TO_SYSLOG 152 | NTS:To be verified (Unix/Windows). 153 | See also syslog(3) [5]. 154 | 155 | If none of these is defined and you didn't define your own back-end (see below), `clue` will select `console` for non-GUI builds, `debugger` for Windows GUI build (`_WINDOWS` is defined), and `syslog` on Unix (TBD). 156 | 157 | ### Define your own back-end or logging destination 158 | 159 | **clue_LOG_EXPRESSION(** *severity*, *expr* **)** 160 | `clue` allows to specify a back-end to log to a destination of your choice. You do this by defining `clue_LOG_EXPRESSION` before inclusion of `clue.hpp`. For example: 161 | 162 | ```Cpp 163 | #define clue_LOG_EXPRESSION( severity, expr ) \ 164 | std::cout << clue::to_severity_text(severity) << ": " << expr 165 | 166 | #include "clue.hpp" 167 | 168 | int main() 169 | { 170 | clue_LOG_NOTICE( "Hello" << " world" ); 171 | } 172 | ``` 173 | 174 | Compile and run: 175 | 176 | ```Text 177 | prompt> g++ -Wall -o example3.exe example3.cpp && example3 178 | Notice: Hello world 179 | ``` 180 | 181 | 182 | ### Define your own string logging object 183 | 184 | **clue_LOG_STRING_EXPRESSION(** *log*, *severity*, *expr* **)** 185 | `clue` allows to specify your own string logging object. You do this by defining `clue_LOG_EXPRESSION` in terms of `clue_LOG_STRING_EXPRESSION` before inclusion of `clue.hpp`. For example: 186 | 187 | ```Cpp 188 | #define clue_LOG_TO_STRING 189 | #define clue_LOG_EXPRESSION( severity, expr ) \ 190 | clue_LOG_STRING_EXPRESSION( my_log, severity, expr ) 191 | 192 | #include "clue.hpp" 193 | #include 194 | 195 | int main() 196 | { 197 | clue::strlog my_log; 198 | 199 | clue_LOG_NOTICE( "Hello" << " world" ); 200 | 201 | std::cout << "my_log.text(): " << my_log.text() << "\n"; 202 | } 203 | ``` 204 | 205 | Compile and run: 206 | 207 | ```Text 208 | prompt> g++ -Wall -Wextra -Weffc++ -I.. -o example3a.exe example3a.cpp && example3a 209 | my_log.text(): Hello world 210 | ``` 211 | 212 | ### Other Macros 213 | 214 | -Dclue_NO_TIMESTAMP 215 | Define this to omit the timestamp from the logged messages. 216 | 217 | -Dclue_OMIT_UNUSED_LOG_EXPRESSIONS 218 | Define this to omit the logging expressions with a severity lower than `LOG_LEVEL_BUILD`. The idea is that code that is inactivated by the chosen build log level normally is included in the code and compiled, but removed in the optimisation phase. For compilers that are not capable to do this, you may define `LOG_OMIT_UNUSED_LOG_EXPRESSIONS` to simplify these expressions to the equivalent of `do {} while(false)`. 219 | 220 | **LOG_LOGGED_SEVERITIES()** 221 | Call this macro to issue a log message with severity LOG_SEV_NONE that enumerates the severities that are included in the build. For example as "[clue]: Emergency, Alert, Critical, Error, Warning, Notice, Info, Debug." (time omitted). 222 | 223 | **LOG_EXPRESSION(** *severity*, *expr* **)** 224 | You can use it like `LOG_EXPRESSION( LOG_NOTICE, "Hello" << " world" )`. 225 | 226 | ### Namespace 227 | 228 | namespace **clue** { } 229 | Types and functions are located in namespace clue. 230 | 231 | ### Utility functions 232 | 233 | `clue` uses several functions that may also be useful to you. 234 | 235 | #### General 236 | 237 | std::string **text_or(** std::string const & *text*, std::string const & *or_text* **)**; 238 | Return *text* if non-empty, otherwise return *or_text*. 239 | 240 | std::string **text_with_or(** std::string const & *prefix*, std::string const & *text*, std::string const & *postfix*, std::string const & *or_text* **)**; 241 | Return *text* enclosed in *prefix* and *postfix* if *text* is non-empty, otherwise return *or_text*. 242 | 243 | std::string **now_text()**; 244 | Return date and time as "*yyyy-mm-dd*‍T‍*hh:mm:ss*" [6], or empty string if `clue_NO_TIMESTAMP` is defined. 245 | 246 | std::string **to_module_text(** std::string const & *module* **)**; 247 | Return ": _module_" or empty string if module itself is empty. 248 | 249 | std::string **to_severity_text(** int const *severity* **)**; 250 | Return a string such as "Emergency", "Notice", etc. for the given severity. 251 | 252 | std::string **to_severities_text(** int const *level*, std::string const & *postfix* = "." **)**; 253 | Return a string enumerating the logged severities. For example `to_severities_text( LOG_SEV_CRITICAL );` yields "Emergency, Alert, Critical.". 254 | 255 | #### When LOG_EVENTLOG is defined 256 | 257 | int **to_eventlog_severity(** int *severity* **)** 258 | Return the eventlog severity for the given `clue` severity. Note that this isn't a one-to-one mapping. 259 | 260 | #### When LOG_SYSLOG is defined 261 | 262 | int **to_syslog_severity(** int *severity* **)**; 263 | Return the syslog severity for the given `clue` severity. 264 | 265 | ## Dependencies 266 | 267 | `clue` depends on the C++ standard library. For logging to the Windows eventlog it requires access to OutputDebugString(). For logging to the Unix system logger it requires openlog(), syslog() and closelog() declared in \. 268 | 269 | ## Reported to work with 270 | 271 | `clue` should work with any C++03 compiler. It has been reported to work with the following compilers: 272 | - g++ 4.6.2, g++ 4.6.3, g++ 4.7.2, g++ 4.8.1 273 | - Visual C++ 6 (Visual Studio 6), VC10 (VS2010), VC11 (VS2012) 274 | 275 | 279 | ## Other logging libraries 280 | 281 | - [spdlog](https://github.com/gabime/spdlog) - Super fast C++ logging library. 282 | - [mlog](https://github.com/zschoche/mlog) - Comfortable lightweight C++ logging library -- cross-platform, C++11. 283 | - [Boost.Log](http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/) - C++ logging library -- simplicity, extensibility, performance. 284 | - [Pantheios](http://www.pantheios.org/) - Pantheios is a C/C++ Diagnostic Logging API library, offering type-safety, efficiency, genericity and extensibility. 285 | 286 | ## Notes and References 287 | 288 | [1] Mark Nelson [Blundering into the One Definition Rule](http://www.drdobbs.com/cpp/blundering-into-the-one-definition-rule/240166489). DrDobbs. 11 March 2014. 289 | 290 | [2] Phil Nash. [CATCH, an automated test framework for C, C++ and Objective-C](http://builds.catch-lib.net/). 291 | 292 | [3] Matthew Wilson. Pantheios Tips 'n' Tricks. [Choosing severity levels](http://blog.pantheios.org/2010/10/choosing-severity-levels.html). 30 October 2010. 293 | 294 | [4] Matthew Wilson. [Quality Matters: The Worst Form of 'Error' Handling Except For All The Others](http://accu.org/index.php/journals/1681), section *A new vocabulary*. Overload, 18(98):28-32, August 2010. 295 | 296 | [5] [syslog(3)](http://man7.org/linux/man-pages/man3/syslog.3.html) 297 | 298 | [6] [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) format, but without timezone. 299 | 300 | [![Build Status](https://travis-ci.org/martinmoene/clue.png?branch=master)](https://travis-ci.org/martinmoene/clue) 301 | -------------------------------------------------------------------------------- /examples/example1.cpp: -------------------------------------------------------------------------------- 1 | // example1.cpp - report logged severities, issue log messages. 2 | 3 | // -DLOG_TO_CONSOLE 4 | // -DLOG_TO_DEBUGGER 5 | // -DLOG_TO_EVENTLOG 6 | // -DLOG_TO_SYSLOG 7 | // -DLOG_LEVEL=LOG_SEV_NOTICE 8 | // -DLOG_LEVEL_BUILD=LOG_SEV_INFO 9 | 10 | //#define LOG_MODULE_NAME "Module" 11 | 12 | #include "clue.hpp" 13 | 14 | int main() 15 | { 16 | // -DLOG_TO_SYSLOG requires use of clue_ prefix 17 | 18 | clue_LOG_LOGGED_SEVERITIES(); 19 | 20 | clue_LOG_EXPRESSION( clue_LOG_SEV_NONE, "The log levels currently turned on are..." ); 21 | clue_LOG_EMERGENCY ( "design by contract violation: " << "irrecoverable, terminating..." ); 22 | clue_LOG_ALERT ( "practically-unrecoverable condition: " << "need more memory; trying hard, likely failing..." ); 23 | clue_LOG_CRITICAL ( "normative behaviour cannot be achieved: " << "severe error" ); 24 | clue_LOG_ERROR ( "normative behaviour cannot be achieved: " << "error" ); 25 | clue_LOG_WARNING ( "you should be aware of: " << "e.g. disk 90% full, please free some" ); 26 | clue_LOG_NOTICE ( "operating normal: " << "database connection achieved" ); 27 | clue_LOG_INFO ( "monitoring health of the system: " << "tracking more actions, not significantly degrading performance" ); 28 | clue_LOG_DEBUG ( "tracking detailed information: " << "speed: " << 3.14 << " m/s" ); 29 | 30 | clue_LOG_EXPRESSION( clue_LOG_SEV_NONE, "see Pantheios: The C++ Logging Library SweetSpot, http://www.pantheios.org/" ); 31 | } 32 | 33 | // g++ -Wall -Wextra -Weffc++ -I.. -o example1.exe example1.cpp && example1 34 | 35 | // cl -nologo -W4 -EHsc -I.. example1.cpp && example1 36 | // cl -nologo -W4 -EHsc -I.. -D_WINDOWS example1.cpp && example1 37 | // cl -nologo -W4 -EHsc -I.. -DLOG_TO_EVENTLOG example1.cpp Advapi32.lib && example1 38 | 39 | // cl -nologo -W4 -EHsc -I.. -DLOG_LEVEL_BUILD=3 example1.cpp && example1 40 | // cl -nologo -W4 -EHsc -I.. -DLOG_LEVEL_BUILD=LOG_SEV_NOTICE example1.cpp && example1 41 | // cl -nologo -W4 -EHsc -I.. -DLOG_LEVEL_BUILD=LOG_SEV_NOTICE -DLOG_TO_DEBUGGER example1.cpp && example1 42 | -------------------------------------------------------------------------------- /examples/example2.cpp: -------------------------------------------------------------------------------- 1 | // example2.cpp - Log to console, include module name 2 | 3 | #define LOG_MODULE_NAME "Gearbox" 4 | #include "clue.hpp" 5 | 6 | int main() 7 | { 8 | const int gear = 5; 9 | clue_LOG_NOTICE( "Gear switched to: " << gear ); 10 | } 11 | 12 | // cl -nologo -W4 -EHsc -I.. example2.cpp && example2 13 | // g++ -Wall -Wextra -Weffc++ -I.. -o example2.exe example2.cpp && example2 14 | // g++ -Wall -Wextra -Weffc++ -I.. -DLOG_TO_SYSLOG -o example2.exe example2.cpp && example2 15 | -------------------------------------------------------------------------------- /examples/example3.cpp: -------------------------------------------------------------------------------- 1 | // example3.cpp - Log to console, report level. 2 | 3 | #define clue_LOG_EXPRESSION( severity, expr ) \ 4 | std::cout << clue::to_severity_text(severity) << ": " << expr 5 | 6 | #include "clue.hpp" 7 | #include 8 | 9 | int main() 10 | { 11 | clue_LOG_NOTICE( "Hello" << " world" ); 12 | } 13 | 14 | // cl -nologo -W4 -EHsc -I.. example3.cpp && example3 15 | // g++ -Wall -Wextra -Weffc++ -I.. -o example3.exe example3.cpp && example3 16 | -------------------------------------------------------------------------------- /examples/example3a.cpp: -------------------------------------------------------------------------------- 1 | // example3a.cpp 2 | 3 | #define clue_LOG_TO_STRING 4 | #define clue_LOG_EXPRESSION( severity, expr ) \ 5 | clue_LOG_STRING_EXPRESSION( my_log, severity, expr ) 6 | 7 | #include "clue.hpp" 8 | #include 9 | 10 | int main() 11 | { 12 | clue::strlog my_log; 13 | 14 | clue_LOG_NOTICE( "Hello" << " world" ); 15 | 16 | std::cout << "my_log.text(): " << my_log.text() << "\n"; 17 | } 18 | 19 | // cl -nologo -W4 -EHsc -I.. example3a.cpp && example3a 20 | // g++ -Wall -Wextra -Weffc++ -I.. -o example3a.exe example3a.cpp && example3a 21 | -------------------------------------------------------------------------------- /examples/example4-file1.cpp: -------------------------------------------------------------------------------- 1 | // example4-file1.cpp 2 | 3 | #define LOG_MODULE_NAME "Gearbox" 4 | #include "clue.hpp" 5 | 6 | extern void engine(); 7 | 8 | int main() 9 | { 10 | engine(); 11 | 12 | const int gear = 5; 13 | clue_LOG_NOTICE( "Gear switched to: " << gear ); 14 | } 15 | 16 | // cl -nologo -W4 -EHsc -I.. example4-file1.cpp example4-file2.cpp && example4-file1 17 | // g++ -Wall -Wextra -Weffc++ -I.. -o example4-file1.exe example4-file1.cpp example4-file2.cpp && example4-file1 18 | -------------------------------------------------------------------------------- /examples/example4-file2.cpp: -------------------------------------------------------------------------------- 1 | // example4-fle2.cpp 2 | 3 | #define LOG_MODULE_NAME "Engine" 4 | #include "clue.hpp" 5 | 6 | void engine() 7 | { 8 | const int rpm = 3000; 9 | clue_LOG_NOTICE( "Rpm: " << rpm ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/example5.cpp: -------------------------------------------------------------------------------- 1 | // example5.cpp - Log to file, include module name. 2 | 3 | #define LOG_MODULE_NAME "Messenger" 4 | #include "clue.hpp" 5 | #include 6 | 7 | int main() 8 | { 9 | clue_LOG_NOTICE( "Hello" << " world" ); 10 | clue_LOG_NOTICE( "End of example 5" ); 11 | } 12 | 13 | // cl -nologo -W4 -EHsc -I.. -Dclue_LOG_TO_FILE=\"clue-log.txt\" example5.cpp && example5 14 | // g++ -Wall -Wextra -Weffc++ -I.. -Dclue_LOG_TO_FILE=\"clue-log.txt\" -o example5.exe example5.cpp && example5 15 | // clang-cl -Wall -Wextra -Weffc++ -I.. -Dclue_LOG_TO_FILE=\"clue-log.txt\" -o example5.exe example5.cpp && example5 16 | -------------------------------------------------------------------------------- /include/clue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2021 by Martin Moene 2 | // 3 | // clue is based on ideas by Mark Nelson, see article at 4 | // http://www.drdobbs.com/cpp/blundering-into-the-one-definition-rule/240166489 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef CLUE_CLUE_H_INCLUDED 10 | #define CLUE_CLUE_H_INCLUDED 11 | 12 | #define clue_MAJOR 1 13 | #define clue_MINOR 0 14 | #define clue_PATCH 0 15 | 16 | #define clue_VERSION clue_STRINGIFY(clue_MAJOR) "." clue_STRINGIFY(clue_MINOR) "." clue_STRINGIFY(clue_PATCH) 17 | 18 | #define clue_STRINGIFY( x ) clue_STRINGIFY_( x ) 19 | #define clue_STRINGIFY_( x ) #x 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #if defined( _MSC_VER ) 26 | # define clue_COMPILER_IS_MSVC 27 | # if ( _MSC_VER >= 1200 ) && ( _MSC_VER < 1300 ) 28 | # define clue_COMPILER_IS_MSVC6 29 | # endif 30 | #endif 31 | 32 | #ifdef clue_COMPILER_IS_MSVC 33 | # pragma warning( push ) 34 | # pragma warning( disable : 4996 ) // _CRT_SECURE_NO_WARNINGS 35 | #endif 36 | 37 | #ifndef clue_NO_SHORT_NAMES 38 | 39 | # ifdef LOG_MODULE_NAME 40 | # define clue_LOG_MODULE_NAME LOG_MODULE_NAME 41 | # endif 42 | 43 | # ifdef LOG_LEVEL 44 | # define clue_LOG_LEVEL LOG_LEVEL 45 | # endif 46 | 47 | # ifdef LOG_LEVEL_BUILD 48 | # define clue_LOG_LEVEL_BUILD LOG_LEVEL_BUILD 49 | # endif 50 | 51 | # ifdef LOG_TO_CONSOLE 52 | # define clue_LOG_TO_CONSOLE LOG_TO_CONSOLE 53 | # endif 54 | 55 | # ifdef LOG_TO_FILE 56 | # define clue_LOG_TO_FILE LOG_TO_FILE 57 | # endif 58 | 59 | # ifdef LOG_TO_DEBUGGER 60 | # define clue_LOG_TO_DEBUGGER LOG_TO_DEBUGGER 61 | # endif 62 | 63 | # ifdef LOG_TO_DEBUGGER_WINDOWS 64 | # define clue_LOG_TO_DEBUGGER_WINDOWS LOG_TO_DEBUGGER_WINDOWS 65 | # endif 66 | 67 | # ifdef LOG_TO_EVENTLOG 68 | # define clue_LOG_TO_EVENTLOG LOG_TO_EVENTLOG 69 | # endif 70 | 71 | # ifdef LOG_TO_STRING 72 | # define clue_LOG_TO_STRING LOG_TO_STRING 73 | # endif 74 | 75 | # ifdef LOG_TO_SYSLOG 76 | # define clue_LOG_TO_SYSLOG LOG_TO_SYSLOG 77 | # endif 78 | 79 | # ifdef LOG_EXPRESSION 80 | # define clue_LOG_EXPRESSION LOG_EXPRESSIONLOG_TO_SYSLOG 81 | # endif 82 | 83 | #endif // clue_NO_SHORT_NAMES 84 | 85 | // now we can determine if we must guess a destination: 86 | 87 | #ifndef clue_OMIT_UNUSED_LOG_EXPRESSION 88 | # define clue_OMIT_UNUSED_LOG_EXPRESSION 0 89 | #endif 90 | 91 | 92 | #if !defined( clue_LOG_TO_CONSOLE ) && \ 93 | !defined( clue_LOG_TO_FILE ) && \ 94 | !defined( clue_LOG_TO_STRING ) && \ 95 | !defined( clue_LOG_TO_DEBUGGER ) && \ 96 | !defined( clue_LOG_TO_EVENTLOG ) && \ 97 | !defined( clue_LOG_TO_SYSLOG ) 98 | # if defined( _WINDOWS ) 99 | # define clue_LOG_TO_DEBUGGER 100 | #elif defined( NTS_TO_BE_DETERMINED_UNIX ) 101 | # define clue_LOG_TO_SYSLOG 102 | # else 103 | # define clue_LOG_TO_CONSOLE 104 | # endif 105 | #endif 106 | 107 | #if defined( clue_LOG_TO_CONSOLE ) + \ 108 | defined( clue_LOG_TO_FILE ) + \ 109 | defined( clue_LOG_TO_STRING ) + \ 110 | defined( clue_LOG_TO_DEBUGGER ) + \ 111 | defined( clue_LOG_TO_EVENTLOG ) + \ 112 | defined( clue_LOG_TO_SYSLOG ) > 1 113 | # error Please specify one, or none of [clue_]LOG_TO_CONSOLE, [clue_]LOG_TO_FILE, [clue_]LOG_TO_STRING, [clue_]LOG_TO_DEBUGGER [clue_]LOG_TO_EVENTLOG and [clue_]LOG_TO_SYSLOG 114 | #endif 115 | 116 | // NTS: add UNIX 117 | #ifdef clue_LOG_TO_DEBUGGER 118 | # define clue_LOG_TO_DEBUGGER_WINDOWS 119 | #endif 120 | 121 | #ifndef clue_NO_SHORT_NAMES 122 | 123 | # define LOG_SEV_NONE clue_LOG_SEV_NONE 124 | # define LOG_SEV_EMERGENCY clue_LOG_SEV_EMERGENCY 125 | # define LOG_SEV_ALERT clue_LOG_SEV_ALERT 126 | # define LOG_SEV_CRITICAL clue_LOG_SEV_CRITICAL 127 | # define LOG_SEV_ERROR clue_LOG_SEV_ERROR 128 | # define LOG_SEV_WARNING clue_LOG_SEV_WARNING 129 | # define LOG_SEV_NOTICE clue_LOG_SEV_NOTICE 130 | # define LOG_SEV_INFO clue_LOG_SEV_INFO 131 | # define LOG_SEV_DEBUG clue_LOG_SEV_DEBUG 132 | # define LOG_SEV_MAX clue_LOG_SEV_MAX 133 | 134 | # ifndef clue_LOG_TO_SYSLOG 135 | # define LOG_EMERGENCY clue_LOG_EMERGENCY 136 | # define LOG_ALERT clue_LOG_ALERT 137 | # define LOG_CRITICAL clue_LOG_CRITICAL 138 | # define LOG_ERROR clue_LOG_ERROR 139 | # define LOG_WARNING clue_LOG_WARNING 140 | # define LOG_NOTICE clue_LOG_NOTICE 141 | # define LOG_INFO clue_LOG_INFO 142 | # define LOG_DEBUG clue_LOG_DEBUG 143 | # endif 144 | 145 | # define LOG_LOGGED_SEVERITIES clue_LOG_LOGGED_SEVERITIES 146 | # define LOG_EXPRESSION clue_LOG_EXPRESSION 147 | 148 | #endif // clue_NO_SHORT_NAMES 149 | 150 | #ifdef clue_LOG_TO_CONSOLE 151 | # include 152 | #endif 153 | 154 | #ifdef clue_LOG_TO_FILE 155 | # include 156 | #endif 157 | 158 | #ifdef clue_LOG_TO_DEBUGGER_WINDOWS 159 | # include 160 | #endif 161 | 162 | #ifdef clue_LOG_TO_DEBUGGER_UNIX 163 | # error log to debugger under Unix not implemented 164 | #endif 165 | 166 | #ifdef clue_LOG_TO_EVENTLOG 167 | # include 168 | #endif 169 | 170 | #ifdef clue_LOG_TO_SYSLOG 171 | # include 172 | #endif 173 | 174 | #define clue_LOG_SEV_NONE -1 175 | #define clue_LOG_SEV_EMERGENCY 0 176 | #define clue_LOG_SEV_ALERT 1 177 | #define clue_LOG_SEV_CRITICAL 2 178 | #define clue_LOG_SEV_ERROR 3 179 | #define clue_LOG_SEV_WARNING 4 180 | #define clue_LOG_SEV_NOTICE 5 181 | #define clue_LOG_SEV_INFO 6 182 | #define clue_LOG_SEV_DEBUG 7 183 | #define clue_LOG_SEV_MAX 7 184 | 185 | #ifndef clue_LOG_LEVEL 186 | # define clue_LOG_LEVEL clue_LOG_SEV_DEBUG 187 | #endif 188 | 189 | #ifndef clue_LOG_LEVEL_BUILD 190 | # define clue_LOG_LEVEL_BUILD clue_LOG_SEV_DEBUG 191 | #endif 192 | 193 | #define clue_LOG_SEV_NONE_TEXT "[clue]" 194 | #define clue_LOG_SEV_EMERGENCY_TEXT "Emergency" 195 | #define clue_LOG_SEV_ALERT_TEXT "Alert" 196 | #define clue_LOG_SEV_CRITICAL_TEXT "Critical" 197 | #define clue_LOG_SEV_ERROR_TEXT "Error" 198 | #define clue_LOG_SEV_WARNING_TEXT "Warning" 199 | #define clue_LOG_SEV_NOTICE_TEXT "Notice" 200 | #define clue_LOG_SEV_INFO_TEXT "Info" 201 | #define clue_LOG_SEV_DEBUG_TEXT "Debug" 202 | 203 | #ifndef clue_LOG_MODULE_NAME 204 | # define clue_LOG_MODULE_NAME "" 205 | #endif 206 | 207 | #ifndef clue_LOG_PREFIX_WIDTH 208 | # define clue_LOG_PREFIX_WIDTH sizeof( clue_LOG_SEV_EMERGENCY_TEXT ) 209 | #endif 210 | 211 | #define clue_is_active( severity ) \ 212 | clue::is_true( severity <= clue_LOG_LEVEL ) 213 | 214 | #define clue_is_active_build( severity ) \ 215 | clue::is_true( clue_IS_ACTIVE_BUILD( severity ) ) 216 | 217 | #define clue_IS_ACTIVE_BUILD( severity ) \ 218 | ( severity <= clue_LOG_LEVEL_BUILD ) 219 | 220 | #define clue_LOG_LOGGED_SEVERITIES() \ 221 | clue_LOG_EXPRESSION( clue_LOG_SEV_NONE, clue::to_severities_text( clue_LOG_LEVEL_BUILD ) ) 222 | 223 | #define clue_LOG_NO_EXPRESSION() \ 224 | do {} while( clue::is_true(false) ) 225 | 226 | #define clue_IS_ACTIVE( severity ) \ 227 | ( clue_IS_ACTIVE_BUILD( severity ) || !clue_OMIT_UNUSED_LOG_EXPRESSION ) 228 | 229 | #if clue_IS_ACTIVE( clue_LOG_SEV_EMERGENCY ) 230 | # define clue_LOG_EMERGENCY( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_EMERGENCY, expr ) 231 | #else 232 | # define clue_LOG_EMERGENCY( expr ) clue_LOG_NO_EXPRESSION() 233 | #endif 234 | 235 | #if clue_IS_ACTIVE( clue_LOG_SEV_ALERT ) 236 | # define clue_LOG_ALERT( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_ALERT, expr ) 237 | #else 238 | # define clue_LOG_ALERT( expr ) clue_LOG_NO_EXPRESSION() 239 | #endif 240 | 241 | #if clue_IS_ACTIVE( clue_LOG_SEV_CRITICAL ) 242 | # define clue_LOG_CRITICAL( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_CRITICAL, expr ) 243 | #else 244 | # define clue_LOG_CRITICAL( expr ) clue_LOG_NO_EXPRESSION() 245 | #endif 246 | 247 | #if clue_IS_ACTIVE( clue_LOG_SEV_ERROR ) 248 | # define clue_LOG_ERROR( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_ERROR, expr ) 249 | #else 250 | # define clue_LOG_ERROR( expr ) clue_LOG_NO_EXPRESSION() 251 | #endif 252 | 253 | #if clue_IS_ACTIVE( clue_LOG_SEV_WARNING ) 254 | # define clue_LOG_WARNING( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_WARNING, expr ) 255 | #else 256 | # define clue_LOG_WARNING( expr ) clue_LOG_NO_EXPRESSION() 257 | #endif 258 | 259 | #if clue_IS_ACTIVE( clue_LOG_SEV_NOTICE ) 260 | # define clue_LOG_NOTICE( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_NOTICE, expr ) 261 | #else 262 | # define clue_LOG_NOTICE( expr ) clue_LOG_NO_EXPRESSION() 263 | #endif 264 | 265 | #if clue_IS_ACTIVE( clue_LOG_SEV_INFO ) 266 | # define clue_LOG_INFO( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_INFO, expr ) 267 | #else 268 | # define clue_LOG_INFO( expr ) clue_LOG_NO_EXPRESSION() 269 | #endif 270 | 271 | #if clue_IS_ACTIVE( clue_LOG_SEV_DEBUG ) && !defined( NDEBUG ) 272 | # define clue_LOG_DEBUG( expr ) clue_LOG_EXPRESSION( clue_LOG_SEV_DEBUG, expr ) 273 | #else 274 | # define clue_LOG_DEBUG( expr ) clue_LOG_NO_EXPRESSION() 275 | #endif 276 | 277 | #if defined( clue_LOG_TO_CONSOLE ) && !defined( clue_LOG_EXPRESSION ) 278 | # define clue_LOG_EXPRESSION( severity, expr ) \ 279 | do { \ 280 | if ( clue_is_active_build( severity ) ) { \ 281 | if ( clue_is_active( severity ) ) { \ 282 | std::clog << \ 283 | clue::now_text() << std::setw( clue_LOG_PREFIX_WIDTH ) << \ 284 | clue::to_severity_text(severity) << \ 285 | clue::to_module_text(clue_LOG_MODULE_NAME) << ": " << expr << "\n"; \ 286 | } \ 287 | } \ 288 | } while( clue::is_true( false ) ) 289 | #endif 290 | 291 | #if defined( clue_LOG_TO_FILE ) && !defined( clue_LOG_EXPRESSION ) 292 | # define clue_LOG_EXPRESSION( severity, expr ) \ 293 | do { \ 294 | if ( clue_is_active_build( severity ) ) { \ 295 | if ( clue_is_active( severity ) ) { \ 296 | clue::filelog() << \ 297 | clue::now_text() << std::setw( clue_LOG_PREFIX_WIDTH ) << \ 298 | clue::to_severity_text(severity) << \ 299 | clue::to_module_text(clue_LOG_MODULE_NAME) << ": " << expr << "\n"; \ 300 | } \ 301 | } \ 302 | } while( clue::is_true( false ) ) 303 | #endif 304 | 305 | #if defined( clue_LOG_TO_STRING ) && !defined( clue_LOG_EXPRESSION ) 306 | # define clue_LOG_EXPRESSION( sev, expr ) \ 307 | clue_LOG_STRING_EXPRESSION( the_log(), sev, expr ) 308 | #endif 309 | 310 | #if defined( clue_LOG_TO_STRING ) && !defined( clue_LOG_STRING_EXPRESSION ) 311 | # define clue_LOG_STRING_EXPRESSION( log, sev, expr ) \ 312 | do { \ 313 | if ( clue_is_active_build( sev ) ) { \ 314 | if ( clue_is_active( sev ) ) { \ 315 | log.severity( sev ); \ 316 | log << \ 317 | clue_LOG_MODULE_NAME << expr; \ 318 | } \ 319 | } \ 320 | } while( clue::is_true(false) ) 321 | #endif 322 | 323 | #if defined( clue_LOG_TO_DEBUGGER_WINDOWS ) && !defined( clue_LOG_EXPRESSION ) 324 | # define clue_LOG_EXPRESSION( severity, expr ) \ 325 | do { \ 326 | if ( clue_is_active_build( severity ) ) { \ 327 | if ( clue_is_active( severity ) ) { \ 328 | clue::windbg() << \ 329 | std::setw( clue_LOG_PREFIX_WIDTH ) << clue::to_severity_text( severity ) << \ 330 | clue::to_module_text(clue_LOG_MODULE_NAME) << ": " << expr; \ 331 | } \ 332 | } \ 333 | } while( clue::is_true(false) ) 334 | #endif 335 | 336 | #if defined( clue_LOG_TO_EVENTLOG ) && !defined( clue_LOG_EXPRESSION ) 337 | # define clue_LOG_EXPRESSION( severity, expr ) \ 338 | do { \ 339 | if ( clue_is_active_build( severity ) ) { \ 340 | if ( clue_is_active( severity ) ) { \ 341 | clue::evtlog( severity, clue_LOG_MODULE_NAME ) << \ 342 | clue::text_with_or( "", clue_LOG_MODULE_NAME, ": ", "" ) << expr; \ 343 | } \ 344 | } \ 345 | } while( clue::is_true(false) ) 346 | #endif 347 | 348 | #if defined( clue_LOG_TO_SYSLOG ) && !defined( clue_LOG_EXPRESSION ) 349 | # define clue_LOG_EXPRESSION( severity, expr ) \ 350 | do { \ 351 | if ( clue_is_active_build( severity ) ) { \ 352 | if ( clue_is_active( severity ) ) { \ 353 | clue::syslog( severity ) << \ 354 | clue_LOG_MODULE_NAME << ": " << expr; \ 355 | } \ 356 | } \ 357 | } while( clue::is_true(false) ) 358 | #endif 359 | 360 | namespace clue 361 | { 362 | 363 | inline bool is_true( bool const on ) { return on; } 364 | 365 | inline std::string text_or( std::string const & text, std::string const & or_text ) 366 | { 367 | return text.length() ? text : or_text; 368 | } 369 | 370 | inline std::string text_with_or( std::string const & prefix, std::string const & text, std::string const & postfix, std::string const & or_text ) 371 | { 372 | return text.length() ? prefix + text + postfix: or_text; 373 | } 374 | 375 | inline std::string to_module_text( std::string const & module ) 376 | { 377 | return text_with_or( ": ", module, "", "" ); 378 | } 379 | 380 | inline std::string to_severity_text( int const severity ) 381 | { 382 | assert( clue_LOG_SEV_NONE <= severity && severity <= clue_LOG_SEV_MAX && "invalid severity" ); 383 | 384 | if ( severity == clue_LOG_SEV_NONE ) 385 | return clue_LOG_SEV_NONE_TEXT; 386 | 387 | assert( clue_LOG_SEV_EMERGENCY == 0 && "required by lookup table" ); 388 | 389 | std::string const cont[] = 390 | { 391 | clue_LOG_SEV_EMERGENCY_TEXT, 392 | clue_LOG_SEV_ALERT_TEXT, 393 | clue_LOG_SEV_CRITICAL_TEXT, 394 | clue_LOG_SEV_ERROR_TEXT, 395 | clue_LOG_SEV_WARNING_TEXT, 396 | clue_LOG_SEV_NOTICE_TEXT, 397 | clue_LOG_SEV_INFO_TEXT, 398 | clue_LOG_SEV_DEBUG_TEXT, 399 | }; 400 | return cont[ severity ]; 401 | } 402 | 403 | inline std::string to_severities_text( int const level, std::string const & postfix = ".", std::string const & result = "" ) 404 | { 405 | if ( level < 0 ) 406 | return result + postfix; 407 | 408 | return to_severities_text( level - 1, postfix, to_severity_text( level ) + text_with_or( ", ", result, "", "" ) ); 409 | } 410 | 411 | } // namespace clue 412 | 413 | #ifdef clue_NO_TIMESTAMP 414 | 415 | namespace clue { 416 | inline std::string now_text() { return ""; } 417 | } 418 | 419 | #else // clue_NO_TIMESTAMP 420 | 421 | # include 422 | 423 | # ifdef clue_COMPILER_IS_MSVC6 424 | namespace std { 425 | using ::time_t; using ::time; using ::strftime; using ::localtime; 426 | } 427 | # endif 428 | 429 | namespace clue { 430 | 431 | inline std::string now_text() 432 | { 433 | char mbstr[100]; 434 | const std::time_t now = std::time(NULL); 435 | 436 | // ISO ISO 8601 date and time format: C++11: %FT%T 437 | if ( std::strftime( mbstr, 100, "%Y-%m-%dT%H:%M:%S", std::localtime( &now ) ) ) 438 | return mbstr; 439 | else 440 | return "[time]"; 441 | } 442 | } // namespace clue 443 | 444 | #endif // clue_NO_TIMESTAMP 445 | 446 | #ifdef clue_LOG_TO_FILE 447 | 448 | namespace clue { 449 | 450 | class filelog 451 | { 452 | public: 453 | filelog() 454 | : stream() {} 455 | 456 | ~filelog() 457 | { 458 | // emit: program-name[pid]: 459 | std::ofstream os( clue_LOG_TO_FILE, std::ios_base::app ); 460 | os << stream.str(); 461 | } 462 | 463 | template 464 | filelog & operator<<( T const & that ) 465 | { 466 | stream << that; 467 | return *this; 468 | } 469 | 470 | private: 471 | std::ostringstream stream; 472 | }; 473 | 474 | } // namespace clue 475 | 476 | #endif // clue_LOG_TO_FILE 477 | 478 | #ifdef clue_LOG_TO_STRING 479 | 480 | namespace clue { 481 | 482 | class strlog 483 | { 484 | public: 485 | strlog() 486 | : severity_( clue_LOG_SEV_NONE ) 487 | , stream() {} 488 | 489 | void clear() 490 | { 491 | severity_ = clue_LOG_SEV_NONE ; 492 | stream.str( std::string() ); 493 | } 494 | 495 | void severity( int const sev ) 496 | { 497 | severity_ = sev; 498 | } 499 | 500 | int severity() const 501 | { 502 | return severity_; 503 | } 504 | 505 | std::string text() const 506 | { 507 | return stream.str(); 508 | } 509 | 510 | template 511 | strlog & operator<<( T const & that ) 512 | { 513 | stream << that; 514 | return *this; 515 | } 516 | 517 | private: 518 | int severity_; 519 | std::ostringstream stream; 520 | }; 521 | 522 | inline strlog & the_log() 523 | { 524 | static strlog log; 525 | return log; 526 | } 527 | 528 | } // namespace clue 529 | 530 | #endif // clue_LOG_TO_STRING 531 | 532 | #ifdef clue_LOG_TO_DEBUGGER_WINDOWS 533 | 534 | namespace clue 535 | { 536 | 537 | class windbg 538 | { 539 | public: 540 | windbg() 541 | : stream() {} 542 | 543 | ~windbg() 544 | { 545 | OutputDebugString( stream.str().c_str() ); 546 | } 547 | 548 | template 549 | windbg & operator<<( T const & that ) 550 | { 551 | stream << that; 552 | return *this; 553 | } 554 | 555 | private: 556 | std::ostringstream stream; 557 | }; 558 | 559 | } // namespace clue 560 | 561 | #endif // clue_LOG_TO_DEBUGGER_WINDOWS 562 | 563 | #ifdef clue_LOG_TO_EVENTLOG 564 | 565 | namespace clue 566 | { 567 | 568 | inline int to_eventlog_severity( int severity ) 569 | { 570 | assert( clue_LOG_SEV_NONE <= severity && severity <= clue_LOG_SEV_MAX && "invalid severity" ); 571 | 572 | switch( severity ) 573 | { 574 | case clue_LOG_SEV_NONE: return EVENTLOG_INFORMATION_TYPE; 575 | case clue_LOG_SEV_EMERGENCY: return EVENTLOG_ERROR_TYPE; 576 | case clue_LOG_SEV_ALERT: return EVENTLOG_ERROR_TYPE; 577 | case clue_LOG_SEV_CRITICAL: return EVENTLOG_ERROR_TYPE; 578 | case clue_LOG_SEV_ERROR: return EVENTLOG_ERROR_TYPE; 579 | case clue_LOG_SEV_WARNING: return EVENTLOG_WARNING_TYPE; 580 | case clue_LOG_SEV_NOTICE: return EVENTLOG_INFORMATION_TYPE; 581 | case clue_LOG_SEV_INFO: return EVENTLOG_INFORMATION_TYPE; 582 | default: 583 | case clue_LOG_SEV_DEBUG: return EVENTLOG_INFORMATION_TYPE; 584 | } 585 | } 586 | 587 | class evtlog 588 | { 589 | public: 590 | evtlog( int const severity, std::string const & module ) 591 | : severity( severity ) 592 | , module( module) 593 | , stream() {} 594 | 595 | ~evtlog() 596 | { 597 | // note string lifetime 598 | const std::string text = stream.str(); 599 | const char *strings[] = { text.c_str(), }; 600 | 601 | const ::HANDLE hlog = ::RegisterEventSource( 602 | 0, text_or( module, "[clue]" ).c_str() ); 603 | 604 | ::ReportEvent( 605 | hlog // HANDLE hEventLog, // handle returned by RegisterEventSource 606 | , to_eventlog_severity(severity) // WORD wType, // event type to log 607 | , 0 // WORD wCategory, // event category 608 | , 0 // DWORD dwEventID, // event identifier 609 | , 0 // PSID lpUserSid, // user security identifier (optional) 610 | , 1 // WORD wNumStrings, // number of strings to merge with message 611 | , 0 // DWORD dwDataSize, // size of binary data, in bytes 612 | , strings // LPCTSTR *lpStrings, // array of strings to merge with message 613 | , 0 // LPVOID lpRawData // address of binary data 614 | ); 615 | 616 | ::DeregisterEventSource( hlog ); 617 | } 618 | 619 | template 620 | evtlog & operator<<( T const & that ) 621 | { 622 | stream << that; 623 | return *this; 624 | } 625 | 626 | private: 627 | const int severity; 628 | const std::string module; 629 | std::ostringstream stream; 630 | }; 631 | 632 | } // namespace clue 633 | 634 | #endif // clue_LOG_TO_DEBUGGER_WINDOWS 635 | 636 | #ifdef clue_LOG_TO_SYSLOG 637 | 638 | namespace clue { 639 | 640 | inline int to_syslog_severity( int severity ) 641 | { 642 | assert( clue_LOG_SEV_NONE <= severity && severity <= clue_LOG_SEV_MAX && "invalid severity" ); 643 | 644 | switch( severity ) 645 | { 646 | case clue_LOG_SEV_NONE: return LOG_INFO; 647 | case clue_LOG_SEV_EMERGENCY: return LOG_EMERG; 648 | case clue_LOG_SEV_ALERT: return LOG_ALERT; 649 | case clue_LOG_SEV_CRITICAL: return LOG_CRIT; 650 | case clue_LOG_SEV_ERROR: return LOG_ERR; 651 | case clue_LOG_SEV_WARNING: return LOG_WARNING; 652 | case clue_LOG_SEV_NOTICE: return LOG_NOTICE; 653 | case clue_LOG_SEV_INFO: return LOG_INFO; 654 | default: 655 | case clue_LOG_SEV_DEBUG: return LOG_DEBUG; 656 | } 657 | } 658 | 659 | class syslog 660 | { 661 | public: 662 | syslog( int const severity ) 663 | : severity( severity ) 664 | , stream() {} 665 | 666 | ~syslog() 667 | { 668 | // emit: program-name[pid]: 669 | ::openlog( NULL, LOG_PID, LOG_USER ); 670 | ::syslog ( to_syslog_severity(severity), "%s", stream.str().c_str() ); 671 | ::closelog(); 672 | } 673 | 674 | template 675 | syslog & operator<<( T const & that ) 676 | { 677 | stream << that; 678 | return *this; 679 | } 680 | 681 | private: 682 | const int severity; 683 | std::ostringstream stream; 684 | }; 685 | 686 | } // namespace clue 687 | 688 | #endif //clue_LOG_TO_SYSLOG 689 | 690 | #ifdef clue_COMPILER_IS_MSVC 691 | # pragma warning( pop ) 692 | #endif 693 | 694 | #endif // CLUE_CLUE_H_INCLUDED 695 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2014 by Martin Moene 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | CXXFLAGS = -Wall -Wextra -Weffc++ -std=c++03 -I../include 7 | 8 | vpath %.hpp ../include 9 | 10 | all: test_clue 11 | 12 | test_clue: test_clue.cpp test_clue_part2.cpp clue.hpp 13 | $(CXX) $(CXXFLAGS) -o test_clue test_clue.cpp test_clue_part2.cpp 14 | ./test_clue 15 | 16 | clean: 17 | rm test_clue 18 | 19 | -------------------------------------------------------------------------------- /tests/lest_cpp03.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2018 by Martin Moene 2 | // 3 | // lest is based on ideas by Kevlin Henney, see video at 4 | // http://skillsmatter.com/podcast/agile-testing/kevlin-henney-rethinking-unit-testing-in-c-plus-plus 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef LEST_LEST_HPP_INCLUDED 10 | #define LEST_LEST_HPP_INCLUDED 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define lest_MAJOR 1 31 | #define lest_MINOR 35 32 | #define lest_PATCH 1 33 | 34 | #define lest_VERSION lest_STRINGIFY(lest_MAJOR) "." lest_STRINGIFY(lest_MINOR) "." lest_STRINGIFY(lest_PATCH) 35 | 36 | #ifndef lest_FEATURE_COLOURISE 37 | # define lest_FEATURE_COLOURISE 0 38 | #endif 39 | 40 | #ifndef lest_FEATURE_LITERAL_SUFFIX 41 | # define lest_FEATURE_LITERAL_SUFFIX 0 42 | #endif 43 | 44 | #ifndef lest_FEATURE_REGEX_SEARCH 45 | # define lest_FEATURE_REGEX_SEARCH 0 46 | #endif 47 | 48 | #ifndef lest_FEATURE_TIME 49 | # define lest_FEATURE_TIME 1 50 | #endif 51 | 52 | #ifndef lest_FEATURE_TIME_PRECISION 53 | #define lest_FEATURE_TIME_PRECISION 0 54 | #endif 55 | 56 | #ifdef _WIN32 57 | # define lest_PLATFORM_IS_WINDOWS 1 58 | #else 59 | # define lest_PLATFORM_IS_WINDOWS 0 60 | #endif 61 | 62 | #if lest_FEATURE_REGEX_SEARCH 63 | # include 64 | #endif 65 | 66 | #if lest_FEATURE_TIME 67 | # if lest_PLATFORM_IS_WINDOWS 68 | # include 69 | # include 70 | # else 71 | # include 72 | # include 73 | # endif 74 | #endif 75 | 76 | // Compiler warning suppression: 77 | 78 | #if defined (__clang__) 79 | # pragma clang diagnostic ignored "-Waggregate-return" 80 | # pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses" 81 | # pragma clang diagnostic push 82 | # pragma clang diagnostic ignored "-Wdate-time" 83 | #elif defined (__GNUC__) 84 | # pragma GCC diagnostic ignored "-Waggregate-return" 85 | # pragma GCC diagnostic push 86 | #endif 87 | 88 | // Suppress shadow and unused-value warning for sections: 89 | 90 | #if defined (__clang__) 91 | # define lest_SUPPRESS_WSHADOW _Pragma( "clang diagnostic push" ) \ 92 | _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) 93 | # define lest_SUPPRESS_WUNUSED _Pragma( "clang diagnostic push" ) \ 94 | _Pragma( "clang diagnostic ignored \"-Wunused-value\"" ) 95 | # define lest_RESTORE_WARNINGS _Pragma( "clang diagnostic pop" ) 96 | 97 | #elif defined (__GNUC__) 98 | # define lest_SUPPRESS_WSHADOW _Pragma( "GCC diagnostic push" ) \ 99 | _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) 100 | # define lest_SUPPRESS_WUNUSED _Pragma( "GCC diagnostic push" ) \ 101 | _Pragma( "GCC diagnostic ignored \"-Wunused-value\"" ) 102 | # define lest_RESTORE_WARNINGS _Pragma( "GCC diagnostic pop" ) 103 | #else 104 | # define lest_SUPPRESS_WSHADOW /*empty*/ 105 | # define lest_SUPPRESS_WUNUSED /*empty*/ 106 | # define lest_RESTORE_WARNINGS /*empty*/ 107 | #endif 108 | 109 | // Stringify: 110 | 111 | #define lest_STRINGIFY( x ) lest_STRINGIFY_( x ) 112 | #define lest_STRINGIFY_( x ) #x 113 | 114 | // Compiler versions: 115 | 116 | #if defined( _MSC_VER ) && !defined( __clang__ ) 117 | # define lest_COMPILER_MSVC_VERSION ( _MSC_VER / 10 - 10 * ( 5 + ( _MSC_VER < 1900 ) ) ) 118 | #else 119 | # define lest_COMPILER_MSVC_VERSION 0 120 | #endif 121 | 122 | #define lest_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) 123 | 124 | #if defined (__clang__) 125 | # define lest_COMPILER_CLANG_VERSION lest_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) 126 | #else 127 | # define lest_COMPILER_CLANG_VERSION 0 128 | #endif 129 | 130 | #if defined (__GNUC__) 131 | # define lest_COMPILER_GNUC_VERSION lest_COMPILER_VERSION( __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ ) 132 | #else 133 | # define lest_COMPILER_GNUC_VERSION 0 134 | #endif 135 | 136 | // C++ language version detection (C++20 is speculative): 137 | // Note: VC14.0/1900 (VS2015) lacks too much from C++14. 138 | 139 | #ifndef lest_CPLUSPLUS 140 | # if defined(_MSVC_LANG ) && !defined(__clang__) 141 | # define lest_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) 142 | # else 143 | # define lest_CPLUSPLUS __cplusplus 144 | # endif 145 | #endif 146 | 147 | #define lest_CPP98_OR_GREATER ( lest_CPLUSPLUS >= 199711L ) 148 | #define lest_CPP11_OR_GREATER ( lest_CPLUSPLUS >= 201103L || lest_COMPILER_MSVC_VERSION >= 120 ) 149 | #define lest_CPP14_OR_GREATER ( lest_CPLUSPLUS >= 201402L ) 150 | #define lest_CPP17_OR_GREATER ( lest_CPLUSPLUS >= 201703L ) 151 | #define lest_CPP20_OR_GREATER ( lest_CPLUSPLUS >= 202000L ) 152 | 153 | #define lest_CPP11_100 (lest_CPP11_OR_GREATER || lest_COMPILER_MSVC_VERSION >= 100) 154 | 155 | #ifndef __has_cpp_attribute 156 | # define __has_cpp_attribute(name) 0 157 | #endif 158 | 159 | // Indicate argument as possibly unused, if possible: 160 | 161 | #if __has_cpp_attribute(maybe_unused) && lest_CPP17_OR_GREATER 162 | # define lest_MAYBE_UNUSED(ARG) [[maybe_unused]] ARG 163 | #elif defined (__GNUC__) 164 | # define lest_MAYBE_UNUSED(ARG) ARG __attribute((unused)) 165 | #else 166 | # define lest_MAYBE_UNUSED(ARG) ARG 167 | #endif 168 | 169 | // Presence of language and library features: 170 | 171 | #define lest_HAVE(FEATURE) ( lest_HAVE_##FEATURE ) 172 | 173 | // Presence of C++11 language features: 174 | 175 | #define lest_HAVE_NOEXCEPT ( lest_CPP11_100 ) 176 | #define lest_HAVE_NULLPTR ( lest_CPP11_100 ) 177 | 178 | // C++ feature usage: 179 | 180 | #if lest_HAVE( NULLPTR ) 181 | # define lest_nullptr nullptr 182 | #else 183 | # define lest_nullptr NULL 184 | #endif 185 | 186 | // Additional includes and tie: 187 | 188 | #if lest_CPP11_100 189 | 190 | # include 191 | # include 192 | # include 193 | 194 | namespace lest 195 | { 196 | using std::tie; 197 | } 198 | 199 | #else 200 | 201 | # if !defined(__clang__) && defined(__GNUC__) 202 | # pragma GCC diagnostic push 203 | # pragma GCC diagnostic ignored "-Weffc++" 204 | # endif 205 | 206 | namespace lest 207 | { 208 | // tie: 209 | 210 | template< typename T1, typename T2 > 211 | struct Tie 212 | { 213 | Tie( T1 & first_, T2 & second_) 214 | : first( first_), second( second_) {} 215 | 216 | std::pair const & 217 | operator=( std::pair const & rhs ) 218 | { 219 | first = rhs.first; 220 | second = rhs.second; 221 | return rhs; 222 | } 223 | 224 | private: 225 | void operator=( Tie const & ); 226 | 227 | T1 & first; 228 | T2 & second; 229 | }; 230 | 231 | template< typename T1, typename T2 > 232 | inline Tie tie( T1 & first, T2 & second ) 233 | { 234 | return Tie( first, second ); 235 | } 236 | } 237 | 238 | # if !defined(__clang__) && defined(__GNUC__) 239 | # pragma GCC diagnostic pop 240 | # endif 241 | 242 | #endif // lest_CPP11_100 243 | 244 | namespace lest 245 | { 246 | using std::abs; 247 | using std::min; 248 | using std::strtol; 249 | using std::rand; 250 | using std::srand; 251 | } 252 | 253 | #if ! defined( lest_NO_SHORT_MACRO_NAMES ) && ! defined( lest_NO_SHORT_ASSERTION_NAMES ) 254 | # define SETUP lest_SETUP 255 | # define SECTION lest_SECTION 256 | 257 | # define EXPECT lest_EXPECT 258 | # define EXPECT_NOT lest_EXPECT_NOT 259 | # define EXPECT_NO_THROW lest_EXPECT_NO_THROW 260 | # define EXPECT_THROWS lest_EXPECT_THROWS 261 | # define EXPECT_THROWS_AS lest_EXPECT_THROWS_AS 262 | 263 | # define SCENARIO lest_SCENARIO 264 | # define GIVEN lest_GIVEN 265 | # define WHEN lest_WHEN 266 | # define THEN lest_THEN 267 | # define AND_WHEN lest_AND_WHEN 268 | # define AND_THEN lest_AND_THEN 269 | #endif 270 | 271 | #define lest_SCENARIO( specification, sketch ) \ 272 | lest_CASE( specification, lest::text("Scenario: ") + sketch ) 273 | #define lest_GIVEN( context ) lest_SETUP( lest::text(" Given: ") + context ) 274 | #define lest_WHEN( story ) lest_SECTION( lest::text(" When: ") + story ) 275 | #define lest_THEN( story ) lest_SECTION( lest::text(" Then: ") + story ) 276 | #define lest_AND_WHEN( story ) lest_SECTION( lest::text("And then: ") + story ) 277 | #define lest_AND_THEN( story ) lest_SECTION( lest::text("And then: ") + story ) 278 | 279 | #define lest_CASE( specification, proposition ) \ 280 | static void lest_FUNCTION( lest::env & ); \ 281 | namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \ 282 | static void lest_FUNCTION( lest_MAYBE_UNUSED( lest::env & lest_env ) ) 283 | 284 | #define lest_ADD_TEST( specification, test ) \ 285 | specification.push_back( test ) 286 | 287 | #define lest_SETUP( context ) \ 288 | for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) \ 289 | for ( lest::ctx lest__ctx_setup( lest_env, context ); lest__ctx_setup; ) 290 | 291 | #define lest_SECTION( proposition ) \ 292 | lest_SUPPRESS_WSHADOW \ 293 | static int lest_UNIQUE( id ) = 0; \ 294 | if ( lest::guard( lest_UNIQUE( id ), lest__section, lest__count ) ) \ 295 | for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) \ 296 | for ( lest::ctx lest__ctx_section( lest_env, proposition ); lest__ctx_section; ) \ 297 | lest_RESTORE_WARNINGS 298 | 299 | #define lest_EXPECT( expr ) \ 300 | do { \ 301 | try \ 302 | { \ 303 | if ( lest::result score = lest_DECOMPOSE( expr ) ) \ 304 | { \ 305 | lest::report( lest_env.os, lest::failure( lest_LOCATION, #expr, score.decomposition ), lest_env.context() ); \ 306 | throw lest::failure( lest_LOCATION, #expr, score.decomposition ); \ 307 | } \ 308 | else if ( lest_env.pass() ) \ 309 | lest::report( lest_env.os, lest::passing( lest_LOCATION, #expr, score.decomposition, lest_env.zen() ), lest_env.context() ); \ 310 | } \ 311 | catch(...) \ 312 | { \ 313 | lest::inform( lest_LOCATION, #expr ); \ 314 | } \ 315 | } while ( lest::is_false() ) 316 | 317 | #define lest_EXPECT_NOT( expr ) \ 318 | do { \ 319 | try \ 320 | { \ 321 | if ( lest::result score = lest_DECOMPOSE( expr ) ) \ 322 | { \ 323 | if ( lest_env.pass() ) \ 324 | lest::report( lest_env.os, lest::passing( lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ), lest_env.zen() ), lest_env.context() ); \ 325 | } \ 326 | else \ 327 | { \ 328 | lest::report( lest_env.os, lest::failure( lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ) ), lest_env.context() ); \ 329 | throw lest::failure( lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ) ); \ 330 | } \ 331 | } \ 332 | catch(...) \ 333 | { \ 334 | lest::inform( lest_LOCATION, lest::not_expr( #expr ) ); \ 335 | } \ 336 | } while ( lest::is_false() ) 337 | 338 | #define lest_EXPECT_NO_THROW( expr ) \ 339 | do \ 340 | { \ 341 | try \ 342 | { \ 343 | lest_SUPPRESS_WUNUSED \ 344 | expr; \ 345 | lest_RESTORE_WARNINGS \ 346 | } \ 347 | catch (...) { lest::inform( lest_LOCATION, #expr ); } \ 348 | if ( lest_env.pass() ) \ 349 | lest::report( lest_env.os, lest::got_none( lest_LOCATION, #expr ), lest_env.context() ); \ 350 | } while ( lest::is_false() ) 351 | 352 | #define lest_EXPECT_THROWS( expr ) \ 353 | do \ 354 | { \ 355 | try \ 356 | { \ 357 | lest_SUPPRESS_WUNUSED \ 358 | expr; \ 359 | lest_RESTORE_WARNINGS \ 360 | } \ 361 | catch (...) \ 362 | { \ 363 | if ( lest_env.pass() ) \ 364 | lest::report( lest_env.os, lest::got( lest_LOCATION, #expr ), lest_env.context() ); \ 365 | break; \ 366 | } \ 367 | throw lest::expected( lest_LOCATION, #expr ); \ 368 | } \ 369 | while ( lest::is_false() ) 370 | 371 | #define lest_EXPECT_THROWS_AS( expr, excpt ) \ 372 | do \ 373 | { \ 374 | try \ 375 | { \ 376 | lest_SUPPRESS_WUNUSED \ 377 | expr; \ 378 | lest_RESTORE_WARNINGS \ 379 | } \ 380 | catch ( excpt & ) \ 381 | { \ 382 | if ( lest_env.pass() ) \ 383 | lest::report( lest_env.os, lest::got( lest_LOCATION, #expr, lest::of_type( #excpt ) ), lest_env.context() ); \ 384 | break; \ 385 | } \ 386 | catch (...) {} \ 387 | throw lest::expected( lest_LOCATION, #expr, lest::of_type( #excpt ) ); \ 388 | } \ 389 | while ( lest::is_false() ) 390 | 391 | #define lest_DECOMPOSE( expr ) ( lest::expression_decomposer() << expr ) 392 | 393 | #define lest_STRING( name ) lest_STRING2( name ) 394 | #define lest_STRING2( name ) #name 395 | 396 | #define lest_UNIQUE( name ) lest_UNIQUE2( name, __LINE__ ) 397 | #define lest_UNIQUE2( name, line ) lest_UNIQUE3( name, line ) 398 | #define lest_UNIQUE3( name, line ) name ## line 399 | 400 | #define lest_LOCATION lest::location(__FILE__, __LINE__) 401 | 402 | #define lest_FUNCTION lest_UNIQUE(__lest_function__ ) 403 | #define lest_REGISTRAR lest_UNIQUE(__lest_registrar__ ) 404 | 405 | #define lest_DIMENSION_OF( a ) ( sizeof(a) / sizeof(0[a]) ) 406 | 407 | namespace lest { 408 | 409 | const int exit_max_value = 255; 410 | 411 | typedef std::string text; 412 | typedef std::vector texts; 413 | 414 | struct env; 415 | 416 | struct test 417 | { 418 | text name; 419 | void (* behaviour)( env & ); 420 | 421 | test( text name_, void (* behaviour_)( env & ) ) 422 | : name( name_), behaviour( behaviour_) {} 423 | }; 424 | 425 | typedef std::vector tests; 426 | typedef tests test_specification; 427 | 428 | struct add_test 429 | { 430 | add_test( tests & specification, test const & test_case ) 431 | { 432 | specification.push_back( test_case ); 433 | } 434 | }; 435 | 436 | struct result 437 | { 438 | const bool passed; 439 | const text decomposition; 440 | 441 | template< typename T > 442 | result( T const & passed_, text decomposition_) 443 | : passed( !!passed_), decomposition( decomposition_) {} 444 | 445 | operator bool() { return ! passed; } 446 | }; 447 | 448 | struct location 449 | { 450 | const text file; 451 | const int line; 452 | 453 | location( text file_, int line_) 454 | : file( file_), line( line_) {} 455 | }; 456 | 457 | struct comment 458 | { 459 | const text info; 460 | 461 | comment( text info_) : info( info_) {} 462 | operator bool() { return ! info.empty(); } 463 | }; 464 | 465 | struct message : std::runtime_error 466 | { 467 | const text kind; 468 | const location where; 469 | const comment note; 470 | 471 | #if ! lest_CPP11_OR_GREATER 472 | ~message() throw() {} 473 | #endif 474 | 475 | message( text kind_, location where_, text expr_, text note_ = "" ) 476 | : std::runtime_error( expr_), kind( kind_), where( where_), note( note_) {} 477 | }; 478 | 479 | struct failure : message 480 | { 481 | failure( location where_, text expr_, text decomposition_) 482 | : message( "failed", where_, expr_ + " for " + decomposition_) {} 483 | }; 484 | 485 | struct success : message 486 | { 487 | success( text kind_, location where_, text expr_, text note_ = "" ) 488 | : message( kind_, where_, expr_, note_) {} 489 | }; 490 | 491 | struct passing : success 492 | { 493 | passing( location where_, text expr_, text decomposition_, bool zen ) 494 | : success( "passed", where_, expr_ + (zen ? "":" for " + decomposition_) ) {} 495 | }; 496 | 497 | struct got_none : success 498 | { 499 | got_none( location where_, text expr_) 500 | : success( "passed: got no exception", where_, expr_) {} 501 | }; 502 | 503 | struct got : success 504 | { 505 | got( location where_, text expr_) 506 | : success( "passed: got exception", where_, expr_) {} 507 | 508 | got( location where_, text expr_, text excpt_) 509 | : success( "passed: got exception " + excpt_, where_, expr_) {} 510 | }; 511 | 512 | struct expected : message 513 | { 514 | expected( location where_, text expr_, text excpt_ = "" ) 515 | : message( "failed: didn't get exception", where_, expr_, excpt_) {} 516 | }; 517 | 518 | struct unexpected : message 519 | { 520 | unexpected( location where_, text expr_, text note_ = "" ) 521 | : message( "failed: got unexpected exception", where_, expr_, note_) {} 522 | }; 523 | 524 | struct guard 525 | { 526 | int & id; 527 | int const & section; 528 | 529 | guard( int & id_, int const & section_, int & count ) 530 | : id( id_ ), section( section_ ) 531 | { 532 | if ( section == 0 ) 533 | id = count++ - 1; 534 | } 535 | operator bool() { return id == section; } 536 | }; 537 | 538 | class approx 539 | { 540 | public: 541 | explicit approx ( double magnitude ) 542 | : epsilon_ ( 100.0 * static_cast( std::numeric_limits::epsilon() ) ) 543 | , scale_ ( 1.0 ) 544 | , magnitude_( magnitude ) {} 545 | 546 | static approx custom() { return approx( 0 ); } 547 | 548 | approx operator()( double new_magnitude ) 549 | { 550 | approx appr( new_magnitude ); 551 | appr.epsilon( epsilon_ ); 552 | appr.scale ( scale_ ); 553 | return appr; 554 | } 555 | 556 | double magnitude() const { return magnitude_; } 557 | 558 | approx & epsilon( double epsilon ) { epsilon_ = epsilon; return *this; } 559 | approx & scale ( double scale ) { scale_ = scale; return *this; } 560 | 561 | friend bool operator == ( double lhs, approx const & rhs ) 562 | { 563 | // Thanks to Richard Harris for his help refining this formula. 564 | return lest::abs( lhs - rhs.magnitude_ ) < rhs.epsilon_ * ( rhs.scale_ + (lest::min)( lest::abs( lhs ), lest::abs( rhs.magnitude_ ) ) ); 565 | } 566 | 567 | friend bool operator == ( approx const & lhs, double rhs ) { return operator==( rhs, lhs ); } 568 | friend bool operator != ( double lhs, approx const & rhs ) { return !operator==( lhs, rhs ); } 569 | friend bool operator != ( approx const & lhs, double rhs ) { return !operator==( rhs, lhs ); } 570 | 571 | friend bool operator <= ( double lhs, approx const & rhs ) { return lhs < rhs.magnitude_ || lhs == rhs; } 572 | friend bool operator <= ( approx const & lhs, double rhs ) { return lhs.magnitude_ < rhs || lhs == rhs; } 573 | friend bool operator >= ( double lhs, approx const & rhs ) { return lhs > rhs.magnitude_ || lhs == rhs; } 574 | friend bool operator >= ( approx const & lhs, double rhs ) { return lhs.magnitude_ > rhs || lhs == rhs; } 575 | 576 | private: 577 | double epsilon_; 578 | double scale_; 579 | double magnitude_; 580 | }; 581 | 582 | inline bool is_false( ) { return false; } 583 | inline bool is_true ( bool flag ) { return flag; } 584 | 585 | inline text not_expr( text message ) 586 | { 587 | return "! ( " + message + " )"; 588 | } 589 | 590 | inline text with_message( text message ) 591 | { 592 | return "with message \"" + message + "\""; 593 | } 594 | 595 | inline text of_type( text type ) 596 | { 597 | return "of type " + type; 598 | } 599 | 600 | inline void inform( location where, text expr ) 601 | { 602 | try 603 | { 604 | throw; 605 | } 606 | catch( failure const & ) 607 | { 608 | throw; 609 | } 610 | catch( std::exception const & e ) 611 | { 612 | throw unexpected( where, expr, with_message( e.what() ) ); \ 613 | } 614 | catch(...) 615 | { 616 | throw unexpected( where, expr, "of unknown type" ); \ 617 | } 618 | } 619 | 620 | // Expression decomposition: 621 | 622 | inline bool unprintable( char c ) { return 0 <= c && c < ' '; } 623 | 624 | inline std::string to_hex_string(char c) 625 | { 626 | std::ostringstream os; 627 | os << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast( static_cast(c) ); 628 | return os.str(); 629 | } 630 | 631 | inline std::string transformed( char chr ) 632 | { 633 | struct Tr { char chr; char const * str; } table[] = 634 | { 635 | {'\\', "\\\\" }, 636 | {'\r', "\\r" }, {'\f', "\\f" }, 637 | {'\n', "\\n" }, {'\t', "\\t" }, 638 | }; 639 | 640 | for ( Tr * pos = table; pos != table + lest_DIMENSION_OF( table ); ++pos ) 641 | { 642 | if ( chr == pos->chr ) 643 | return pos->str; 644 | } 645 | 646 | return unprintable( chr ) ? to_hex_string( chr ) : std::string( 1, chr ); 647 | } 648 | 649 | inline std::string make_tran_string( std::string const & txt ) 650 | { 651 | std::ostringstream os; 652 | for( std::string::const_iterator pos = txt.begin(); pos != txt.end(); ++pos ) 653 | os << transformed( *pos ); 654 | return os.str(); 655 | } 656 | 657 | template< typename T > 658 | inline std::string to_string( T const & value ); 659 | 660 | #if lest_CPP11_OR_GREATER || lest_COMPILER_MSVC_VERSION >= 100 661 | inline std::string to_string( std::nullptr_t const & ) { return "nullptr"; } 662 | #endif 663 | inline std::string to_string( std::string const & txt ) { return "\"" + make_tran_string( txt ) + "\""; } 664 | inline std::string to_string( char const * const & txt ) { return "\"" + make_tran_string( txt ) + "\""; } 665 | inline std::string to_string( char const & chr ) { return "'" + make_tran_string( std::string( 1, chr ) ) + "'"; } 666 | 667 | inline std::string to_string( signed char const & chr ) { return to_string( static_cast( chr ) ); } 668 | inline std::string to_string( unsigned char const & chr ) { return to_string( static_cast( chr ) ); } 669 | 670 | inline std::ostream & operator<<( std::ostream & os, approx const & appr ) 671 | { 672 | return os << appr.magnitude(); 673 | } 674 | 675 | template< typename T > 676 | inline std::string make_string( T const * ptr ) 677 | { 678 | // Note showbase affects the behavior of /integer/ output; 679 | std::ostringstream os; 680 | os << std::internal << std::hex << std::showbase << std::setw( 2 + 2 * sizeof(T*) ) << std::setfill('0') << reinterpret_cast( ptr ); 681 | return os.str(); 682 | } 683 | 684 | template< typename C, typename R > 685 | inline std::string make_string( R C::* ptr ) 686 | { 687 | std::ostringstream os; 688 | os << std::internal << std::hex << std::showbase << std::setw( 2 + 2 * sizeof(R C::* ) ) << std::setfill('0') << ptr; 689 | return os.str(); 690 | } 691 | 692 | template< typename T > 693 | struct string_maker 694 | { 695 | static std::string to_string( T const & value ) 696 | { 697 | std::ostringstream os; os << std::boolalpha << value; 698 | return os.str(); 699 | } 700 | }; 701 | 702 | template< typename T > 703 | struct string_maker< T* > 704 | { 705 | static std::string to_string( T const * ptr ) 706 | { 707 | return ! ptr ? lest_STRING( lest_nullptr ) : make_string( ptr ); 708 | } 709 | }; 710 | 711 | template< typename C, typename R > 712 | struct string_maker< R C::* > 713 | { 714 | static std::string to_string( R C::* ptr ) 715 | { 716 | return ! ptr ? lest_STRING( lest_nullptr ) : make_string( ptr ); 717 | } 718 | }; 719 | 720 | template< typename T > 721 | inline std::string to_string( T const & value ) 722 | { 723 | return string_maker::to_string( value ); 724 | } 725 | 726 | template< typename T1, typename T2 > 727 | std::string to_string( std::pair const & pair ) 728 | { 729 | std::ostringstream oss; 730 | oss << "{ " << to_string( pair.first ) << ", " << to_string( pair.second ) << " }"; 731 | return oss.str(); 732 | } 733 | 734 | #if lest_CPP11_OR_GREATER 735 | 736 | template< typename TU, std::size_t N > 737 | struct make_tuple_string 738 | { 739 | static std::string make( TU const & tuple ) 740 | { 741 | std::ostringstream os; 742 | os << to_string( std::get( tuple ) ) << ( N < std::tuple_size::value ? ", ": " "); 743 | return make_tuple_string::make( tuple ) + os.str(); 744 | } 745 | }; 746 | 747 | template< typename TU > 748 | struct make_tuple_string 749 | { 750 | static std::string make( TU const & ) { return ""; } 751 | }; 752 | 753 | template< typename ...TS > 754 | auto to_string( std::tuple const & tuple ) -> std::string 755 | { 756 | return "{ " + make_tuple_string, sizeof...(TS)>::make( tuple ) + "}"; 757 | } 758 | 759 | #endif // lest_CPP11_OR_GREATER 760 | 761 | template< typename L, typename R > 762 | std::string to_string( L const & lhs, std::string op, R const & rhs ) 763 | { 764 | std::ostringstream os; os << to_string( lhs ) << " " << op << " " << to_string( rhs ); return os.str(); 765 | } 766 | 767 | template< typename L > 768 | struct expression_lhs 769 | { 770 | L lhs; 771 | 772 | expression_lhs( L lhs_) : lhs( lhs_) {} 773 | 774 | operator result() { return result( !!lhs, to_string( lhs ) ); } 775 | 776 | template< typename R > result operator==( R const & rhs ) { return result( lhs == rhs, to_string( lhs, "==", rhs ) ); } 777 | template< typename R > result operator!=( R const & rhs ) { return result( lhs != rhs, to_string( lhs, "!=", rhs ) ); } 778 | template< typename R > result operator< ( R const & rhs ) { return result( lhs < rhs, to_string( lhs, "<" , rhs ) ); } 779 | template< typename R > result operator<=( R const & rhs ) { return result( lhs <= rhs, to_string( lhs, "<=", rhs ) ); } 780 | template< typename R > result operator> ( R const & rhs ) { return result( lhs > rhs, to_string( lhs, ">" , rhs ) ); } 781 | template< typename R > result operator>=( R const & rhs ) { return result( lhs >= rhs, to_string( lhs, ">=", rhs ) ); } 782 | }; 783 | 784 | struct expression_decomposer 785 | { 786 | template< typename L > 787 | expression_lhs operator<< ( L const & operand ) 788 | { 789 | return expression_lhs( operand ); 790 | } 791 | }; 792 | 793 | // Reporter: 794 | 795 | #if lest_FEATURE_COLOURISE 796 | 797 | inline text red ( text words ) { return "\033[1;31m" + words + "\033[0m"; } 798 | inline text green( text words ) { return "\033[1;32m" + words + "\033[0m"; } 799 | inline text gray ( text words ) { return "\033[1;30m" + words + "\033[0m"; } 800 | 801 | inline bool starts_with( text words, text with ) 802 | { 803 | return 0 == words.find( with ); 804 | } 805 | 806 | inline text replace( text words, text from, text to ) 807 | { 808 | size_t pos = words.find( from ); 809 | return pos == std::string::npos ? words : words.replace( pos, from.length(), to ); 810 | } 811 | 812 | inline text colour( text words ) 813 | { 814 | if ( starts_with( words, "failed" ) ) return replace( words, "failed", red ( "failed" ) ); 815 | else if ( starts_with( words, "passed" ) ) return replace( words, "passed", green( "passed" ) ); 816 | 817 | return replace( words, "for", gray( "for" ) ); 818 | } 819 | 820 | inline bool is_cout( std::ostream & os ) { return &os == &std::cout; } 821 | 822 | struct colourise 823 | { 824 | const text words; 825 | 826 | colourise( text words ) 827 | : words( words ) {} 828 | 829 | // only colourise for std::cout, not for a stringstream as used in tests: 830 | 831 | std::ostream & operator()( std::ostream & os ) const 832 | { 833 | return is_cout( os ) ? os << colour( words ) : os << words; 834 | } 835 | }; 836 | 837 | inline std::ostream & operator<<( std::ostream & os, colourise words ) { return words( os ); } 838 | #else 839 | inline text colourise( text words ) { return words; } 840 | #endif 841 | 842 | inline text pluralise( text word,int n ) 843 | { 844 | return n == 1 ? word : word + "s"; 845 | } 846 | 847 | inline std::ostream & operator<<( std::ostream & os, comment note ) 848 | { 849 | return os << (note ? " " + note.info : "" ); 850 | } 851 | 852 | inline std::ostream & operator<<( std::ostream & os, location where ) 853 | { 854 | #ifdef __GNUG__ 855 | return os << where.file << ":" << where.line; 856 | #else 857 | return os << where.file << "(" << where.line << ")"; 858 | #endif 859 | } 860 | 861 | inline void report( std::ostream & os, message const & e, text test ) 862 | { 863 | os << e.where << ": " << colourise( e.kind ) << e.note << ": " << test << ": " << colourise( e.what() ) << std::endl; 864 | } 865 | 866 | // Test runner: 867 | 868 | #if lest_FEATURE_REGEX_SEARCH 869 | inline bool search( text re, text line ) 870 | { 871 | return std::regex_search( line, std::regex( re ) ); 872 | } 873 | #else 874 | inline bool case_insensitive_equal( char a, char b ) 875 | { 876 | return tolower( a ) == tolower( b ); 877 | } 878 | 879 | inline bool search( text part, text line ) 880 | { 881 | return std::search( 882 | line.begin(), line.end(), 883 | part.begin(), part.end(), case_insensitive_equal ) != line.end(); 884 | } 885 | #endif 886 | 887 | inline bool match( texts whats, text line ) 888 | { 889 | for ( texts::iterator what = whats.begin(); what != whats.end() ; ++what ) 890 | { 891 | if ( search( *what, line ) ) 892 | return true; 893 | } 894 | return false; 895 | } 896 | 897 | inline bool hidden( text name ) 898 | { 899 | #if lest_FEATURE_REGEX_SEARCH 900 | texts skipped; skipped.push_back( "\\[\\.\\]" ); skipped.push_back( "\\[hide\\]" ); 901 | #else 902 | texts skipped; skipped.push_back( "[." ); skipped.push_back( "[hide]" ); 903 | #endif 904 | return match( skipped, name ); 905 | } 906 | 907 | inline bool none( texts args ) 908 | { 909 | return args.size() == 0; 910 | } 911 | 912 | inline bool select( text name, texts include ) 913 | { 914 | if ( none( include ) ) 915 | { 916 | return ! hidden( name ); 917 | } 918 | 919 | bool any = false; 920 | for ( texts::reverse_iterator pos = include.rbegin(); pos != include.rend(); ++pos ) 921 | { 922 | text & part = *pos; 923 | 924 | if ( part == "@" || part == "*" ) 925 | return true; 926 | 927 | if ( search( part, name ) ) 928 | return true; 929 | 930 | if ( '!' == part[0] ) 931 | { 932 | any = true; 933 | if ( search( part.substr(1), name ) ) 934 | return false; 935 | } 936 | else 937 | { 938 | any = false; 939 | } 940 | } 941 | return any && ! hidden( name ); 942 | } 943 | 944 | inline int indefinite( int repeat ) { return repeat == -1; } 945 | 946 | #if lest_CPP11_OR_GREATER 947 | typedef std::mt19937::result_type seed_t; 948 | #else 949 | typedef unsigned int seed_t; 950 | #endif 951 | 952 | struct options 953 | { 954 | options() 955 | : help(false), abort(false), count(false), list(false), tags(false), time(false) 956 | , pass(false), zen(false), lexical(false), random(false), verbose(false), version(false), repeat(1), seed(0) {} 957 | 958 | bool help; 959 | bool abort; 960 | bool count; 961 | bool list; 962 | bool tags; 963 | bool time; 964 | bool pass; 965 | bool zen; 966 | bool lexical; 967 | bool random; 968 | bool verbose; 969 | bool version; 970 | int repeat; 971 | seed_t seed; 972 | }; 973 | 974 | struct env 975 | { 976 | std::ostream & os; 977 | options opt; 978 | text testing; 979 | std::vector< text > ctx; 980 | 981 | env( std::ostream & out, options option ) 982 | : os( out ), opt( option ), testing(), ctx() {} 983 | 984 | env & operator()( text test ) 985 | { 986 | clear(); testing = test; return *this; 987 | } 988 | 989 | bool abort() { return opt.abort; } 990 | bool pass() { return opt.pass; } 991 | bool zen() { return opt.zen; } 992 | 993 | void clear() { ctx.clear(); } 994 | void pop() { ctx.pop_back(); } 995 | void push( text proposition ) { ctx.push_back( proposition ); } 996 | 997 | text context() { return testing + sections(); } 998 | 999 | text sections() 1000 | { 1001 | if ( ! opt.verbose ) 1002 | return ""; 1003 | 1004 | text msg; 1005 | for( size_t i = 0; i != ctx.size(); ++i ) 1006 | { 1007 | msg += "\n " + ctx[i]; 1008 | } 1009 | return msg; 1010 | } 1011 | }; 1012 | 1013 | struct ctx 1014 | { 1015 | env & environment; 1016 | bool once; 1017 | 1018 | ctx( env & environment_, text proposition_ ) 1019 | : environment( environment_), once( true ) 1020 | { 1021 | environment.push( proposition_); 1022 | } 1023 | 1024 | ~ctx() 1025 | { 1026 | #if lest_CPP17_OR_GREATER 1027 | if ( std::uncaught_exceptions() == 0 ) 1028 | #else 1029 | if ( ! std::uncaught_exception() ) 1030 | #endif 1031 | { 1032 | environment.pop(); 1033 | } 1034 | } 1035 | 1036 | operator bool() { bool result = once; once = false; return result; } 1037 | }; 1038 | 1039 | struct action 1040 | { 1041 | std::ostream & os; 1042 | 1043 | action( std::ostream & out ) : os( out ) {} 1044 | 1045 | operator int() { return 0; } 1046 | bool abort() { return false; } 1047 | action & operator()( test ) { return *this; } 1048 | 1049 | private: 1050 | action( action const & ); 1051 | void operator=( action const & ); 1052 | }; 1053 | 1054 | struct print : action 1055 | { 1056 | print( std::ostream & out ) : action( out ) {} 1057 | 1058 | print & operator()( test testing ) 1059 | { 1060 | os << testing.name << "\n"; return *this; 1061 | } 1062 | }; 1063 | 1064 | inline texts tags( text name, texts result = texts() ) 1065 | { 1066 | size_t none = std::string::npos; 1067 | size_t lb = name.find_first_of( "[" ); 1068 | size_t rb = name.find_first_of( "]" ); 1069 | 1070 | if ( lb == none || rb == none ) 1071 | return result; 1072 | 1073 | result.push_back( name.substr( lb, rb - lb + 1 ) ); 1074 | 1075 | return tags( name.substr( rb + 1 ), result ); 1076 | } 1077 | 1078 | struct ptags : action 1079 | { 1080 | std::set result; 1081 | 1082 | ptags( std::ostream & out ) : action( out ), result() {} 1083 | 1084 | ptags & operator()( test testing ) 1085 | { 1086 | texts tags_( tags( testing.name ) ); 1087 | for ( texts::iterator pos = tags_.begin(); pos != tags_.end() ; ++pos ) 1088 | result.insert( *pos ); 1089 | 1090 | return *this; 1091 | } 1092 | 1093 | ~ptags() 1094 | { 1095 | std::copy( result.begin(), result.end(), std::ostream_iterator( os, "\n" ) ); 1096 | } 1097 | }; 1098 | 1099 | struct count : action 1100 | { 1101 | int n; 1102 | 1103 | count( std::ostream & out ) : action( out ), n( 0 ) {} 1104 | 1105 | count & operator()( test ) { ++n; return *this; } 1106 | 1107 | ~count() 1108 | { 1109 | os << n << " selected " << pluralise("test", n) << "\n"; 1110 | } 1111 | }; 1112 | 1113 | #if lest_FEATURE_TIME 1114 | 1115 | #if lest_PLATFORM_IS_WINDOWS 1116 | # if ! lest_CPP11_OR_GREATER && ! lest_COMPILER_MSVC_VERSION 1117 | typedef unsigned long uint64_t; 1118 | # elif lest_COMPILER_MSVC_VERSION >= 60 && lest_COMPILER_MSVC_VERSION < 100 1119 | typedef /*un*/signed __int64 uint64_t; 1120 | # else 1121 | using ::uint64_t; 1122 | # endif 1123 | #else 1124 | # if ! lest_CPP11_OR_GREATER 1125 | typedef unsigned long long uint64_t; 1126 | # endif 1127 | #endif 1128 | 1129 | #if lest_PLATFORM_IS_WINDOWS 1130 | inline uint64_t current_ticks() 1131 | { 1132 | static LARGE_INTEGER hz = {{ 0,0 }}, hzo = {{ 0,0 }}; 1133 | if ( ! hz.QuadPart ) 1134 | { 1135 | QueryPerformanceFrequency( &hz ); 1136 | QueryPerformanceCounter ( &hzo ); 1137 | } 1138 | LARGE_INTEGER t = {{ 0,0 }}; QueryPerformanceCounter( &t ); 1139 | 1140 | return uint64_t( ( ( t.QuadPart - hzo.QuadPart ) * 1000000 ) / hz.QuadPart ); 1141 | } 1142 | #else 1143 | inline uint64_t current_ticks() 1144 | { 1145 | timeval t; gettimeofday( &t, lest_nullptr ); 1146 | return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); 1147 | } 1148 | #endif 1149 | 1150 | struct timer 1151 | { 1152 | const uint64_t start_ticks; 1153 | 1154 | timer() : start_ticks( current_ticks() ) {} 1155 | 1156 | double elapsed_seconds() const 1157 | { 1158 | return static_cast( current_ticks() - start_ticks ) / 1e6; 1159 | } 1160 | }; 1161 | 1162 | struct times : action 1163 | { 1164 | env output; 1165 | int selected; 1166 | int failures; 1167 | 1168 | timer total; 1169 | 1170 | times( std::ostream & out, options option ) 1171 | : action( out ), output( out, option ), selected( 0 ), failures( 0 ), total() 1172 | { 1173 | os << std::setfill(' ') << std::fixed << std::setprecision( lest_FEATURE_TIME_PRECISION ); 1174 | } 1175 | 1176 | operator int() { return failures; } 1177 | 1178 | bool abort() { return output.abort() && failures > 0; } 1179 | 1180 | times & operator()( test testing ) 1181 | { 1182 | timer t; 1183 | 1184 | try 1185 | { 1186 | testing.behaviour( output( testing.name ) ); 1187 | } 1188 | catch( message const & ) 1189 | { 1190 | ++failures; 1191 | } 1192 | 1193 | os << std::setw(5) << ( 1000 * t.elapsed_seconds() ) << " ms: " << testing.name << "\n"; 1194 | 1195 | return *this; 1196 | } 1197 | 1198 | ~times() 1199 | { 1200 | os << "Elapsed time: " << std::setprecision(1) << total.elapsed_seconds() << " s\n"; 1201 | } 1202 | }; 1203 | #else 1204 | struct times : action { times( std::ostream & out, options ) : action( out ) {} }; 1205 | #endif 1206 | 1207 | struct confirm : action 1208 | { 1209 | env output; 1210 | int selected; 1211 | int failures; 1212 | 1213 | confirm( std::ostream & out, options option ) 1214 | : action( out ), output( out, option ), selected( 0 ), failures( 0 ) {} 1215 | 1216 | operator int() { return failures; } 1217 | 1218 | bool abort() { return output.abort() && failures > 0; } 1219 | 1220 | confirm & operator()( test testing ) 1221 | { 1222 | try 1223 | { 1224 | ++selected; testing.behaviour( output( testing.name ) ); 1225 | } 1226 | catch( message const & e ) 1227 | { 1228 | ++failures; report( os, e, output.context() ); 1229 | } 1230 | return *this; 1231 | } 1232 | 1233 | ~confirm() 1234 | { 1235 | if ( failures > 0 ) 1236 | { 1237 | os << failures << " out of " << selected << " selected " << pluralise("test", selected) << " " << colourise( "failed.\n" ); 1238 | } 1239 | else if ( output.pass() ) 1240 | { 1241 | os << "All " << selected << " selected " << pluralise("test", selected) << " " << colourise( "passed.\n" ); 1242 | } 1243 | } 1244 | }; 1245 | 1246 | template< typename Action > 1247 | bool abort( Action & perform ) 1248 | { 1249 | return perform.abort(); 1250 | } 1251 | 1252 | template< typename Action > 1253 | Action & for_test( tests specification, texts in, Action & perform, int n = 1 ) 1254 | { 1255 | for ( int i = 0; indefinite( n ) || i < n; ++i ) 1256 | { 1257 | for ( tests::iterator pos = specification.begin(); pos != specification.end() ; ++pos ) 1258 | { 1259 | test & testing = *pos; 1260 | 1261 | if ( select( testing.name, in ) ) 1262 | if ( abort( perform( testing ) ) ) 1263 | return perform; 1264 | } 1265 | } 1266 | return perform; 1267 | } 1268 | 1269 | inline bool test_less( test const & a, test const & b ) { return a.name < b.name; } 1270 | 1271 | inline void sort( tests & specification ) 1272 | { 1273 | std::sort( specification.begin(), specification.end(), test_less ); 1274 | } 1275 | 1276 | // Use struct to avoid VC6 error C2664 when using free function: 1277 | 1278 | struct rng { int operator()( int n ) { return lest::rand() % n; } }; 1279 | 1280 | inline void shuffle( tests & specification, options option ) 1281 | { 1282 | #if lest_CPP11_OR_GREATER 1283 | std::shuffle( specification.begin(), specification.end(), std::mt19937( option.seed ) ); 1284 | #else 1285 | lest::srand( option.seed ); 1286 | 1287 | rng generator; 1288 | std::random_shuffle( specification.begin(), specification.end(), generator ); 1289 | #endif 1290 | } 1291 | 1292 | inline int stoi( text num ) 1293 | { 1294 | return static_cast( lest::strtol( num.c_str(), lest_nullptr, 10 ) ); 1295 | } 1296 | 1297 | inline bool is_number( text arg ) 1298 | { 1299 | const text digits = "0123456789"; 1300 | return text::npos != arg.find_first_of ( digits ) 1301 | && text::npos == arg.find_first_not_of( digits ); 1302 | } 1303 | 1304 | inline seed_t seed( text opt, text arg ) 1305 | { 1306 | // std::time_t: implementation dependent 1307 | 1308 | if ( arg == "time" ) 1309 | return static_cast( time( lest_nullptr ) ); 1310 | 1311 | if ( is_number( arg ) ) 1312 | return static_cast( lest::stoi( arg ) ); 1313 | 1314 | throw std::runtime_error( "expecting 'time' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); 1315 | } 1316 | 1317 | inline int repeat( text opt, text arg ) 1318 | { 1319 | const int num = lest::stoi( arg ); 1320 | 1321 | if ( indefinite( num ) || num >= 0 ) 1322 | return num; 1323 | 1324 | throw std::runtime_error( "expecting '-1' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); 1325 | } 1326 | 1327 | inline std::pair 1328 | split_option( text arg ) 1329 | { 1330 | text::size_type pos = arg.rfind( '=' ); 1331 | 1332 | return pos == text::npos 1333 | ? std::make_pair( arg, text() ) 1334 | : std::make_pair( arg.substr( 0, pos ), arg.substr( pos + 1 ) ); 1335 | } 1336 | 1337 | inline std::pair 1338 | split_arguments( texts args ) 1339 | { 1340 | options option; texts in; 1341 | 1342 | bool in_options = true; 1343 | 1344 | for ( texts::iterator pos = args.begin(); pos != args.end() ; ++pos ) 1345 | { 1346 | text opt, val, arg = *pos; 1347 | tie( opt, val ) = split_option( arg ); 1348 | 1349 | if ( in_options ) 1350 | { 1351 | if ( opt[0] != '-' ) { in_options = false; } 1352 | else if ( opt == "--" ) { in_options = false; continue; } 1353 | else if ( opt == "-h" || "--help" == opt ) { option.help = true; continue; } 1354 | else if ( opt == "-a" || "--abort" == opt ) { option.abort = true; continue; } 1355 | else if ( opt == "-c" || "--count" == opt ) { option.count = true; continue; } 1356 | else if ( opt == "-g" || "--list-tags" == opt ) { option.tags = true; continue; } 1357 | else if ( opt == "-l" || "--list-tests" == opt ) { option.list = true; continue; } 1358 | else if ( opt == "-t" || "--time" == opt ) { option.time = true; continue; } 1359 | else if ( opt == "-p" || "--pass" == opt ) { option.pass = true; continue; } 1360 | else if ( opt == "-z" || "--pass-zen" == opt ) { option.zen = true; continue; } 1361 | else if ( opt == "-v" || "--verbose" == opt ) { option.verbose = true; continue; } 1362 | else if ( "--version" == opt ) { option.version = true; continue; } 1363 | else if ( opt == "--order" && "declared" == val ) { /* by definition */ ; continue; } 1364 | else if ( opt == "--order" && "lexical" == val ) { option.lexical = true; continue; } 1365 | else if ( opt == "--order" && "random" == val ) { option.random = true; continue; } 1366 | else if ( opt == "--random-seed" ) { option.seed = seed ( "--random-seed", val ); continue; } 1367 | else if ( opt == "--repeat" ) { option.repeat = repeat( "--repeat" , val ); continue; } 1368 | else throw std::runtime_error( "unrecognised option '" + opt + "' (try option --help)" ); 1369 | } 1370 | in.push_back( arg ); 1371 | } 1372 | option.pass = option.pass || option.zen; 1373 | 1374 | return std::make_pair( option, in ); 1375 | } 1376 | 1377 | inline int usage( std::ostream & os ) 1378 | { 1379 | os << 1380 | "\nUsage: test [options] [test-spec ...]\n" 1381 | "\n" 1382 | "Options:\n" 1383 | " -h, --help this help message\n" 1384 | " -a, --abort abort at first failure\n" 1385 | " -c, --count count selected tests\n" 1386 | " -g, --list-tags list tags of selected tests\n" 1387 | " -l, --list-tests list selected tests\n" 1388 | " -p, --pass also report passing tests\n" 1389 | " -z, --pass-zen ... without expansion\n" 1390 | #if lest_FEATURE_TIME 1391 | " -t, --time list duration of selected tests\n" 1392 | #endif 1393 | " -v, --verbose also report passing or failing sections\n" 1394 | " --order=declared use source code test order (default)\n" 1395 | " --order=lexical use lexical sort test order\n" 1396 | " --order=random use random test order\n" 1397 | " --random-seed=n use n for random generator seed\n" 1398 | " --random-seed=time use time for random generator seed\n" 1399 | " --repeat=n repeat selected tests n times (-1: indefinite)\n" 1400 | " --version report lest version and compiler used\n" 1401 | " -- end options\n" 1402 | "\n" 1403 | "Test specification:\n" 1404 | " \"@\", \"*\" all tests, unless excluded\n" 1405 | " empty all tests, unless tagged [hide] or [.optional-name]\n" 1406 | #if lest_FEATURE_REGEX_SEARCH 1407 | " \"re\" select tests that match regular expression\n" 1408 | " \"!re\" omit tests that match regular expression\n" 1409 | #else 1410 | " \"text\" select tests that contain text (case insensitive)\n" 1411 | " \"!text\" omit tests that contain text (case insensitive)\n" 1412 | #endif 1413 | ; 1414 | return 0; 1415 | } 1416 | 1417 | inline text compiler() 1418 | { 1419 | std::ostringstream os; 1420 | #if defined (__clang__ ) 1421 | os << "clang " << __clang_version__; 1422 | #elif defined (__GNUC__ ) 1423 | os << "gcc " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__; 1424 | #elif defined ( _MSC_VER ) 1425 | os << "MSVC " << lest_COMPILER_MSVC_VERSION << " (" << _MSC_VER << ")"; 1426 | #else 1427 | os << "[compiler]"; 1428 | #endif 1429 | return os.str(); 1430 | } 1431 | 1432 | inline int version( std::ostream & os ) 1433 | { 1434 | os << "lest version " << lest_VERSION << "\n" 1435 | << "Compiled with " << compiler() << " on " << __DATE__ << " at " << __TIME__ << ".\n" 1436 | << "For more information, see https://github.com/martinmoene/lest.\n"; 1437 | return 0; 1438 | } 1439 | 1440 | inline int run( tests specification, texts arguments, std::ostream & os = std::cout ) 1441 | { 1442 | try 1443 | { 1444 | options option; texts in; 1445 | tie( option, in ) = split_arguments( arguments ); 1446 | 1447 | if ( option.lexical ) { sort( specification ); } 1448 | if ( option.random ) { shuffle( specification, option ); } 1449 | 1450 | if ( option.help ) { return usage ( os ); } 1451 | if ( option.version ) { return version( os ); } 1452 | if ( option.count ) { count count_( os ); return for_test( specification, in, count_ ); } 1453 | if ( option.list ) { print print_( os ); return for_test( specification, in, print_ ); } 1454 | if ( option.tags ) { ptags ptags_( os ); return for_test( specification, in, ptags_ ); } 1455 | if ( option.time ) { times times_( os, option ); return for_test( specification, in, times_ ); } 1456 | 1457 | { confirm confirm_( os, option ); return for_test( specification, in, confirm_, option.repeat ); } 1458 | } 1459 | catch ( std::exception const & e ) 1460 | { 1461 | os << "Error: " << e.what() << "\n"; 1462 | return 1; 1463 | } 1464 | } 1465 | 1466 | // VC6: make(first,last) replaces cont(first,last) 1467 | 1468 | template< typename C, typename T > 1469 | C make( T const * first, T const * const last ) 1470 | { 1471 | C result; 1472 | for ( ; first != last; ++first ) 1473 | { 1474 | result.push_back( *first ); 1475 | } 1476 | return result; 1477 | } 1478 | 1479 | inline tests make_tests( test const * first, test const * const last ) 1480 | { 1481 | return make( first, last ); 1482 | } 1483 | 1484 | inline texts make_texts( char const * const * first, char const * const * last ) 1485 | { 1486 | return make( first, last ); 1487 | } 1488 | 1489 | // Traversal of test[N] (test_specification[N]) set up to also work with MSVC6: 1490 | 1491 | template< typename C > test const * test_begin( C const & c ) { return &*c; } 1492 | template< typename C > test const * test_end( C const & c ) { return test_begin( c ) + lest_DIMENSION_OF( c ); } 1493 | 1494 | template< typename C > char const * const * text_begin( C const & c ) { return &*c; } 1495 | template< typename C > char const * const * text_end( C const & c ) { return text_begin( c ) + lest_DIMENSION_OF( c ); } 1496 | 1497 | template< typename C > tests make_tests( C const & c ) { return make_tests( test_begin( c ), test_end( c ) ); } 1498 | template< typename C > texts make_texts( C const & c ) { return make_texts( text_begin( c ), text_end( c ) ); } 1499 | 1500 | inline int run( tests const & specification, int argc, char ** argv, std::ostream & os = std::cout ) 1501 | { 1502 | return run( specification, make_texts( argv + 1, argv + argc ), os ); 1503 | } 1504 | 1505 | inline int run( tests const & specification, std::ostream & os = std::cout ) 1506 | { 1507 | std::cout.sync_with_stdio( false ); 1508 | return (min)( run( specification, texts(), os ), exit_max_value ); 1509 | } 1510 | 1511 | template< typename C > 1512 | int run( C const & specification, texts args, std::ostream & os = std::cout ) 1513 | { 1514 | return run( make_tests( specification ), args, os ); 1515 | } 1516 | 1517 | template< typename C > 1518 | int run( C const & specification, int argc, char ** argv, std::ostream & os = std::cout ) 1519 | { 1520 | return run( make_tests( specification ), argv, argc, os ); 1521 | } 1522 | 1523 | template< typename C > 1524 | int run( C const & specification, std::ostream & os = std::cout ) 1525 | { 1526 | return run( make_tests( specification ), os ); 1527 | } 1528 | 1529 | } // namespace lest 1530 | 1531 | #if defined (__clang__) 1532 | # pragma clang diagnostic pop 1533 | #elif defined (__GNUC__) 1534 | # pragma GCC diagnostic pop 1535 | #endif 1536 | 1537 | #endif // LEST_LEST_HPP_INCLUDED 1538 | -------------------------------------------------------------------------------- /tests/test_clue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Martin Moene 2 | // 3 | // clue is based on ideas by Mark Nelson, see article at 4 | // http://www.drdobbs.com/cpp/blundering-into-the-one-definition-rule/240166489 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #define clue_LOG_TO_STRING 10 | 11 | #include "clue.hpp" 12 | #include "lest_cpp03.hpp" 13 | 14 | using namespace clue; 15 | 16 | lest::tests & specification() 17 | { 18 | static lest::tests tests; 19 | return tests; 20 | } 21 | 22 | #define CASE( name ) lest_CASE( specification(), name ) 23 | 24 | #define clue_PRESENT( x ) \ 25 | std::cout << #x << ": " << x << "\n" 26 | 27 | CASE( "clue version" "[.clue][.version]" ) 28 | { 29 | clue_PRESENT( clue_MAJOR ); 30 | clue_PRESENT( clue_MINOR ); 31 | clue_PRESENT( clue_PATCH ); 32 | clue_PRESENT( clue_VERSION ); 33 | } 34 | 35 | CASE( "Function is_true() to suppress warning \"expression has no effect\" acts as identity function." ) 36 | { 37 | EXPECT( false == is_true( false ) ); 38 | EXPECT( true == is_true( true ) ); 39 | } 40 | 41 | CASE( "Function text_or() gives text for non-empty text." ) 42 | { 43 | EXPECT( "text" == text_or( "text", "or_text" ) ); 44 | } 45 | 46 | CASE( "Function text_or() gives or_text for empty text." ) 47 | { 48 | EXPECT( "or_text" == text_or( "", "or_text" ) ); 49 | } 50 | 51 | CASE( "Function text_with_or() gives enclosed text for non-empty text." ) 52 | { 53 | EXPECT( "^text$" == text_with_or( "^", "text", "$", "or_text" ) ); 54 | } 55 | 56 | CASE( "Function text_with_or() gives or_text for empty text." ) 57 | { 58 | EXPECT( "or_text" == text_with_or( "^", "", "$", "or_text" ) ); 59 | } 60 | 61 | CASE( "Function to_module_text() prepends a colon to text." ) 62 | { 63 | EXPECT( ": Module" == to_module_text( "Module" ) ); 64 | } 65 | 66 | CASE( "Function to_severity_text() return correct text for severity." ) 67 | { 68 | struct Table { 69 | int severity; char const * text; 70 | } 71 | table[] = 72 | { 73 | { clue_LOG_SEV_NONE , clue_LOG_SEV_NONE_TEXT, }, 74 | { clue_LOG_SEV_EMERGENCY, clue_LOG_SEV_EMERGENCY_TEXT, }, 75 | { clue_LOG_SEV_ALERT , clue_LOG_SEV_ALERT_TEXT, }, 76 | { clue_LOG_SEV_CRITICAL , clue_LOG_SEV_CRITICAL_TEXT, }, 77 | { clue_LOG_SEV_ERROR , clue_LOG_SEV_ERROR_TEXT, }, 78 | { clue_LOG_SEV_WARNING , clue_LOG_SEV_WARNING_TEXT, }, 79 | { clue_LOG_SEV_NOTICE , clue_LOG_SEV_NOTICE_TEXT, }, 80 | { clue_LOG_SEV_INFO , clue_LOG_SEV_INFO_TEXT, }, 81 | { clue_LOG_SEV_DEBUG , clue_LOG_SEV_DEBUG_TEXT, }, 82 | { clue_LOG_SEV_MAX , clue_LOG_SEV_DEBUG_TEXT, }, 83 | { 0, 0, }, 84 | }; 85 | 86 | for ( Table * pos = table; pos->text; ++pos ) 87 | { 88 | EXPECT( pos->text == to_severity_text( pos->severity ) ); 89 | } 90 | } 91 | 92 | CASE( "Function to_severities_text() with clue_LOG_SEV_NONE gives postfix dot only" ) 93 | { 94 | EXPECT( "." == to_severities_text( clue_LOG_SEV_NONE ) ); 95 | } 96 | 97 | CASE( "Function to_severities_text() with clue_LOG_SEV_DEBUG gives all severities." ) 98 | { 99 | char const * const text = "Emergency, Alert, Critical, Error, Warning, Notice, Info, Debug."; 100 | EXPECT( text == to_severities_text( clue_LOG_SEV_DEBUG ) ); 101 | } 102 | 103 | CASE( "Function to_severities_text() with clue_LOG_SEV_ALERT gives two severities." ) 104 | { 105 | char const * const text = "Emergency, Alert."; 106 | EXPECT( text == to_severities_text( clue_LOG_SEV_ALERT ) ); 107 | } 108 | 109 | CASE( "Function to_severities_text() with non-default postfix ends correctly." ) 110 | { 111 | char const * const text = "Emergency, Alert$$"; 112 | EXPECT( text == to_severities_text( clue_LOG_SEV_ALERT, "$$" ) ); 113 | } 114 | 115 | CASE( "Macro LOG_ALERT() correctly records severity." ) 116 | { 117 | the_log().clear(); 118 | LOG_ALERT( "" ); 119 | EXPECT( the_log().severity() == clue_LOG_SEV_ALERT ); 120 | } 121 | 122 | CASE( "Macro LOG_TO_STRING() correctly records text." ) 123 | { 124 | std::string text = "hello world"; 125 | 126 | the_log().clear(); 127 | LOG_ALERT( text ); 128 | EXPECT( the_log().text() == text ); 129 | } 130 | 131 | int main( int argc, char * argv[] ) 132 | { 133 | return lest::run( specification(), argc, argv ); 134 | } 135 | 136 | // cl -nologo -W3 -EHsc -I.. test_clue.cpp test_clue_part2.cpp && test_clue 137 | // cl -nologo -Wall -EHsc -I.. test_clue.cpp test_clue_part2.cpp && test_clue 138 | // g++ -Wall -Wextra -Weffc++ -std=c++11 -I.. -o test_clue.exe test_clue.cpp test_clue_part2.cpp && test_clue 139 | // g++ -Wall -Wextra -Weffc++ -std=c++03 -I.. -o test_clue.exe test_clue.cpp test_clue_part2.cpp && test_clue 140 | -------------------------------------------------------------------------------- /tests/test_clue_part2.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Martin Moene 2 | // 3 | // clue is based on ideas by Mark Nelson, see article at 4 | // http://www.drdobbs.com/cpp/blundering-into-the-one-definition-rule/240166489 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #define clue_LOG_TO_STRING 10 | #define clue_LOG_EXPRESSION( sev, expr ) \ 11 | clue_LOG_STRING_EXPRESSION( my_log, sev, expr ) 12 | 13 | #include "clue.hpp" 14 | #include "lest_cpp03.hpp" 15 | 16 | using namespace clue; 17 | 18 | extern lest::tests & specification(); 19 | 20 | #define CASE( name ) lest_CASE( specification(), name ) 21 | 22 | namespace { 23 | 24 | strlog my_log; 25 | 26 | CASE( "Macro LOG_ALERT() correctly records severity with user-provided string logger." ) 27 | { 28 | my_log.clear(); 29 | LOG_ALERT( "" ); 30 | EXPECT( my_log.severity() == clue_LOG_SEV_ALERT ); 31 | } 32 | 33 | CASE( "Macro LOG_TO_STRING() correctly records text with user-provided string logger." ) 34 | { 35 | std::string text = "hello world"; 36 | 37 | my_log.clear(); 38 | LOG_ALERT( text ); 39 | EXPECT( my_log.text() == text ); 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------