diff --git a/BUILD.bazel b/BUILD.bazel index 8401e10..19fed88 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -28,6 +28,8 @@ cc_library( "src/detail/priority.hpp", "src/detail/relation.hpp", "src/detail/remove_cvref.hpp", + "src/detail/static_closure.hpp", + "src/detail/test_style.hpp", "src/detail/trim_substring.hpp", "src/detail/type_name.hpp", "src/expect.hpp", diff --git a/README.md b/README.md index eb44ca3..e5f94ab 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ When running tests, `skytest` will attempt to invoke test closures at compile-time. If able to, results will be classified `CONSTEXPR PASS` or `CONSTEXPR FAIL` instead of `PASS` or `FAIL`. +The `ctest` literal can be used to require closures to be tested at +compile-time. In order to be usable with `ctest`, test closures must be empty +and non-constexpr functions must not be invoked. + ## examples #### binary comparisons diff --git a/scripts/README.md.tmpl b/scripts/README.md.tmpl index ad8c19b..2371692 100644 --- a/scripts/README.md.tmpl +++ b/scripts/README.md.tmpl @@ -29,6 +29,10 @@ When running tests, `skytest` will attempt to invoke test closures at compile-time. If able to, results will be classified `CONSTEXPR PASS` or `CONSTEXPR FAIL` instead of `PASS` or `FAIL`. +The `ctest` literal can be used to require closures to be tested at +compile-time. In order to be usable with `ctest`, test closures must be empty +and non-constexpr functions must not be invoked. + ## examples #### binary comparisons diff --git a/src/detail/static_closure.hpp b/src/detail/static_closure.hpp new file mode 100644 index 0000000..2d5547d --- /dev/null +++ b/src/detail/static_closure.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "src/detail/remove_cvref.hpp" + +#include + +namespace skytest::detail { + +template > +struct static_closure : F +{ + constexpr static_closure() : F{f} {} +}; + +template +struct is_static_closure : std::false_type +{}; +template +struct is_static_closure> : std::true_type +{}; + +template +constexpr auto is_static_closure_v = is_static_closure::value; + +template +constexpr auto is_static_closure_constructible_v = + std::is_empty_v and std::is_copy_constructible_v; + +} // namespace skytest::detail diff --git a/src/detail/test_style.hpp b/src/detail/test_style.hpp new file mode 100644 index 0000000..148c990 --- /dev/null +++ b/src/detail/test_style.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "src/detail/priority.hpp" +#include "src/detail/static_closure.hpp" + +#include +#include + +namespace skytest::detail { + +struct test_style +{ + struct runtime_only + { + template + static constexpr auto value = std::optional{}; + }; + + struct compile_time_if_possible + { + private: + template < + class F, + std::enable_if_t, bool> result = bool{F{}()}> + static constexpr auto try_eval(priority<1>) + { + return std::optional{result}; + } + template + static constexpr auto try_eval(priority<0>) + { + return std::optional{}; + } + + public: + template + static constexpr auto value = try_eval(priority<1>{}); + }; + + struct compile_time + { + template + static constexpr auto value = std::optional{bool{F{}()}}; + }; +}; + +} // namespace skytest::detail diff --git a/src/rope.hpp b/src/rope.hpp index d2d288b..3e95a84 100644 --- a/src/rope.hpp +++ b/src/rope.hpp @@ -38,6 +38,8 @@ class rope_ref template struct rope { + static constexpr auto size = N; + std::array strings; template > diff --git a/src/test.hpp b/src/test.hpp index 9b9dfe5..5eaf32e 100644 --- a/src/test.hpp +++ b/src/test.hpp @@ -3,13 +3,14 @@ #include "src/cfg.hpp" #include "src/detail/is_specialization_of.hpp" #include "src/detail/remove_cvref.hpp" +#include "src/detail/static_closure.hpp" +#include "src/detail/test_style.hpp" #include "src/detail/type_name.hpp" #include "src/result.hpp" #include "src/rope.hpp" #include "src/version.hpp" #include -#include #include #include #include @@ -17,32 +18,9 @@ namespace skytest { namespace detail { -template +template class parameterized_test; -struct runtime_only_result -{ - static constexpr auto value = std::optional{}; -}; -template -struct compile_time_result : runtime_only_result -{}; -template -struct compile_time_result> -{ - static constexpr auto value = std::optional{bool{F{}()}}; -}; - -template > -struct static_closure : F -{ - constexpr static_closure() : F{f} {} -}; - -template -constexpr auto is_static_closure_constructible_v = - std::is_empty_v and std::is_copy_constructible_v; - template struct returns_result : is_specialization_of< @@ -53,26 +31,25 @@ struct returns_result template inline constexpr auto returns_result_v = returns_result::value; -template +template class test { - rope name_; + using rope_type = Rope; - template < - class F, - class Pass = compile_time_result, - class Override = override> + rope_type name_; + + template auto assign_impl(const F& func) -> void { auto result = func(); result.name = name_; - result.compile_time = Pass::value; + result.compile_time = S::template value; cfg.report(result); } public: - constexpr explicit test(rope name) : name_{name} {} + constexpr explicit test(rope_type name) : name_{name} {} template < class F, @@ -81,7 +58,7 @@ class test bool> = true> auto operator=(const F& func) && -> void { - assign_impl(func); + assign_impl(func); } template < class F, @@ -98,10 +75,10 @@ class test constexpr friend auto operator*(const test& t, const Params& params) { static_assert( - N == 1, + rope_type::size == 1, "parameterization of an already " "parameterized test"); - return detail::parameterized_test{t.name_, params}; + return detail::parameterized_test{t.name_, params}; } }; @@ -110,7 +87,17 @@ class test namespace literals { constexpr auto operator""_test(const char* name, std::size_t len) { - return detail::test{rope<1>{std::string_view{name, len}}}; + using rope_type = rope<1>; + using style_type = detail::test_style::compile_time_if_possible; + return detail::test{ + rope_type{std::string_view{name, len}}}; +} +constexpr auto operator""_ctest(const char* name, std::size_t len) +{ + using rope_type = rope<1>; + using style_type = detail::test_style::compile_time; + return detail::test{ + rope_type{std::string_view{name, len}}}; } } // namespace literals } // namespace skytest diff --git a/src/test_param.hpp b/src/test_param.hpp index a1f13eb..feddef7 100644 --- a/src/test_param.hpp +++ b/src/test_param.hpp @@ -170,24 +170,28 @@ struct param_bound_static_closure } }; -template +template class parameterized_test { public: using params_type = Params; + using style_type = Style; private: + using rope_type = rope<1>; + using derived_rope_type = rope<4>; + const params_type& params_; - rope<1> basename_; + rope_type basename_; constexpr auto value_param_name(std::string_view s) const { - return rope<4>{basename_, " [", s, "]"}; + return derived_rope_type{basename_, " [", s, "]"}; } template constexpr auto type_param_name() const { - return rope<4>{ + return derived_rope_type{ basename_, " <", type_name>, ">"}; } @@ -221,7 +225,7 @@ class parameterized_test // other options will alloc // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) std::sprintf(s.data(), "%zu", i++); - test{value_param_name(s.data())} = g[it]; + test{value_param_name(s.data())} = g[it]; } } template @@ -230,12 +234,15 @@ class parameterized_test static constexpr auto name_kind = is_range>{}; std::ignore = - ((test{param_name(name_kind)} = g[constant{}], true) and ...); + ((test{param_name(name_kind)} = + g[constant{}], + true) and + ...); } public: constexpr explicit parameterized_test( - rope<1> basename, const params_type& params) + rope_type basename, const params_type& params) : params_{params}, basename_{basename} {} diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 7311818..f069684 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -313,3 +313,26 @@ skytest_test( "11 |\\s*expect\\(.*\\).*;", ], ) + +skytest_test( + name = "requires_constexpr_test", + srcs = ["requires_constexpr_test.cpp"], + stdout = [ + "test1.*CONSTEXPR", + "test2.*CONSTEXPR", + "all tests passed.*2 tests", + ], +) + +skytest_test( + name = "ctest_build_failure_test", + srcs = ["ctest_build_failure.sh.tmpl"], + binary_type = sh_binary_template, + return_code = 1, + stderr = [ + "11.*test1", + "11.*error.*the value of .*x.* is not usable in a constant expression", + "15.*test2", + "15.*error.*the value of .*x.* is not usable in a constant expression", + ], +) diff --git a/test/ctest_build_failure.sh.tmpl b/test/ctest_build_failure.sh.tmpl new file mode 100644 index 0000000..6364483 --- /dev/null +++ b/test/ctest_build_failure.sh.tmpl @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +source test/prelude.sh +prelude "${BASH_SOURCE[0]}" + +cat >>BUILD.bazel <ctest_build_failure.cpp < int +{ + using namespace ::skytest::literals; + using ::skytest::expect; + using ::skytest::types; + + "test1"_ctest = [] { + return expect(x); + }; + + "test2"_ctest * types = [](auto) { + return expect(x); + }; +} +EOF + +bazel build -s //:ctest_build_failure \ No newline at end of file diff --git a/test/requires_constexpr_test.cpp b/test/requires_constexpr_test.cpp new file mode 100644 index 0000000..8af9e23 --- /dev/null +++ b/test/requires_constexpr_test.cpp @@ -0,0 +1,16 @@ +#include "skytest/skytest.hpp" + +auto main() -> int +{ + using namespace ::skytest::literals; + using ::skytest::eq; + using ::skytest::expect; + using ::skytest::types; + + "test1"_ctest = [] { return expect(eq(1, 1)); }; + + "test2"_ctest * types = [](auto param) { + using T = typename decltype(param)::type; + return expect(eq(0, T{})); + }; +}