File: testutils.h

package info (click to toggle)
martchus-cpp-utilities 5.28.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,352 kB
  • sloc: cpp: 12,471; awk: 18; ansic: 12; makefile: 10
file content (398 lines) | stat: -rw-r--r-- 15,633 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#ifndef TESTUTILS_H
#define TESTUTILS_H

#include "../application/argumentparser.h"
#include "../chrono/format.h"
#include "../misc/traits.h"

#include <iomanip>
#include <optional>
#include <ostream>
#include <string>

#if defined(PLATFORM_UNIX) || defined(CPP_UTILITIES_BOOST_PROCESS)
#define CPP_UTILITIES_HAS_EXEC_APP
#endif

// ensure CppUnit's macros produce unique variable names when doing unity builds
#if defined(__COUNTER__)
#undef CPPUNIT_UNIQUE_COUNTER
#define CPPUNIT_UNIQUE_COUNTER __COUNTER__
#endif

namespace CppUtilities {

/*!
 * \brief The WorkingCopyMode enum specifies additional options to influence behavior of TestApplication::workingCopyPath().
 */
enum class WorkingCopyMode {
    CreateCopy, /**< a working copy of the test file is created */
    NoCopy, /**< only the directory for the working copy is created but not the test file itself */
    Cleanup, /**< the directory for the working copy is created if needed or a previously existing file is deleted */
};

class CPP_UTILITIES_EXPORT TestApplication {
public:
    // construction/destruction
    explicit TestApplication();
    explicit TestApplication(int argc, const char *const *argv);
    ~TestApplication();
    operator bool() const;

    // helper for tests
    std::string testFilePath(const std::string &relativeTestFilePath) const;
    std::string testDirPath(const std::string &relativeTestDirPath) const;
    std::string workingCopyPath(const std::string &relativeTestFilePath, WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
    std::string workingCopyPathAs(const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath,
        WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
#ifdef CPP_UTILITIES_HAS_EXEC_APP
    int execApp(const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1) const;
#endif

    // read-only accessors
    const std::vector<std::string> &testFilePaths() const;
    const std::string &workingDirectory() const;
    const char *applicationPath();
    bool unitsSpecified() const;
    const std::vector<const char *> &units() const;
    bool onlyListUnits() const;

    // static read-only accessors
    static const TestApplication *instance();
    static const char *appPath();

private:
    static std::string readTestfilePathFromEnv();
    static std::vector<std::string> readTestfilePathFromSrcRef();

    ArgumentParser m_parser;
    OperationArgument m_listArg;
    OperationArgument m_runArg;
    ConfigValueArgument m_testFilesPathArg;
    ConfigValueArgument m_applicationPathArg;
    ConfigValueArgument m_workingDirArg;
    ConfigValueArgument m_unitsArg;
    std::vector<std::string> m_testFilesPaths;
    std::string m_workingDir;
    bool m_valid;
    static TestApplication *s_instance;
};

/*!
 * \brief Returns whether the TestApplication instance is valid.
 *
 * An instance is considered invalid if an error occurred when
 * parsing the command line arguments.
 */
inline TestApplication::operator bool() const
{
    return m_valid;
}

/*!
 * \brief Returns the current TestApplication instance.
 */
inline const TestApplication *TestApplication::instance()
{
    return TestApplication::s_instance;
}

/*!
 * \brief Returns the application path or an empty string if no application path has been set.
 */
inline const char *TestApplication::appPath()
{
    return s_instance ? s_instance->applicationPath() : "";
}

/*!
 * \brief Returns the list of directories to look for test files.
 */
inline const std::vector<std::string> &TestApplication::testFilePaths() const
{
    return m_testFilesPaths;
}

/*!
 * \brief Returns the directory which is supposed to used for storing files created by tests.
 */
inline const std::string &TestApplication::workingDirectory() const
{
    return m_workingDir;
}

/*!
 * \brief Returns the application path or an empty string if no application path has been set.
 */
inline const char *TestApplication::applicationPath()
{
    return m_applicationPathArg.firstValue() ? m_applicationPathArg.firstValue() : "";
}

/*!
 * \brief Returns whether particular units have been specified.
 */
inline bool TestApplication::unitsSpecified() const
{
    return m_unitsArg.isPresent();
}

/*!
 * \brief Returns the specified test units.
 * \remarks The units argument must be present.
 */
inline const std::vector<const char *> &TestApplication::units() const
{
    return m_unitsArg.values();
}

/*!
 * \brief Returns whether the test application should only list available units and not actually run any tests.
 */
inline bool TestApplication::onlyListUnits() const
{
    return m_listArg.isPresent();
}

/*!
 * \brief Convenience function to invoke TestApplication::testFilePath().
 * \remarks A TestApplication must be present.
 */
inline CPP_UTILITIES_EXPORT std::string testFilePath(const std::string &relativeTestFilePath)
{
    return TestApplication::instance()->testFilePath(relativeTestFilePath);
}

/*!
 * \brief Convenience function to invoke TestApplication::testDirPath().
 * \remarks A TestApplication must be present.
 */
inline CPP_UTILITIES_EXPORT std::string testDirPath(const std::string &relativeTestDirPath)
{
    return TestApplication::instance()->testDirPath(relativeTestDirPath);
}

/*!
 * \brief Convenience function to invoke TestApplication::workingCopyPath().
 * \remarks A TestApplication must be present.
 */
inline CPP_UTILITIES_EXPORT std::string workingCopyPath(const std::string &relativeTestFilePath, WorkingCopyMode mode = WorkingCopyMode::CreateCopy)
{
    return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeTestFilePath, mode);
}

/*!
 * \brief Convenience function to invoke TestApplication::workingCopyPathAs().
 * \remarks A TestApplication must be present.
 */
inline CPP_UTILITIES_EXPORT std::string workingCopyPathAs(
    const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath, WorkingCopyMode mode = WorkingCopyMode::CreateCopy)
{
    return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeWorkingCopyPath, mode);
}

#ifdef CPP_UTILITIES_HAS_EXEC_APP
/*!
 * \brief Convenience function which executes the application to be tested with the specified \a args.
 * \remarks A TestApplication must be present.
 * \sa TestApplication::execApp()
 */
inline CPP_UTILITIES_EXPORT int execApp(const char *const *args, std::string &output, std::string &errors)
{
    return TestApplication::instance()->execApp(args, output, errors);
}

CPP_UTILITIES_EXPORT int execHelperApp(
    const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
CPP_UTILITIES_EXPORT int execHelperAppInSearchPath(
    const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
#endif

/*!
 * \brief Allows printing std::optional objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
 */
template <typename Optional, Traits::EnableIf<Traits::IsSpecializationOf<Optional, std::optional>> * = nullptr>
inline std::ostream &operator<<(std::ostream &out, const Optional &optional)
{
    if (optional.has_value()) {
        return out << *optional;
    } else {
        return out << "[no value]";
    }
}

/*!
 * \brief The AsHexNumber class allows printing values asserted with cppunit (or similar test framework) using the
 *        hex system in the error case.
 */
template <typename T> class AsHexNumber {
public:
    /// \brief Constructs a new instance; use asHexNumber() for convenience instead.
    AsHexNumber(const T &value)
        : value(value)
    {
    }
    const T &value;
};

/*!
 * \brief Provides operator == required by CPPUNIT_ASSERT_EQUAL.
 */
template <typename T> bool operator==(const AsHexNumber<T> &lhs, const AsHexNumber<T> &rhs)
{
    return lhs.value == rhs.value;
}

/*!
 * \brief Provides the actual formatting of the output for AsHexNumber class.
 */
template <typename T> std::ostream &operator<<(std::ostream &out, const AsHexNumber<T> &value)
{
    return out << '0' << 'x' << std::hex << std::setfill('0') << std::setw(2) << unsigned(value.value) << std::dec;
}

/*!
 * \brief Wraps a value to be printed using the hex system in the error case when asserted
 *        with cppunit (or similar test framework).
 */
template <typename T> AsHexNumber<T> asHexNumber(const T &value)
{
    return AsHexNumber<T>(value);
}

/*!
 * \brief Wraps a value to be printed using the hex system in the error case when asserted
 *        with cppunit (or similar test framework).
 * \remarks Only affects integral types. Values of other types are printed as usual.
 */
template <typename T, Traits::EnableIf<std::is_integral<T>> * = nullptr> AsHexNumber<T> integralsAsHexNumber(const T &value)
{
    return AsHexNumber<T>(value);
}

/*!
 * \brief Wraps a value to be printed using the hex system in the error case when asserted
 *        with cppunit (or similar test framework).
 * \remarks Only affects integral types. Values of other types are printed as usual.
 */
template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const T &integralsAsHexNumber(const T &value)
{
    return value;
}

/*!
 * \brief Asserts successful execution of the application with the specified CLI \a args.
 *
 * The application is executed via TestApplication::execApp(). Output is stored in the std::string variables stdout
 * and stderr.
 *
 * \remarks Requires cppunit.
 */
#define TESTUTILS_ASSERT_EXEC(args) TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, 0)

/*!
 * \brief Asserts the execution of the application with the specified CLI \a args and the specified \a expectedExitStatus.
 *
 * The application is executed via TestApplication::execApp(). Output is stored in the std::string variables stdout
 * and stderr.
 *
 * \remarks Requires cppunit.
 */
#ifdef CPP_UTILITIES_BOOST_PROCESS
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus)                                                                                  \
    {                                                                                                                                                \
        const auto status = execApp(args, stdout, stderr);                                                                                           \
        if (status != expectedExitStatus) {                                                                                                          \
            CPPUNIT_FAIL(::CppUtilities::argsToString(                                                                                               \
                "app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr));                 \
        }                                                                                                                                            \
    }
#else
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus)                                                                                  \
    {                                                                                                                                                \
        const auto status = execApp(args, stdout, stderr);                                                                                           \
        if (!WIFEXITED(status)) {                                                                                                                    \
            CPPUNIT_FAIL(::CppUtilities::argsToString("app did not terminate normally\nstdout: ", stdout, "\nstderr: ", stderr));                    \
        }                                                                                                                                            \
        if (const auto exitStatus = WEXITSTATUS(status); exitStatus != expectedExitStatus) {                                                         \
            CPPUNIT_FAIL(::CppUtilities::argsToString(                                                                                               \
                "app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr));             \
        }                                                                                                                                            \
    }
#endif

/*!
 * \brief Asserts whether the specified \a string matches the specified \a regex.
 * \remarks Requires cppunit.
 */
#define TESTUTILS_ASSERT_LIKE_FLAGS(message, expectedRegex, regexFlags, actualString)                                                                \
    (CPPUNIT_NS::Asserter::failIf(!(std::regex_match(actualString, std::regex(expectedRegex, regexFlags))),                                          \
        CPPUNIT_NS::Message(                                                                                                                         \
            CppUtilities::argsToString('\"', actualString, "\"\n    not like\n\"", expectedRegex, '\"'), "Expression: " #actualString, message),     \
        CPPUNIT_SOURCELINE()))

/*!
 * \brief Asserts whether the specified \a string matches the specified \a regex.
 * \remarks Requires cppunit.
 */
#define TESTUTILS_ASSERT_LIKE(message, expectedRegex, actualString)                                                                                  \
    TESTUTILS_ASSERT_LIKE_FLAGS(message, expectedRegex, std::regex::ECMAScript, actualString)

/*!
 * \brief Allows printing pairs so key/values of maps/hashes can be asserted using CPPUNIT_ASSERT_EQUAL.
 */
template <typename Pair, CppUtilities::Traits::EnableIf<CppUtilities::Traits::IsSpecializationOf<Pair, std::pair>> * = nullptr>
inline std::ostream &operator<<(std::ostream &out, const Pair &pair)
{
    return out << "key: " << pair.first << "; value: " << pair.second << '\n';
}

/*!
 * \brief Allows printing iteratable objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
 */
template <typename Iteratable, Traits::EnableIf<Traits::IsIteratable<Iteratable>, Traits::Not<Traits::IsString<Iteratable>>> * = nullptr>
inline std::ostream &operator<<(std::ostream &out, const Iteratable &iteratable)
{
    out << '\n';
    std::size_t index = 0;
    for (const auto &item : iteratable) {
        out << std::setw(2) << index << ':' << ' ' << integralsAsHexNumber(item) << '\n';
        ++index;
    }
    return out;
}

/*!
 * \brief Contains literals to ease asserting with CPPUNIT_ASSERT_EQUAL.
 */
namespace Literals {
/*!
 * \brief Literal for std::size_t to ease asserting std::size_t with CPPUNIT_ASSERT_EQUAL.
 * \remarks Just using "ul"-suffix does not compile under 32-bit architecture!
 */
constexpr std::size_t operator"" _st(unsigned long long size)
{
    return static_cast<std::size_t>(size);
}

/*!
 * \brief Literal for uint64 to ease asserting uint64 with CPPUNIT_ASSERT_EQUAL.
 * \remarks Just using "ul"-suffix does not compile under 32-bit architecture!
 */
constexpr std::uint64_t operator"" _uint64(unsigned long long size)
{
    return static_cast<std::uint64_t>(size);
}

/*!
 * \brief Literal for int64 to ease asserting int64 with CPPUNIT_ASSERT_EQUAL.
 * \remarks Just using "l"-suffix does not compile under 32-bit architecture!
 */
constexpr std::int64_t operator"" _int64(unsigned long long size)
{
    return static_cast<std::int64_t>(size);
}
} // namespace Literals
} // namespace CppUtilities

#endif // TESTUTILS_H