Implement basic test functionality

Closes #3
Closes #4
This commit is contained in:
RYAN 2024-09-03 01:43:56 +02:00
parent e15a1f3265
commit aab07c0720
Signed by: RYAN
GPG Key ID: 3BD93EABD1407B82
11 changed files with 701 additions and 2 deletions

27
arc/validate/asserts.hxx Normal file
View File

@ -0,0 +1,27 @@
#ifndef arc__validate__asserts_hxx_
#define arc__validate__asserts_hxx_
#include <arc/validate/except.hxx>
#include <source_location>
#include <string>
#define ARC_VALIDATE_ASSERT_TRUE(expr) \
::arc::validate::asserts::assert_true(expr)
namespace arc::validate::asserts
{
template<typename T>
void
assert_true(T const&, std::source_location = std::source_location::current());
template<typename T>
void
assert_false(T const&, std::source_location = std::source_location::current());
} // namespace arc::validate::asserts
#include <arc/validate/asserts.txx>
#endif

46
arc/validate/asserts.txx Normal file
View File

@ -0,0 +1,46 @@
namespace arc::validate::asserts
{
template<typename T>
void
assert_true(T const& expr, std::source_location origin)
{
struct extras_t
{
std::source_location origin;
void
print(std::ostream& o) const
{
o << " assertion failed: " << origin.file_name() << ':' << origin.line() << '\n';
}
};
if (expr != true) {
throw failure_t{origin, "assertion failed", extras_t{origin}};
}
}
template<typename T>
void
assert_false(T const& expr, std::source_location origin)
{
struct extras_t
{
std::source_location origin;
void
print(std::ostream& o) const
{
o << " assertion failed: " << origin.file_name() << ':' << origin.line() << '\n';
}
};
if (expr != false) {
throw failure_t{origin, "assertion failed", extras_t{origin}};
}
}
} // namespace arc::validate::asserts

View File

@ -0,0 +1,112 @@
#include <arc/validate/command-line.hxx>
namespace arc::validate
{
command_line_t
parse_command_line(std::vector<std::string> const& args)
{
command_line_t command_line;
bool only_files{false};
for (auto it = args.begin(); it != args.end(); ++it) {
auto const& option = *it;
// fixme: should empty options be an error?
//
if (option.size() < 1) {
continue;
}
if (only_files || '-' != option[0] || 1 == option.size()) {
throw command_line_error_t{"invalid file argument '" + option + "'"};
}
if ("--" == option) {
only_files = true;
continue;
}
if ("--version" == option) {
command_line.print_version = true;
continue;
}
if ("--help" == option) {
command_line.print_usage = true;
continue;
}
if ("--print-information" == option) {
command_line.print_information = true;
continue;
}
if ("--print-warnings" == option) {
command_line.print_warnings = true;
continue;
}
if ("--print-success" == option) {
command_line.print_success = true;
continue;
}
if ("--print-failure" == option) {
command_line.print_failure = true;
continue;
}
if ("--verbose" == option) {
command_line.print_information = true;
command_line.print_warnings = true;
command_line.print_success = true;
command_line.print_failure = true;
continue;
}
if ("-i" == option || "--include" == option) {
++it;
if (it == args.end()) {
throw command_line_error_t{"missing argument to '--include'"};
}
if (!command_line.exclude.empty()) {
throw command_line_error_t{"cannot specify both `-i, --include` and `-e, --exclude`"};
}
command_line.include.emplace(*it);
continue;
}
if ("-e" == option || "--exclude" == option) {
++it;
if (it == args.end()) {
throw command_line_error_t{"missing argument to '--exclude'"};
}
if (!command_line.include.empty()) {
throw command_line_error_t{"cannot specify both `-i, --include` and `-e, --exclude`"};
}
command_line.exclude.emplace(*it);
continue;
}
if ("-l" == option || "--list" == option) {
command_line.list = true;
continue;
}
throw command_line_error_t{"unrecognized option '" + option + "'"};
}
return command_line;
}
}

View File

@ -0,0 +1,46 @@
#ifndef arc__validate__command_line_hxx_
#define arc__validate__command_line_hxx_
#include <arc/validate/export.hxx>
#include <set>
#include <stdexcept>
#include <string>
#include <vector>
namespace arc::validate
{
struct LIBARC_VALIDATE_SYMEXPORT command_line_t
{
bool print_version{};
bool print_usage{};
bool print_information{};
bool print_warnings{};
bool print_success{};
bool print_failure{};
std::set<std::string> include;
std::set<std::string> exclude;
bool list{};
};
LIBARC_VALIDATE_SYMEXPORT
command_line_t
parse_command_line(std::vector<std::string> const&);
/// Exception class used to indicate command line error.
///
class LIBARC_VALIDATE_SYMEXPORT command_line_error_t
: public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
} // namespace arc::validate
#endif

27
arc/validate/except.cxx Normal file
View File

@ -0,0 +1,27 @@
#include <arc/validate/except.hxx>
namespace arc::validate
{
failure_t::
failure_t(std::source_location origin,
std::string message,
extras_t extras)
: extras_{std::move(extras)}
{}
failure_t::extras_t const&
failure_t::
extras() const
{
return extras_;
}
std::ostream&
operator<<(std::ostream& o, failure_t::extras_t const& extras)
{
extras.extras_->print(o);
return o;
}
} // namespace arc::validate

88
arc/validate/except.hxx Normal file
View File

@ -0,0 +1,88 @@
#ifndef arc__validate__except_hxx_
#define arc__validate__except_hxx_
#include <arc/validate/export.hxx>
#include <stdexcept>
#include <iostream>
#include <memory>
#include <source_location>
namespace arc::validate
{
class LIBARC_VALIDATE_SYMEXPORT skipped_t
{};
class LIBARC_VALIDATE_SYMEXPORT not_implemented_t
{};
class LIBARC_VALIDATE_SYMEXPORT failure_t
{
public:
class LIBARC_VALIDATE_SYMEXPORT extras_t
{
public:
template<typename T>
extras_t(T extras)
: extras_{std::make_shared<container_t<T>>(std::move(extras))}
{}
friend
std::ostream&
operator<<(std::ostream&, extras_t const&);
private:
struct concept_t
{
virtual
~concept_t() noexcept = default;
virtual
void
print(std::ostream&) const = 0;
};
template<typename T>
struct container_t
: concept_t
{
template<typename... Args>
container_t(Args&&... args)
: extras{std::forward<Args>(args)...}
{}
void
print(std::ostream& o) const
{
extras.print(o);
}
T extras;
};
std::shared_ptr<concept_t const> extras_;
};
failure_t(std::source_location,
std::string,
extras_t);
extras_t const&
extras() const;
private:
extras_t extras_;
};
LIBARC_VALIDATE_SYMEXPORT
std::ostream&
operator<<(std::ostream&, failure_t::extras_t const&);
} // namespace arc::validate
#endif

155
arc/validate/main.cxx Normal file
View File

@ -0,0 +1,155 @@
#include <arc/validate/command-line.hxx>
#include <arc/validate/except.hxx>
#include <arc/validate/main.hxx>
#include <arc/validate/test.hxx>
#include <arc/validate/version.hxx>
#include <iostream>
namespace arc::validate
{
static
void
print_version()
{
std::cout << "libarc-validate test runner (" << LIBARC_VALIDATE_VERSION_ID << ")\n";
}
static
void
print_usage(std::string const& execname)
{
std::cout << "Usage: " << execname << " [<option>...] [<file>...]\n"
<< '\n'
<< "Mandatory arguments to long options are mandatory for short options too.\n"
<< '\n'
<< " -- Treat all remaining arguments as files.\n"
<< " --version Print version information and exit.\n"
<< " --help Display this help and exit.\n"
<< " --print-information Print test information.\n"
<< " --print-warnings Print warnings.\n"
<< " --print-success Print successful test information.\n"
<< " --print-failure Print failed test information.\n"
<< " --verbose Print everything.\n"
<< " -i, --include <name> Include execution of test <name>.\n"
<< " -e, --exclude <name> Exclude execution of test <name>.\n"
<< " -l, --list List available tests and exit.\n"
<< '\n'
<< "If any filenames are specified on the command line the program will terminate.\n"
<< '\n'
<< "Unknown tests passed as arguments to --include or --exclude are ignored.\n"
<< '\n'
;
}
static
bool
execute_test(test_t const& test,
bool print_information,
bool print_warnings,
bool print_success,
bool print_failure)
{
if (print_information) {
std::cout << "Executing test: " << test.name() << '\n';
}
try {
test.function()();
if (print_success) {
std::cout << "Test passed: " << test.name() << '\n';
}
}
catch (skipped_t const&) {
if (print_information) {
std::cout << "Test skipped: " << test.name() << '\n';
}
}
catch (not_implemented_t const&) {
if (print_warnings) {
std::cout << "Test not implemented: " << test.name() << '\n';
}
}
catch (failure_t const& failure) {
if (print_failure) {
std::cout << "Test failed: " << test.name() << '\n';
std::cout << failure.extras() << '\n';
}
return false;
}
return true;
}
int
main(int argc, char* argv[])
{
return main(argv[0], {argv + 1, argv + argc});
}
int
main(std::string const& execname, std::vector<std::string> const& args)
try
{
auto command_line = parse_command_line(args);
if (command_line.print_version) {
print_version();
return 0;
}
if (command_line.print_usage) {
print_usage(execname);
return 0;
}
if (command_line.list) {
std::cout << "Available tests:\n\n";
for (auto const& j : test_t::get_tests()) {
std::cout << " - \"" << j->name() << "\"\n";
}
std::cout << '\n';
return 0;
}
// Let's execute some tests!
//
bool failed{false};
auto tests = test_t::get_tests(command_line.include, command_line.exclude);
for (auto const& j : tests) {
bool success = execute_test(
*j,
command_line.print_information,
command_line.print_warnings,
command_line.print_success,
command_line.print_failure
);
if (!success) {
failed = true;
}
}
if (failed) {
return 1;
}
return 0;
}
catch (std::exception const& ex) {
std::cerr << execname << ": error: " << ex.what() << ".\n";
return 1;
}
catch (...) {
std::cerr << execname << ": unknown error.\n";
return 1;
}
} // namespace arc::validate

22
arc/validate/main.hxx Normal file
View File

@ -0,0 +1,22 @@
#ifndef arc__validate__main_hxx_
#define arc__validate__main_hxx_
#include <arc/validate/export.hxx>
#include <string>
#include <vector>
namespace arc::validate
{
LIBARC_VALIDATE_SYMEXPORT
int
main(int, char*[]);
LIBARC_VALIDATE_SYMEXPORT
int
main(std::string const&, std::vector<std::string> const&);
} // namespace arc::validate
#endif

97
arc/validate/test.cxx Normal file
View File

@ -0,0 +1,97 @@
#include <arc/validate/test.hxx>
#include <stdexcept>
namespace arc::validate
{
std::map<std::string, test_t*> test_t::tests_;
test_t::
test_t(std::string file, int line, std::string name, std::function<void()> f)
: file_{std::move(file)},
line_{line},
name_{
name.empty() ? throw std::invalid_argument{"invalid name"} : name
},
function_{std::move(f)}
{
if (tests_.contains(name)) {
throw std::invalid_argument{"test already registered: " + name};
}
tests_.emplace(name, this);
}
test_t::
~test_t() noexcept
{
tests_.erase(name_);
}
std::string const&
test_t::
file() const
{
return file_;
}
int
test_t::
line() const
{
return line_;
}
std::string const&
test_t::
name() const
{
return name_;
}
std::function<void()> const&
test_t::
function() const
{
return function_;
}
std::vector<test_t*>
test_t::
get_tests()
{
std::vector<test_t*> tests;
for (auto const& j : tests_) {
tests.emplace_back(j.second);
}
return tests;
}
std::vector<test_t*>
test_t::
get_tests(std::set<std::string> const& include,
std::set<std::string> const& exclude)
{
std::vector<test_t*> tests;
auto all_tests = get_tests();
for (auto const& j : all_tests) {
if (!include.empty() && !include.contains(j->name())) {
continue;
}
if (exclude.contains(j->name())) {
continue;
}
tests.emplace_back(j);
}
return tests;
}
} // namespace arc::validate

79
arc/validate/test.hxx Normal file
View File

@ -0,0 +1,79 @@
#ifndef arc__validate__test_hxx_
#define arc__validate__test_hxx_
#include <arc/validate/export.hxx>
#include <functional>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#define ARC_VALIDATE_DEFINE_TEST_3(file, line, name) \
static void test_func_##line(); \
static ::arc::validate::test_t test_##line(file, line, name, test_func_##line); \
static void test_func_##line()
#define ARC_VALIDATE_DEFINE_TEST_2(file, line, name) ARC_VALIDATE_DEFINE_TEST_3(file, line, name)
#ifndef ARC_VALIDATE_UNPREFIXED_MACROS
# define ARC_VALIDATE_DEFINE_TEST(name) ARC_VALIDATE_DEFINE_TEST_2(__FILE__, __LINE__, #name)
#else
# define DEFINE_TEST(name) ARC_VALIDATE_DEFINE_TEST_2(__FILE__, __LINE__, #name)
#endif
namespace arc::validate
{
/// Represents a single test case.
///
class LIBARC_VALIDATE_SYMEXPORT test_t
{
public:
test_t(std::string, int, std::string, std::function<void()>);
test_t(test_t const&) = delete;
test_t(test_t&&) = delete;
~test_t() noexcept;
std::string const&
file() const;
int
line() const;
std::string const&
name() const;
std::function<void()> const&
function() const;
test_t& operator=(test_t const&) = delete;
test_t& operator=(test_t&&) = delete;
static
std::vector<test_t*>
get_tests();
static
std::vector<test_t*>
get_tests(std::set<std::string> const&,
std::set<std::string> const&);
private:
std::string file_;
int line_;
std::string name_;
std::function<void()> function_;
/// Holds a map of all registered tests.
///
static std::map<std::string, test_t*> tests_;
};
} // namespace arc::validate
#endif

View File

@ -1,5 +1,5 @@
#ifndef arc__validate__export_hxx_
#define arc__validate__export_hxx_
#ifndef arc__validate__version_hxx_
#define arc__validate__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//