blob: 4f7ba3e7eaa3854b3310de4ec37cd6c1386bce14 [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/RefCountedFixedVector.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, PromiseOption::Default | PromiseOption::NonExclusive>;
using TestPromiseExcl = NativePromise<int, double>;
class WorkQueueWithShutdown : public WorkQueue {
public:
static Ref<WorkQueueWithShutdown> create(ASCIILiteral 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(ASCIILiteral 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"_s))
{
}
Ref<WorkQueueWithShutdown> queue() { return m_workQueue; }
~AutoWorkQueue()
{
m_workQueue->waitUntilShutdown();
}
private:
Ref<WorkQueueWithShutdown> m_workQueue;
};
struct RefCountedProducer final : public ThreadSafeRefCounted<RefCountedProducer> {
Ref<TestPromise> promise() { return producer; }
typename TestPromise::Producer producer;
};
class DelayedSettle final : public ThreadSafeRefCounted<DelayedSettle> {
public:
DelayedSettle(WorkQueue& workQueue, RefPtr<RefCountedProducer> producer, TestPromise::Result&& result, int iterations)
: m_producer(producer)
, m_iterations(iterations)
, m_workQueue(workQueue)
, m_result(WTF::move(result))
{
}
void dispatch()
{
m_workQueue->dispatch([protectedThis = RefPtr { this }] {
protectedThis->run();
});
}
void run()
{
assertIsCurrent(m_workQueue);
Locker lock { m_lock };
if (!m_producer) {
// Canceled.
return;
}
if (!--m_iterations) {
m_producer->producer.settle(m_result);
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 auto doFail()
{
return [] {
EXPECT_TRUE(false);
};
}
static auto doFailAndReject(Logger::LogSiteIdentifier location = Logger::LogSiteIdentifier(__builtin_FUNCTION(), 0))
{
return [location = WTF::move(location)] {
EXPECT_TRUE(false);
return TestPromise::createAndReject(0.0, location);
};
}
static void runInCurrentRunLoop(Function<void(RunLoop&)>&& function)
{
WTF::initializeMainThread();
auto& runLoop = RunLoop::currentSingleton();
function(runLoop);
bool done = false;
runLoop.dispatch([&] {
done = true;
});
while (!done)
runLoop.cycle();
}
static void runInCurrentRunLoopUntilDone(Function<void(RunLoop&, bool&)>&& function)
{
WTF::initializeMainThread();
auto& runLoop = RunLoop::currentSingleton();
bool done = false;
function(runLoop, done);
while (!done)
runLoop.cycle();
}
// Basis usage, create a resolved promise, then on a different workqueue.
TEST(NativePromise, BasicResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndResolve(42)->then(queue,
[queue](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
queue->beginShutdown();
}, doFail());
});
}
// Basis usage, create a rejected promise, then on a different workqueue.
TEST(NativePromise, BasicReject)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndReject(42.0)->then(queue,
doFail(),
[queue](int rejectValue) {
EXPECT_EQ(rejectValue, 42.0);
queue->beginShutdown();
});
});
}
// Basis usage, create a resolved promise, whenSettled on a different workqueue.
TEST(NativePromise, BasicSettleResolved)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndResolve(42)->whenSettled(queue,
[queue](const TestPromise::Result& result) {
EXPECT_TRUE(result);
EXPECT_EQ(result.value(), 42);
queue->beginShutdown();
});
});
}
// Basis usage, create a rejected promise, whenSettled on a different workqueue.
TEST(NativePromise, BasicSettleRejected)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
TestPromise::createAndReject(42.0)->whenSettled(queue,
[queue](const TestPromise::Result& result) {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), 42.0);
queue->beginShutdown();
});
});
}
// Example use with a GenericPromise which is a NativePromise<void, void>
//
TEST(NativePromise, GenericPromise)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
GenericPromise::createAndResolve()->then(queue,
[]() {
EXPECT_TRUE(true);
},
doFail());
GenericPromise::createAndResolve()->whenSettled(queue,
[](GenericPromise::Result result) {
EXPECT_TRUE(result);
});
GenericPromise::createAndReject()->whenSettled(queue,
[](GenericPromise::Result result) {
EXPECT_TRUE(!result);
});
GenericPromise::Producer producer1;
// A producer is directly convertible into the promise it can settle.
Ref<GenericPromise> promise1 = producer1;
producer1.resolve();
promise1->then(queue,
[]() {
EXPECT_TRUE(true);
},
doFail());
GenericPromise::Producer producer2;
Ref<GenericPromise> promise2 = producer2;
producer2.reject();
promise2->then(queue,
doFail(),
[]() {
EXPECT_TRUE(true);
});
GenericPromise::Producer producer3;
Ref<GenericPromise> promise3 = producer3;
auto request3 = NativePromiseRequest::create();
// Note that if you're not interested in the result you can provide two Function<void()> to then()
promise3->then(queue, doFail(), doFail()).track(request3.get());
producer3.resolve();
// We are no longer interested by the result of the promise. We disconnect the request holder.
// doFail() above will never be called.
request3->disconnect();
// Note that if you're not interested in the result you can also provide one Function<void()> with whenSettled()
GenericPromise::Producer producer4;
Ref<GenericPromise> promise4 = producer4;
promise4->whenSettled(queue, []() {
});
producer4.resolve();
// 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 producer5;
using MyPromise = NativePromise<int, int>;
producer5->whenSettled(queue,
[](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
return MyPromise::createAndResolve(1);
})->whenSettled(queue,
[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();
});
producer5->resolve();
});
}
TEST(NativePromise, CallbacksWithAuto)
{
using MyPromiseNonExclusive = NativePromise<bool, bool, PromiseOption::Default | PromiseOption::NonExclusive>;
runInCurrentRunLoop([&](auto& runLoop) {
MyPromiseNonExclusive::Producer producer;
Ref<MyPromiseNonExclusive> promise = producer;
promise->then(runLoop, [] (auto val) {
EXPECT_TRUE(val);
}, [] (auto val) {
EXPECT_TRUE(val);
});
promise->then(runLoop, [] (bool val) {
EXPECT_TRUE(val);
}, [] (auto val) {
EXPECT_TRUE(val);
});
promise->then(runLoop, [] (auto val) {
EXPECT_TRUE(val);
}, [] (bool val) {
EXPECT_TRUE(val);
});
promise->then(runLoop, [] {
EXPECT_TRUE(true);
}, [] (auto val) {
EXPECT_TRUE(val);
});
promise->then(runLoop, [] (auto val) {
EXPECT_TRUE(val);
}, [] {
EXPECT_TRUE(true);
});
promise->whenSettled(runLoop, [] (auto val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
promise->whenSettled(runLoop, [] (const auto val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
promise->whenSettled(runLoop, [] (const auto& val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
promise->whenSettled(runLoop, [] (const MyPromiseNonExclusive::Result& val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
promise->whenSettled(runLoop, [] (MyPromiseNonExclusive::Result val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
promise->whenSettled(runLoop, [] {
});
producer.resolve(true);
});
using MyPromise = NativePromise<bool, bool>;
runInCurrentRunLoop([&](auto& runLoop) {
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] (auto&& val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] (auto val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] (const auto val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] (const auto& val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] (MyPromise::Result&& val) {
EXPECT_TRUE(val);
EXPECT_TRUE(val.value());
});
MyPromise::createAndResolve(true)->whenSettled(runLoop, [] () {
EXPECT_TRUE(true);
});
});
}
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>;
auto request1 = NativePromiseRequest::create();
runInCurrentRunLoop([&](auto& runLoop) {
MyPromise::Producer producer1;
Ref<MyPromise> promise1 = producer1;
producer1.resolve(true);
promise1->whenSettled(runLoop,
[&](MyPromise::Result&& result) {
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(result.value());
EXPECT_TRUE(request1->hasCallback());
request1->complete();
EXPECT_FALSE(request1->hasCallback());
}).track(request1.get());
});
// PromiseRequest allows to use capture by reference or pointer to ref-counted object and ensure the
// lifetime of the object.
bool objectToShare = true;
runInCurrentRunLoop([&](auto& runLoop) {
auto request2 = NativePromiseRequest::create();
GenericPromise::Producer producer2;
Ref<GenericPromise> promise2 = producer2;
promise2->whenSettled(runLoop,
[&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.get());
EXPECT_TRUE(request2->hasCallback());
request2->disconnect();
EXPECT_FALSE(request2->hasCallback());
producer2.resolve();
EXPECT_TRUE(objectToShare);
});
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) {
auto request = NativePromiseRequest::create();
TestPromise::Producer producer;
Ref<TestPromise> promise = producer;
promise->then(runLoop, doFail(), doFail()).track(request.get());
producer.resolve(1);
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) {
auto request = NativePromiseRequest::create();
TestPromise::Producer producer;
Ref<TestPromise> promise = producer;
producer.resolve(1);
promise->then(runLoop, doFail(), doFail())->track(request.get());
request->disconnect();
});
}
TEST(NativePromise, AsyncResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
auto producer = adoptRef(new RefCountedProducer());
auto promise = producer->promise();
// Kick off three racing tasks, and make sure we get the one that finishes
// earliest.
auto delayedSettleTask1 = adoptRef(new DelayedSettle(queue, producer, TestPromise::Result(32), 10));
auto delayedSettleTask2 = adoptRef(new DelayedSettle(queue, producer, TestPromise::Result(42), 5));
auto delayedSettleTask3 = adoptRef(new DelayedSettle(queue, producer, TestPromise::Error(32.0), 7));
delayedSettleTask1->dispatch();
delayedSettleTask2->dispatch();
delayedSettleTask3->dispatch();
promise->then(queue,
[queue, delayedSettleTask1, delayedSettleTask2, delayedSettleTask3](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
delayedSettleTask1->cancel();
delayedSettleTask2->cancel();
delayedSettleTask3->cancel();
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, CompletionPromises)
{
bool invokedPass { false }; // Only ever accessed on the WorkQueue
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &invokedPass] {
TestPromise::createAndResolve(40)->then(queue,
[](int val) {
return TestPromise::createAndResolve(val + 10);
},
doFailAndReject())->then(queue,
[&invokedPass](int val) {
invokedPass = true;
return TestPromise::createAndResolve(val);
},
doFailAndReject())->then(queue,
[queue](int val) {
auto producer = adoptRef(new RefCountedProducer());
auto p = producer->promise();
auto resolver = adoptRef(new DelayedSettle(queue, producer, TestPromise::Result(val - 8), 10));
resolver->dispatch();
return p;
},
doFailAndReject())->then(queue,
[](int val) {
return TestPromise::createAndReject(double(val - 42) + 42.0);
},
doFailAndReject())->then(queue,
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 settleWithNothing()
{
EXPECT_TRUE(true);
}
void settleWithResult(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)->then(queue, myClass.get(), &MyClass::resolveWithNothing, &MyClass::rejectWithNothing);
TestPromise::createAndReject(2.0)->then(queue, myClass.get(), &MyClass::resolveWithValue, &MyClass::rejectWithValue);
TestPromise::createAndResolve(3)->whenSettled(queue, myClass.get(), &MyClass::settleWithNothing);
TestPromise::createAndResolve(1)->whenSettled(queue, myClass.get(), &MyClass::settleWithResult);
queue->dispatch([queue] {
queue->beginShutdown();
});
});
}
static Ref<GenericPromise> myMethod()
{
assertIsCurrent(RunLoop::mainSingleton());
// You would normally do some work here.
return GenericPromise::createAndResolve();
}
TEST(NativePromise, InvokeAsync)
{
bool done = false;
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &done] {
invokeAsync(RunLoop::mainSingleton(), &myMethod)->whenSettled(queue,
[queue, &done](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
queue->beginShutdown();
done = true;
});
});
Util::run(&done);
}
// A chained promise (that is promise->whenSettled([] { return GenericPromise }) is itself a promise
static Ref<GenericPromise> myMethodReturningThenCommandWithPromise()
{
assertIsCurrent(RunLoop::mainSingleton());
// You would normally do some work here.
return GenericPromise::createAndResolve()->whenSettled(RunLoop::mainSingleton(),
[](GenericPromise::Result result) {
return GenericPromise::createAndSettle(WTF::move(result));
});
}
static Ref<GenericPromise> myMethodReturningThenCommandWithVoid()
{
assertIsCurrent(RunLoop::mainSingleton());
// You would normally do some work here.
return GenericPromise::createAndReject()->whenSettled(RunLoop::mainSingleton(),
[](GenericPromise::Result result) {
EXPECT_FALSE(result.has_value());
});
}
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] {
invokeAsync(RunLoop::mainSingleton(), &myMethodReturningThenCommandWithPromise)->whenSettled(queue,
[](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
});
});
queue->dispatch([queue, &done] {
invokeAsync(RunLoop::mainSingleton(), &myMethodReturningThenCommandWithVoid)->whenSettled(queue,
[queue, &done](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
queue->beginShutdown();
done = true;
});
});
Util::run(&done);
}
TEST(NativePromise, InvokeAsyncWithExpected)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
auto asyncMethodWithExpected = [] {
return Expected<int, long> { 1 };
};
invokeAsync(runLoop, WTF::move(asyncMethodWithExpected))->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(result.value(), 1L);
done = true;
});
});
}
TEST(NativePromise, InvokeAsyncWithVoid)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
invokeAsync(runLoop, [] {
EXPECT_TRUE(true);
})->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
static_assert(std::is_same_v<std::remove_reference_t<decltype(result)>, GenericPromise::Result>, "We must be getting a GenericPromise");
static_assert(std::is_void_v<typename std::remove_reference_t<decltype(result)>::value_type>);
done = true;
});
});
}
static Ref<GenericPromise> myMethodReturningProducer()
{
assertIsCurrent(RunLoop::mainSingleton());
// You would normally do some work here.
return GenericPromise::createAndResolve()->whenSettled(RunLoop::mainSingleton(),
[](GenericPromise::Result result) {
GenericPromise::Producer producer;
producer.settle(WTF::move(result));
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::mainSingleton(), &myMethodReturningProducer)->whenSettled(queue,
[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));
promises.append(TestPromise::createAndResolve(32));
promises.append(TestPromise::createAndResolve(42));
TestPromise::all(promises)->then(queue,
[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, PromiseVoidAllResolve)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<GenericPromise>> promises;
promises.append(GenericPromise::createAndResolve());
promises.append(GenericPromise::createAndResolve());
promises.append(GenericPromise::createAndResolve());
GenericPromise::all(promises)->then(queue,
[] () {
EXPECT_TRUE(true);
},
doFail());
GenericPromise::all(Vector<Ref<GenericPromise>>(10, [](size_t) {
return GenericPromise::createAndResolve();
}))->then(queue,
[queue] () {
queue->beginShutdown();
},
doFail());
});
}
TEST(NativePromise, PromiseAllResolveAsync)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
Vector<Ref<TestPromise>> promises;
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndResolve(22);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndResolve(32);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndResolve(42);
}));
TestPromise::all(promises)->then(queue,
[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));
promises.append(TestPromise::createAndReject(32.0));
promises.append(TestPromise::createAndResolve(42));
// Ensure that more than one rejection doesn't cause a crash
promises.append(TestPromise::createAndReject(52.0));
TestPromise::all(promises)->then(queue,
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, [] {
return TestPromise::createAndResolve(22);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndReject(32.0);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndResolve(42);
}));
// Ensure that more than one rejection doesn't cause a crash
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndReject(52.0);
}));
TestPromise::all(promises)->then(queue,
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));
promises.append(TestPromise::createAndReject(32.0));
promises.append(TestPromise::createAndResolve(42));
promises.append(TestPromise::createAndReject(52.0));
TestPromise::allSettled(promises)->then(
queue,
[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, [] {
return TestPromise::createAndResolve(22);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndReject(32.0);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndResolve(42);
}));
promises.append(invokeAsync(queue, [] {
return TestPromise::createAndReject(52.0);
}));
TestPromise::allSettled(promises)->then(queue,
[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());
});
}
// Chain 100 promises, and disconnect the chain after the 50th resolve.
TEST(NativePromise, Chaining)
{
// We declare this variable before |awq| to ensure the destructor is run after |holder.disconnect()|.
auto holder = NativePromiseRequest::create();
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &holder] {
auto promise = TestPromise::createAndResolve(42);
const size_t kIterations = 100;
for (size_t i = 0; i < kIterations; ++i) {
promise = promise->then(queue,
[](int val) {
EXPECT_EQ(val, 42);
return TestPromise::createAndResolve(val);
},
[](double val) {
return TestPromise::createAndReject(val);
});
if (i == kIterations / 2) {
promise->then(queue,
[queue, &holder] {
holder->disconnect();
queue->beginShutdown();
},
doFail());
}
}
// We will hit the assertion if we don't disconnect the leaf Request
// in the promise chain.
promise->whenSettled(queue, [] { })->track(holder.get());
});
}
TEST(NativePromise, MoveOnlyType)
{
using MyPromise = NativePromise<std::unique_ptr<int>, std::unique_ptr<int>>;
AutoWorkQueue awq;
auto queue = awq.queue();
MyPromise::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(87))->then(queue,
[](std::unique_ptr<int> val) {
EXPECT_EQ(87, *val);
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(87))->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_EQ(87, *(val.value()));
});
MyPromise::createAndReject(makeUniqueWithoutFastMallocCheck<int>(87))->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_FALSE(val.has_value());
EXPECT_EQ(87, *(val.error()));
queue->beginShutdown();
});
}
// A WTFString can be directly returned by a producer. More generically, so long as an object implements an `isolatedCopy()` method, it will be automatically called.
TEST(NativePromise, WTFString)
{
using MyPromise = NativePromise<String, String>;
AutoWorkQueue awq2;
auto queue2 = awq2.queue();
AutoWorkQueue awq;
auto queue = awq.queue();
MyPromise::createAndResolve("hello"_s)->then(queue,
[](String&& val) {
EXPECT_EQ(String("hello"_s), val);
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve("hello"_s)->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_EQ(String("hello"_s), val.value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
});
MyPromise::createAndReject("error"_s)->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_FALSE(val.has_value());
EXPECT_EQ(String("error"_s), val.error());
EXPECT_TRUE(val.error().isSafeToSendToAnotherThread());
});
MyPromise::createAndResolve(String("hello"_s))->then(queue,
[](String&& val) {
EXPECT_EQ(String("hello"_s), val);
EXPECT_TRUE(val.isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
// Check that we can receive the value by const reference too.
MyPromise::createAndResolve(String("hello"_s))->then(queue,
[](const String& val) {
EXPECT_EQ(String("hello"_s), val);
EXPECT_TRUE(val.isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
// Can pass object implecitly convertible to ResolveValueType
MyPromise::createAndResolve(AtomString("hello"_s))->then(queue,
[](String&& val) {
EXPECT_EQ(String("hello"_s), val);
EXPECT_TRUE(val.isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(AtomString("hello"_s))->then(queue,
[](const String& val) {
EXPECT_EQ(String("hello"_s), val);
EXPECT_TRUE(val.isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(AtomString("hello"_s))->then(queue,
[](String val) {
EXPECT_EQ(String("hello"_s), val);
EXPECT_TRUE(val.isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(String("hello"_s))->whenSettled(queue,
[](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
});
MyPromise::createAndResolve(String("hello"_s))->whenSettled(queue,
[](MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
});
MyPromise::createAndResolve(String("hello"_s))->whenSettled(queue,
[](const MyPromise::Result& val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
});
MyPromise::createAndReject(String("error"_s))->whenSettled(queue,
[](MyPromise::Result&& val) {
EXPECT_FALSE(val.has_value());
EXPECT_TRUE(val.error().isSafeToSendToAnotherThread());
EXPECT_EQ(String("error"_s), val.error());
});
MyPromise::createAndResolve(AtomString("hello"_s))->whenSettled(queue,
[](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
return MyPromise::createAndSettle(WTF::move(val));
})->whenSettled(queue2,
[](MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
});
MyPromise::createAndResolve(AtomString("hello"_s))->whenSettled(queue,
[queue](MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
queue->beginShutdown();
// Don't move the result to make sure we get a new isolatedCopy.
return MyPromise::createAndSettle(val);
})->whenSettled(queue2,
[queue2](MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
queue2->beginShutdown();
});
}
TEST(NativePromise, WTFStringWithDelayedResolve)
{
using MyPromise = NativePromise<String, String>;
// The following steps runs strictly serially.
// 1. We create a promise on the main thread.
// 2. Dispatch a task that will `whenSettled()` on that promise on WorkQueue2.
// 3. We resolve on the main thread the promise with an AtomString.
// 4. Resolver will be called on WorkQueue1 and check that the string content is correct and the string created on the main thread was safely moved.
AutoWorkQueue awq1;
awq1.queue();
auto queue1 = awq1.queue();
MyPromise::Producer producer;
bool hasRun = false; // AutoWorkQueue guarantees that there can't be concurrent accesses to hasRun.
{
AutoWorkQueue awq2;
auto queue2 = awq2.queue();
queue2->dispatch([queue1, queue2, promise = Ref<MyPromise> { producer }, &hasRun] {
promise->whenSettled(queue1,
[queue1](MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("hello"_s), val.value());
queue1->beginShutdown();
});
hasRun = true;
queue2->beginShutdown();
});
}
EXPECT_TRUE(hasRun);
producer.resolve(AtomString("hello"_s));
}
TEST(NativePromise, NonExclusiveWithCrossThreadCopy)
{
int resolution = 0;
{
AutoWorkQueue awq;
auto queue = awq.queue();
// If you replace PromiseOption::WithCrossThreadCopy with PromiseOption::WithoutCrossThreadCopy, this test will crash due to the AtomString being deleted on the target queue.
using MyPromise = NativePromise<Expected<String, AtomString>, bool, PromiseOption::NonExclusive | PromiseOption::WithCrossThreadCopy>;
static_assert(CrossThreadCopier<Expected<String, AtomString>>::IsNeeded);
MyPromise::Producer producer;
Ref<MyPromise> promise = producer;
promise->whenSettled(queue, [&resolution] (const MyPromise::Result& val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().has_value());
EXPECT_TRUE(val.value().value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("that worked"_s), val.value().value());
resolution++;
});
promise->whenSettled(queue, [&resolution] (MyPromise::Result val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().has_value());
// Being a non-exclusive promise, the value is passed by const reference, so we copied the object in val.
EXPECT_TRUE(!val.value().value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("that worked"_s), val.value().value());
resolution++;
});
promise->whenSettled(queue, [queue, &resolution] (const MyPromise::Result& val) {
EXPECT_TRUE(val.has_value());
EXPECT_TRUE(val.value().has_value());
// The previous whenSettled() has run already, and the object was derefed.
EXPECT_TRUE(val.value().value().isSafeToSendToAnotherThread());
EXPECT_EQ(String("that worked"_s), val.value().value());
resolution++;
queue->beginShutdown();
});
producer.resolve(String(AtomString("that worked"_s)));
}
EXPECT_EQ(3, resolution);
}
TEST(NativePromise, WithCrossThreadCopyType)
{
using MyPromiseWithString = NativePromise<String, AtomString>;
static_assert(MyPromiseWithString::WithAutomaticCrossThreadCopy);
static_assert(CrossThreadCopier<typename MyPromiseWithString::RejectValueType>::IsNeeded);
// Check that if making a NativePromise with an AtomString, you actually get a String
static_assert(std::is_same_v<typename MyPromiseWithString::RejectValueType, String>);
using MyPromiseWithoutString = NativePromise<int, bool>;
static_assert(!MyPromiseWithoutString::WithAutomaticCrossThreadCopy);
using MyPromiseWithArrayOfString = NativePromise<Vector<String>, bool>;
static_assert(MyPromiseWithArrayOfString::WithAutomaticCrossThreadCopy);
using MyNonExclusivePromise = NativePromise<Vector<int>, bool, PromiseOption::Default | PromiseOption::NonExclusive>;
// No need for crossThreadProxy for a Vector not containing a type with isolatedCopy() method.
static_assert(!MyNonExclusivePromise::WithAutomaticCrossThreadCopy);
}
TEST(NativePromise, ExpectedWithString)
{
using MyPromise = NativePromise<Expected<String, String>, int>;
AutoWorkQueue awq;
auto queue = awq.queue();
MyPromise::createAndResolve(String("hello"_s))->then(queue,
[](MyPromise::ResolveValueType&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_EQ(String("hello"_s), val.value());
EXPECT_TRUE(val.value().isSafeToSendToAnotherThread());
},
[] {
EXPECT_TRUE(false);
});
MyPromise::createAndResolve(String("hello"_s))->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_EQ(String("hello"_s), val.value());
EXPECT_TRUE(val.value().value().isSafeToSendToAnotherThread());
});
Expected<String, String> error = Unexpected<String>("error"_s);
MyPromise::createAndResolve(WTF::move(error))->whenSettled(queue,
[queue](MyPromise::Result&& val) {
EXPECT_TRUE(val.has_value());
EXPECT_FALSE(val.value().has_value());
EXPECT_EQ(String("error"_s), val.value().error());
EXPECT_TRUE(val.value().error().isSafeToSendToAnotherThread());
queue->beginShutdown();
});
}
TEST(NativePromise, HeterogeneousChaining)
{
using Promise1 = NativePromise<std::unique_ptr<char>, bool>;
using Promise2 = NativePromise<std::unique_ptr<int>, bool>;
auto holder = NativePromiseRequest::create();
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue, &holder] {
Promise1::createAndResolve(makeUniqueWithoutFastMallocCheck<char>(0))->whenSettled(queue,
[&holder] {
holder->disconnect();
return Promise2::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(0));
})->whenSettled(queue,
[] {
// Shouldn't be called for we've disconnected the request.
EXPECT_FALSE(true);
})->track(holder.get());
});
Promise1::createAndResolve(makeUniqueWithoutFastMallocCheck<char>(87))->then(queue,
[](std::unique_ptr<char> val) {
EXPECT_EQ(87, *val);
return Promise2::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(94));
},
[] {
return Promise2::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(95));
})->then(queue,
[](std::unique_ptr<int> val) {
EXPECT_EQ(94, *val);
},
doFail());
Promise1::createAndResolve(makeUniqueWithoutFastMallocCheck<char>(87))->whenSettled(queue,
[](Promise1::Result&& result) {
EXPECT_EQ(87, *(result.value()));
return Promise2::createAndResolve(makeUniqueWithoutFastMallocCheck<int>(94));
})->whenSettled(queue,
[queue](Promise2::Result&& result) {
EXPECT_EQ(94, *(result.value()));
});
// Chaining promises of different types, even if returned from within callback
TestPromise::createAndResolve(1)->whenSettled(queue,
[queue] {
return TestPromiseExcl::createAndResolve(2)->whenSettled(queue, [] {
return TestPromise::createAndResolve(3);
});
})->whenSettled(queue,
[queue](TestPromise::Result result) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(3, result.value());
});
TestPromise::createAndResolve(1)->whenSettled(queue,
[queue] {
return TestPromiseExcl::createAndResolve(2)->whenSettled(queue, [] {
return GenericPromise::createAndResolve();
});
})->whenSettled(queue,
[queue](GenericPromise::Result result) {
EXPECT_TRUE(result.has_value());
});
TestPromise::createAndResolve(1)->whenSettled(queue,
[queue] {
return TestPromiseExcl::createAndResolve(2)->whenSettled(queue, [] {
return GenericPromise::createAndReject();
});
})->whenSettled(queue,
[queue](GenericPromise::Result result) {
EXPECT_FALSE(result.has_value());
queue->beginShutdown();
});
}
TEST(NativePromise, RunLoop)
{
runInCurrentRunLoop([](auto& runLoop) {
TestPromise::createAndResolve(42)->then(runLoop,
[](int resolveValue) {
EXPECT_EQ(resolveValue, 42);
},
doFail());
});
}
TEST(NativePromise, ImplicitConversionWithForwardPreviousReturn)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
TestPromise::Producer producer;
Ref<TestPromise> promise = producer;
promise = promise->whenSettled(runLoop,
[](const TestPromise::Result& result) {
return TestPromise::createAndSettle(result);
});
promise->whenSettled(runLoop, [promise, &done](const TestPromise::Result& result) {
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(promise->isResolved());
done = true;
});
producer.resolve(1);
Ref<TestPromise> originalPromise = producer;
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)
{
using VectorPromise = NativePromise<Ref<RefCountedFixedVector<int>>, bool, PromiseOption::Default | PromiseOption::NonExclusive>;
auto resultVector = RefCountedFixedVector<int>::createFromVector(Vector<int>(5, 42));
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer(), producer2 = VectorPromise::Producer()](auto& runLoop) mutable {
auto promise = VectorPromise::createAndResolve(resultVector);
producer1->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(resolveValue.get(), RefCountedFixedVector<int>::createFromVector(Vector<int>(5, 42)).get()); },
doFail());
producer2->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(resolveValue.get(), RefCountedFixedVector<int>::createFromVector(Vector<int>(5, 42)).get()); },
doFail());
// As promise1 is already resolved, it will automatically resolve/reject producer1 and producer2 with its resolved/reject value.
promise->chainTo(WTF::move(producer1));
promise->chainTo(WTF::move(producer2));
});
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer(), producer2 = VectorPromise::Producer()](auto& runLoop) mutable {
producer2->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(resolveValue.get(), RefCountedFixedVector<int>::createFromVector(Vector<int>(5, 42)).get()); },
doFail());
// When producer1 is resolved, it will automatically settle producer2 with the resolved/reject value.
producer1->chainTo(WTF::move(producer2));
VectorPromise::createAndResolve(resultVector)->chainTo(WTF::move(producer1));
});
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer()](auto& runLoop) mutable {
auto promise = VectorPromise::createAndResolve(resultVector);
producer1->then(runLoop,
[&](auto&& resolveValue) { EXPECT_EQ(resolveValue.get(), RefCountedFixedVector<int>::createFromVector(Vector<int>(5, 42)).get()); },
doFail());
// As promise1 is already resolved, it will automatically resolve/reject producer1 with its resolved/reject value.
promise->chainTo(WTF::move(producer1));
});
}
TEST(NativePromise, ChainToNonMovable)
{
using VectorPromise = NativePromise<std::unique_ptr<Vector<int>>, bool, PromiseOption::Default | PromiseOption::NonExclusive>;
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer(), producer2 = VectorPromise::Producer()](auto& runLoop) mutable {
auto promise = VectorPromise::createAndResolve(makeUnique<Vector<int>>(5, 42));
producer1->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(*resolveValue, Vector<int>(5, 42)); },
doFail());
producer2->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(*resolveValue, Vector<int>(5, 42)); },
doFail());
// As promise1 is already resolved, it will automatically resolve/reject producer1 and producer2 with its resolved/reject value.
promise->chainTo(WTF::move(producer1));
promise->chainTo(WTF::move(producer2));
});
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer(), producer2 = VectorPromise::Producer()](auto& runLoop) mutable {
producer2->then(runLoop,
[&](const auto& resolveValue) { EXPECT_EQ(*resolveValue, Vector<int>(5, 42)); },
doFail());
// When producer1 is resolved, it will automatically settle producer2 with the resolved/reject value.
producer1->chainTo(WTF::move(producer2));
VectorPromise::createAndResolve(makeUnique<Vector<int>>(5, 42))->chainTo(WTF::move(producer1));
});
runInCurrentRunLoop([&, producer1 = VectorPromise::Producer()](auto& runLoop) mutable {
auto promise = VectorPromise::createAndResolve(makeUnique<Vector<int>>(5, 42));
producer1->then(runLoop,
[&](auto&& resolveValue) { EXPECT_EQ(*resolveValue, Vector<int>(5, 42)); },
doFail());
// As promise1 is already resolved, it will automatically resolve/reject producer1 with its resolved/reject value.
promise->chainTo(WTF::move(producer1));
});
}
TEST(NativePromise, RunSynchronouslyOnTarget)
{
// Check that the callback is executed immediately when the promise is resolved.
runInCurrentRunLoop([](auto& runLoop) {
GenericPromise::Producer producer(PromiseDispatchMode::RunSynchronouslyOnTarget);
int result = 0;
producer.resolve();
producer->whenSettled(runLoop, [&] {
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(PromiseDispatchMode::RunSynchronouslyOnTarget);
int result = 0;
producer->whenSettled(runLoop, [&] {
// We need to configure this promise too as RunSynchronouslyOnTarget
GenericPromise::Producer producer(PromiseDispatchMode::RunSynchronouslyOnTarget);
producer.resolve();
return producer;
})->whenSettled(runLoop, [&] {
result = 42;
});
producer.resolve();
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(PromiseDispatchMode::RunSynchronouslyOnTarget);
Ref<GenericPromise> promise = producer;
int result = 0;
producer.resolve();
{
AutoWorkQueue awq;
promise->whenSettled(awq.queue().get(), [&, queue = awq.queue()] {
assertIsCurrent(queue);
result = 42;
queue->beginShutdown();
});
}
EXPECT_EQ(result, 42);
});
}
// Test that you can convert a NativePromise of different types (exclusive vs non-exclusive)
TEST(NativePromise, PromiseConversion)
{
using MyPromise = NativePromise<void, int>;
using MyNonExclusivePromise = NativePromise<void, int, PromiseOption::Default | PromiseOption::NonExclusive>;
using MyNonExclusiveLongPromise = NativePromise<void, long, PromiseOption::Default | PromiseOption::NonExclusive>;
runInCurrentRunLoop([](auto& runLoop) {
auto nonExclusivePromise = MyNonExclusivePromise::createAndResolve();
Ref<MyPromise> promise1 = nonExclusivePromise.get();
promise1->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
Ref<MyPromise> promise2 = nonExclusivePromise.get();
promise2->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
auto promise3 = nonExclusivePromise->convert<GenericPromise>();
static_assert(std::is_same_v<Ref<GenericPromise>, decltype(promise3)>);
promise3->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
// Converting a promise taking void as reject.
auto promise4 = MyNonExclusiveLongPromise::createAndReject(1)->convert<GenericPromise>();
static_assert(std::is_same_v<Ref<GenericPromise>, decltype(promise4)>);
promise4->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!result);
});
// Converting a promise with different type.
using IntPromise = NativePromise<int, int>;
using DoublePromise = NativePromise<double, double>;
auto promise5 = IntPromise::createAndResolve(1)->convert<DoublePromise>();
static_assert(std::is_same_v<Ref<DoublePromise>, decltype(promise5)>);
promise5->whenSettled(runLoop, [](auto&& result) {
static_assert(std::is_same_v<double, std::remove_reference_t<decltype(result.value())>>);
EXPECT_TRUE(result.has_value());
});
// Can convert to promise taking a data/void mix
using DoubleVoidPromise = NativePromise<double, void>;
auto promise6 = IntPromise::createAndReject(1)->convert<DoubleVoidPromise>();
promise6->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!result);
});
});
}
TEST(NativePromise, MismatchChainTo)
{
using MyPromise = NativePromise<void, int>;
using MyNonExclusivePromise = NativePromise<void, int, PromiseOption::Default | PromiseOption::NonExclusive>;
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
// Chaining resolved promise from producer
auto nonExclusivePromise = MyNonExclusivePromise::createAndResolve();
MyPromise::Producer producer1;
producer1->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
nonExclusivePromise->chainTo(WTF::move(producer1));
// Chaining promise, not yet resolved
MyNonExclusivePromise::Producer producer2;
auto promise2 = producer2.promise();
MyPromise::Producer producer3;
auto promise3 = producer3.promise();
promise3->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
producer2->chainTo(WTF::move(producer3));
EXPECT_FALSE(promise2->isSettled());
EXPECT_FALSE(promise3->isSettled());
producer2->resolve();
// Resolving one producer synchronously resolve the other.
EXPECT_TRUE(promise2->isSettled());
EXPECT_TRUE(promise3->isSettled());
// Chaining promise from convertible type
using IntPromise = NativePromise<int, int>;
using LongPromise = NativePromise<long, long>;
auto intPromise1 = IntPromise::createAndResolve(1);
LongPromise::Producer longPromiseProducer1;
auto longPromise1 = longPromiseProducer1.promise();
intPromise1->chainTo(WTF::move(longPromiseProducer1));
longPromise1->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(1));
});
// Chaining non-exclusive promise to exclusive, check the end result is movable.
using IntPromiseNonExcl = NativePromise<int, int, PromiseOption::Default | PromiseOption::NonExclusive>;
auto intPromise2 = IntPromiseNonExcl::createAndResolve(1);
LongPromise::Producer longPromiseProducer2;
auto longPromise2 = longPromiseProducer2.promise();
intPromise2->chainTo(WTF::move(longPromiseProducer2));
longPromise2->whenSettled(runLoop, [](auto&& result) {
using NonRefQualifiedType = typename std::remove_reference<decltype(result)>::type;
static_assert(!std::is_const<NonRefQualifiedType>::value, "result is const qualified.");
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(1));
});
intPromise2->whenSettled(runLoop, [](auto result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, 1);
});
// chaining ThenCommand
auto intPromise3 = IntPromise::createAndResolve(1);
LongPromise::Producer longPromiseProducer3;
auto longPromise3 = longPromiseProducer3.promise();
longPromise3->whenSettled(runLoop, [&](auto&& result) mutable {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(2));
done = true;
});
intPromise3->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(1));
return IntPromise::createAndResolve(2);
})->chainTo(WTF::move(longPromiseProducer3));
auto intPromise4 = IntPromise::createAndResolve(1);
LongPromise::Producer longPromiseProducer4;
auto longPromise4 = longPromiseProducer4.promise();
intPromise4->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(1));
return IntPromise::createAndReject(2);
})->chainTo(WTF::move(longPromiseProducer4));
longPromise4->whenSettled(runLoop, [&](auto&& result) mutable {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), long(2));
done = true;
});
});
}
TEST(NativePromise, MismatchChainToVoidPromise)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
// chaining with void promise
using IntPromise = NativePromise<int, int>;
using LongPromise = NativePromise<long, long>;
auto intPromise1 = IntPromise::createAndResolve(1);
LongPromise::Producer longPromiseProducer1;
auto longPromise1 = longPromiseProducer1.promise();
intPromise1->chainTo(WTF::move(longPromiseProducer1));
longPromise1->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, long(1));
});
auto intPromise2 = IntPromise::createAndResolve(1);
GenericPromise::Producer genericPromiseProducer1;
genericPromiseProducer1->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!!result);
});
intPromise2->chainTo(WTF::move(genericPromiseProducer1));
auto intPromise3 = IntPromise::createAndReject(1);
GenericPromise::Producer genericPromiseProducer2;
genericPromiseProducer2->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!result);
done = true;
});
intPromise3->chainTo(WTF::move(genericPromiseProducer2));
});
}
TEST(NativePromise, CreateSettledPromise)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
using MyExpected = Expected<int, long>;
createSettledPromise(MyExpected { makeUnexpected<long>(1) })->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), 1L);
});
createSettledPromise(MyExpected { 1 })->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(result.value(), 1);
done = true;
});
});
}
TEST(NativePromise, DisconnectNotOwnedInstance)
{
GenericPromise::Producer producer;
auto request = NativePromiseRequest::create();
WeakPtr weakRequest { request.get() };
producer->whenSettled(RunLoop::mainSingleton(), [request = WTF::move(request)] (auto&& result) mutable {
request->complete();
EXPECT_TRUE(false);
})->track(*weakRequest);
weakRequest->disconnect();
EXPECT_FALSE(!!weakRequest);
producer.resolve();
}
TEST(NativePromise, AutoRejectProducer)
{
AutoWorkQueue awq;
auto queue = awq.queue();
queue->dispatch([queue] {
RefPtr<GenericPromise> promise1;
{
GenericPromise::AutoRejectProducer producer;
promise1 = producer.promise();
}
promise1->then(queue, doFail(), [] {
EXPECT_TRUE(true);
});
RefPtr<NativePromise<int, int>> promise2;
{
NativePromise<int, int>::AutoRejectProducer producer(-1);
promise2 = producer.promise();
}
promise2->then(queue, doFail(), [](auto result) {
EXPECT_EQ(result, -1);
});
// Check that AutoRejectProducer is usable with non-copyable type.
RefPtr<NativePromise<int, std::unique_ptr<int>>> promise3;
{
NativePromise<int, std::unique_ptr<int>>::AutoRejectProducer producer(makeUniqueWithoutFastMallocCheck<int>(-1));
promise3 = producer.promise();
}
promise3->then(queue, doFail(), [](auto&& result) {
EXPECT_EQ(*result, -1);
});
RefPtr<NativePromise<int, std::unique_ptr<int>>> promise4;
{
NativePromise<int, std::unique_ptr<int>>::AutoRejectProducer producer(makeUniqueWithoutFastMallocCheck<int>(-2));
promise4 = producer.promise();
producer.setDefaultReject(makeUniqueWithoutFastMallocCheck<int>(-1));
}
promise4->then(queue, doFail(), [queue](auto&& result) {
EXPECT_EQ(*result, -1);
queue->beginShutdown();
});
});
}
TEST(NativePromise, ResolveWithFunction)
{
AutoWorkQueue awq;
auto queue = awq.queue();
// Test settled with expected value, before whenSettled.
NativePromise<int, int>::Producer producer1;
producer1.settleWithFunction([queue]() -> NativePromise<int, int>::Result {
assertIsCurrent(queue);
return 1;
});
producer1.promise()->whenSettled(queue, [](auto result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, 1);
});
// Test settled with rejected value, before whenSettled.
NativePromise<int, int>::Producer producer2;
producer2.settleWithFunction([queue]() -> NativePromise<int, int>::Result {
assertIsCurrent(queue);
return makeUnexpected(2);
});
producer2.promise()->whenSettled(queue, [](auto result) {
EXPECT_FALSE(!!result);
EXPECT_EQ(result.error(), 2);
});
// Test settled with expected value, after whenSettled.
NativePromise<int, int>::Producer producer3;
producer3.promise()->whenSettled(queue, [](auto result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, 1);
});
producer3.settleWithFunction([queue]() -> NativePromise<int, int>::Result {
assertIsCurrent(queue);
return 1;
});
// Test settled with rejected value, after whenSettled.
NativePromise<int, int>::Producer producer4;
producer4.promise()->whenSettled(queue, [](auto result) {
EXPECT_FALSE(!!result);
EXPECT_EQ(result.error(), 2);
});
producer4.settleWithFunction([queue]() -> NativePromise<int, int>::Result {
assertIsCurrent(queue);
return makeUnexpected(2);
});
// Test with settle(Function) syntax
NativePromise<int, int>::Producer producer5;
producer5.settle([queue]() -> NativePromise<int, int>::Result {
assertIsCurrent(queue);
return 1;
});
producer5.promise()->whenSettled(queue, [queue](auto result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(*result, 1);
queue->beginShutdown();
});
}
// Example:
// Consider a PhotoProducer class that can take a photo and returns an image and its mimetype.
// The PhotoProducer uses some system framework that takes a completion handler which will receive the photo once taken.
// The PhotoProducer uses its own WorkQueue to perform the work so that it won't block the thread it's called on.
// We want the PhotoProducer to be able to be called on any threads.
// This would be the system framework.
struct AVCaptureMethod {
static void captureImage(std::function<void(std::vector<uint8_t>&&, std::string&&)>&& handler)
{
handler({ 1, 2, 3, 4, 5 }, "image/jpeg");
}
};
struct PhotoSettings { };
// Needed until we get c++23 moveonly_function
template<class F>
auto makeMoveableFunction(F&& f)
{
return [sharedPtr = std::make_shared<std::decay_t<F>>(std::forward<F>(f))] (auto&&... args) {
return std::invoke(*sharedPtr, decltype(args)(args)...);
};
}
class PhotoProducer : public ThreadSafeRefCounted<PhotoProducer> {
public:
using PhotoPromise = NativePromise<std::pair<Vector<uint8_t>, String>, int>;
static Ref<PhotoProducer> create(const PhotoSettings& settings) { return adoptRef(*new PhotoProducer(settings)); }
Ref<PhotoPromise> takePhoto() const
{
// This can be called on any threads.
// It uses invokeAsync which returns a NativePromise that will be settled when the promise returned by the method will itself be settled.
// (the invokeAsync promise is "chained" to the promise returned by `takePhotoImpl()`)
return invokeAsync(m_generatePhotoQueue, [protectedThis = Ref { *this }] {
assertIsCurrent(protectedThis->m_generatePhotoQueue);
return protectedThis->takePhotoImpl();
});
}
private:
explicit PhotoProducer(const PhotoSettings& settings)
: m_generatePhotoQueue(WorkQueue::create("takePhoto queue"_s))
{
}
Ref<PhotoPromise> takePhotoImpl() const
{
PhotoPromise::Producer producer;
Ref<PhotoPromise> promise = producer;
AVCaptureMethod::captureImage(makeMoveableFunction([producer = WTF::move(producer)] (std::vector<uint8_t>&& image, std::string&& mimeType) {
// Note that you can resolve a NativePromise on any threads. Unlike with a CompletionHandler it is not the responsibility of the producer to resolve the promise
// on a particular thread.
// The consumer specifies the thread on which it wants to be called back.
producer.resolve(std::make_pair<Vector<uint8_t>, String>(std::span { image }, std::span<const char> { mimeType }));
}));
// Return the promise which the producer will resolve at a later stage.
return promise;
}
Ref<WorkQueue> m_generatePhotoQueue;
};
TEST(NativePromise, TakePhotoExample)
{
AutoWorkQueue awk;
auto queue = awk.queue();
queue->dispatch([queue] {
auto photoProducer = PhotoProducer::create(PhotoSettings { });
photoProducer->takePhoto()->whenSettled(queue, [queue] (PhotoProducer::PhotoPromise::Result&& result) mutable {
static_assert(std::is_same_v<decltype(result.value()), std::pair<Vector<uint8_t>, String>&>);
if (result)
EXPECT_EQ(result.value().second, "image/jpeg"_s);
else
EXPECT_TRUE(false); // Got an unexpected error.
queue->beginShutdown();
});
});
}
} // namespace TestWebKitAPI