blob: 7d85b036aa4b7912c6655663b47bdb2e4364f828 [file] [log] [blame] [edit]
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Portions are Copyright (C) 2017-2021 Mozilla Corporation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <wtf/NativePromise.h>
#include "Test.h"
#include "Utilities.h"
#include <wtf/Atomics.h>
#include <wtf/Lock.h>
#include <wtf/Locker.h>
#include <wtf/Ref.h>
#include <wtf/RefPtr.h>
#include <wtf/RunLoop.h>
#include <wtf/ThreadSafeRefCounted.h>
#include <wtf/WorkQueue.h>
#include <wtf/threads/BinarySemaphore.h>
using namespace WTF;
namespace TestWebKitAPI {
using TestPromise = NativePromise<int, double, false>;
using TestPromiseExcl = NativePromise<int, double, true /* exclusive */>;
class WorkQueueWithShutdown : public WorkQueue {
public:
static Ref<WorkQueueWithShutdown> create(const char* name) { return adoptRef(*new WorkQueueWithShutdown(name)); }
void beginShutdown()
{
dispatch([this, strong = Ref { *this }] {
m_shutdown = true;
m_semaphore.signal();
});
}
void waitUntilShutdown()
{
while (!m_shutdown)
m_semaphore.wait();
}
private:
WorkQueueWithShutdown(const char* name)
: WorkQueue(name, QOS::Default)
{
}
std::atomic<bool> m_shutdown { false };
BinarySemaphore m_semaphore;
};
class AutoWorkQueue {
public:
AutoWorkQueue()
: m_workQueue(WorkQueueWithShutdown::create("com.apple.WebKit.Test.simple"))
{
}
Ref<WorkQueueWithShutdown> queue() { return m_workQueue; }
~AutoWorkQueue()
{
m_workQueue->waitUntilShutdown();
}
private:
Ref<WorkQueueWithShutdown> m_workQueue;
};
struct RefCountedProducer final : public ThreadSafeRefCounted<RefCountedProducer> {
RefCountedProducer()
: producer(__func__)
{
}
Ref<TestPromise> promise() { return producer; }
typename TestPromise::Producer producer;
};
class DelayedResolveOrReject final : public ThreadSafeRefCounted<DelayedResolveOrReject> {
public:
DelayedResolveOrReject(WorkQueue& workQueue, RefPtr<RefCountedProducer> producer, TestPromise::Result&& result, int iterations)
: m_producer(producer)
, m_iterations(iterations)
, m_workQueue(workQueue)
, m_result(WTFMove(result))
{
}
void dispatch()
{
m_workQueue->dispatch([strongThis = RefPtr { this }] {
strongThis->run();
});
}
void run()
{
assertIsCurrent(m_workQueue);
Locker lock { m_lock };
if (!m_producer) {
// Canceled.
return;
}
if (!--m_iterations) {
m_producer->producer.resolveOrReject(m_result, __func__);
return;
}
dispatch();
}
void cancel()
{
Locker lock { m_lock };
m_producer = nullptr;
}
private:
Lock m_lock;
RefPtr<RefCountedProducer> m_producer WTF_GUARDED_BY_LOCK(m_lock);
int m_iterations WTF_GUARDED_BY_LOCK(m_lock);
Ref<WorkQueue> m_workQueue;
const TestPromise::Result m_result;
};
static std::function<void()> doFail()
{
return [] {
EXPECT_TRUE(false);
};
}
static auto doFailAndReject(const char* location)
{
return [location] {
EXPECT_TRUE(false);
return TestPromise::createAndReject(0.0, location);
};
}
static void runInCurrentRunLoop(Function<void(RunLoop&)>&& function)
{
WTF::initializeMainThread();
auto& runLoop = RunLoop::current();
function(runLoop);
bool done = false;
runLoop.dispatch([&] {
done = true;
});
while (!done)
runLoop.cycle();
}
TEST(NativePromise, BasicResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndResolve(42, __func__)->then(queue, __func__,
[queue](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
queue->beginShutdown();
}, doFail());
});
}
TEST(NativePromise, BasicReject)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndReject(42.0, __func__)->then(queue, __func__,
doFail(),
[queue](int rejectValue) {
EXPECT_EQ(rejectValue, 42.0);
queue->beginShutdown();
});
});
}
TEST(NativePromise, BasicResolveOrRejectResolved)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndResolve(42, __func__)->whenSettled(queue, __func__,
[queue](const TestPromise::Result& result) {
EXPECT_TRUE(result);
EXPECT_EQ(result.value(), 42);
queue->beginShutdown();
});
});
}
TEST(NativePromise, BasicResolveOrRejectRejected)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndReject(42.0, __func__)->whenSettled(queue, __func__,
[queue](const TestPromise::Result& result) {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), 42.0);
queue->beginShutdown();
});
});
}
TEST(NativePromise, GenericPromise)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
GenericPromise::createAndResolve(__func__)->then(queue, __func__,
[]() {
EXPECT_TRUE(true);
},
doFail());
GenericPromise::createAndResolve(__func__)->whenSettled(queue, __func__,
[](GenericPromise::Result result) {
EXPECT_TRUE(result);
});
GenericPromise::createAndReject(123, __func__)->whenSettled(queue, __func__,
[](GenericPromise::Result result) {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), 123);
});
GenericPromise::Producer p1(__func__);
p1.resolve(__func__);
p1.then(queue, __func__,
[]() {
EXPECT_TRUE(true);
},
doFail());
GenericPromise::Producer p2(__func__);
p2.reject(123, __func__);
p2.then(queue, __func__,
[]() {
EXPECT_TRUE(true);
},
[](int value) {
EXPECT_EQ(value, 123);
});
GenericPromise::Producer p3(__func__);
NativePromiseRequest<GenericPromise> request;
// Note that if you're not interested in the result you can provide two Function<void()> to then()
p3.then(queue, __func__, doFail(), doFail()).track(request);
p3.resolve(__func__);
// We are no longer interested by the result of the promise. We disconnect the request holder.
// doFail() above will never be called.
request.disconnect();
// Note that if you're not interested in the result you can also provide one Function<void()> with whenSettled()
GenericPromise::Producer p4(__func__);
p4.whenSettled(queue, __func__, []() {
});
p4.resolve(__func__);
// You can mix & match promise types and chain them together.
// Producer also accepts syntax using operator-> for consistency with a consumer's promise.
GenericPromise::Producer p5(__func__);
using MyPromise = NativePromise<int, int, true>;
p5->whenSettled(queue, __func__,
[](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
return MyPromise::createAndResolve(1, __func__);
})->whenSettled(queue, __func__,
[queue](MyPromise::Result result) {
static_assert(std::is_same_v<MyPromise::Result::value_type, int>, "The type received is the same as the last promise returned");
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), 1);
queue->beginShutdown();
});
p5->resolve(__func__);
});
}
TEST(NativePromise, PromiseRequest)
{
// We declare the Request holder before using the runLoop to ensure it stays in scope for the entire run.
// ASSERTION FAILED: !m_request
using MyPromise = NativePromise<bool, bool, true>;
NativePromiseRequest<MyPromise> request1;
runInCurrentRunLoop([&](auto& runLoop) {
MyPromise::Producer p1(__func__);
p1.resolve(true, __func__);
p1.whenSettled(runLoop, __func__,
[&](MyPromise::Result&& result) {
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(result.value());
request1.complete();
}).track(request1);
});
// PromiseRequest allows to use capture by reference or pointer to ref-counted object and ensure the
// lifetime of the object.
runInCurrentRunLoop([&](auto& runLoop) {
NativePromiseRequest<GenericPromise> request2;
bool objectToShare = true;
GenericPromise::Producer p2(__func__);
p2.whenSettled(runLoop, __func__,
[&objectToShare](GenericPromise::Result&&) mutable {
// It would be normally unsafe to access `objectToShare` as it went out of scope.
// but this function will never run as we've disconnected the ThenCommand.
objectToShare = false;
}).track(request2);
request2.disconnect();
p2.resolve(__func__);
EXPECT_TRUE(objectToShare);
});
}
// Ensure that callbacks aren't run when request holder is disconnected after the promise was resolved and then() called.
TEST(NativePromise, PromiseRequestDisconnected1)
{
runInCurrentRunLoop([](auto& runLoop) {
NativePromiseRequest<TestPromise> request;
TestPromise::Producer p(__func__);
p.then(runLoop, __func__, doFail(), doFail()).track(request);
p.resolve(1, __func__);
request.disconnect();
});
}
// Ensure that callbacks aren't run when request holder is disconnected even if promise was resolved first.
TEST(NativePromise, PromiseRequestDisconnected2)
{
runInCurrentRunLoop([](auto& runLoop) {
NativePromiseRequest<TestPromise> request;
TestPromise::Producer p(__func__);
p->resolve(1, __func__);
p->then(runLoop, __func__, doFail(), doFail())->track(request);
request.disconnect();
});
}
TEST(NativePromise, AsyncResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
auto producer = adoptRef(new RefCountedProducer());
auto p = producer->promise();
// Kick off three racing tasks, and make sure we get the one that finishes
// earliest.
auto a = adoptRef(new DelayedResolveOrReject(queue, producer, TestPromise::Result(32), 10));
auto b = adoptRef(new DelayedResolveOrReject(queue, producer, TestPromise::Result(42), 5));
auto c = adoptRef(new DelayedResolveOrReject(queue, producer, TestPromise::Error(32.0), 7));
a->dispatch();
b->dispatch();
c->dispatch();
p->then(queue, __func__,
[queue, a, b, c](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
a->cancel();
b->cancel();
c->cancel();
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, CompletionPromises)
{
std::atomic<bool> invokedPass { false };
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &invokedPass] {
TestPromise::createAndResolve(40, __func__)->then(queue, __func__,
[](int val) {
return TestPromise::createAndResolve(val + 10, __func__);
},
doFailAndReject(__func__))->then(queue, __func__,
[&invokedPass](int val) {
invokedPass = true;
return TestPromise::createAndResolve(val, __func__);
},
doFailAndReject(__func__))->then(queue, __func__,
[queue](int val) {
auto producer = adoptRef(new RefCountedProducer());
auto p = producer->promise();
auto resolver = adoptRef(new DelayedResolveOrReject(queue, producer, TestPromise::Result(val - 8), 10));
resolver->dispatch();
return p;
},
doFailAndReject(__func__))->then(queue, __func__,
[](int val) {
return TestPromise::createAndReject(double(val - 42) + 42.0, __func__);
},
doFailAndReject(__func__))->then(queue, __func__,
doFail(),
[queue, &invokedPass](double val) {
EXPECT_EQ(val, 42.0);
EXPECT_TRUE((bool)invokedPass);
queue->beginShutdown();
});
});
}
TEST(NativePromise, UsingMethods)
{
class MyClass : public ThreadSafeRefCounted<MyClass> {
public:
static Ref<MyClass> create() { return adoptRef(*new MyClass()); }
void resolveWithNothing()
{
EXPECT_TRUE(true);
}
void rejectWithNothing()
{
EXPECT_TRUE(false);
}
void resolveWithValue(int value)
{
EXPECT_EQ(value, 1);
}
void rejectWithValue(double value)
{
EXPECT_EQ(value, 2.0);
}
void resolveOrRejectWithNothing()
{
EXPECT_TRUE(true);
}
void resolveOrRejectWithResult(const TestPromise::Result& result)
{
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), 1);
}
};
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, myClass = MyClass::create()] {
TestPromise::createAndResolve(1, __func__)->then(queue, __func__, myClass.get(), &MyClass::resolveWithNothing, &MyClass::rejectWithNothing);
TestPromise::createAndReject(2.0, __func__)->then(queue, __func__, myClass.get(), &MyClass::resolveWithValue, &MyClass::rejectWithValue);
TestPromise::createAndResolve(3, __func__)->whenSettled(queue, __func__, myClass.get(), &MyClass::resolveOrRejectWithNothing);
TestPromise::createAndResolve(1, __func__)->whenSettled(queue, __func__, myClass.get(), &MyClass::resolveOrRejectWithResult);
queue->dispatch([queue] {
queue->beginShutdown();
});
});
}
static Ref<GenericPromise> myMethod()
{
assertIsCurrent(RunLoop::main());
// You would normally do some work here.
return GenericPromise::createAndResolve(__func__);
}
TEST(NativePromise, InvokeAsync)
{
bool done = false;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &done] {
invokeAsync(RunLoop::main(), __func__, &myMethod)->whenSettled(queue, __func__,
[queue, &done](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
queue->beginShutdown();
done = true;
});
});
Util::run(&done);
}
static Ref<GenericPromise> myMethodReturningThenCommand()
{
assertIsCurrent(RunLoop::main());
// You would normally do some work here.
return GenericPromise::createAndResolve(__func__)->whenSettled(RunLoop::main(), __func__,
[](GenericPromise::Result result) {
return GenericPromise::createAndResolveOrReject(WTFMove(result), __func__);
});
}
TEST(NativePromise, InvokeAsyncAutoConversion)
{
// Ensure that there's no need to cast NativePromise::then() result
bool done = false;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &done] {
invokeAsync(RunLoop::main(), __func__, &myMethodReturningThenCommand)->whenSettled(queue, __func__,
[queue, &done](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
queue->beginShutdown();
done = true;
});
});
Util::run(&done);
}
static Ref<GenericPromise> myMethodReturningProducer()
{
assertIsCurrent(RunLoop::main());
// You would normally do some work here.
return GenericPromise::createAndResolve(__func__)->whenSettled(RunLoop::main(), __func__,
[](GenericPromise::Result result) {
GenericPromise::Producer producer(__func__);
producer.resolveOrReject(WTFMove(result), __func__);
return producer;
});
}
TEST(NativePromise, InvokeAsyncAutoConversionWithProducer)
{
// Ensure that there's no need to cast NativePromise::whenSettled() result
bool done = false;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &done] {
invokeAsync(RunLoop::main(), __func__, &myMethodReturningProducer)->whenSettled(queue, __func__,
[queue, &done](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
queue->beginShutdown();
done = true;
});
});
Util::run(&done);
}
TEST(NativePromise, PromiseAllResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(TestPromise::createAndResolve(22, __func__));
promises.append(TestPromise::createAndResolve(32, __func__));
promises.append(TestPromise::createAndResolve(42, __func__));
TestPromise::all(queue, promises)->then(queue, __func__,
[queue](const Vector<int>& resolveValues) {
EXPECT_EQ(resolveValues.size(), 3UL);
EXPECT_EQ(resolveValues[0], 22);
EXPECT_EQ(resolveValues[1], 32);
EXPECT_EQ(resolveValues[2], 42);
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, PromiseAllResolveAsync)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(22, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(32, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(42, __func__);
}));
TestPromise::all(queue, promises)->then(queue, __func__,
[queue](const Vector<int>& resolveValues) {
EXPECT_EQ(resolveValues.size(), 3UL);
EXPECT_EQ(resolveValues[0], 22);
EXPECT_EQ(resolveValues[1], 32);
EXPECT_EQ(resolveValues[2], 42);
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, PromiseAllReject)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(TestPromise::createAndResolve(22, __func__));
promises.append(TestPromise::createAndReject(32.0, __func__));
promises.append(TestPromise::createAndResolve(42, __func__));
// Ensure that more than one rejection doesn't cause a crash
promises.append(TestPromise::createAndReject(52.0, __func__));
TestPromise::all(queue, promises)->then(queue, __func__,
doFail(),
[queue](float rejectValue) {
EXPECT_EQ(rejectValue, 32.0);
queue->beginShutdown();
});
});
}
TEST(NativePromise, PromiseAllRejectAsync)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(22, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndReject(32.0, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(42, __func__);
}));
// Ensure that more than one rejection doesn't cause a crash
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndReject(52.0, __func__);
}));
TestPromise::all(queue, promises)->then(queue, __func__,
doFail(),
[queue](float rejectValue) {
EXPECT_EQ(rejectValue, 32.0);
queue->beginShutdown();
});
});
}
TEST(NativePromise, PromiseAllSettled)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(TestPromise::createAndResolve(22, __func__));
promises.append(TestPromise::createAndReject(32.0, __func__));
promises.append(TestPromise::createAndResolve(42, __func__));
promises.append(TestPromise::createAndReject(52.0, __func__));
TestPromise::allSettled(queue, promises)->then(
queue, __func__,
[queue](const TestPromise::AllSettledPromiseType::ResolveValueType& resolveValues) {
EXPECT_EQ(resolveValues.size(), 4UL);
EXPECT_TRUE(resolveValues[0]);
EXPECT_EQ(resolveValues[0].value(), 22);
EXPECT_FALSE(resolveValues[1]);
EXPECT_EQ(resolveValues[1].error(), 32.0);
EXPECT_TRUE(resolveValues[2]);
EXPECT_EQ(resolveValues[2].value(), 42);
EXPECT_FALSE(resolveValues[3]);
EXPECT_EQ(resolveValues[3].error(), 52.0);
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, PromiseAllSettledAsync)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(22, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndReject(32.0, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndResolve(42, __func__);
}));
promises.append(invokeAsync(queue, __func__, [] {
return TestPromise::createAndReject(52.0, __func__);
}));
TestPromise::allSettled(queue, promises)->then(queue, __func__,
[queue](const TestPromise::AllSettledPromiseType::ResolveValueType& resolveValues) {
EXPECT_EQ(resolveValues.size(), 4UL);
EXPECT_TRUE(resolveValues[0].has_value());
EXPECT_EQ(resolveValues[0].value(), 22);
EXPECT_FALSE(resolveValues[1].has_value());
EXPECT_EQ(resolveValues[1].error(), 32.0);
EXPECT_TRUE(resolveValues[2].has_value());
EXPECT_EQ(resolveValues[2], 42);
EXPECT_FALSE(resolveValues[3].has_value());
EXPECT_EQ(resolveValues[3].error(), 52.0);
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, Chaining)
{
// We declare this variable before |awq| to ensure the destructor is run after |holder.disconnect()|.
NativePromiseRequest<TestPromise> holder;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &holder] {
auto p = TestPromise::createAndResolve(42, __func__);
const size_t kIterations = 100;
for (size_t i = 0; i < kIterations; ++i) {
p = p->then(queue, __func__,
[](int val) {
EXPECT_EQ(val, 42);
return TestPromise::createAndResolve(val, __func__);
},
[](double val) {
return TestPromise::createAndReject(val, __func__);
});
if (i == kIterations / 2) {
p->then(queue, __func__,
[queue, &holder] {
holder.disconnect();
queue->beginShutdown();
},
doFail());
}
}
// We will hit the assertion if we don't disconnect the leaf Request
// in the promise chain.
p->whenSettled(queue, __func__, [] { })->track(holder);
});
}
TEST(NativePromise, MoveOnlyType)
{
using MyPromise = NativePromise<std::unique_ptr<int>, bool, true>;
AutoWorkQueue awq;
auto queue = awq.queue();
MyPromise::createAndResolve(std::make_unique<int>(87), __func__)->then(queue, __func__,
[](std::unique_ptr<int> val) {
EXPECT_EQ(87, *val);
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(std::make_unique<int>(87), __func__)->whenSettled(queue, __func__,
[queue](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_EQ(87, *(val.value()));
queue->beginShutdown();
});
}
TEST(NativePromise, HeterogeneousChaining)
{
using Promise1 = NativePromise<std::unique_ptr<char>, bool, true>;
using Promise2 = NativePromise<std::unique_ptr<int>, bool, true>;
NativePromiseRequest<Promise2> holder;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &holder] {
Promise1::createAndResolve(std::make_unique<char>(0), __func__)->whenSettled(queue, __func__,
[&holder] {
holder.disconnect();
return Promise2::createAndResolve(std::make_unique<int>(0), __func__);
})->whenSettled(queue, __func__,
[] {
// Shouldn't be called for we've disconnected the request.
EXPECT_FALSE(true);
})->track(holder);
});
Promise1::createAndResolve(std::make_unique<char>(87), __func__)->then(queue, __func__,
[](std::unique_ptr<char> val) {
EXPECT_EQ(87, *val);
return Promise2::createAndResolve(std::make_unique<int>(94), __func__);
},
[] {
return Promise2::createAndResolve(std::make_unique<int>(95), __func__);
})->then(queue, __func__,
[](std::unique_ptr<int> val) {
EXPECT_EQ(94, *val);
},
doFail());
Promise1::createAndResolve(std::make_unique<char>(87), __func__)->whenSettled(queue, __func__,
[](Promise1::Result&& result) {
EXPECT_EQ(87, *(result.value()));
return Promise2::createAndResolve(std::make_unique<int>(94), __func__);
})->whenSettled(queue, __func__,
[queue](Promise2::Result&& result) {
EXPECT_EQ(94, *(result.value()));
});
// Chaining promises of different types, even if returned from within callback
TestPromise::createAndResolve(1, __func__)->whenSettled(queue, __func__,
[queue] {
return TestPromiseExcl::createAndResolve(2, __func__)->whenSettled(queue, __func__, [] {
return TestPromise::createAndResolve(3, __func__);
});
})->whenSettled(queue, __func__,
[queue](TestPromise::Result result) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(3, result.value());
});
TestPromise::createAndResolve(1, __func__)->whenSettled(queue, __func__,
[queue] {
return TestPromiseExcl::createAndResolve(2, __func__)->whenSettled(queue, __func__, [] {
return GenericPromise::createAndResolve(__func__);
});
})->whenSettled(queue, __func__,
[queue](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
});
TestPromise::createAndResolve(1, __func__)->whenSettled(queue, __func__,
[queue] {
return TestPromiseExcl::createAndResolve(2, __func__)->whenSettled(queue, __func__, [] {
return GenericPromise::createAndReject(1, __func__);
});
})->whenSettled(queue, __func__,
[queue](GenericPromise::Result result) {
EXPECT_FALSE(result.has_value());
EXPECT_EQ(1, result.error());
queue->beginShutdown();
});
}
TEST(NativePromise, RunLoop)
{
runInCurrentRunLoop([](auto& runLoop) {
TestPromise::createAndResolve(42, __func__)->then(runLoop, __func__,
[](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
},
doFail());
});
}
TEST(NativePromise, ImplicitConversionWithForwardPreviousReturn)
{
runInCurrentRunLoop([](auto& runLoop) {
TestPromise::Producer p(__func__);
Ref<TestPromise> promise = p->whenSettled(runLoop, __func__,
[](const TestPromise::Result& result) {
return TestPromise::createAndResolveOrReject(result, __func__);
});
promise->whenSettled(runLoop, __func__, [&](const TestPromise::Result& result) {
EXPECT_TRUE(!result.has_value());
EXPECT_FALSE(promise->isResolved());
});
p.resolve(1, __func__);
Ref<TestPromise> originalPromise = p;
EXPECT_TRUE(originalPromise->isResolved());
// This could be written as EXPECT_TRUE(static_cast<Ref<TestPromise>>(p)->isResolved());
// but MSVC errors on it.
EXPECT_FALSE(promise->isResolved()); // The callback hasn't been run yet.
});
}
TEST(NativePromise, ChainTo)
{
runInCurrentRunLoop([&, producer2 = TestPromise::Producer(__func__)](auto& runLoop) mutable {
auto promise1 = TestPromise::createAndResolve(42, __func__);
producer2->then(runLoop, __func__,
[&](int resolveValue) { EXPECT_EQ(resolveValue, 42); },
doFail());
// As promise1 is already resolved, it will automatically resolve/reject producer2 with its resolved/reject value.
promise1->chainTo(WTFMove(producer2), __func__);
});
runInCurrentRunLoop([&, producer2 = TestPromise::Producer(__func__), producer1 = TestPromise::Producer(__func__)](auto& runLoop) mutable {
producer2->then(runLoop, __func__,
[&](int resolveValue) { EXPECT_EQ(resolveValue, 42); },
doFail());
// When producer1 is resolved, it will automatically resolve/reject producer2 with the resolved/reject value.
producer1->chainTo(WTFMove(producer2), __func__);
producer1.resolve(42, __func__);
});
}
TEST(NativePromise, RunSynchronouslyOnTarget)
{
// Check that the callback is executed immediately when the promise is resolved.
runInCurrentRunLoop([](auto& runLoop) {
GenericPromise::Producer producer(__func__, PromiseDispatchMode::RunSynchronouslyOnTarget);
int result = 0;
producer.resolve(__func__);
producer->whenSettled(runLoop, __func__, [&] {
result = 42;
});
EXPECT_EQ(result, 42);
});
// Check that the callback is executed immediately when using chained promise and that itself is set to RunSynchronouslyOnTarget
runInCurrentRunLoop([](auto& runLoop) {
GenericPromise::Producer producer(__func__, PromiseDispatchMode::RunSynchronouslyOnTarget);
int result = 0;
producer->whenSettled(runLoop, __func__, [&] {
// We need to configure this promise too as RunSynchronouslyOnTarget
GenericPromise::Producer producer(__func__, PromiseDispatchMode::RunSynchronouslyOnTarget);
producer.resolve(__func__);
return producer;
})->whenSettled(runLoop, __func__, [&] {
result = 42;
});
producer.resolve(__func__);
EXPECT_EQ(result, 42);
});
// Check that the callback will still run on the proper target queue, even if RunSynchronouslyOnTarget is set.
runInCurrentRunLoop([](auto& runLoop) {
GenericPromise::Producer producer(__func__, PromiseDispatchMode::RunSynchronouslyOnTarget);
int result = 0;
producer.resolve(__func__);
{
AutoWorkQueue awq;
producer->whenSettled(awq.queue().get(), __func__, [&, queue = awq.queue()] {
assertIsCurrent(queue);
result = 42;
queue->beginShutdown();
});
}
EXPECT_EQ(result, 42);
});
}
} // namespace TestWebKitAPI