/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "lib/self_test.h" #include "ps/Future.h" #include #include #include class TestFuture : public CxxTest::TestSuite { public: void test_future_basic() { bool executed{false}; Future noret; auto task = noret.Wrap([&]{ executed = true; }); task(); TS_ASSERT(executed); } void test_future_return() { { Future future; std::function task = future.Wrap([]() { return 1; }); task(); TS_ASSERT_EQUALS(future.Get(), 1); } static int destroyed = 0; // No trivial constructor or destructor. Also not copiable. struct NonDef { NonDef() = delete; NonDef(int input) : value(input) {}; NonDef(const NonDef&) = delete; NonDef(NonDef&& o) { value = o.value; o.value = 0; } ~NonDef() { if (value != 0) destroyed++; } int value = 0; }; TS_ASSERT_EQUALS(destroyed, 0); { Future future; std::function task = future.Wrap([]() { return NonDef{1}; }); task(); TS_ASSERT_EQUALS(future.Get().value, 1); } TS_ASSERT_EQUALS(destroyed, 1); { Future future; std::function task = future.Wrap([]() { return NonDef{1}; }); } TS_ASSERT_EQUALS(destroyed, 1); /** * TODO: find a way to test this { Future future; std::function task = future.Wrap([]() { return NonDef{1}; }); future.CancelOrWait(); TS_ASSERT_THROWS(future.Get(), const Future::BadFutureAccess&); } */ TS_ASSERT_EQUALS(destroyed, 1); } void test_future_moving() { Future future; std::function function; // Set things up so all temporaries passed into the futures will be reset to obviously invalid memory. std::aligned_storage_t), alignof(Future)> futureStorage; std::aligned_storage_t), alignof(std::function)> functionStorage; Future* f = &future; // CppCheck doesn't read placement new correctly, do this to silence errors. std::function* c = &function; f = new (&futureStorage) Future{}; c = new (&functionStorage) std::function{}; *c = []() { return 7; }; std::function task = f->Wrap(std::move(*c)); future = std::move(*f); function = std::move(*c); // Destroy and clear the memory f->~Future(); c->~function(); memset(&futureStorage, 0xFF, sizeof(decltype(futureStorage))); memset(&functionStorage, 0xFF, sizeof(decltype(functionStorage))); // Let's move the packaged task while at it. std::function task2 = std::move(task); task2(); TS_ASSERT_EQUALS(future.Get(), 7); } void test_move_only_function() { Future future; class MoveOnlyType { public: MoveOnlyType() = default; MoveOnlyType(MoveOnlyType&) = delete; MoveOnlyType& operator=(MoveOnlyType&) = delete; MoveOnlyType(MoveOnlyType&&) = default; MoveOnlyType& operator=(MoveOnlyType&&) = default; int fn() const { return 7; } }; auto task = future.Wrap([t = MoveOnlyType{}]{ return t.fn(); }); task(); TS_ASSERT_EQUALS(future.Get(), 7); } struct TestException : std::exception { using std::exception::exception; }; void test_exception() { Future future; auto packedTask = future.Wrap([]() -> int { throw TestException{}; }); packedTask(); TS_ASSERT(future.IsReady()); TS_ASSERT_THROWS(future.Get(), const TestException&); } void test_voidException() { Future future; auto packedTask = future.Wrap([] { throw TestException{}; }); packedTask(); TS_ASSERT(future.IsReady()); TS_ASSERT_THROWS(future.Get(), const TestException&); } void test_implicitException() { // If the function does not throw but it's the cause something is thrown the exception should // also be reported to the code receiving the result. class ThrowsOnMove { public: ThrowsOnMove() = default; ThrowsOnMove(ThrowsOnMove&&) { throw TestException{}; } }; Future future; auto packedTask = future.Wrap([] { return ThrowsOnMove{}; }); packedTask(); TS_ASSERT(future.IsReady()); TS_ASSERT_THROWS(future.Get(), const TestException&); } };