/******************************************************************************* * This file is part of the "promise.hpp" * For conditions of distribution and use, see copyright notice in LICENSE.md * Copyright (C) 2018 Matvey Cherevko ******************************************************************************/ #define CATCH_CONFIG_FAST_COMPILE #include "catch.hpp" #include #include #include "jobber.hpp" #include "promise.hpp" #include "scheduler.hpp" namespace jb = jobber_hpp; namespace pr = promise_hpp; namespace sd = scheduler_hpp; namespace { struct obj_t { }; bool check_hello_fail_exception(std::exception_ptr e) { try { std::rethrow_exception(e); } catch (std::logic_error& ee) { return 0 == std::strcmp(ee.what(), "hello fail"); } catch (...) { return false; } } bool check_hello_fail2_exception(std::exception_ptr e) { try { std::rethrow_exception(e); } catch (std::logic_error& ee) { return 0 == std::strcmp(ee.what(), "hello fail2"); } catch (...) { return false; } } class auto_thread final { public: template < typename F, typename... Args > auto_thread(F&& f, Args&&... args) : thread_(std::forward(f), std::forward(args)...) {} ~auto_thread() noexcept { if ( thread_.joinable() ) { thread_.join(); } } void join() { thread_.join(); } private: std::thread thread_; }; } TEST_CASE("is_promise") { SECTION("positive") { static_assert( pr::is_promise>::value, "unit test fail"); static_assert( pr::is_promise>::value, "unit test fail"); static_assert( pr::is_promise>::value, "unit test fail"); static_assert( pr::is_promise>::value, "unit test fail"); static_assert( pr::is_promise>::value, "unit test fail"); static_assert( pr::is_promise>::value, "unit test fail"); } SECTION("negative") { static_assert( !pr::is_promise&>::value, "unit test fail"); static_assert( !pr::is_promise*>::value, "unit test fail"); static_assert( !pr::is_promise&>::value, "unit test fail"); static_assert( !pr::is_promise::value, "unit test fail"); static_assert( !pr::is_promise::value, "unit test fail"); static_assert( !pr::is_promise::value, "unit test fail"); } } TEST_CASE("is_promise_r") { SECTION("positive") { static_assert( pr::is_promise_r>::value, "unit test fail"); static_assert( pr::is_promise_r>::value, "unit test fail"); static_assert( pr::is_promise_r>::value, "unit test fail"); } SECTION("negative") { static_assert( !pr::is_promise_r>::value, "unit test fail"); static_assert( !pr::is_promise_r>::value, "unit test fail"); static_assert( !pr::is_promise_r>::value, "unit test fail"); static_assert( !pr::is_promise_r::value, "unit test fail"); } } TEST_CASE("promise") { SECTION("basic") { { auto p1 = pr::promise(); auto p2 = pr::promise(); REQUIRE_FALSE(p1 == p2); REQUIRE(p1 != p2); REQUIRE((p1 < p2 || p2 < p1)); REQUIRE(p1.hash() != p2.hash()); REQUIRE(p1.hash() == std::hash>()(p1)); } { auto p1 = pr::promise(); auto p2 = pr::promise(); REQUIRE_FALSE(p1 == p2); REQUIRE(p1 != p2); REQUIRE((p1 < p2 || p2 < p1)); REQUIRE(p1.hash() != p2.hash()); REQUIRE(p1.hash() == std::hash>()(p1)); } { auto p1 = pr::promise(); auto p2 = pr::promise(); auto p3 = p1; REQUIRE(p1 == p3); p3 = p2; REQUIRE(p2 == p3); } { auto p1 = pr::promise(); auto p2 = pr::promise(); auto p3 = p1; p1.swap(p2); REQUIRE(p2 == p3); REQUIRE_FALSE(p1 == p3); } { auto p1 = pr::promise(); auto p2 = pr::promise(); auto p3 = p1; p1.swap(p2); REQUIRE(p2 == p3); REQUIRE_FALSE(p1 == p3); } } SECTION("resolved") { { int check_42_int = 0; auto p = pr::promise(); p.resolve(42); p.then([&check_42_int](int value){ check_42_int = value; }); REQUIRE(check_42_int == 42); } { int check_42_int = 0; auto p = pr::promise(); p.resolve(42); p.except([](std::exception_ptr){ }).then([&check_42_int](int value){ check_42_int = value; }); REQUIRE(check_42_int == 42); } { int check_84_int = 0; bool check_void_call = false; int check_100500_transform = 0; auto p = pr::promise(); p.resolve(42); p.then([](int value){ return value * 2; }).then([&check_84_int](int value){ check_84_int = value; }).then([&check_void_call](){ check_void_call = true; }).then([](){ return 100500; }).then([&check_100500_transform](int value){ check_100500_transform = value; }); REQUIRE(check_84_int == 84); REQUIRE(check_void_call); REQUIRE(check_100500_transform == 100500); } } SECTION("rejected") { { bool call_fail_with_logic_error = false; bool not_call_then_on_reject = true; auto p = pr::promise(); p.reject(std::logic_error("hello fail")); p.then([¬_call_then_on_reject](int value) { (void)value; not_call_then_on_reject = false; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } { std::logic_error ee("hello fail"); bool call_fail_with_logic_error = false; auto p = pr::promise(); p.reject(ee); p.then([](int){ }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { std::logic_error ee("hello fail"); bool call_fail_with_logic_error = false; auto p = pr::promise(); p.reject(std::make_exception_ptr(ee)); p.then([](int){ }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { int check_multi_fail = 0; auto p = pr::promise<>(); p.reject(std::logic_error("hello fail")); p.except([&check_multi_fail](std::exception_ptr){ ++check_multi_fail; }).except([&check_multi_fail](std::exception_ptr){ ++check_multi_fail; }); REQUIRE(check_multi_fail == 2); } } SECTION("unresolved") { { int check_42_int = 0; bool not_call_before_resolve = true; auto p = pr::promise(); p.then([¬_call_before_resolve](int value){ not_call_before_resolve = false; return value * 2; }).then([&check_42_int, ¬_call_before_resolve](int value){ not_call_before_resolve = false; check_42_int = value; }); REQUIRE(check_42_int == 0); REQUIRE(not_call_before_resolve); p.resolve(42); REQUIRE(check_42_int == 84); REQUIRE_FALSE(not_call_before_resolve); } { bool not_call_then_on_reject = true; bool call_fail_with_logic_error = false; auto p = pr::promise(); p.then([¬_call_then_on_reject](int){ not_call_then_on_reject = false; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE_FALSE(call_fail_with_logic_error); p.reject(std::make_exception_ptr(std::logic_error("hello fail"))); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } } SECTION("make_promise") { { int check_84_int = 0; auto p = pr::make_promise([](auto resolve, auto reject){ (void)reject; resolve(42); }); p.then([](int value){ return value * 2; }).then([&check_84_int](int value){ check_84_int = value; }); REQUIRE(check_84_int == 84); } { bool call_fail_with_logic_error = false; auto p = pr::make_promise([](auto resolve, auto reject){ (void)resolve; reject(std::logic_error("hello fail")); }); p.except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p = pr::make_promise([](auto resolve, auto reject){ (void)resolve; (void)reject; throw std::logic_error("hello fail"); }); p.except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } } SECTION("make_resolved_promise") { { bool call_check = false; pr::make_resolved_promise() .then([&call_check]{ call_check = true; }); REQUIRE(call_check); } { int check_42_int = 0; pr::make_resolved_promise(42) .then([&check_42_int](int value){ check_42_int = value; }); REQUIRE(check_42_int == 42); } } SECTION("make_rejected_promise") { { bool call_fail_with_logic_error = false; pr::make_rejected_promise(std::logic_error("hello fail")) .except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; pr::make_rejected_promise(std::logic_error("hello fail")) .except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } } SECTION("exceptions") { { bool not_call_then_on_reject = true; bool call_fail_with_logic_error = false; auto p = pr::promise(); p.resolve(42); p.then([](int){ throw std::logic_error("hello fail"); }).then([¬_call_then_on_reject](){ not_call_then_on_reject = false; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } { bool not_call_then_on_reject = true; bool call_fail_with_logic_error = false; auto p = pr::promise(); p.resolve(42); p.then([](int){ throw std::logic_error("hello fail"); }, [](std::exception_ptr){ throw std::logic_error("hello fail2"); }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail2_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } } SECTION("multi_then") { { auto p = pr::promise(); int pa_value = 0; { auto pa = p.then([](int value){ return value * 2; }).then([&pa_value](int value){ pa_value = value; }); } int pb_value = 0; { auto pb = p.then([](int value){ return value / 2; }).then([&pb_value](int value){ pb_value = value; }); } REQUIRE(pa_value == 0); REQUIRE(pb_value == 0); p.resolve(42); REQUIRE(pa_value == 84); REQUIRE(pb_value == 21); } { auto p = pr::promise(); int pa_value = 0; { auto pa = p.then([](int){ throw std::logic_error("hello fail"); }).except([&pa_value](std::exception_ptr e){ if ( check_hello_fail_exception(e) ) { pa_value = 84; } }); } int pb_value = 0; { auto pb = p.then([](int value){ return value / 2; }).then([&pb_value](int value){ pb_value = value; }); } REQUIRE(pa_value == 0); REQUIRE(pb_value == 0); p.resolve(42); REQUIRE(pa_value == 84); REQUIRE(pb_value == 21); } } SECTION("chaining") { { int check_84_int = 0; auto p1 = pr::make_resolved_promise(42); auto p2 = pr::make_resolved_promise(84); p1.then([&p2](int v){ (void)v; return p2; }).then([&check_84_int](int v2){ check_84_int = v2; }); REQUIRE(check_84_int == 84); } { int check_84_int = 0; auto p1 = pr::make_resolved_promise(); auto p2 = pr::make_resolved_promise(84); p1.then([&p2](){ return p2; }).then([&check_84_int](int v){ check_84_int = v; }); REQUIRE(check_84_int == 84); } { int check_84_int = 0; auto p1 = pr::make_resolved_promise(42); auto p2 = pr::make_resolved_promise(); p1.then([&p2](int v){ (void)v; return p2; }).then([&check_84_int](){ check_84_int = 84; }); REQUIRE(check_84_int == 84); } { int check_84_int = 0; auto p1 = pr::make_resolved_promise(); auto p2 = pr::make_resolved_promise(); p1.then([&p2](){ return p2; }).then([&check_84_int](){ check_84_int = 84; }); REQUIRE(check_84_int == 84); } } SECTION("lazy_chaining") { { int check_84_int = 0; auto p1 = pr::make_promise(); auto p2 = pr::make_promise(); p1.then([&p2](int v){ (void)v; return p2; }).then([&check_84_int](int v2){ check_84_int = v2; }); REQUIRE(check_84_int == 0); p1.resolve(42); REQUIRE(check_84_int == 0); p2.resolve(84); REQUIRE(check_84_int == 84); } } SECTION("typed_chaining_fails") { { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(42); p1.then([](int v) -> pr::promise { (void)v; throw std::logic_error("hello fail"); }).then([](int v2){ (void)v2; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(42); auto p2 = pr::make_resolved_promise(84); p1.then([&p2](int v){ (void)v; return p2; }).then([](int v2){ (void)v2; throw std::logic_error("hello fail"); }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_rejected_promise(std::logic_error("hello fail")); auto p2 = pr::make_resolved_promise(84); p1.then([&p2](int v){ (void)v; return p2; }).then([](int v2){ (void)v2; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(42); auto p2 = pr::make_rejected_promise(std::logic_error("hello fail")); p1.then([&p2](int v){ (void)v; return p2; }).then([](int v2){ (void)v2; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } } SECTION("void_chaining_fails") { { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(); p1.then([]() -> pr::promise { throw std::logic_error("hello fail"); }).then([](){ }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(); auto p2 = pr::make_resolved_promise(); p1.then([&p2](){ return p2; }).then([](){ throw std::logic_error("hello fail"); }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_rejected_promise(std::logic_error("hello fail")); auto p2 = pr::make_resolved_promise(); p1.then([&p2](){ return p2; }).then([](){ }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { bool call_fail_with_logic_error = false; auto p1 = pr::make_resolved_promise(); auto p2 = pr::make_rejected_promise(std::logic_error("hello fail")); p1.then([&p2](){ return p2; }).then([](){ }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } } SECTION("make_all_promise") { { bool all_is_ok = false; pr::make_all_promise(std::vector>()) .then([&all_is_ok](const std::vector& c){ all_is_ok = c.empty(); }); REQUIRE(all_is_ok); } { bool all_is_ok = false; auto p = pr::make_all_promise(std::vector>{ pr::make_resolved_promise(32), pr::make_resolved_promise(10) }).then([&all_is_ok](const std::vector& c){ all_is_ok = (2 == c.size()) && c[0] == 32 && c[1] == 10; }); REQUIRE(all_is_ok); } { auto p1 = pr::promise(); auto p2 = pr::promise(); int call_then_only_once = 0; pr::make_all_promise(std::vector>{p1, p2}) .then([&call_then_only_once](const std::vector& c){ (void)c; ++call_then_only_once; }); p1.resolve(1); p2.resolve(2); REQUIRE(call_then_only_once == 1); } } SECTION("make_any_promise") { { auto p1 = pr::promise(); auto p2 = pr::promise(); int check_42_int = 0; int call_then_only_once = 0; pr::make_any_promise(std::vector>{p1, p2}) .then([&check_42_int, &call_then_only_once](const int& v){ check_42_int = v; ++call_then_only_once; }); p1.resolve(42); REQUIRE(check_42_int == 42); REQUIRE(call_then_only_once == 1); p2.resolve(84); REQUIRE(check_42_int == 42); REQUIRE(call_then_only_once == 1); } { auto p1 = pr::promise(); auto p2 = pr::promise(); int check_42_int = 0; int call_then_only_once = 0; pr::make_any_promise(std::vector>{p1, p2}) .then([&check_42_int, &call_then_only_once](const int& v){ check_42_int = v; ++call_then_only_once; }); p2.resolve(42); REQUIRE(check_42_int == 42); REQUIRE(call_then_only_once == 1); p1.resolve(84); REQUIRE(check_42_int == 42); REQUIRE(call_then_only_once == 1); } } SECTION("make_all_promise_fail") { { bool call_fail_with_logic_error = false; bool not_call_then_on_reject = true; auto p = pr::make_all_promise(std::vector>{ pr::make_rejected_promise(std::logic_error("hello fail")), pr::make_resolved_promise(10) }).then([¬_call_then_on_reject](const std::vector& c){ (void)c; not_call_then_on_reject = false; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } } SECTION("make_any_promise_fail") { REQUIRE_THROWS_AS( pr::make_any_promise(std::vector>{}), std::logic_error); { bool call_fail_with_logic_error = false; bool not_call_then_on_reject = true; auto p = pr::make_any_promise(std::vector>{ pr::make_rejected_promise(std::logic_error("hello fail")), pr::make_resolved_promise(10) }).then([¬_call_then_on_reject](const int& c){ (void)c; not_call_then_on_reject = false; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(not_call_then_on_reject); REQUIRE(call_fail_with_logic_error); } } SECTION("then_all") { { int check_42_int = 0; pr::make_resolved_promise() .then_all([](){ return std::vector>{ pr::make_resolved_promise(32), pr::make_resolved_promise(10)}; }).then([&check_42_int](const std::vector& v){ if ( v.size() == 2) { check_42_int = v[0] + v[1]; } }); REQUIRE(check_42_int == 42); } { int check_42_int = 0; int check_42_int2 = 0; pr::make_resolved_promise(42) .then_all([&check_42_int](int v){ check_42_int = v; return std::vector>{ pr::make_resolved_promise(32), pr::make_resolved_promise(10)}; }).then([&check_42_int2](const std::vector& v){ if ( v.size() == 2) { check_42_int2 = v[0] + v[1]; } }); REQUIRE(check_42_int == 42); REQUIRE(check_42_int2 == 42); } } SECTION("then_any") { { int check_42_int = 0; pr::make_resolved_promise() .then_any([](){ return std::vector>{ pr::make_resolved_promise(42), pr::make_rejected_promise(std::logic_error("hello fail"))}; }).then([&check_42_int](const int& v){ check_42_int = v; }); REQUIRE(check_42_int == 42); } { bool call_fail_with_logic_error = false; pr::make_resolved_promise() .then_any([](){ return std::vector>{ pr::make_rejected_promise(std::logic_error("hello fail")), pr::make_resolved_promise(42)}; }).except([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); } { int check_42_int = 0; int check_42_int2 = 0; int call_then_only_once = 0; pr::make_resolved_promise(42) .then_any([&check_42_int](int v){ check_42_int = v; return std::vector>{ pr::make_resolved_promise(42), pr::make_resolved_promise(10)}; }).then([&call_then_only_once, &check_42_int2](const int& v){ ++call_then_only_once; check_42_int2 = v; }); REQUIRE(check_42_int == 42); REQUIRE(check_42_int2 == 42); REQUIRE(call_then_only_once == 1); } } } TEST_CASE("get_and_wait") { SECTION("get_void_promises") { { auto p = pr::make_resolved_promise(); REQUIRE_NOTHROW(p.get()); } { auto p = pr::make_rejected_promise(std::logic_error("hello fail")); REQUIRE_THROWS_AS(p.get(), std::logic_error); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(5)); p.resolve(); }}; t.join(); REQUIRE_NOTHROW(p.get()); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(5)); p.resolve(); }}; REQUIRE_NOTHROW(p.get()); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(50)); p.resolve(); }}; REQUIRE(p.wait_for( std::chrono::milliseconds(5)) == pr::promise_wait_status::timeout); REQUIRE(p.wait_for( std::chrono::milliseconds(200)) == pr::promise_wait_status::no_timeout); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(50)); p.resolve(); }}; REQUIRE(p.wait_until( std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(5)) == pr::promise_wait_status::timeout); REQUIRE(p.wait_until( std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(200)) == pr::promise_wait_status::no_timeout); } } SECTION("get_typed_promises") { { auto p = pr::make_resolved_promise(42); REQUIRE(p.get() == 42); } { auto p = pr::make_rejected_promise(std::logic_error("hello fail")); REQUIRE_THROWS_AS(p.get(), std::logic_error); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(5)); p.resolve(42); }}; t.join(); REQUIRE(p.get() == 42); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(5)); p.resolve(42); }}; REQUIRE(p.get() == 42); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(50)); p.resolve(42); }}; REQUIRE(p.wait_for( std::chrono::milliseconds(5)) == pr::promise_wait_status::timeout); REQUIRE(p.wait_for( std::chrono::milliseconds(200)) == pr::promise_wait_status::no_timeout); REQUIRE(p.get() == 42); } { auto p = pr::promise(); auto_thread t{[p]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(50)); p.resolve(42); }}; REQUIRE(p.wait_until( std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(5)) == pr::promise_wait_status::timeout); REQUIRE(p.wait_until( std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(200)) == pr::promise_wait_status::no_timeout); REQUIRE(p.get() == 42); } } } TEST_CASE("jobber") { { jb::jobber j(1); auto pv0 = j.async([](){ throw std::exception(); }); REQUIRE_THROWS_AS(pv0.get(), std::exception); } { auto pv0 = pr::promise(); { jb::jobber j{0}; pv0 = j.async([](){ return 42; }); } REQUIRE_THROWS_AS(pv0.get(), jb::jobber_cancelled_exception); } { int v5 = 5; jb::jobber j(1); auto pv0 = j.async([](int v){ REQUIRE(v == 5); throw std::exception(); }, v5); REQUIRE_THROWS_AS(pv0.get(), std::exception); auto pv1 = j.async([](int& v){ REQUIRE(v == 5); return v != 5 ? 0 : throw std::exception(); }, std::ref(v5)); REQUIRE_THROWS_AS(pv1.get(), std::exception); auto pv3 = j.async([](int& v){ v = 4; return v; }, std::ref(v5)); REQUIRE(pv3.get() == v5); REQUIRE(v5 == 4); } { const float pi = 3.14159265358979323846264338327950288f; jb::jobber j(1); auto p0 = j.async([](float angle){ return std::sin(angle); }, pi); auto p1 = j.async([](float angle){ return std::cos(angle); }, pi * 2); REQUIRE(p0.get() == Approx(0.f).margin(0.01f)); REQUIRE(p1.get() == Approx(1.f).margin(0.01f)); } { jb::jobber j(1); j.pause(); jb::jobber_priority max_priority = jb::jobber_priority::highest; j.async([](){ std::this_thread::sleep_for(std::chrono::milliseconds(2)); }); for ( std::size_t i = 0; i < 10; ++i ) { jb::jobber_priority p = static_cast( i % static_cast(jb::jobber_priority::highest)); j.async(p, [&max_priority](jb::jobber_priority priority) { REQUIRE(priority <= max_priority); max_priority = priority; }, p); } j.resume(); j.wait_all(); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 10; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } j.resume(); REQUIRE(counter < 10); j.wait_all(); REQUIRE(counter == 10); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 10; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } REQUIRE(counter < 10); j.active_wait_all(); REQUIRE(counter == 10); } { jb::jobber j(1); const auto time_now = [](){ return std::chrono::high_resolution_clock::now(); }; REQUIRE(jb::jobber_wait_status::no_timeout == j.wait_all_for(std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::no_timeout == j.wait_all_until(time_now() + std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::no_timeout == j.active_wait_all_for(std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::no_timeout == j.active_wait_all_until(time_now() + std::chrono::milliseconds(-1))); j.pause(); j.async([]{}); REQUIRE(jb::jobber_wait_status::timeout == j.wait_all_for(std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::timeout == j.wait_all_until(time_now() + std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::timeout == j.active_wait_all_for(std::chrono::milliseconds(-1))); REQUIRE(jb::jobber_wait_status::timeout == j.active_wait_all_until(time_now() + std::chrono::milliseconds(-1))); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 10; ++i ) { j.async([&counter](){ ++counter; }); } const auto time_now = [](){ return std::chrono::high_resolution_clock::now(); }; j.wait_all_for(std::chrono::milliseconds(10)); j.wait_all_until(time_now() + std::chrono::milliseconds(10)); REQUIRE(counter == 0); j.active_wait_all_for(std::chrono::milliseconds(10)); j.active_wait_all_until(time_now() + std::chrono::milliseconds(10)); REQUIRE(counter > 0); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 50; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } const auto time_now = [](){ return std::chrono::high_resolution_clock::now(); }; const auto b = time_now(); j.resume(); j.wait_all_for(std::chrono::milliseconds(100)); REQUIRE(time_now() - b > std::chrono::milliseconds(50)); REQUIRE(counter > 2); REQUIRE(counter < 50); j.wait_all_until(time_now() + std::chrono::seconds(3)); REQUIRE(counter == 50); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 50; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } const auto time_now = [](){ return std::chrono::high_resolution_clock::now(); }; const auto b = time_now(); j.wait_all_for(std::chrono::milliseconds(15)); REQUIRE(time_now() - b > std::chrono::milliseconds(10)); REQUIRE(counter == 0); j.wait_all_until(time_now() + std::chrono::milliseconds(15)); REQUIRE(time_now() - b > std::chrono::milliseconds(20)); REQUIRE(counter == 0); j.active_wait_all_for(std::chrono::milliseconds(100)); REQUIRE(time_now() - b > std::chrono::milliseconds(70)); REQUIRE(counter > 2); REQUIRE(counter < 50); j.active_wait_all_until(time_now() + std::chrono::seconds(3)); REQUIRE(counter == 50); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 30; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } j.resume(); REQUIRE(jb::jobber_wait_status::timeout == j.wait_all_for(std::chrono::milliseconds(50))); REQUIRE(counter > 0); REQUIRE(jb::jobber_wait_status::no_timeout == j.wait_all_for(std::chrono::seconds(5))); REQUIRE(counter == 30); } { jb::jobber j(1); std::atomic counter = ATOMIC_VAR_INIT(0); j.pause(); for ( std::size_t i = 0; i < 30; ++i ) { j.async([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } REQUIRE(jb::jobber_wait_status::timeout == j.active_wait_all_for(std::chrono::milliseconds(50))); REQUIRE(counter > 0); REQUIRE(jb::jobber_wait_status::no_timeout == j.active_wait_all_for(std::chrono::seconds(5))); REQUIRE(counter == 30); } { jb::jobber j(2); jb::jobber g(2); std::vector> jp(50); for ( auto& jpi : jp ) { jpi = j.async([&g](){ std::vector> gp(50); for ( std::size_t i = 0; i < gp.size(); ++i ) { gp[i] = g.async([](float angle){ return std::sin(angle); }, static_cast(i)); } return std::accumulate(gp.begin(), gp.end(), 0.f, [](float r, pr::promise& f){ return r + f.get(); }); }); } float r0 = std::accumulate(jp.begin(), jp.end(), 0.f, [](float r, pr::promise& f){ return r + f.get(); }); float r1 = 0.f; for ( std::size_t i = 0; i < 50; ++i ) { r1 += std::sin(static_cast(i)); } REQUIRE(r0 == Approx(r1 * 50.f).margin(0.01f)); } } TEST_CASE("scheduler") { { sd::scheduler s; auto pv0 = s.schedule([](){ throw std::exception(); }); s.process_all_tasks(); REQUIRE_THROWS_AS(pv0.get(), std::exception); } { auto pv0 = pr::promise(); { sd::scheduler s; pv0 = s.schedule([](){ return 42; }); } REQUIRE_THROWS_AS(pv0.get(), sd::scheduler_cancelled_exception); } { sd::scheduler s; int counter = 0; s.schedule([&counter](){ ++counter; }); REQUIRE(counter == 0); s.process_all_tasks(); REQUIRE(counter == 1); s.schedule([&counter](){ ++counter; }); s.schedule([&counter](){ ++counter; }); REQUIRE(counter == 1); s.process_all_tasks(); REQUIRE(counter == 3); } { sd::scheduler s; int counter = 0; for ( std::size_t i = 0; i < 50; ++i ) { s.schedule([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } s.process_tasks_for(std::chrono::milliseconds(-1)); s.process_tasks_for(std::chrono::milliseconds(0)); REQUIRE(counter == 0); s.process_tasks_for(std::chrono::milliseconds(100)); REQUIRE(counter > 2); REQUIRE(counter < 50); s.process_tasks_for(std::chrono::seconds(3)); REQUIRE(counter == 50); } { sd::scheduler s; int counter = 0; for ( std::size_t i = 0; i < 50; ++i ) { s.schedule([&counter](){ ++counter; std::this_thread::sleep_for(std::chrono::milliseconds(5)); }); } const auto time_now = [](){ return std::chrono::high_resolution_clock::now(); }; const auto b = time_now(); s.process_tasks_until(time_now() - std::chrono::milliseconds(1)); s.process_tasks_until(time_now()); REQUIRE(counter == 0); s.process_tasks_until(time_now() + std::chrono::milliseconds(100)); REQUIRE(time_now() - b > std::chrono::milliseconds(50)); REQUIRE(counter > 2); REQUIRE(counter < 50); s.process_tasks_until(time_now() + std::chrono::seconds(3)); REQUIRE(counter == 50); } { sd::scheduler s; std::string accumulator; s.schedule(sd::scheduler_priority::lowest, [](std::string& acc){ acc.append("o"); }, std::ref(accumulator)); s.schedule(sd::scheduler_priority::below_normal, [](std::string& acc){ acc.append("l"); }, std::ref(accumulator)); s.schedule(sd::scheduler_priority::highest, [](std::string& acc){ acc.append("h"); }, std::ref(accumulator)); s.schedule(sd::scheduler_priority::above_normal, [](std::string& acc){ acc.append("e"); }, std::ref(accumulator)); s.schedule(sd::scheduler_priority::normal, [](std::string& acc){ acc.append("l"); }, std::ref(accumulator)); s.process_all_tasks(); REQUIRE(accumulator == "hello"); } }