1
0
Fork 0
mirror of https://gitlab.com/niansa/libcrosscoro.git synced 2025-03-06 20:53:32 +01:00

Add tests for tasks that throw (#4)

* Add tests for tasks that throw

* Additional task types for throwing coverage
This commit is contained in:
Josh Baldwin 2020-10-12 17:29:47 -06:00 committed by GitHub
parent 31dded8611
commit 1a2ec073ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 268 additions and 65 deletions

67
.clang-format Normal file
View file

@ -0,0 +1,67 @@
---
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveMacros: 'true'
AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true'
AlignEscapedNewlines: Right
AlignOperands: 'true'
AlignTrailingComments: 'true'
AllowAllArgumentsOnNextLine: 'true'
AllowAllConstructorInitializersOnNextLine: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'true'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakAfterJavaFieldAnnotations: 'true'
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: 'true'
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: 'false'
ColumnLimit: '120'
CompactNamespaces: 'false'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '4'
Cpp11BracedListStyle: 'true'
FixNamespaceComments: 'true'
IncludeBlocks: Preserve
IndentCaseLabels: 'true'
IndentPPDirectives: BeforeHash
IndentWidth: '4'
IndentWrappedFunctionNames: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
Language: Cpp
MaxEmptyLinesToKeep: '1'
NamespaceIndentation: None
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'true'
SortUsingDeclarations: 'true'
SpaceAfterCStyleCast: 'false'
SpaceAfterLogicalNot: 'false'
SpaceAfterTemplateKeyword: 'false'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: 'false'
SpaceBeforeCtorInitializerColon: 'true'
SpaceBeforeInheritanceColon: 'true'
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: 'true'
SpaceInEmptyParentheses: 'false'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
UseTab: Never
...

56
Makefile Normal file
View file

@ -0,0 +1,56 @@
.DEFAULT_GOAL := debug
# Builds the project and tests in the Debug directory.
debug:
@$(MAKE) compile BUILD_TYPE=Debug --no-print-directory
# Builds the project and tests in the RelWithDebInfo directory.
release-with-debug-info:
@$(MAKE) compile BUILD_TYPE=RelWithDebInfo --no-print-directory
# Builds the project and tests in the Release directory.
release:
@$(MAKE) compile BUILD_TYPE=Release --no-print-directory
# Internal target for all build targets to call.
compile:
mkdir -p ${BUILD_TYPE}; \
cd ${BUILD_TYPE}; \
cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ..; \
cmake --build . -- -j $(nproc)
# Run Debug tests.
debug-test:
@$(MAKE) test BUILD_TYPE=Debug --no-print-directory
# Run RelWithDebInfo tests.
release-with-debug-info-test:
@$(MAKE) test BUILD_TYPE=RelWithDebInfo --no-print-directory
# Run Release tests.
release-test:
@$(MAKE) test BUILD_TYPE=Release --no-print-directory
# Internal target for all test targets to call.
.PHONY: test
test:
cd ${BUILD_TYPE}; \
ctest -VV
# Cleans all build types.
.PHONY: clean
clean:
rm -rf Debug
rm -rf RelWithDebInfo
rm -rf Release
# Runs clang-format with the project's .clang-format.
format:
# Inlcude *.hpp|*.h|*.cpp but ignore catch lib as well as RelWithDebInfo|Release|Debug|build
find . \( -name '*.hpp' -or -name '*.h' -or -name '*.cpp' \) \
-and -not -name '*catch*' \
-and -not -iwholename '*/RelWithDebInfo/*' \
-and -not -iwholename '*/Release/*' \
-and -not -iwholename '*/Debug/*' \
-and -not -iwholename '*/build/*' \
-exec clang-format -i --style=file {} \;

View file

@ -6,7 +6,7 @@ libcoro C++20 Coroutines
[![language][badge.language]][language]
[![license][badge.license]][license]
[badge.language]: https://img.shields.io/badge/language-C%2B%2B17-yellow.svg
[badge.language]: https://img.shields.io/badge/language-C%2B%2B20-yellow.svg
[badge.license]: https://img.shields.io/badge/license-Apache--2.0-blue
[language]: https://en.wikipedia.org/wiki/C%2B%2B17
@ -19,3 +19,20 @@ Libcoro is a C++20 coroutine library. So far most inspiration has been gleaned
# Goal
Libcoro is currently more of a learning experience for myself but ultimately I'd like to turn this into a great linux coroutine base library with an easy to use HTTP scheduler/server.
# Building
There is a root makefile with various commands to help make building and running tests on this project easier.
```bash
# Build targets
make debug|release-with-debug-info|release
# Run tests targets
make debug-test|release-with-debug-info-tests|release-tests
# Clean all builds.
make clean
# clang-format the code
make format
```

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,3 @@
Start testing: Oct 12 14:19 MST
----------------------------------------------------------
End testing: Oct 12 14:19 MST

View file

@ -162,19 +162,19 @@ public:
auto operator=(const resume_token&) -> resume_token& = delete;
auto operator=(resume_token&&) -> resume_token& = default;
auto resume(return_type result) noexcept -> void;
auto resume(return_type value) noexcept -> void;
auto result() const & -> const return_type&
auto return_value() const & -> const return_type&
{
return m_result;
return m_return_value;
}
auto result() && -> return_type&&
auto return_value() && -> return_type&&
{
return std::move(m_result);
return std::move(m_return_value);
}
private:
return_type m_result;
return_type m_return_value;
};
template<>
@ -578,7 +578,7 @@ public:
}
else
{
co_return token.result();
co_return token.return_value();
}
}
@ -758,7 +758,7 @@ private:
}
else
{
co_return token.result();
co_return token.return_value();
}
}
@ -935,12 +935,12 @@ private:
};
template<typename return_type>
inline auto resume_token<return_type>::resume(return_type result) noexcept -> void
inline auto resume_token<return_type>::resume(return_type value) noexcept -> void
{
void* old_value = m_state.exchange(this, std::memory_order::acq_rel);
if(old_value != this)
{
m_result = std::move(result);
m_return_value = std::move(value);
auto* waiters = static_cast<awaiter*>(old_value);
while(waiters != nullptr)

View file

@ -13,7 +13,7 @@ auto sync_wait(task_type&& task) -> decltype(auto)
{
task.resume();
}
return task.promise().result();
return task.promise().return_value();
}
template<typename ... tasks>

View file

@ -1,10 +1,6 @@
#pragma once
#include <atomic>
#include <coroutine>
#include <optional>
#include <iostream>
namespace coro
{
@ -71,70 +67,70 @@ struct promise_base
protected:
std::coroutine_handle<> m_continuation{nullptr};
std::optional<std::exception_ptr> m_exception_ptr{std::nullopt};
std::exception_ptr m_exception_ptr{};
};
template<typename return_type>
struct promise final : public promise_base
{
using task_type = task<return_type>;
using coro_handle = std::coroutine_handle<promise<return_type>>;
using coroutine_handle = std::coroutine_handle<promise<return_type>>;
promise() noexcept = default;
~promise() = default;
auto get_return_object() noexcept -> task_type;
auto return_value(return_type result) -> void
auto return_value(return_type value) -> void
{
m_result = std::move(result);
m_return_value = std::move(value);
}
auto result() const & -> const return_type&
auto return_value() const & -> const return_type&
{
if(this->m_exception_ptr.has_value())
if(m_exception_ptr)
{
std::rethrow_exception(this->m_exception_ptr.value());
std::rethrow_exception(m_exception_ptr);
}
return m_result;
return m_return_value;
}
auto result() && -> return_type&&
auto return_value() && -> return_type&&
{
if(this->m_exception_ptr.has_value())
if(m_exception_ptr)
{
std::rethrow_exception(this->m_exception_ptr.value());
std::rethrow_exception(m_exception_ptr);
}
return std::move(m_result);
return std::move(m_return_value);
}
private:
return_type m_result;
return_type m_return_value;
};
template<>
struct promise<void> : public promise_base
{
using task_type = task<void>;
using coro_handle = std::coroutine_handle<promise<void>>;
using coroutine_handle = std::coroutine_handle<promise<void>>;
promise() noexcept = default;
~promise() = default;
auto get_return_object() noexcept -> task_type;
auto return_void() -> void
auto return_void() noexcept -> void
{
}
auto result() const -> void
auto return_value() const -> void
{
if(this->m_exception_ptr.has_value())
if(m_exception_ptr)
{
std::rethrow_exception(this->m_exception_ptr.value());
std::rethrow_exception(m_exception_ptr);
}
}
};
@ -147,11 +143,11 @@ class task
public:
using task_type = task<return_type>;
using promise_type = detail::promise<return_type>;
using coro_handle = std::coroutine_handle<promise_type>;
using coroutine_handle = std::coroutine_handle<promise_type>;
struct awaitable_base
{
awaitable_base(std::coroutine_handle<promise_type> coroutine) noexcept
awaitable_base(coroutine_handle coroutine) noexcept
: m_coroutine(coroutine)
{
@ -177,7 +173,7 @@ public:
}
task(coro_handle handle)
task(coroutine_handle handle)
: m_coroutine(handle)
{
@ -249,7 +245,7 @@ public:
{
auto await_resume() noexcept -> decltype(auto)
{
return this->m_coroutine.promise().result();
return this->m_coroutine.promise().return_value();
}
};
@ -270,13 +266,13 @@ public:
return std::move(m_coroutine.promise());
}
auto handle() -> coro_handle
auto handle() -> coroutine_handle
{
return m_coroutine;
}
private:
coro_handle m_coroutine{nullptr};
coroutine_handle m_coroutine{nullptr};
};
namespace detail
@ -285,15 +281,14 @@ namespace detail
template<typename return_type>
inline auto promise<return_type>::get_return_object() noexcept -> task<return_type>
{
return task<return_type>{coro_handle::from_promise(*this)};
return task<return_type>{coroutine_handle::from_promise(*this)};
}
inline auto promise<void>::get_return_object() noexcept -> task<>
{
return task<>{coro_handle::from_promise(*this)};
return task<>{coroutine_handle::from_promise(*this)};
}
} // namespace detail
} // namespace coro

View file

@ -20,7 +20,7 @@ TEST_CASE("event single awaiter")
REQUIRE_FALSE(task.is_ready());
e.set(); // this will automaticaly resume the task that is awaiting the event.
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 42);
REQUIRE(task.promise().return_value() == 42);
}
@ -47,7 +47,7 @@ TEST_CASE("event one watcher")
producer(e);
REQUIRE(value.promise().result() == 42);
REQUIRE(value.promise().return_value() == 42);
}
TEST_CASE("event multiple watchers")
@ -66,7 +66,7 @@ TEST_CASE("event multiple watchers")
producer(e);
REQUIRE(value1.promise().result() == 42);
REQUIRE(value2.promise().result() == 42);
REQUIRE(value3.promise().result() == 42);
REQUIRE(value1.promise().return_value() == 42);
REQUIRE(value2.promise().return_value() == 42);
REQUIRE(value3.promise().return_value() == 42);
}

View file

@ -18,7 +18,7 @@ TEST_CASE("latch count=0")
task.resume();
REQUIRE(task.is_ready()); // The latch never waits due to zero count.
REQUIRE(task.promise().result() == 42);
REQUIRE(task.promise().return_value() == 42);
}
TEST_CASE("latch count=1")
@ -37,7 +37,7 @@ TEST_CASE("latch count=1")
l.count_down();
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 1);
REQUIRE(task.promise().return_value() == 1);
}
TEST_CASE("latch count=1 count_down=5")
@ -56,7 +56,7 @@ TEST_CASE("latch count=1 count_down=5")
l.count_down(5);
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 1);
REQUIRE(task.promise().return_value() == 1);
}
TEST_CASE("latch count=5 count_down=1 x5")
@ -83,7 +83,7 @@ TEST_CASE("latch count=5 count_down=1 x5")
REQUIRE_FALSE(task.is_ready());
l.count_down(1);
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 5);
REQUIRE(task.promise().return_value() == 5);
}
TEST_CASE("latch count=5 count_down=5")
@ -102,5 +102,5 @@ TEST_CASE("latch count=5 count_down=5")
l.count_down(5);
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 5);
REQUIRE(task.promise().return_value() == 5);
}

View file

@ -97,7 +97,7 @@ TEST_CASE("scheduler task with multiple yields on event")
std::cerr << "1st suspend\n";
co_await s.yield(token);
std::cerr << "1st resume\n";
counter += token.result();
counter += token.return_value();
token.reset();
std::cerr << "never suspend\n";
co_await std::suspend_never{};
@ -105,12 +105,12 @@ TEST_CASE("scheduler task with multiple yields on event")
co_await s.yield(token);
token.reset();
std::cerr << "2nd resume\n";
counter += token.result();
counter += token.return_value();
std::cerr << "3rd suspend\n";
co_await s.yield(token);
token.reset();
std::cerr << "3rd resume\n";
counter += token.result();
counter += token.return_value();
co_return;
};
@ -485,7 +485,7 @@ TEST_CASE("scheduler yield user event")
auto func = [&]() -> coro::task<void>
{
co_await s.yield(token);
REQUIRE(token.result() == expected_result);
REQUIRE(token.return_value() == expected_result);
co_return;
};
@ -552,3 +552,21 @@ TEST_CASE("scheduler manual process events with self generating coroutine (stack
while(s.process_events()) ;
std::cerr << "Recursive test done.\n";
}
TEST_CASE("scheduler task throws")
{
coro::scheduler s{};
auto func = []() -> coro::task<void>
{
// Is it possible to actually notify the user when running a task in a scheduler?
// Seems like the user will need to manually catch.
throw std::runtime_error{"I always throw."};
co_return;
};
s.schedule(func());
s.shutdown();
REQUIRE(s.empty());
}

View file

@ -13,8 +13,8 @@ TEST_CASE("task hello world")
auto h = []() -> task_type { co_return "Hello"; }();
auto w = []() -> task_type { co_return "World"; }();
REQUIRE(h.promise().result().empty());
REQUIRE(w.promise().result().empty());
REQUIRE(h.promise().return_value().empty());
REQUIRE(w.promise().return_value().empty());
h.resume(); // task suspends immediately
w.resume();
@ -22,11 +22,11 @@ TEST_CASE("task hello world")
REQUIRE(h.is_ready());
REQUIRE(w.is_ready());
auto w_value = std::move(w).promise().result();
auto w_value = std::move(w).promise().return_value();
REQUIRE(h.promise().result() == "Hello");
REQUIRE(h.promise().return_value() == "Hello");
REQUIRE(w_value == "World");
REQUIRE(w.promise().result().empty());
REQUIRE(w.promise().return_value().empty());
}
TEST_CASE("task void")
@ -61,7 +61,7 @@ TEST_CASE("task exception thrown")
bool thrown{false};
try
{
auto value = task.promise().result();
auto value = task.promise().return_value();
}
catch(const std::exception& e)
{
@ -172,7 +172,7 @@ TEST_CASE("task multiple suspends return integer")
task.resume(); // third internal suspend
REQUIRE(task.is_ready());
REQUIRE(task.promise().result() == 11);
REQUIRE(task.promise().return_value() == 11);
}
TEST_CASE("task resume from promise to coroutine handles of different types")
@ -203,8 +203,54 @@ TEST_CASE("task resume from promise to coroutine handles of different types")
REQUIRE(task1.is_ready());
REQUIRE(coro_handle1.done());
REQUIRE(task1.promise().result() == 42);
REQUIRE(task1.promise().return_value() == 42);
REQUIRE(task2.is_ready());
REQUIRE(coro_handle2.done());
}
TEST_CASE("task throws void")
{
auto task = []() -> coro::task<void>
{
throw std::runtime_error{"I always throw."};
co_return;
}();
task.resume();
REQUIRE(task.is_ready());
REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error);
}
TEST_CASE("task throws non-void l-value")
{
auto task = []() -> coro::task<int>
{
throw std::runtime_error{"I always throw."};
co_return 42;
}();
task.resume();
REQUIRE(task.is_ready());
REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error);
}
TEST_CASE("task throws non-void r-value")
{
struct type
{
int m_value;
};
auto task = []() -> coro::task<type>
{
type return_value{42};
throw std::runtime_error{"I always throw."};
co_return std::move(return_value);
}();
task.resume();
REQUIRE(task.is_ready());
REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error);
}