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
|