Skip to content

Commit

Permalink
define ctest to specify tests must run at compile-time (#28)
Browse files Browse the repository at this point in the history
Change-Id: I02db2963c778bec9423f2653ba066960e70ad7d5
  • Loading branch information
oliverlee committed Jan 5, 2024
1 parent d8524da commit 690e079
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 44 deletions.
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions scripts/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions src/detail/static_closure.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include "src/detail/remove_cvref.hpp"

#include <type_traits>

namespace skytest::detail {

template <const auto& f, class F = remove_cvref_t<decltype(f)>>
struct static_closure : F
{
constexpr static_closure() : F{f} {}
};

template <class T>
struct is_static_closure : std::false_type
{};
template <const auto& f, class F>
struct is_static_closure<static_closure<f, F>> : std::true_type
{};

template <class T>
constexpr auto is_static_closure_v = is_static_closure<T>::value;

template <class T>
constexpr auto is_static_closure_constructible_v =
std::is_empty_v<T> and std::is_copy_constructible_v<T>;

} // namespace skytest::detail
47 changes: 47 additions & 0 deletions src/detail/test_style.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include "src/detail/priority.hpp"
#include "src/detail/static_closure.hpp"

#include <optional>
#include <type_traits>

namespace skytest::detail {

struct test_style
{
struct runtime_only
{
template <class>
static constexpr auto value = std::optional<bool>{};
};

struct compile_time_if_possible
{
private:
template <
class F,
std::enable_if_t<is_static_closure_v<F>, bool> result = bool{F{}()}>
static constexpr auto try_eval(priority<1>)
{
return std::optional<bool>{result};
}
template <class F>
static constexpr auto try_eval(priority<0>)
{
return std::optional<bool>{};
}

public:
template <class F>
static constexpr auto value = try_eval<F>(priority<1>{});
};

struct compile_time
{
template <class F>
static constexpr auto value = std::optional<bool>{bool{F{}()}};
};
};

} // namespace skytest::detail
2 changes: 2 additions & 0 deletions src/rope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class rope_ref
template <std::size_t N>
struct rope
{
static constexpr auto size = N;

std::array<std::string_view, N> strings;

template <std::size_t M = N, class = std::enable_if_t<M == 1>>
Expand Down
61 changes: 24 additions & 37 deletions src/test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,24 @@
#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 <cstddef>
#include <optional>
#include <string_view>
#include <type_traits>
#include <utility>

namespace skytest {
namespace detail {

template <class Params>
template <class Params, class Style>
class parameterized_test;

struct runtime_only_result
{
static constexpr auto value = std::optional<bool>{};
};
template <class F, class = void>
struct compile_time_result : runtime_only_result
{};
template <class F>
struct compile_time_result<F, std::enable_if_t<bool(F{}()) or true>>
{
static constexpr auto value = std::optional<bool>{bool{F{}()}};
};

template <const auto& f, class F = remove_cvref_t<decltype(f)>>
struct static_closure : F
{
constexpr static_closure() : F{f} {}
};

template <class T>
constexpr auto is_static_closure_constructible_v =
std::is_empty_v<T> and std::is_copy_constructible_v<T>;

template <class F, class... Args>
struct returns_result
: is_specialization_of<
Expand All @@ -53,26 +31,25 @@ struct returns_result
template <class F, class... Args>
inline constexpr auto returns_result_v = returns_result<F, Args...>::value;

template <std::size_t N>
template <class Rope, class Style>
class test
{
rope<N> name_;
using rope_type = Rope;

template <
class F,
class Pass = compile_time_result<F>,
class Override = override>
rope_type name_;

template <class F, class S = Style, class Override = override>
auto assign_impl(const F& func) -> void
{
auto result = func();
result.name = name_;
result.compile_time = Pass::value;
result.compile_time = S::template value<F>;

cfg<Override>.report(result);
}

public:
constexpr explicit test(rope<N> name) : name_{name} {}
constexpr explicit test(rope_type name) : name_{name} {}

template <
class F,
Expand All @@ -81,7 +58,7 @@ class test
bool> = true>
auto operator=(const F& func) && -> void
{
assign_impl<F, runtime_only_result>(func);
assign_impl(func);
}
template <
class F,
Expand All @@ -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<Params>{t.name_, params};
return detail::parameterized_test<Params, Style>{t.name_, params};
}
};

Expand All @@ -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, style_type>{
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, style_type>{
rope_type{std::string_view{name, len}}};
}
} // namespace literals
} // namespace skytest
21 changes: 14 additions & 7 deletions src/test_param.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,24 +170,28 @@ struct param_bound_static_closure
}
};

template <class Params>
template <class Params, class Style>
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 <std::size_t I>
constexpr auto type_param_name() const
{
return rope<4>{
return derived_rope_type{
basename_, " <", type_name<param_reference_t<I, params_type>>, ">"};
}

Expand Down Expand Up @@ -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<derived_rope_type, style_type>{value_param_name(s.data())} = g[it];
}
}
template <std::size_t... Is, class G>
Expand All @@ -230,12 +234,15 @@ class parameterized_test
static constexpr auto name_kind = is_range<param_resolve_t<params_type>>{};

std::ignore =
((test{param_name<Is>(name_kind)} = g[constant<Is>{}], true) and ...);
((test<derived_rope_type, style_type>{param_name<Is>(name_kind)} =
g[constant<Is>{}],
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}
{}

Expand Down
23 changes: 23 additions & 0 deletions test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
43 changes: 43 additions & 0 deletions test/ctest_build_failure.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

source test/prelude.sh
prelude "${BASH_SOURCE[0]}"

cat >>BUILD.bazel <<EOF
cc_binary(
name = "ctest_build_failure",
srcs = ["ctest_build_failure.cpp"],
copts = [
"-std=c++$CC_BINARY_CXXSTD",
"-Werror",
"-Wall",
"-Wextra",
],
malloc = "$CC_BINARY_MALLOC",
deps = [":external_skytest"],
)
EOF

cat >ctest_build_failure.cpp <<EOF
#include "skytest/skytest.hpp"
auto x = false;
auto main() -> int
{
using namespace ::skytest::literals;
using ::skytest::expect;
using ::skytest::types;
"test1"_ctest = [] {
return expect(x);
};
"test2"_ctest * types<int> = [](auto) {
return expect(x);
};
}
EOF

bazel build -s //:ctest_build_failure
16 changes: 16 additions & 0 deletions test/requires_constexpr_test.cpp
Original file line number Diff line number Diff line change
@@ -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<int> = [](auto param) {
using T = typename decltype(param)::type;
return expect(eq(0, T{}));
};
}

0 comments on commit 690e079

Please sign in to comment.