blob: 17aaef3e5ab567734ba0bac028d22319343496a9 [file] [log] [blame]
#include "napi.h"
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#if (NAPI_VERSION > 3)
using namespace Napi;
namespace {
struct ProgressData {
size_t progress;
};
class TestWorkerWithNoCb : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 1: {
Function cb = info[0].As<Function>();
TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb);
worker->Queue();
} break;
case 2: {
std::string resName = info[0].As<String>();
Function cb = info[1].As<Function>();
TestWorkerWithNoCb* worker =
new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb);
worker->Queue();
} break;
case 3: {
std::string resName = info[0].As<String>();
Object resObject = info[1].As<Object>();
Function cb = info[2].As<Function>();
TestWorkerWithNoCb* worker =
new TestWorkerWithNoCb(info.Env(), resName.c_str(), resObject, cb);
worker->Queue();
} break;
default:
break;
}
}
protected:
void Execute(const ExecutionProgress& progress) override {
ProgressData data{1};
progress.Send(&data, 1);
}
void OnProgress(const ProgressData*, size_t /* count */) override {
_cb.Call({});
}
private:
TestWorkerWithNoCb(Napi::Env env, Function cb) : AsyncProgressWorker(env) {
_cb.Reset(cb, 1);
}
TestWorkerWithNoCb(Napi::Env env, const char* resourceName, Function cb)
: AsyncProgressWorker(env, resourceName) {
_cb.Reset(cb, 1);
}
TestWorkerWithNoCb(Napi::Env env,
const char* resourceName,
const Object& resourceObject,
Function cb)
: AsyncProgressWorker(env, resourceName, resourceObject) {
_cb.Reset(cb, 1);
}
FunctionReference _cb;
};
class TestWorkerWithRecv : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 2: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb);
worker->Queue();
} break;
case 3: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
std::string resName = info[2].As<String>();
TestWorkerWithRecv* worker =
new TestWorkerWithRecv(recv, cb, resName.c_str());
worker->Queue();
} break;
case 4: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
std::string resName = info[2].As<String>();
Object resObject = info[3].As<Object>();
TestWorkerWithRecv* worker =
new TestWorkerWithRecv(recv, cb, resName.c_str(), resObject);
worker->Queue();
} break;
default:
break;
}
}
protected:
void Execute(const ExecutionProgress&) override {}
void OnProgress(const ProgressData*, size_t /* count */) override {}
private:
TestWorkerWithRecv(const Object& recv, const Function& cb)
: AsyncProgressWorker(recv, cb) {}
TestWorkerWithRecv(const Object& recv,
const Function& cb,
const char* resourceName)
: AsyncProgressWorker(recv, cb, resourceName) {}
TestWorkerWithRecv(const Object& recv,
const Function& cb,
const char* resourceName,
const Object& resourceObject)
: AsyncProgressWorker(recv, cb, resourceName, resourceObject) {}
};
class TestWorkerWithCb : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 1: {
Function cb = info[0].As<Function>();
TestWorkerWithCb* worker = new TestWorkerWithCb(cb);
worker->Queue();
} break;
case 2: {
Function cb = info[0].As<Function>();
std::string asyncResName = info[1].As<String>();
TestWorkerWithCb* worker =
new TestWorkerWithCb(cb, asyncResName.c_str());
worker->Queue();
} break;
default:
break;
}
}
protected:
void Execute(const ExecutionProgress&) override {}
void OnProgress(const ProgressData*, size_t /* count */) override {}
private:
TestWorkerWithCb(Function cb) : AsyncProgressWorker(cb) {}
TestWorkerWithCb(Function cb, const char* res_name)
: AsyncProgressWorker(cb, res_name) {}
};
class TestWorker : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
int32_t times = info[0].As<Number>().Int32Value();
Function cb = info[1].As<Function>();
Function progress = info[2].As<Function>();
TestWorker* worker =
new TestWorker(cb, progress, "TestResource", Object::New(info.Env()));
worker->_times = times;
worker->Queue();
}
protected:
void Execute(const ExecutionProgress& progress) override {
if (_times < 0) {
SetError("test error");
}
ProgressData data{0};
for (int32_t idx = 0; idx < _times; idx++) {
data.progress = idx;
progress.Send(&data, 1);
{
std::unique_lock<std::mutex> lk(_cvm);
_cv.wait(lk, [this] { return dataSent; });
dataSent = false;
}
}
}
void OnProgress(const ProgressData* data, size_t /* count */) override {
Napi::Env env = Env();
if (!_progress.IsEmpty()) {
Number progress = Number::New(env, data->progress);
_progress.MakeCallback(Receiver().Value(), {progress});
}
{
std::lock_guard<std::mutex> lk(_cvm);
dataSent = true;
_cv.notify_one();
}
}
private:
TestWorker(Function cb,
Function progress,
const char* resource_name,
const Object& resource)
: AsyncProgressWorker(cb, resource_name, resource) {
_progress.Reset(progress, 1);
}
bool dataSent = false;
std::condition_variable _cv;
std::mutex _cvm;
int32_t _times;
FunctionReference _progress;
};
class MalignWorker : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
Function cb = info[0].As<Function>();
Function progress = info[1].As<Function>();
MalignWorker* worker =
new MalignWorker(cb, progress, "TestResource", Object::New(info.Env()));
worker->Queue();
}
protected:
void Execute(const ExecutionProgress& progress) override {
{
std::unique_lock<std::mutex> lock(_cvm);
// Testing a nullptr send is acceptable.
progress.Send(nullptr, 0);
_cv.wait(lock, [this] { return _test_case_count == 1; });
}
{
std::unique_lock<std::mutex> lock(_cvm);
progress.Signal();
_cv.wait(lock, [this] { return _test_case_count == 2; });
}
// Testing busy looping on send doesn't trigger unexpected empty data
// OnProgress call.
for (size_t i = 0; i < 1000000; i++) {
ProgressData data{0};
progress.Send(&data, 1);
}
}
void OnProgress(const ProgressData* /* data */, size_t count) override {
Napi::Env env = Env();
{
std::lock_guard<std::mutex> lock(_cvm);
_test_case_count++;
}
bool error = false;
Napi::String reason = Napi::String::New(env, "No error");
if (_test_case_count <= 2 && count != 0) {
error = true;
reason =
Napi::String::New(env, "expect 0 count of data on 1st and 2nd call");
}
if (_test_case_count > 2 && count != 1) {
error = true;
reason = Napi::String::New(
env, "expect 1 count of data on non-1st and non-2nd call");
}
_progress.MakeCallback(Receiver().Value(),
{Napi::Boolean::New(env, error), reason});
_cv.notify_one();
}
private:
MalignWorker(Function cb,
Function progress,
const char* resource_name,
const Object& resource)
: AsyncProgressWorker(cb, resource_name, resource) {
_progress.Reset(progress, 1);
}
size_t _test_case_count = 0;
std::condition_variable _cv;
std::mutex _cvm;
FunctionReference _progress;
};
// Calling a Signal after a SendProgress should not clear progress data
class SignalAfterProgressTestWorker : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
Function cb = info[0].As<Function>();
Function progress = info[1].As<Function>();
SignalAfterProgressTestWorker* worker = new SignalAfterProgressTestWorker(
cb, progress, "TestResource", Object::New(info.Env()));
worker->Queue();
}
protected:
void Execute(const ExecutionProgress& progress) override {
ProgressData data{0};
progress.Send(&data, 1);
progress.Signal();
}
void OnProgress(const ProgressData* /* data */, size_t count) override {
Napi::Env env = Env();
bool error = false;
Napi::String reason = Napi::String::New(env, "No error");
if (count != 1) {
error = true;
reason = Napi::String::New(env, "expect 1 count of data");
}
_progress.MakeCallback(Receiver().Value(),
{Napi::Boolean::New(env, error), reason});
}
private:
SignalAfterProgressTestWorker(Function cb,
Function progress,
const char* resource_name,
const Object& resource)
: AsyncProgressWorker(cb, resource_name, resource) {
_progress.Reset(progress, 1);
}
FunctionReference _progress;
};
} // namespace
Object InitAsyncProgressWorker(Env env) {
Object exports = Object::New(env);
exports["doWork"] = Function::New(env, TestWorker::DoWork);
exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork);
exports["doSignalAfterProgressTest"] =
Function::New(env, SignalAfterProgressTestWorker::DoWork);
exports["runWorkerNoCb"] = Function::New(env, TestWorkerWithNoCb::DoWork);
exports["runWorkerWithRecv"] = Function::New(env, TestWorkerWithRecv::DoWork);
exports["runWorkerWithCb"] = Function::New(env, TestWorkerWithCb::DoWork);
return exports;
}
#endif