From: Eric Fiselier Date: Sun, 28 May 2017 19:38:21 +0000 (+0000) Subject: [coroutines] Add end-to-end tests within libc++ X-Git-Tag: llvmorg-5.0.0-rc1~4068 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c34a497b407ebb65e6d8c582ba5fe1c45cc88958;p=platform%2Fupstream%2Fllvm.git [coroutines] Add end-to-end tests within libc++ This patch adds end-to-end/breathing tests for coroutines into libc++. The tests aren't specifically to test libc++ requirements but instead are intented to ensure coroutines are working fine in general. Although libc++ isn't exactly the most correct place for these tests to live, there is one major advantage. The libc++ test suite is also used by MSVC and by adding the tests here it ensures they will be run against all currently available coroutine implementations. llvm-svn: 304101 --- diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/await_result.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/await_result.sh.cpp new file mode 100644 index 0000000..cca875d --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/await_result.sh.cpp @@ -0,0 +1,72 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include + +using namespace std::experimental; + +struct coro_t { + struct promise_type { + coro_t get_return_object() { + coroutine_handle{}; + return {}; + } + suspend_never initial_suspend() { return {}; } + suspend_never final_suspend() { return {}; } + void return_void(){} + static void unhandled_exception() {} + }; +}; + +struct B { + ~B() {} + bool await_ready() { return true; } + B await_resume() { return {}; } + template void await_suspend(F) {} +}; + + +struct A { + ~A(){} + bool await_ready() { return true; } + int await_resume() { return 42; } + template void await_suspend(F) {} +}; + +int last_value = -1; +void set_value(int x) { + last_value = x; +} + +coro_t f(int n) { + if (n == 0) { + set_value(0); + co_return; + } + int val = co_await A{}; + set_value(42); +} + +coro_t g() { B val = co_await B{}; } + +int main() { + last_value = -1; + f(0); + assert(last_value == 0); + f(1); + assert(last_value == 42); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/bool_await_suspend.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/bool_await_suspend.sh.cpp new file mode 100644 index 0000000..1e5c106 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/bool_await_suspend.sh.cpp @@ -0,0 +1,67 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include + +using namespace std::experimental; + +struct coro_t { + struct promise_type { + coro_t get_return_object() { + coroutine_handle{}; + return {}; + } + suspend_never initial_suspend() { return {}; } + suspend_never final_suspend() { return {}; } + void return_void(){} + void unhandled_exception() {} + }; +}; + +struct NoSuspend { + bool await_ready() { return false; } + void await_resume() {} + template bool await_suspend(F) { return false; } +}; + +struct DoSuspend { + bool await_ready() { return false; } + void await_resume() {} + template bool await_suspend(F) { return true; } +}; + +bool f_started, f_resumed = false; +coro_t f() { + f_started = true; + co_await DoSuspend{}; + f_resumed = true; +} + +bool g_started, g_resumed = false; +coro_t g() { + g_started = true; + co_await NoSuspend{}; + g_resumed = true; +} + +int main() { + assert(!f_started && !f_resumed && !g_started && !g_resumed); + f(); + assert(f_started && !f_resumed); + g(); + assert(g_started && g_resumed); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/expected.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/expected.sh.cpp new file mode 100644 index 0000000..e3690a4 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/expected.sh.cpp @@ -0,0 +1,93 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include +using namespace std::experimental; + +struct error {}; + +template +struct expected { + + struct Data { + T val; + Error error; + }; + Data data; + + struct DataPtr { + Data *p; + ~DataPtr() { delete p; } + }; + + expected() {} + expected(T val) : data{std::move(val),{}} {} + expected(struct error, Error error) : data{{}, std::move(error)} {} + expected(DataPtr & p) : data{std::move(p.p->val), std::move(p.p->error)} {} + + struct promise_type { + Data* data; + DataPtr get_return_object() { data = new Data; return {data}; } + suspend_never initial_suspend() { return {}; } + suspend_never final_suspend() { return {}; } + void return_value(T v) { data->val = std::move(v); data->error = {};} + void unhandled_exception() {} + }; + + bool await_ready() { return !data.error; } + T await_resume() { return std::move(data.val); } + void await_suspend(coroutine_handle h) { + h.promise().data->error =std::move(data.error); + h.destroy(); + } + + T const& value() { return data.val; } + Error const& error() { return data.error; } +}; + +expected g() { return {0}; } +expected h() { return {error{}, 42}; } + +extern "C" void print(int); + +bool f1_started, f1_resumed = false; +expected f1() { + f1_started = true; + (void)(co_await g()); + f1_resumed = true; + co_return 100; +} + +bool f2_started, f2_resumed = false; +expected f2() { + f2_started = true; + (void)(co_await h()); + f2_resumed = true; + co_return 200; +} + +int main() { + auto c1 = f1(); + assert(f1_started && f1_resumed); + assert(c1.value() == 100); + assert(c1.error() == 0); + + auto c2 = f2(); + assert(f2_started && !f2_resumed); + assert(c2.value() == 0); + assert(c2.error() == 42); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/fullexpr-dtor.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/fullexpr-dtor.sh.cpp new file mode 100644 index 0000000..2442029 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/fullexpr-dtor.sh.cpp @@ -0,0 +1,113 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include + +using namespace std::experimental; + +int alive = 0; +int ctor_called = 0; +int dtor_called = 0; +void reset() { + assert(alive == 0); + alive = 0; + ctor_called = 0; + dtor_called = 0; +} +struct Noisy { + Noisy() { ++alive; ++ctor_called; } + Noisy(Noisy const&) = delete; + ~Noisy() { --alive; ++dtor_called; } +}; + +struct Bug { + bool await_ready() { return true; } + void await_suspend(std::experimental::coroutine_handle<>) {} + Noisy await_resume() { return {}; } +}; +struct coro2 { + struct promise_type { + suspend_never initial_suspend() { return{}; } + suspend_never final_suspend() { return{}; } + coro2 get_return_object() { return{}; } + void return_void() {} + Bug yield_value(int) { return {}; } + void unhandled_exception() {} + }; +}; + +// Checks that destructors are correctly invoked for the object returned by +// coawait. +// CHECK-LABEL: @a( +coro2 a() { + reset(); + { + auto x = co_await Bug{}; + assert(alive == 1); + assert(ctor_called == 1); + assert(dtor_called == 0); + } + assert(alive == 0); + assert(dtor_called == 1); +} + +coro2 b() { + reset(); + { + co_await Bug{}; + assert(ctor_called == 1); + assert(dtor_called == 1); + assert(alive == 0); + } + assert(ctor_called == 1); + assert(dtor_called == 1); + assert(alive == 0); + +} + +coro2 c() { + reset(); + { + auto x = co_yield 42; + assert(alive == 1); + assert(ctor_called == 1); + assert(dtor_called == 0); + } + assert(alive == 0); + assert(ctor_called == 1); + assert(dtor_called == 1); +} + +coro2 d() { + reset(); + { + co_yield 42; + assert(ctor_called == 1); + assert(dtor_called == 1); + assert(alive == 0); + } + assert(alive == 0); + assert(ctor_called == 1); + assert(dtor_called == 1); +} + +int main() { + a(); + b(); + c(); + d(); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/generator.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/generator.sh.cpp new file mode 100644 index 0000000..922b3a0 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/generator.sh.cpp @@ -0,0 +1,104 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include +#include + +#include "coroutine_types.h" + +using namespace std::experimental; + +struct minig { + struct promise_type { + int current_value; + suspend_always yield_value(int value) { + this->current_value = value; + return {}; + } + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + minig get_return_object() { return minig{this}; }; + void return_void() {} + void unhandled_exception() {} + }; + + bool move_next() { + p.resume(); + return !p.done(); + } + int current_value() { return p.promise().current_value; } + + minig(minig &&rhs) : p(rhs.p) { rhs.p = nullptr; } + + ~minig() { + if (p) + p.destroy(); + } + +private: + explicit minig(promise_type *p) + : p(coroutine_handle::from_promise(*p)) {} + + coroutine_handle p; +}; + + +minig mini_count(int n) { + for (int i = 0; i < n; i++) { + co_yield i; + } +} + +generator count(int n) { + for (int i = 0; i < n; ++i) + co_yield i; +} + +generator range(int from, int n) { + for (int i = from; i < n; ++i) + co_yield i; +} + +void test_count() { + const std::vector expect = {0, 1, 2, 3, 4}; + std::vector got; + for (auto x : count(5)) + got.push_back(x); + assert(expect == got); +} + +void test_range() { + int sum = 0; + for (auto v: range(1, 20)) + sum += v; + assert(sum == 190); +} + +void test_mini_generator() { + int sum = 0; + auto g = mini_count(5); + while (g.move_next()) { + sum += g.current_value(); + } + assert(sum == 10); +} + +int main() { + test_count(); + test_range(); + test_mini_generator(); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/go.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/go.sh.cpp new file mode 100644 index 0000000..e0d6910 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/go.sh.cpp @@ -0,0 +1,182 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include + +using namespace std::experimental; + +bool cancel = false; + +struct goroutine +{ + static int const N = 10; + static int count; + static coroutine_handle<> stack[N]; + + static void schedule(coroutine_handle<>& rh) + { + assert(count < N); + stack[count++] = rh; + rh = nullptr; + } + + ~goroutine() {} + + static void go(goroutine) {} + + static void run_one() + { + assert(count > 0); + stack[--count](); + } + + struct promise_type + { + suspend_never initial_suspend() { + return {}; + } + suspend_never final_suspend() { + return {}; + } + void return_void() {} + goroutine get_return_object() { + return{}; + } + void unhandled_exception() {} + }; +}; +int goroutine::count; +coroutine_handle<> goroutine::stack[N]; + +coroutine_handle workaround; + +class channel; + +struct push_awaiter { + channel* ch; + bool await_ready() {return false; } + void await_suspend(coroutine_handle<> rh); + void await_resume() {} +}; + +struct pull_awaiter { + channel * ch; + + bool await_ready(); + void await_suspend(coroutine_handle<> rh); + int await_resume(); +}; + +class channel +{ + using T = int; + + friend struct push_awaiter; + friend struct pull_awaiter; + + T const* pvalue = nullptr; + coroutine_handle<> reader = nullptr; + coroutine_handle<> writer = nullptr; +public: + push_awaiter push(T const& value) + { + assert(pvalue == nullptr); + assert(!writer); + pvalue = &value; + + return { this }; + } + + pull_awaiter pull() + { + assert(!reader); + + return { this }; + } + + void sync_push(T const& value) + { + assert(!pvalue); + pvalue = &value; + assert(reader); + reader(); + assert(!pvalue); + reader = nullptr; + } + + auto sync_pull() + { + while (!pvalue) goroutine::run_one(); + auto result = *pvalue; + pvalue = nullptr; + if (writer) + { + auto wr = writer; + writer = nullptr; + wr(); + } + return result; + } +}; + +void push_awaiter::await_suspend(coroutine_handle<> rh) +{ + ch->writer = rh; + if (ch->reader) goroutine::schedule(ch->reader); +} + + +bool pull_awaiter::await_ready() { + return !!ch->writer; +} +void pull_awaiter::await_suspend(coroutine_handle<> rh) { + ch->reader = rh; +} +int pull_awaiter::await_resume() { + auto result = *ch->pvalue; + ch->pvalue = nullptr; + if (ch->writer) { + //goroutine::schedule(ch->writer); + auto wr = ch->writer; + ch->writer = nullptr; + wr(); + } + return result; +} + +goroutine pusher(channel& left, channel& right) +{ + for (;;) { + auto val = co_await left.pull(); + co_await right.push(val + 1); + } +} + +const int N = 100; //100'000'000; +const int repeat = 1; + +channel* c = new channel[N + 1]; + +int main() { + for (int i = 0; i < N; ++i) + goroutine::go(pusher(c[i], c[i + 1])); + + c[0].sync_push(0); + int result = c[N].sync_pull(); + + assert(result == 100); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/multishot_func.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/multishot_func.sh.cpp new file mode 100644 index 0000000..e13196c --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/multishot_func.sh.cpp @@ -0,0 +1,91 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include + +using namespace std::experimental; + +// This file tests, multishot, movable std::function like thing using coroutine +// for compile-time type erasure and unerasure. +template struct func { + struct Input {R a, b;}; + + struct promise_type { + Input* I; + R result; + func get_return_object() { return {this}; } + suspend_always initial_suspend() { return {}; } + suspend_never final_suspend() { return {}; } + void return_void() {} + template + suspend_always yield_value(F&& f) { + result = f(I->a, I->b); + return {}; + } + void unhandled_exception() {} + }; + + R operator()(Input I) { + h.promise().I = &I; + h.resume(); + R result = h.promise().result; + return result; + }; + + func() {} + func(func &&rhs) : h(rhs.h) { rhs.h = nullptr; } + func(func const &) = delete; + + func &operator=(func &&rhs) { + if (this != &rhs) { + if (h) + h.destroy(); + h = rhs.h; + rhs.h = nullptr; + } + return *this; + } + + template static func Create(F f) { + for (;;) { + co_yield f; + } + } + + template func(F f) : func(Create(f)) {} + + ~func() { + if (h) + h.destroy(); + } + +private: + func(promise_type *promise) + : h(coroutine_handle::from_promise(*promise)) {} + coroutine_handle h; +}; + +int Do(int acc, int n, func f) { + for (int i = 0; i < n; ++i) + acc = f({acc, i}); + return acc; +} + +int main() { + int result = Do(1, 10, [](int a, int b) {return a + b;}); + assert(result == 46); +} diff --git a/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/oneshot_func.sh.cpp b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/oneshot_func.sh.cpp new file mode 100644 index 0000000..9c94f73 --- /dev/null +++ b/libcxx/test/std/experimental/language.support/support.coroutines/end.to.end/oneshot_func.sh.cpp @@ -0,0 +1,87 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11 +// REQUIRES: fcoroutines-ts + +// RUN: %build -fcoroutines-ts +// RUN: %run + +#include +#include +#include + +using namespace std::experimental; + +// This file tests, one shot, movable std::function like thing using coroutine +// for compile-time type erasure and unerasure. + +template struct func { + struct promise_type { + R result; + func get_return_object() { return {this}; } + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + void return_value(R v) { result = v; } + void unhandled_exception() {} + }; + + R operator()() { + h.resume(); + R result = h.promise().result; + h.destroy(); + h = nullptr; + return result; + }; + + func() {} + func(func &&rhs) : h(rhs.h) { rhs.h = nullptr; } + func(func const &) = delete; + + func &operator=(func &&rhs) { + if (this != &rhs) { + if (h) + h.destroy(); + h = rhs.h; + rhs.h = nullptr; + } + return *this; + } + + template static func Create(F f) { co_return f(); } + + template func(F f) : func(Create(f)) {} + + ~func() { + if (h) + h.destroy(); + } + +private: + func(promise_type *promise) + : h(coroutine_handle::from_promise(*promise)) {} + coroutine_handle h; +}; + +std::vector yielded_values = {}; +int yield(int x) { yielded_values.push_back(x); return x + 1; } +float fyield(int x) { yielded_values.push_back(x); return x + 2; } + +void Do1(func f) { yield(f()); } +void Do2(func f) { yield(f()); } + +int main() { + Do1([] { return yield(43); }); + assert((yielded_values == std::vector{43, 44})); + + yielded_values = {}; + Do2([] { return fyield(44); }); + assert((yielded_values == std::vector{44, 46})); +} diff --git a/libcxx/test/support/coroutine_types.h b/libcxx/test/support/coroutine_types.h new file mode 100644 index 0000000..f592bed --- /dev/null +++ b/libcxx/test/support/coroutine_types.h @@ -0,0 +1,75 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef SUPPORT_COROUTINE_TYPES_H +#define SUPPORT_COROUTINE_TYPES_H + +#include + +template struct generator { + struct promise_type { + Ty current_value; + std::experimental::suspend_always yield_value(Ty value) { + this->current_value = value; + return {}; + } + std::experimental::suspend_always initial_suspend() { return {}; } + std::experimental::suspend_always final_suspend() { return {}; } + generator get_return_object() { return generator{this}; }; + void return_void() {} + void unhandled_exception() {} + }; + + struct iterator { + std::experimental::coroutine_handle _Coro; + bool _Done; + + iterator(std::experimental::coroutine_handle Coro, bool Done) + : _Coro(Coro), _Done(Done) {} + + iterator &operator++() { + _Coro.resume(); + _Done = _Coro.done(); + return *this; + } + + bool operator==(iterator const &_Right) const { + return _Done == _Right._Done; + } + + bool operator!=(iterator const &_Right) const { return !(*this == _Right); } + + Ty const &operator*() const { return _Coro.promise().current_value; } + + Ty const *operator->() const { return &(operator*()); } + }; + + iterator begin() { + p.resume(); + return {p, p.done()}; + } + + iterator end() { return {p, true}; } + + generator(generator &&rhs) : p(rhs.p) { rhs.p = nullptr; } + + ~generator() { + if (p) + p.destroy(); + } + +private: + explicit generator(promise_type *p) + : p(std::experimental::coroutine_handle::from_promise(*p)) {} + + std::experimental::coroutine_handle p; +}; + +#endif // SUPPORT_COROUTINE_TYPES_H