Changeset View
Changeset View
Standalone View
Standalone View
source/ps/tests/test_Future.h
/* Copyright (C) 2021 Wildfire Games. | /* Copyright (C) 2022 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
Show All 15 Lines | |||||
class TestFuture : public CxxTest::TestSuite | class TestFuture : public CxxTest::TestSuite | ||||
{ | { | ||||
public: | public: | ||||
void test_future_basic() | void test_future_basic() | ||||
{ | { | ||||
int counter = 0; | int counter = 0; | ||||
{ | { | ||||
Future<void> noret; | Future<void> noret; | ||||
std::function<void()> task = noret.Wrap([&counter]() mutable { counter++; }); | PackagedTask task = noret.Wrap([&counter]() mutable { counter++; }); | ||||
task(); | task(); | ||||
TS_ASSERT_EQUALS(counter, 1); | TS_ASSERT_EQUALS(counter, 1); | ||||
} | } | ||||
{ | { | ||||
Future<void> noret; | Future<void> noret; | ||||
{ | { | ||||
std::function<void()> task = noret.Wrap([&counter]() mutable { counter++; }); | PackagedTask task = noret.Wrap([&counter]() mutable { counter++; }); | ||||
// Auto-cancels the task. | // Auto-cancels the task. | ||||
} | } | ||||
} | } | ||||
TS_ASSERT_EQUALS(counter, 1); | TS_ASSERT_EQUALS(counter, 1); | ||||
} | } | ||||
void test_future_return() | void test_future_return() | ||||
{ | { | ||||
{ | { | ||||
Future<int> future; | Future<int> future; | ||||
std::function<void()> task = future.Wrap([]() { return 1; }); | PackagedTask task = future.Wrap([]() { return 1; }); | ||||
task(); | TS_ASSERT(future.Valid()); | ||||
TS_ASSERT_EQUALS(future.Get(), 1); | |||||
} | |||||
// Convertible type. | |||||
{ | |||||
Future<int> future; | |||||
std::function<void()> task = future.Wrap([]() -> u8 { return 1; }); | |||||
task(); | task(); | ||||
TS_ASSERT(future.Valid()); | |||||
TS_ASSERT_EQUALS(future.Get(), 1); | TS_ASSERT_EQUALS(future.Get(), 1); | ||||
} | } | ||||
static int destroyed = 0; | static int destroyed = 0; | ||||
// No trivial constructor or destructor. Also not copiable. | // No trivial constructor or destructor. Also not copiable. | ||||
struct NonDef | struct NonDef | ||||
{ | { | ||||
NonDef() = delete; | NonDef() = delete; | ||||
NonDef(int input) : value(input) {}; | NonDef(int input) : value(input) {}; | ||||
NonDef(const NonDef&) = delete; | NonDef(const NonDef&) = delete; | ||||
NonDef(NonDef&& o) | NonDef(NonDef&& o) | ||||
{ | { | ||||
value = o.value; | value = o.value; | ||||
o.value = 0; | o.value = 0; | ||||
} | } | ||||
~NonDef() { if (value != 0) destroyed++; } | ~NonDef() { if (value != 0) destroyed++; } | ||||
int value = 0; | int value = 0; | ||||
}; | }; | ||||
TS_ASSERT_EQUALS(destroyed, 0); | TS_ASSERT_EQUALS(destroyed, 0); | ||||
{ | { | ||||
Future<NonDef> future; | Future<NonDef> future; | ||||
std::function<void()> task = future.Wrap([]() { return 1; }); | PackagedTask task = future.Wrap([]() { return NonDef{1}; }); | ||||
task(); | task(); | ||||
TS_ASSERT_EQUALS(future.Get().value, 1); | TS_ASSERT_EQUALS(future.Get().value, 1); | ||||
} | } | ||||
TS_ASSERT_EQUALS(destroyed, 1); | TS_ASSERT_EQUALS(destroyed, 1); | ||||
{ | { | ||||
Future<NonDef> future; | Future<NonDef> future; | ||||
std::function<void()> task = future.Wrap([]() { return 1; }); | PackagedTask task = future.Wrap([]() { return NonDef{1}; }); | ||||
} | } | ||||
TS_ASSERT_EQUALS(destroyed, 1); | TS_ASSERT_EQUALS(destroyed, 1); | ||||
/** | /** | ||||
* TODO: find a way to test this | * TODO: find a way to test this | ||||
{ | { | ||||
Future<NonDef> future; | Future<NonDef> future; | ||||
std::function<void()> task = future.Wrap([]() { return 1; }); | PackagedTask task = future.Wrap([]() { return NonDef{1}; }); | ||||
future.Cancel(); | future.Cancel(); | ||||
future.Wait(); | future.Wait(); | ||||
TS_ASSERT_THROWS(future.Get(), const Future<NonDef>::BadFutureAccess&); | TS_ASSERT_THROWS(future.Get(), const Future<NonDef>::BadFutureAccess&); | ||||
} | } | ||||
*/ | */ | ||||
TS_ASSERT_EQUALS(destroyed, 1); | TS_ASSERT_EQUALS(destroyed, 1); | ||||
} | } | ||||
void test_future_moving() | void test_future_moving() | ||||
{ | { | ||||
Future<int> future; | Future<int> future; | ||||
std::function<int()> function; | std::function<int()> function; | ||||
// Set things up so all temporaries passed into the futures will be reset to obviously invalid memory. | // Set things up so all temporaries passed into the futures will be reset to obviously invalid memory. | ||||
std::aligned_storage_t<sizeof(Future<int>), alignof(Future<int>)> futureStorage; | std::aligned_storage_t<sizeof(Future<int>), alignof(Future<int>)> futureStorage; | ||||
std::aligned_storage_t<sizeof(std::function<int()>), alignof(std::function<int()>)> functionStorage; | std::aligned_storage_t<sizeof(std::function<int()>), alignof(std::function<int()>)> functionStorage; | ||||
Future<int>* f = &future; // CppCheck doesn't read placement new correctly, do this to silence errors. | Future<int>* f = &future; // CppCheck doesn't read placement new correctly, do this to silence errors. | ||||
std::function<int()>* c = &function; | std::function<int()>* c = &function; | ||||
f = new (&futureStorage) Future<int>{}; | f = new (&futureStorage) Future<int>{}; | ||||
c = new (&functionStorage) std::function<int()>{}; | c = new (&functionStorage) std::function<int()>{}; | ||||
*c = []() { return 7; }; | *c = []() { return 7; }; | ||||
std::function<void()> task = f->Wrap(std::move(*c)); | PackagedTask task = f->Wrap(std::move(*c)); | ||||
future = std::move(*f); | future = std::move(*f); | ||||
function = std::move(*c); | function = std::move(*c); | ||||
// Destroy and clear the memory | // Destroy and clear the memory | ||||
f->~Future(); | f->~Future(); | ||||
c->~function(); | c->~function(); | ||||
memset(&futureStorage, 0xFF, sizeof(decltype(futureStorage))); | memset(&futureStorage, 0xFF, sizeof(decltype(futureStorage))); | ||||
memset(&functionStorage, 0xFF, sizeof(decltype(functionStorage))); | memset(&functionStorage, 0xFF, sizeof(decltype(functionStorage))); | ||||
// Let's move the packaged task while at it. | // Let's move the packaged task while at it. | ||||
std::function<void()> task2 = std::move(task); | PackagedTask task2 = std::move(task); | ||||
task2(); | task2(); | ||||
TS_ASSERT_EQUALS(future.Get(), 7); | TS_ASSERT_EQUALS(future.Get(), 7); | ||||
} | } | ||||
}; | }; |
Wildfire Games · Phabricator