/* 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&);
}
};