blob: 6e25215e84569574f5af02576ba04beb226b594d [file] [log] [blame]
/**
*Licensed to the Apache Software Foundation (ASF) under one
*or more contributor license agreements. See the NOTICE file
*distributed with this work for additional information
*regarding copyright ownership. The ASF licenses this file
*to you under the Apache License, Version 2.0 (the
*"License"); you may not use this file except in compliance
*with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing,
*software distributed under the License is distributed on an
*"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
*specific language governing permissions and limitations
*under the License.
*/
#include <gtest/gtest.h>
#include <future>
#include "celix/PromiseFactory.h"
class VoidPromiseTestSuite : public ::testing::Test {
public:
~VoidPromiseTestSuite() noexcept override {
factory->wait();
}
std::shared_ptr<celix::DefaultExecutor> executor = std::make_shared<celix::DefaultExecutor>();
std::shared_ptr<celix::PromiseFactory> factory = std::make_shared<celix::PromiseFactory>(executor);
};
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-result"
#endif
TEST_F(VoidPromiseTestSuite, simplePromise) {
auto deferred = factory->deferred<void>();
std::thread t{[&deferred] () {
std::this_thread::sleep_for(std::chrono::milliseconds{50});
deferred.resolve();
}};
auto promise = deferred.getPromise();
EXPECT_TRUE(promise.getValue()); //block until ready
EXPECT_TRUE(promise.isDone()); //got value, so promise is done
EXPECT_ANY_THROW(promise.getFailure()); //succeeded, so no exception available
EXPECT_TRUE(promise.getValue()); //note multiple call are valid;
t.join();
}
TEST_F(VoidPromiseTestSuite, failingPromise) {
auto deferred = factory->deferred<void>();
auto cpy = deferred;
std::thread t{[&deferred] () {
deferred.fail(std::logic_error{"failing"});
}};
auto promise = deferred.getPromise();
EXPECT_THROW(promise.getValue(), celix::PromiseInvocationException);
EXPECT_TRUE(promise.getFailure() != nullptr);
t.join();
}
TEST_F(VoidPromiseTestSuite, failingPromiseWithExceptionPtr) {
auto deferred = factory->deferred<void>();
std::thread t{[&deferred]{
try {
std::string{}.at(1); // this generates an std::out_of_range
//or use std::make_exception_ptr
} catch (...) {
deferred.fail(std::current_exception());
}
}};
auto promise = deferred.getPromise();
EXPECT_THROW(promise.getValue(), celix::PromiseInvocationException);
EXPECT_TRUE(promise.getFailure() != nullptr);
t.join();
}
TEST_F(VoidPromiseTestSuite, onSuccessHandling) {
auto deferred = factory->deferred<void>();
bool called = false;
bool resolveCalled = false;
auto p = deferred.getPromise()
.onSuccess([&called]() {
called = true;
})
.onResolve([&resolveCalled]() {
resolveCalled = true;
});
deferred.resolve();
factory->wait();
EXPECT_EQ(true, called);
EXPECT_EQ(true, resolveCalled);
}
TEST_F(VoidPromiseTestSuite, onFailureHandling) {
auto deferred = factory->deferred<void>();
bool successCalled = false;
bool failureCalled = false;
bool resolveCalled = false;
auto p = deferred.getPromise()
.onSuccess([&]() {
successCalled = true;
})
.onFailure([&](const std::exception &e) {
failureCalled = true;
std::cout << "got error: " << e.what() << std::endl;
})
.onResolve([&resolveCalled]() {
resolveCalled = true;
});
try {
std::string{}.at(1); // this generates an std::out_of_range
//or use std::make_exception_ptr
} catch (...) {
deferred.fail(std::current_exception());
}
factory->wait();
EXPECT_EQ(false, successCalled);
EXPECT_EQ(true, failureCalled);
EXPECT_EQ(true, resolveCalled);
}
TEST_F(VoidPromiseTestSuite, onFailureHandlingLogicError) {
auto deferred = factory->deferred<void>();
std::atomic<bool> successCalled = false;
std::atomic<bool> failureCalled = false;
std::atomic<bool> resolveCalled = false;
auto p = deferred.getPromise()
.onSuccess([&]() {
successCalled = true;
})
.onFailure([&](const std::exception &e) {
failureCalled = true;
ASSERT_TRUE(0 == strcmp("Some Exception", e.what())) << std::string(e.what());
})
.onResolve([&]() {
resolveCalled = true;
});
deferred.fail(std::logic_error("Some Exception"));
factory->wait();
EXPECT_EQ(false, successCalled);
EXPECT_EQ(true, failureCalled);
EXPECT_EQ(true, resolveCalled);
}
TEST_F(VoidPromiseTestSuite, onFailureHandlingPromiseIllegalStateException) {
auto deferred = factory->deferred<void>();
std::atomic<bool> successCalled = false;
std::atomic<bool> failureCalled = false;
std::atomic<bool> resolveCalled = false;
auto p = deferred.getPromise()
.onSuccess([&]() {
successCalled = true;
})
.onFailure([&](const std::exception &e) {
failureCalled = true;
ASSERT_TRUE(0 == strcmp("Illegal state", e.what())) << std::string(e.what());
})
.onResolve([&]() {
resolveCalled = true;
});
deferred.fail(celix::PromiseIllegalStateException());
factory->wait();
EXPECT_EQ(false, successCalled);
EXPECT_EQ(true, failureCalled);
EXPECT_EQ(true, resolveCalled);
}
TEST_F(VoidPromiseTestSuite, onFailureHandlingPromiseInvocationException) {
auto deferred = factory->deferred<void>();
std::atomic<bool> successCalled = false;
std::atomic<bool> failureCalled = false;
std::atomic<bool> resolveCalled = false;
auto p = deferred.getPromise()
.onSuccess([&]() {
successCalled = true;
})
.onFailure([&](const std::exception &e) {
failureCalled = true;
ASSERT_TRUE(0 == strcmp("MyExceptionText", e.what())) << std::string(e.what());
})
.onResolve([&]() {
resolveCalled = true;
});
deferred.fail(celix::PromiseInvocationException("MyExceptionText"));
factory->wait();
EXPECT_EQ(false, successCalled);
EXPECT_EQ(true, failureCalled);
EXPECT_EQ(true, resolveCalled);
}
TEST_F(VoidPromiseTestSuite, resolveSuccessWith) {
auto deferred1 = factory->deferred<void>();
bool called = false;
deferred1.getPromise()
.onSuccess([&called]() {
called = true;
});
auto deferred2 = factory->deferred<int>();
deferred1.resolveWith(deferred2.getPromise());
deferred2.resolve(42);
factory->wait();
EXPECT_EQ(true, called);
auto deferred3 = factory->deferred<void>();
called = false;
deferred3.getPromise()
.onSuccess([&called]() {
called = true;
});
auto deferred4 = factory->deferred<void>();
deferred3.resolveWith(deferred4.getPromise());
deferred4.resolve();
factory->wait();
EXPECT_EQ(true, called);
}
TEST_F(VoidPromiseTestSuite, resolveFailureWith) {
auto deferred1 = factory->deferred<void>();
auto deferred2 = factory->deferred<void>();
std::atomic<bool> failureCalled = false;
std::atomic<bool> successCalled = false;
deferred2.getPromise()
.onSuccess([&]() {
successCalled = true;
})
.onFailure([&](const std::exception &e) {
failureCalled = true;
std::cout << "got error: " << e.what() << std::endl;
});
//currently deferred1 will be resolved in thread, and onSuccess is trigger on the promise of deferred2
//now resolving deferred2 with the promise of deferred1
deferred2.resolveWith(deferred1.getPromise());
auto p = deferred2.getPromise();
try {
std::string().at(1); // this generates an std::out_of_range
//or use std::make_exception_ptr
} catch (...) {
deferred1.fail(std::current_exception());
}
factory->wait();
EXPECT_EQ(false, successCalled);
EXPECT_EQ(true, failureCalled);
}
TEST_F(VoidPromiseTestSuite, returnPromiseOnSuccessfulResolveWith) {
auto deferred1 = factory->deferred<void>();
auto deferred2 = factory->deferred<void>();
celix::Promise<void> promise = deferred1.resolveWith(deferred2.getPromise());
EXPECT_FALSE(promise.isDone());
deferred2.resolve();
factory->wait();
EXPECT_TRUE(promise.isDone());
EXPECT_TRUE(promise.isSuccessfullyResolved());
}
TEST_F(VoidPromiseTestSuite, returnPromiseOnInvalidResolveWith) {
auto deferred1 = factory->deferred<void>();
auto deferred2 = factory->deferred<long>();
celix::Promise<void> promise = deferred1.resolveWith(deferred2.getPromise());
EXPECT_FALSE(promise.isDone());
deferred1.resolve(); //Rule is deferred1 is resolved, before the resolveWith the returned promise should fail
deferred2.resolve(42);
factory->wait();
EXPECT_TRUE(promise.isDone());
EXPECT_FALSE(promise.isSuccessfullyResolved());
EXPECT_THROW(std::rethrow_exception(promise.getFailure()), celix::PromiseIllegalStateException);
}
//gh-333 fix timeout test on macos
#ifndef __APPLE__
TEST_F(VoidPromiseTestSuite, resolveWithTimeout) {
auto promise1 = factory->deferredTask<void>([](auto d) {
std::this_thread::sleep_for(std::chrono::milliseconds{50});
try {
d.resolve();
} catch(...) {
//note resolve with throws an exception if promise is already resolved
}
});
std::atomic<bool> firstSuccessCalled = false;
std::atomic<bool> secondSuccessCalled = false;
std::atomic<bool> secondFailedCalled = false;
promise1
.onSuccess([&]() {
firstSuccessCalled = true;
})
.timeout(std::chrono::milliseconds{10})
.onSuccess([&]() {
secondSuccessCalled = true;
})
.onFailure([&](const std::exception&) {
secondFailedCalled = true;
});
promise1.wait();
factory->wait();
EXPECT_EQ(true, firstSuccessCalled);
EXPECT_EQ(false, secondSuccessCalled);
EXPECT_EQ(true, secondFailedCalled);
firstSuccessCalled = false;
secondSuccessCalled = false;
secondFailedCalled = false;
auto promise2 = factory->deferredTask<void>([](auto d) {
d.resolve();
});
promise2
.onSuccess([&]() {
firstSuccessCalled = true;
})
.timeout(std::chrono::milliseconds{250}) /*NOTE: more than the possible delay introduced by the executor*/
.onSuccess([&]() {
secondSuccessCalled = true;
})
.onFailure([&](const std::exception&) {
secondFailedCalled = true;
});
promise2.wait();
factory->wait();
EXPECT_EQ(true, firstSuccessCalled);
EXPECT_EQ(true, secondSuccessCalled);
EXPECT_EQ(false, secondFailedCalled);
}
#endif
TEST_F(VoidPromiseTestSuite, resolveWithSetTimeout) {
auto promise = factory->deferred<void>().getPromise().setTimeout(std::chrono::milliseconds{5});
factory->wait();
EXPECT_TRUE(promise.isDone());
EXPECT_FALSE(promise.isSuccessfullyResolved());
}
TEST_F(VoidPromiseTestSuite, resolveWithDelay) {
auto deferred1 = factory->deferred<void>();
std::atomic<bool> successCalled = false;
std::atomic<bool> failedCalled = false;
auto t1 = std::chrono::system_clock::now();
std::atomic<std::chrono::system_clock::time_point> t2{t1};
auto p = deferred1.getPromise()
.delay(std::chrono::milliseconds{50})
.onSuccess([&]() {
successCalled = true;
t2 = std::chrono::system_clock::now();
})
.onFailure([&](const std::exception&) {
failedCalled = true;
});
deferred1.resolve();
p.wait();
factory->wait();
EXPECT_EQ(true, successCalled);
EXPECT_EQ(false, failedCalled);
auto durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(t2.load() - t1);
EXPECT_GE(durationInMs, std::chrono::milliseconds{10});
}
TEST_F(VoidPromiseTestSuite, resolveWithRecover) {
auto deferred1 = factory->deferred<void>();
std::atomic<bool> successCalled = false;
deferred1.getPromise()
.recover([]{ return 42; })
.onSuccess([&]() {
successCalled = true;
});
try {
throw std::logic_error("failure");
} catch (...) {
deferred1.fail(std::current_exception());
}
factory->wait();
EXPECT_EQ(true, successCalled);
}
TEST_F(VoidPromiseTestSuite, chainAndMapResult) {
auto deferred1 = factory->deferred<void>();
std::thread t{[&deferred1]{
deferred1.resolve();
}};
int two = deferred1.getPromise()
.map<int>([]() {
return 2;
}).getValue();
t.join();
factory->wait();
EXPECT_EQ(2, two);
}
TEST_F(VoidPromiseTestSuite, chainWithThenAccept) {
auto deferred1 = factory->deferred<void>();
std::atomic<bool> called = false;
deferred1.getPromise()
.thenAccept([&](){
called = true;
});
deferred1.resolve();
factory->wait();
EXPECT_TRUE(called);
}
TEST_F(VoidPromiseTestSuite, promiseWithFallbackTo) {
auto deferred1 = factory->deferred<void>();
try {
throw std::logic_error("failure");
} catch (...) {
deferred1.fail(std::current_exception());
}
auto deferred2 = factory->deferred<void>();
deferred2.resolve();
long val = deferred1.getPromise().fallbackTo(deferred2.getPromise()).getValue();
EXPECT_TRUE(val);
}
TEST_F(VoidPromiseTestSuite, outOfScopeUnresolvedPromises) {
std::atomic<bool> called = false;
{
auto deferred1 = factory->deferred<void>();
deferred1.getPromise().onResolve([&]{
called = true;
});
//promise and deferred out of scope
}
factory->wait();
EXPECT_FALSE(called);
}
TEST_F(VoidPromiseTestSuite, chainPromises) {
auto success = [&](celix::Promise<void> p) -> celix::Promise<long> {
//TODO Promises::resolved(p.getValue() + p.getValue())
auto result = factory->deferred<long>();
p.getValue();
result.resolve(42);
return result.getPromise();
};
auto initial = factory->deferred<void>();
initial.resolve();
long result = initial.getPromise().then<long>(success).getValue();
EXPECT_EQ(42, result);
}
TEST_F(VoidPromiseTestSuite, chainFailedPromises) {
std::atomic<bool> called = false;
auto success = [](celix::Promise<void> p) -> celix::Promise<void> {
//nop
return p;
};
auto failed = [&](const celix::Promise<void>& /*p*/) -> void {
called = true;
};
auto deferred = factory->deferred<void>();
deferred.fail(std::logic_error{"fail"});
deferred.getPromise().then<void>(success, failed);
factory->wait();
EXPECT_TRUE(called);
}
TEST_F(VoidPromiseTestSuite, failedResolvedWithPromiseFactory) {
auto factory = celix::PromiseFactory{};
auto p1 = factory.failed<void>(std::logic_error{"test"});
EXPECT_TRUE(p1.isDone());
EXPECT_NE(nullptr, p1.getFailure());
auto p2 = factory.resolved();
EXPECT_TRUE(p2.isDone());
EXPECT_TRUE(p2.getValue());
}
TEST_F(VoidPromiseTestSuite, deferredTaskCall) {
auto t1 = std::chrono::system_clock::now();
auto promise = factory->deferredTask<void>([](auto deferred) {
std::this_thread::sleep_for(std::chrono::milliseconds{12});
deferred.resolve();
});
promise.wait();
auto t2 = std::chrono::system_clock::now();
auto durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
EXPECT_GT(durationInMs, std::chrono::milliseconds{10});
}
TEST_F(VoidPromiseTestSuite, testRobustFailAndResolve) {
std::atomic<int> failCount{};
std::atomic<int> successCount{};
auto failCb = [&failCount](const std::exception& /*e*/) {
failCount++;
};
auto successCb = [&successCount]() {
successCount++;
};
auto def = factory->deferred<void>();
def.getPromise().onFailure(failCb);
//Rule a second fail should not lead to an exception, to ensure a more robust usage.
//But also should only lead to a single resolve chain.
def.fail(std::logic_error{"error1"});
def.fail(std::logic_error{"error2"});
factory->wait();
EXPECT_EQ(failCount.load(), 1);
def = factory->deferred<void>();
def.getPromise().onSuccess(successCb);
//Rule a second resolve should not lead to an exception, to ensure a more robust usage.
//But also should only lead to a single resolve chain.
def.resolve();
def.resolve();
factory->wait();
EXPECT_EQ(successCount.load(), 1);
failCount = 0;
successCount = 0;
def = factory->deferred<void>();
def.getPromise().onSuccess(successCb).onFailure(failCb);
//Rule a resolve after fail should not lead to an exception, to ensure a more robust usage.
//But also should only lead to a single resolve chain.
def.fail(std::logic_error("error3"));
def.resolve();
factory->wait();
EXPECT_EQ(failCount.load(), 1);
EXPECT_EQ(successCount.load(), 0);
failCount = 0;
successCount = 0;
def = factory->deferred<void>();
def.getPromise().onSuccess(successCb).onFailure(failCb);
//Rule a fail after resolve should not lead to an exception, to ensure a more robust usage.
//But also should only lead to a single resolve chain.
def.resolve();
def.fail(std::logic_error("error3"));
factory->wait();
EXPECT_EQ(failCount.load(), 0);
EXPECT_EQ(successCount.load(), 1);
}
TEST_F(VoidPromiseTestSuite, testTryFailAndResolve) {
std::atomic<int> failCount{};
std::atomic<int> successCount{};
auto failCb = [&failCount](const std::exception& /*e*/) {
failCount++;
};
auto successCb = [&successCount]() {
successCount++;
};
//first resolve, then try rest
auto def = factory->deferred<void>();
def.getPromise().onSuccess(successCb).onFailure(failCb);
EXPECT_TRUE(def.tryResolve());
EXPECT_FALSE(def.tryFail(std::logic_error{"error"}));
EXPECT_FALSE(def.tryFail(std::make_exception_ptr(std::logic_error{"error"})));
factory->wait();
EXPECT_EQ(failCount.load(), 0);
EXPECT_EQ(successCount.load(), 1);
//first fail with exp ref, then try rest
failCount = 0;
successCount = 0;
def = factory->deferred<void>();
def.getPromise().onSuccess(successCb).onFailure(failCb);
EXPECT_TRUE(def.tryFail(std::logic_error{"error"}));
EXPECT_FALSE(def.tryResolve());
EXPECT_FALSE(def.tryFail(std::logic_error{"error"}));
EXPECT_FALSE(def.tryFail(std::make_exception_ptr(std::logic_error{"error"})));
factory->wait();
EXPECT_EQ(failCount.load(), 1);
EXPECT_EQ(successCount.load(), 0);
//first fail with exp ptr, then try rest
failCount = 0;
successCount = 0;
def = factory->deferred<void>();
def.getPromise().onSuccess(successCb).onFailure(failCb);
EXPECT_TRUE(def.tryFail(std::make_exception_ptr(std::logic_error{"error"})));
EXPECT_FALSE(def.tryResolve());
EXPECT_FALSE(def.tryFail(std::logic_error{"error"}));
EXPECT_FALSE(def.tryFail(std::make_exception_ptr(std::logic_error{"error"})));
factory->wait();
EXPECT_EQ(failCount.load(), 1);
EXPECT_EQ(successCount.load(), 0);
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif