├── .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 | [](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 |
--------------------------------------------------------------------------------