| #include "unique_resource.h" | #include "unique_resource.h" | ||||
| #include <functional> | #include <functional> | ||||
| #include <catch.hpp> | #include <catch.hpp> | ||||
| #include <trompeloeil.hpp> | |||||
| using namespace trompeloeil; | |||||
| using Handle = int; | using Handle = int; | ||||
| using PtrHandle = std::add_pointer_t<Handle>; | using PtrHandle = std::add_pointer_t<Handle>; | ||||
| TEST_CASE("deleter called on destruction", "[UniqueResource]") | |||||
| namespace mock | |||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| struct CallMock | |||||
| { | { | ||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE(calls == 1); | |||||
| MAKE_MOCK1(deleter, void(Handle)); | |||||
| }; | |||||
| } | } | ||||
| TEST_CASE("deleter is not called if released", "[UniqueResource]") | |||||
| namespace | |||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| mock::CallMock m; | |||||
| void deleter(Handle h) | |||||
| { | { | ||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| guard.release(); | |||||
| m.deleter(h); | |||||
| } | } | ||||
| REQUIRE(calls == 0); | |||||
| } | } | ||||
| TEST_CASE("deleter not called if checked valid", "[UniqueResource]") | |||||
| { | |||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto guard = sr::unique_resource_checked(Handle{3}, Handle{6}, [&calls](auto) { ++calls; }); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| TEST_CASE("deleter called on destruction", "[UniqueResource]") | |||||
| { | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| REQUIRE(calls == 1); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("deleter not called if checked invalid", "[UniqueResource]") | |||||
| TEST_CASE("deleter is not called if released", "[UniqueResource]") | |||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| REQUIRE_CALL(m, deleter(3)).TIMES(0); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.release(); | |||||
| } | |||||
| { | |||||
| auto guard = sr::unique_resource_checked(Handle{3}, Handle{3}, [&calls](auto) { ++calls; }); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| TEST_CASE("deleter called if checked valid", "[UniqueResource]") | |||||
| { | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| auto guard = sr::unique_resource_checked(Handle{3}, Handle{6}, deleter); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE(calls == 0); | |||||
| TEST_CASE("deleter not called if checked invalid", "[UniqueResource]") | |||||
| { | |||||
| REQUIRE_CALL(m, deleter(3)).TIMES(0); | |||||
| auto guard = sr::unique_resource_checked(Handle{3}, Handle{3}, deleter); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("release returns reference to resource", "[UniqueResource]") | TEST_CASE("release returns reference to resource", "[UniqueResource]") | ||||
| TEST_CASE("move releases moved-from object", "[UniqueResource]") | TEST_CASE("move releases moved-from object", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE(calls == 1); | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("move transfers state", "[UniqueResource]") | TEST_CASE("move transfers state", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE(calls == 1); | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("move transfers state if released", "[UniqueResource]") | TEST_CASE("move transfers state if released", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| movedFrom.release(); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(ANY(Handle))).TIMES(0); // TODO: Use ANY(T) | |||||
| REQUIRE(calls == 0); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| movedFrom.release(); | |||||
| auto guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("move assignment releases moved-from object", "[UniqueResource]") | TEST_CASE("move assignment releases moved-from object", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| std::function<void(Handle)> del = [&calls](auto) { ++calls; }; | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, del); | |||||
| auto guard = sr::unique_resource(Handle{3}, del); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(5)); | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| REQUIRE(calls == 2); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| auto guard = sr::unique_resource(Handle{5}, deleter); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("move assignment transfers state", "[UniqueResource]") | TEST_CASE("move assignment transfers state", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| std::function<void(Handle)> del = [&calls](auto) { ++calls; }; | |||||
| REQUIRE_CALL(m, deleter(5)); | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, del); | |||||
| auto guard = sr::unique_resource(Handle{3}, del); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE(calls == 2); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| auto guard = sr::unique_resource(Handle{5}, deleter); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("move assignment transfers state if released", "[UniqueResource]") | TEST_CASE("move assignment transfers state if released", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| std::function<void(Handle)> del = [&calls](auto) { ++calls; }; | |||||
| { | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, del); | |||||
| movedFrom.release(); | |||||
| auto guard = sr::unique_resource(Handle{3}, del); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(5)); | |||||
| REQUIRE(calls == 1); | |||||
| auto movedFrom = sr::unique_resource(Handle{3}, deleter); | |||||
| movedFrom.release(); | |||||
| auto guard = sr::unique_resource(Handle{5}, deleter); | |||||
| guard = std::move(movedFrom); | |||||
| static_cast<void>(guard); | |||||
| } | } | ||||
| TEST_CASE("no exception propagation from deleter", "[UniqueResource]") | TEST_CASE("no exception propagation from deleter", "[UniqueResource]") | ||||
| TEST_CASE("invoke executes deleter on resource", "[UniqueResource]") | TEST_CASE("invoke executes deleter on resource", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| { | |||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto h) | |||||
| { | |||||
| REQUIRE(h == 3); | |||||
| ++calls; | |||||
| }); | |||||
| guard.invoke(); | |||||
| } | |||||
| REQUIRE(calls == 1); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.invoke(); | |||||
| } | } | ||||
| TEST_CASE("invoke executes only multiple times if again strategy", "[UniqueResource]") | TEST_CASE("invoke executes only multiple times if again strategy", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto h) { if( h == 3 ) { ++calls;} }); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(3)).TIMES(3); | |||||
| REQUIRE(calls == 3); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| } | } | ||||
| TEST_CASE("invoke does nothing if released", "[UniqueResource]") | TEST_CASE("invoke does nothing if released", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| guard.release(); | |||||
| guard.invoke(sr::invoke_it::once); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(ANY(Handle))).TIMES(0); | |||||
| REQUIRE(calls == 0); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.release(); | |||||
| guard.invoke(sr::invoke_it::once); | |||||
| } | } | ||||
| TEST_CASE("invoke executes after release if again strategy", "[UniqueResource]") | TEST_CASE("invoke executes after release if again strategy", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| { | |||||
| auto guard = sr::unique_resource(Handle{3}, [&calls](auto) { ++calls; }); | |||||
| guard.release(); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| } | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| REQUIRE(calls == 1); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.release(); | |||||
| guard.invoke(sr::invoke_it::again); | |||||
| } | } | ||||
| TEST_CASE("invoke does not propagate exception", "[UniqueResource]") | TEST_CASE("invoke does not propagate exception", "[UniqueResource]") | ||||
| TEST_CASE("reset releases old ressource", "[UniqueResource]") | TEST_CASE("reset releases old ressource", "[UniqueResource]") | ||||
| { | { | ||||
| std::size_t calls{0}; | |||||
| REQUIRE_CALL(m, deleter(3)); | |||||
| REQUIRE_CALL(m, deleter(7)); | |||||
| { | |||||
| auto d = [&calls](auto v) | |||||
| { | |||||
| if( calls == 0 ) | |||||
| { | |||||
| REQUIRE(v == 3); | |||||
| } | |||||
| else | |||||
| { | |||||
| REQUIRE(v == 7); | |||||
| } | |||||
| ++calls; | |||||
| }; | |||||
| auto guard = sr::unique_resource(Handle{3}, d); | |||||
| guard.reset(Handle{7}); | |||||
| } | |||||
| REQUIRE(calls == 2); | |||||
| auto guard = sr::unique_resource(Handle{3}, deleter); | |||||
| guard.reset(Handle{7}); | |||||
| } | } | ||||
| TEST_CASE("reset sets ressource", "[UniqueResource]") | TEST_CASE("reset sets ressource", "[UniqueResource]") |