Compare commits
2 commits
1247b1ac69
...
ee4483bfbb
Author | SHA1 | Date | |
---|---|---|---|
ee4483bfbb | |||
bddef4238d |
12 changed files with 344 additions and 16 deletions
|
@ -21,3 +21,4 @@ add_subdirectory(./app)
|
|||
# apps
|
||||
add_subdirectory(./mirror)
|
||||
|
||||
add_subdirectory(test)
|
||||
|
|
|
@ -103,7 +103,8 @@ void Application::update_layers()
|
|||
{
|
||||
for (auto &it : *m_layer_stack)
|
||||
{
|
||||
it->on_update(m_timer.get_elapsed_time());
|
||||
// narrowing double -> float
|
||||
it->on_update(static_cast<float>(m_timer.elapsed_time().count()));
|
||||
}
|
||||
|
||||
// TODO(Light): each layer should have their own "delta time"
|
||||
|
|
4
modules/test/CMakeLists.txt
Normal file
4
modules/test/CMakeLists.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
add_library_module(test test.cpp entrypoint.cpp)
|
||||
|
||||
add_executable(test_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/test.cpp)
|
||||
target_link_libraries(test_tests PRIVATE test)
|
128
modules/test/include/test/expects.hpp
Normal file
128
modules/test/include/test/expects.hpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <format>
|
||||
#include <source_location>
|
||||
|
||||
namespace lt::test {
|
||||
|
||||
template<typename T>
|
||||
concept Printable = requires(std::ostream &os, T t) {
|
||||
{ os << t } -> std::same_as<std::ostream &>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Testable = Printable<T> && std::equality_comparable<T>;
|
||||
|
||||
constexpr void expect_eq(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs != rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed equality expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void expect_ne(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed un-equality expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void expect_true(
|
||||
bool expression,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (!expression)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed true expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: true\n"
|
||||
"\tlocation: {}:{}",
|
||||
expression,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void expect_false(
|
||||
bool expression,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (expression)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed false expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: true\n"
|
||||
"\tlocation: {}:{}",
|
||||
expression,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void expect_le(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs > rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed false expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: >= {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::test
|
101
modules/test/include/test/test.hpp
Normal file
101
modules/test/include/test/test.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <test/expects.hpp>
|
||||
|
||||
namespace lt::test {
|
||||
|
||||
namespace concepts {
|
||||
|
||||
template<typename T>
|
||||
concept printable = requires(std::ostream &os, T t) {
|
||||
{ os << t } -> std::same_as<std::ostream &>;
|
||||
};
|
||||
|
||||
template<
|
||||
class T,
|
||||
auto expr =
|
||||
[] {
|
||||
}>
|
||||
concept test = requires(T test) {
|
||||
{ test.name } -> printable;
|
||||
|
||||
{ test = expr } -> std::same_as<void>;
|
||||
};
|
||||
|
||||
} // namespace concepts
|
||||
|
||||
|
||||
struct Case
|
||||
{
|
||||
auto operator=(std::invocable auto test) -> void // NOLINT
|
||||
{
|
||||
std::cout << "Running... " << name;
|
||||
|
||||
try
|
||||
{
|
||||
test();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
std::cout << " --> FAIL !" << '\n';
|
||||
std::cout << exp.what() << "\n\n";
|
||||
return; // TODO(Light): Should we run the remaining tests after a failure?
|
||||
}
|
||||
|
||||
std::cout << " --> SUCCESS :D" << "\n";
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
|
||||
class Registry
|
||||
{
|
||||
public:
|
||||
using Suite = void (*)();
|
||||
|
||||
static void register_suite(Suite suite)
|
||||
{
|
||||
instance().m_suites.emplace_back(suite);
|
||||
}
|
||||
|
||||
static void run_all()
|
||||
{
|
||||
for (auto &test : instance().m_suites)
|
||||
{
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Registry() = default;
|
||||
|
||||
[[nodiscard]] static auto instance() -> Registry &
|
||||
{
|
||||
static auto registry = Registry {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
std::vector<void (*)()> m_suites;
|
||||
};
|
||||
|
||||
|
||||
} // namespace details
|
||||
|
||||
struct TestSuite
|
||||
{
|
||||
template<class TSuite>
|
||||
constexpr TestSuite(TSuite suite)
|
||||
{
|
||||
#ifndef LIGHT_SKIP_TESTS
|
||||
details::Registry::register_suite(+suite);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
using Suite = const TestSuite;
|
||||
|
||||
} // namespace lt::test
|
15
modules/test/src/entrypoint.cpp
Normal file
15
modules/test/src/entrypoint.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include <test/test.hpp>
|
||||
|
||||
auto main() -> int32_t
|
||||
try
|
||||
{
|
||||
using namespace ::lt::test;
|
||||
using namespace ::lt::test::details;
|
||||
|
||||
Registry::run_all();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
std::cout << "Terminated after uncaught exception:\n"; // NOLINT
|
||||
std::cout << "exception.what: " << exp.what();
|
||||
}
|
0
modules/test/src/test.cpp
Normal file
0
modules/test/src/test.cpp
Normal file
25
modules/test/tests/test.cpp
Normal file
25
modules/test/tests/test.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include <test/test.hpp>
|
||||
|
||||
lt::test::Suite meta = []() {
|
||||
using lt::test::expect_eq;
|
||||
|
||||
lt::test::Case { "test_1" } = [] {
|
||||
expect_eq(5, 5);
|
||||
};
|
||||
|
||||
lt::test::Case { "test_2" } = [] {
|
||||
expect_eq(20.0, 20.0);
|
||||
};
|
||||
|
||||
lt::test::Case { "test_3" } = [] {
|
||||
expect_eq(true, false);
|
||||
};
|
||||
|
||||
lt::test::Case { "test_4" } = [] {
|
||||
expect_eq(true, 1);
|
||||
};
|
||||
|
||||
lt::test::Case { "test_5" } = [] {
|
||||
throw std::runtime_error("Uncaught std exception!");
|
||||
};
|
||||
};
|
|
@ -1 +1,6 @@
|
|||
add_library_module(time timer.cpp)
|
||||
|
||||
add_executable(timer_tests ${CMAKE_CURRENT_SOURCE_DIR}/src/timer.tests.cpp)
|
||||
target_include_directories(timer_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(timer_tests PRIVATE time test)
|
||||
|
||||
|
|
|
@ -4,28 +4,22 @@
|
|||
|
||||
namespace lt {
|
||||
|
||||
/** Simple timer class to keep track of the elapsed time */
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
Timer();
|
||||
using Timepoint = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using Duration = std::chrono::duration<double>;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
[[nodiscard]] auto get_elapsed_time() const -> float
|
||||
{
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::steady_clock;
|
||||
Timer(Timepoint start = Clock::now());
|
||||
|
||||
auto rep = duration_cast<milliseconds>(steady_clock::now() - m_start).count();
|
||||
return static_cast<float>(rep) / 1000.f;
|
||||
}
|
||||
void reset(Timepoint start = Clock::now());
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
[[nodiscard]] auto elapsed_time() const -> Duration;
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_start;
|
||||
Timepoint m_start;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
|
|
@ -2,8 +2,18 @@
|
|||
|
||||
namespace lt {
|
||||
|
||||
Timer::Timer(): m_start(std::chrono::steady_clock::now())
|
||||
Timer::Timer(Timepoint start): m_start(start)
|
||||
{
|
||||
}
|
||||
|
||||
void Timer::reset(Timepoint start)
|
||||
{
|
||||
m_start = start;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Timer::elapsed_time() const -> Duration
|
||||
{
|
||||
return { std::chrono::steady_clock::now() - m_start };
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
|
44
modules/time/src/timer.tests.cpp
Normal file
44
modules/time/src/timer.tests.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#include <ranges>
|
||||
#include <test/test.hpp>
|
||||
#include <time/timer.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
using lt::test::expect_le;
|
||||
|
||||
lt::test::Suite raii = [] {
|
||||
lt::test::Case { "default" } = [] {
|
||||
auto timer = Timer {};
|
||||
};
|
||||
|
||||
lt::test::Case { "plenty" } = [] {
|
||||
for (auto i : std::views::iota(0, 101))
|
||||
{
|
||||
auto timer = Timer {};
|
||||
}
|
||||
};
|
||||
|
||||
lt::test::Case { "unhappy" } = [] {
|
||||
};
|
||||
|
||||
lt::test::Case { "has sane elapsed time" } = [] {
|
||||
auto elapsed_time = Timer {}.elapsed_time();
|
||||
expect_le(elapsed_time, std::chrono::seconds { 1 });
|
||||
};
|
||||
};
|
||||
|
||||
lt::test::Suite reset = [] {
|
||||
lt::test::Case { "non-throwing" } = [] {
|
||||
auto timer = Timer {};
|
||||
timer.reset();
|
||||
};
|
||||
|
||||
lt::test::Case { "resets elapsed time" } = [] {
|
||||
auto timer = Timer {};
|
||||
auto elapsed_time = timer.elapsed_time();
|
||||
|
||||
timer.reset();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace lt
|
Loading…
Add table
Reference in a new issue