Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding unit tests for FunctionReference class #1035

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 172 additions & 1 deletion test/function_reference.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,163 @@

using namespace Napi;

class FuncRefObject : public Napi::ObjectWrap<FuncRefObject> {
public:
FuncRefObject(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<FuncRefObject>(info) {
Napi::Env env = info.Env();
int argLen = info.Length();
if (argLen <= 0 || !info[0].IsNumber()) {
Napi::TypeError::New(env, "First param should be a number")
.ThrowAsJavaScriptException();
return;
}
Napi::Number value = info[0].As<Napi::Number>();
this->_value = value.Int32Value();
}

Napi::Value GetValue(const Napi::CallbackInfo& info) {
int value = this->_value;
return Napi::Number::New(info.Env(), value);
}

private:
int _value;
};

namespace {

Value ConstructRefFromExisitingRef(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
FunctionReference movedRef;
ref.Reset(info[0].As<Function>());
movedRef = std::move(ref);

return MaybeUnwrap(movedRef({}));
}

Value CallWithVectorArgs(const CallbackInfo& info) {
HandleScope scope(info.Env());
std::vector<napi_value> newVec;
FunctionReference ref;
ref.Reset(info[0].As<Function>());

for (int i = 1; i < (int)info.Length(); i++) {
newVec.push_back(info[i]);
}
return MaybeUnwrap(ref.Call(newVec));
}

Value CallWithInitList(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
ref.Reset(info[0].As<Function>());

return MaybeUnwrap(ref.Call({info[1], info[2], info[3]}));
}

Value CallWithRecvInitList(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
ref.Reset(info[0].As<Function>());

return MaybeUnwrap(ref.Call(info[1], {info[2], info[3], info[4]}));
}

Value CallWithRecvVector(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
std::vector<napi_value> newVec;
ref.Reset(info[0].As<Function>());

for (int i = 2; i < (int)info.Length(); i++) {
newVec.push_back(info[i]);
}
return MaybeUnwrap(ref.Call(info[1], newVec));
}

Value CallWithRecvArgc(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
int argLength = info.Length() - 2;
napi_value* args = new napi_value[argLength];
ref.Reset(info[0].As<Function>());

int argIdx = 0;
for (int i = 2; i < (int)info.Length(); i++, argIdx++) {
args[argIdx] = info[i];
}

return MaybeUnwrap(ref.Call(info[1], argLength, args));
}

Value MakeAsyncCallbackWithInitList(const Napi::CallbackInfo& info) {
Napi::FunctionReference ref;
ref.Reset(info[0].As<Function>());

Napi::AsyncContext context(info.Env(), "func_ref_resources", {});

return MaybeUnwrap(
ref.MakeCallback(Napi::Object::New(info.Env()), {}, context));
}

Value MakeAsyncCallbackWithVector(const Napi::CallbackInfo& info) {
Napi::FunctionReference ref;
ref.Reset(info[0].As<Function>());
std::vector<napi_value> newVec;
Napi::AsyncContext context(info.Env(), "func_ref_resources", {});

for (int i = 1; i < (int)info.Length(); i++) {
newVec.push_back(info[i]);
}

return MaybeUnwrap(
ref.MakeCallback(Napi::Object::New(info.Env()), newVec, context));
}

Value MakeAsyncCallbackWithArgv(const Napi::CallbackInfo& info) {
Napi::FunctionReference ref;
ref.Reset(info[0].As<Function>());
int argLength = info.Length() - 1;
napi_value* args = new napi_value[argLength];

int argIdx = 0;
for (int i = 1; i < (int)info.Length(); i++, argIdx++) {
args[argIdx] = info[i];
}

Napi::AsyncContext context(info.Env(), "func_ref_resources", {});
return MaybeUnwrap(ref.MakeCallback(
Napi::Object::New(info.Env()), argLength, args, context));
}

Value CreateFunctionReferenceUsingNew(const Napi::CallbackInfo& info) {
Napi::Function func = ObjectWrap<FuncRefObject>::DefineClass(
info.Env(),
"MyObject",
{ObjectWrap<FuncRefObject>::InstanceMethod("getValue",
&FuncRefObject::GetValue)});
Napi::FunctionReference* constructor = new Napi::FunctionReference();
*constructor = Napi::Persistent(func);

return MaybeUnwrapOr(constructor->New({info[0].As<Number>()}), Object());
}

Value CreateFunctionReferenceUsingNewVec(const Napi::CallbackInfo& info) {
Napi::Function func = ObjectWrap<FuncRefObject>::DefineClass(
info.Env(),
"MyObject",
{ObjectWrap<FuncRefObject>::InstanceMethod("getValue",
&FuncRefObject::GetValue)});
Napi::FunctionReference* constructor = new Napi::FunctionReference();
*constructor = Napi::Persistent(func);
std::vector<napi_value> newVec;
newVec.push_back(info[0]);

return MaybeUnwrapOr(constructor->New(newVec), Object());
}

Value Call(const CallbackInfo& info) {
HandleScope scope(info.Env());
FunctionReference ref;
Expand All @@ -23,7 +179,22 @@ Value Construct(const CallbackInfo& info) {

Object InitFunctionReference(Env env) {
Object exports = Object::New(env);

exports["CreateFuncRefWithNew"] =
Function::New(env, CreateFunctionReferenceUsingNew);
exports["CreateFuncRefWithNewVec"] =
Function::New(env, CreateFunctionReferenceUsingNewVec);
exports["CallWithRecvArgc"] = Function::New(env, CallWithRecvArgc);
exports["CallWithRecvVector"] = Function::New(env, CallWithRecvVector);
exports["CallWithRecvInitList"] = Function::New(env, CallWithRecvInitList);
exports["CallWithInitList"] = Function::New(env, CallWithInitList);
exports["CallWithVec"] = Function::New(env, CallWithVectorArgs);
exports["ConstructWithMove"] =
Function::New(env, ConstructRefFromExisitingRef);
exports["AsyncCallWithInitList"] =
Function::New(env, MakeAsyncCallbackWithInitList);
exports["AsyncCallWithVector"] =
Function::New(env, MakeAsyncCallbackWithVector);
exports["AsyncCallWithArgv"] = Function::New(env, MakeAsyncCallbackWithArgv);
exports["call"] = Function::New(env, Call);
exports["construct"] = Function::New(env, Construct);

Expand Down
147 changes: 142 additions & 5 deletions test/function_reference.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,157 @@
'use strict';

const assert = require('assert');
const asyncHook = require('async_hooks');

module.exports = require('./common').runTest(binding => {
test(binding.functionreference);
module.exports = require('./common').runTest(async (binding) => {
await test(binding.functionreference);
});

function test(binding) {
function installAsyncHook () {
let id;
let destroyed;
let hook;
const events = [];
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (destroyed) {
hook.disable();
clearInterval(interval);
resolve(events);
}
}, 10);

hook = asyncHook
.createHook({
init (asyncId, type, triggerAsyncId, resource) {
if (id === undefined && type === 'func_ref_resources') {
id = asyncId;
events.push({ eventName: 'init', type, triggerAsyncId, resource });
}
},
before (asyncId) {
if (asyncId === id) {
events.push({ eventName: 'before' });
}
},
after (asyncId) {
if (asyncId === id) {
events.push({ eventName: 'after' });
}
},
destroy (asyncId) {
if (asyncId === id) {
events.push({ eventName: 'destroy' });
destroyed = true;
}
}
})
.enable();
});
}

function canConstructRefFromExistingRef (binding) {
const testFunc = () => 240;
assert(binding.ConstructWithMove(testFunc) === 240);
}

function canCallFunctionWithDifferentOverloads (binding) {
let outsideRef = {};
const testFunc = (a, b) => a * a - b * b;
const testFuncB = (a, b, c) => a + b - c * c;
const testFuncC = (a, b, c) => {
outsideRef.a = a;
outsideRef.b = b;
outsideRef.c = c;
};
const testFuncD = (a, b, c, d) => {
outsideRef.result = a + b * c - d;
return outsideRef.result;
};

assert(binding.CallWithVec(testFunc, 5, 4) === testFunc(5, 4));
assert(binding.CallWithInitList(testFuncB, 2, 4, 5) === testFuncB(2, 4, 5));

binding.CallWithRecvVector(testFuncC, outsideRef, 1, 2, 4);
assert(outsideRef.a === 1 && outsideRef.b === 2 && outsideRef.c === 4);

outsideRef = {};
binding.CallWithRecvInitList(testFuncC, outsideRef, 1, 2, 4);
assert(outsideRef.a === 1 && outsideRef.b === 2 && outsideRef.c === 4);

outsideRef = {};
binding.CallWithRecvArgc(testFuncD, outsideRef, 2, 4, 5, 6);
assert(outsideRef.result === testFuncD(2, 4, 5, 6));
}

async function canCallAsyncFunctionWithDifferentOverloads (binding) {
const testFunc = () => 2100;
const testFuncB = (a, b, c, d) => a + b + c + d;
let hook = installAsyncHook();
binding.AsyncCallWithInitList(testFunc);
let triggerAsyncId = asyncHook.executionAsyncId();
let res = await hook;
assert.deepStrictEqual(res, [
{
eventName: 'init',
type: 'func_ref_resources',
triggerAsyncId: triggerAsyncId,
resource: {}
},
{ eventName: 'before' },
{ eventName: 'after' },
{ eventName: 'destroy' }
]);

hook = installAsyncHook();
triggerAsyncId = asyncHook.executionAsyncId();
assert(
binding.AsyncCallWithVector(testFuncB, 2, 4, 5, 6) === testFuncB(2, 4, 5, 6)
);
res = await hook;
assert.deepStrictEqual(res, [
{
eventName: 'init',
type: 'func_ref_resources',
triggerAsyncId: triggerAsyncId,
resource: {}
},
{ eventName: 'before' },
{ eventName: 'after' },
{ eventName: 'destroy' }
]);

hook = installAsyncHook();
triggerAsyncId = asyncHook.executionAsyncId();
assert(
binding.AsyncCallWithArgv(testFuncB, 2, 4, 5, 6) === testFuncB(2, 4, 5, 6)
);
}
async function test (binding) {
const e = new Error('foobar');
const functionMayThrow = () => { throw e; };
const classMayThrow = class { constructor() { throw e; } };
const functionMayThrow = () => {
throw e;
};
const classMayThrow = class {
constructor () {
throw e;
}
};

const newRef = binding.CreateFuncRefWithNew(120);
assert(newRef.getValue() === 120);

const newRefWithVecArg = binding.CreateFuncRefWithNewVec(80);
assert(newRefWithVecArg.getValue() === 80);

assert.throws(() => {
binding.call(functionMayThrow);
}, /foobar/);
assert.throws(() => {
binding.construct(classMayThrow);
}, /foobar/);

canConstructRefFromExistingRef(binding);
canCallFunctionWithDifferentOverloads(binding);
await canCallAsyncFunctionWithDifferentOverloads(binding);
}