Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kelbon committed Jul 30, 2023
1 parent 79d6ccb commit 5e815f2
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 69 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

/build
/build*
/.cache
/.vscode
/Testing

23 changes: 22 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
{
"version": 5,
"configurePresets": [
{
"name": "debug_dev",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build_debug",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "1",
"CMAKE_BUILD_TYPE": "Debug",
"KELCORO_ENABLE_TESTING": "ON"
}
},
{
"name": "release_dev",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build_release",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "1",
"CMAKE_BUILD_TYPE": "Release",
"KELCORO_ENABLE_TESTING": "ON"
}
},
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "1"
"CMAKE_EXPORT_COMPILE_COMMANDS": "1",
"CMAKE_BUILD_TYPE": "Release"
}
}
]
Expand Down
77 changes: 31 additions & 46 deletions include/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
#endif

namespace dd {
// TODO rename everything (_base / giterator)

template <typename>
struct elements_of_t;

// TODO think about size
template <typename Yield>
struct generator_promise {
// invariant: root != nullptr
generator_promise* root = this;
// invariant: never nullptr, initialized when generator created
// TODO hmm about const here
Yield* current_result;
std::coroutine_handle<> current_worker = get_return_object();
std::coroutine_handle<> owner = std::noop_coroutine();
Expand Down Expand Up @@ -89,19 +89,27 @@ struct generator_promise {
}
};

// TODO эта штука должна не зависеть от Yield
template <typename Yield>
struct giterator {
struct generator_iterator {
private:
// but.. why?
// Its required for guarantee that default constructed generator is an empty range
// so i store erased handle when iterator is default constructed and 'handle' otherwise
// P.S. i know about formal UB here
union {
// invariants: always != nullptr, if !erased_handle.done(), then 'handle' stored
std::coroutine_handle<> erased_handle = always_done_coroutine();
std::coroutine_handle<> erased_handle;
std::coroutine_handle<generator_promise<Yield>> handle;
};

public:
generator_iterator() noexcept : erased_handle(always_done_coroutine()) {
}
// precondition: h != nullptr
generator_iterator(std::coroutine_handle<generator_promise<Yield>> h) noexcept : handle(h) {
assert(h != nullptr);
}

using iterator_category = std::input_iterator_tag;
using value_type = Yield;
using difference_type = ptrdiff_t;
Expand All @@ -110,12 +118,13 @@ struct giterator {
assert(handle != nullptr); // invariant
return erased_handle.done();
}
// TODO rvalue reference here...
// * may be invoked > 1 times,
// * change of value by reference may be observed from generator if lvalue was yielded
value_type& operator*() const noexcept {
assert(!handle.done());
return *handle.promise().current_result;
}
giterator& operator++() {
generator_iterator& operator++() {
assert(!handle.done());
handle.promise().produce_next();
return *this;
Expand All @@ -124,46 +133,19 @@ struct giterator {
++(*this);
}
};
// TODO support для случая, когда yield string(...), ты же хочешь вымувать это значение наружу
// а вместо этого происходит копирование получается
// TODO нахуй эти референс дерьмо, реально и без них отлично всё будет работать, если поддержать вымув( с ними непонятно как)
// Хмм, TODO всегда мувать, но тогда для lvalue нужно будет копировать... Лучше сделать non const
// как у меня и есть, а для констант создавать их на месте(чекнуть что они не создаются сразу... через &&)
// TODO? iter_move / iter_swap?
// TODO!!!!!!!!! begin_to(out iterator returning pointers to uninitialized memorys) с RVO сразу (может быть output итератор...?)
// TODO reference type?
// можно чет типа emplace_yield... но куда..
// TODO promise must work for Reference
// TODO попробовать тупо аллокатор байтами сложить в конец, потом всё равно нужно будет его достать перед удалением памяти
// и можно избежать проблем с алигментом таким образом всех
// но для этого нужно потребовать trivially copyable аллокатор, но это не проблема, если он тупо указатель на ресурс
// НО! Можно просто и требовать собственно... Указатель на std::memory resource... Типа а почему нет?
// Либо stateless аллокатор, либо std::memory_resource* !
// + какой то тег в котором его нужно передавать, типа TODO я хочу чтобы это было как то снаружи, чтобы не пихать
// в интерфейс корутины это
// ХММ, а что если сделать thread local std::memory_resource*... И передавать туда...
// А потом функцию dd::co_alloc(&resource, &co_fn)
// !!! TODO !!! вся логика будет ВСЕГДА thread_local_mr.allocate(c, align); store_ptr_after_frame; tread_local_mr = new_delete()
// а деаллокация достаёт поинтер за фреймом через memcpy и вызывает функцию.
// TODO надо попробовать каким то образом воспользоваться тем фактом, что аллокация всегда ровно одна, например сохранять
// не memory_resource*, а конкретный поинтер или чёт такое

template <typename Yield>
struct generator {
using iterator = giterator<Yield>;
using iterator = generator_iterator<Yield>;
using handle_type = std::coroutine_handle<generator_promise<Yield>>;
private:
handle_type handle = nullptr;

// TODO less friends
template <typename>
friend struct generator_promise;
template<typename>
friend struct elements_of_t;
template<typename>
friend struct attach_leaf;
public:
static_assert(!std::is_reference_v<Yield>);
// TODO check ranges value_t?

using value_type = Yield;
using promise_type = generator_promise<Yield>;
constexpr generator() noexcept = default;
Expand All @@ -185,10 +167,10 @@ struct generator {
handle.destroy();
}

giterator<Yield> begin() const noexcept {
iterator begin() const noexcept [[clang::lifetimebound]] {
if (!handle) [[unlikely]]
return iterator{};
return iterator{handle};
return iterator(handle);
}
static constexpr std::default_sentinel_t end() noexcept {
return std::default_sentinel;
Expand All @@ -199,10 +181,11 @@ struct generator {
}
};

// TODO noexport
// implementation detail
template<typename Yield>
struct attach_leaf {
generator<Yield>& leaf;

bool await_ready() const noexcept {
return leaf.empty();
}
Expand All @@ -216,12 +199,12 @@ struct attach_leaf {
}
static constexpr void await_resume() noexcept {}
};

template <typename Yield>
auto generator_promise<Yield>::yield_value(elements_of_t<Yield>&& e) noexcept {
return attach_leaf<Yield>{e.g};
}

// TODO hmm, а важно ли тут что конкретно бросается из генератора...
template <typename Yield>
struct elements_of_t {
generator<Yield> g;
Expand All @@ -230,16 +213,18 @@ template <typename T>
elements_of_t(generator<T>) -> elements_of_t<T>;

template <std::ranges::range R>
[[nodiscard]] auto elements_of(R&& r) {
auto g = [&]() -> generator<std::ranges::range_value_t<R>> { // hmm, reference_t?..
[[nodiscard]] auto elements_of(R&& r [[clang::lifetimebound]]) {
// takes reference to 'r' and its not a error, because it must be used
// in yield expression co_yield dd::elements_of(rng{}); - 'rng' outlives created generator
auto make_gen = [](R&& r) -> generator<std::ranges::range_value_t<R>> { // hmm, reference_t?..
for (auto&& x : r)
co_yield x;
}();
return elements_of_t{g};
};
return elements_of_t{make_gen(std::forward<R>(r))};
}

template <typename Yield>
[[nodiscard]] constexpr elements_of_t<Yield> elements_of(generator<Yield> g) noexcept {
[[nodiscard("co yield it")]] constexpr elements_of_t<Yield> elements_of(generator<Yield> g) noexcept {
return elements_of_t{std::move(g)};
}

Expand Down
2 changes: 1 addition & 1 deletion tests/test_coroutines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ inline dd::generator<int> foo() {

TEST(generator) {
static_assert(std::ranges::input_range<dd::generator<size_t>&&>);
static_assert(std::input_iterator<dd::giterator<std::vector<int>>>);
static_assert(std::input_iterator<dd::generator_iterator<std::vector<int>>>);
static_assert(std::ranges::input_range<dd::generator<int>>);
int i = 1;
dd::generator gen = foo();
Expand Down
79 changes: 59 additions & 20 deletions tests/tests_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
static bool flip() {
static std::mt19937 rng = [] {
auto seed = std::random_device{}();
std::clog << seed << std::endl;
std::clog << "SEED: " << seed << std::endl;
return std::mt19937{seed};
}();
return std::bernoulli_distribution(0.5)(rng);
Expand All @@ -28,11 +28,16 @@ TEST(empty) {
error_if(!g.empty());
for (auto x : g)
error_if(true);
dd::generator_iterator<int> it{};
return error_count;
}
dd::generator<int> g1() {
for (int i = 0; i < 100; ++i)
for (int i = 0; i < 98; ++i)
co_yield i;
const int i = 98;
co_yield i;
const int j = 99;
co_yield std::move(j);
}
TEST(reuse) {
dd::generator g = g1();
Expand Down Expand Up @@ -75,23 +80,57 @@ TEST(recursive) {
return error_count;
}

//TEST(reuse_recursive) {
// int i = 0;
// dd::generator g = g4(i);
// std::vector<int> v;
// while (!g.empty()) {
// for (int& x : g) {
// if (flip())
// break;
// v.push_back(x);
// }
// }
// std::vector<int> check(100);
// std::iota(begin(check), end(check), 0);
// error_if(check != v);
// return error_count;
//}
// TODO test yield const objects(and const &&)
dd::generator<int> g5(int& i) {
co_yield dd::elements_of(std::vector(15, -1));
for (; i < 15; ++i)
co_yield i;
}

dd::generator<int> g4(int& i) {
co_yield dd::elements_of(std::vector(15, -1));
for (int x = i; i < x + 15; ++i)
co_yield i;
if (i < 300) {
co_yield dd::elements_of(g4(i));
}
}

TEST(reuse_recursive) {
int i = 0;
dd::generator g = g4(i);
std::vector<int> v;
while (!g.empty()) {
for (int& x : g) {
if (flip())
break;
v.push_back(x);
}
}
std::vector<int> check(300);
std::iota(begin(check), end(check), 0);
std::erase_if(v, [](int x) { return x == -1; });
error_if(check != v);
return error_count;
}

dd::generator<std::string> str_g(std::string s) {
while(s.size() < 10) {
co_yield s;
s.push_back('A');
}
if (flip())
co_yield dd::elements_of(str_g(std::move(s)));
}

TEST(string_generator) {
std::string check;
for(std::string& s : str_g(std::string{})) {
error_if(s != check);
check.push_back('A');
}
return error_count;
}

int main() {
return TESTempty() + TESTreuse() + TESTempty_recursive() + TESTrecursive();
return TESTempty() + TESTreuse() + TESTempty_recursive() + TESTrecursive() + TESTreuse_recursive() + TESTstring_generator();
}

0 comments on commit 5e815f2

Please sign in to comment.