Skip to content

Commit

Permalink
n-api: Reference and external tests
Browse files Browse the repository at this point in the history
 - Add a test project to addons-napi that covers the
   N-API reference and external APIs
 - Fix a bug in napi_typeof that was found by the new tests

Backport-PR-URL: #19447
PR-URL: #12551
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
jasongin authored and MylesBorins committed Apr 16, 2018
1 parent fcb019f commit bc25250
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/node_api.cc
Expand Up @@ -1465,6 +1465,10 @@ napi_status napi_typeof(napi_env env,
// This test has to come before IsObject because IsFunction
// implies IsObject
*result = napi_function;
} else if (v->IsExternal()) {
// This test has to come before IsObject because IsExternal
// implies IsObject
*result = napi_external;
} else if (v->IsObject()) {
*result = napi_object;
} else if (v->IsBoolean()) {
Expand All @@ -1475,8 +1479,6 @@ napi_status napi_typeof(napi_env env,
*result = napi_symbol;
} else if (v->IsNull()) {
*result = napi_null;
} else if (v->IsExternal()) {
*result = napi_external;
} else {
// Should not get here unless V8 has added some new kind of value.
return napi_set_last_error(env, napi_invalid_arg);
Expand Down
3 changes: 3 additions & 0 deletions test/addons-napi/common.h
Expand Up @@ -50,3 +50,6 @@

#define DECLARE_NAPI_PROPERTY(name, func) \
{ (name), 0, (func), 0, 0, 0, napi_default, 0 }

#define DECLARE_NAPI_GETTER(name, func) \
{ (name), 0, 0, (func), 0, 0, napi_default, 0 }
8 changes: 8 additions & 0 deletions test/addons-napi/test_reference/binding.gyp
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_reference",
"sources": [ "test_reference.c" ]
}
]
}
87 changes: 87 additions & 0 deletions test/addons-napi/test_reference/test.js
@@ -0,0 +1,87 @@
'use strict';
// Flags: --expose-gc

const common = require('../../common');
const assert = require('assert');

const test_reference = require(`./build/${common.buildType}/test_reference`);

// This test script uses external values with finalizer callbacks
// in order to track when values get garbage-collected. Each invocation
// of a finalizer callback increments the finalizeCount property.
assert.strictEqual(test_reference.finalizeCount, 0);

{
// External value without a finalizer
let value = test_reference.createExternal();
assert.strictEqual(test_reference.finalizeCount, 0);
assert.strictEqual(typeof value, 'object');
test_reference.checkExternal(value);
value = null;
global.gc();
assert.strictEqual(test_reference.finalizeCount, 0);
}

{
// External value with a finalizer
let value = test_reference.createExternalWithFinalize();
assert.strictEqual(test_reference.finalizeCount, 0);
assert.strictEqual(typeof value, 'object');
test_reference.checkExternal(value);
value = null;
global.gc();
assert.strictEqual(test_reference.finalizeCount, 1);
}

{
// Weak reference
let value = test_reference.createExternalWithFinalize();
assert.strictEqual(test_reference.finalizeCount, 0);
test_reference.createReference(value, 0);
assert.strictEqual(test_reference.referenceValue, value);
value = null;
global.gc(); // Value should be GC'd because there is only a weak ref
assert.strictEqual(test_reference.referenceValue, undefined);
assert.strictEqual(test_reference.finalizeCount, 1);
test_reference.deleteReference();
}

{
// Strong reference
let value = test_reference.createExternalWithFinalize();
assert.strictEqual(test_reference.finalizeCount, 0);
test_reference.createReference(value, 1);
assert.strictEqual(test_reference.referenceValue, value);
value = null;
global.gc(); // Value should NOT be GC'd because there is a strong ref
assert.strictEqual(test_reference.finalizeCount, 0);
test_reference.deleteReference();
global.gc(); // Value should be GC'd because the strong ref was deleted
assert.strictEqual(test_reference.finalizeCount, 1);
}

{
// Strong reference, increment then decrement to weak reference
let value = test_reference.createExternalWithFinalize();
assert.strictEqual(test_reference.finalizeCount, 0);
test_reference.createReference(value, 1);
value = null;
global.gc(); // Value should NOT be GC'd because there is a strong ref
assert.strictEqual(test_reference.finalizeCount, 0);

assert.strictEqual(test_reference.incrementRefcount(), 2);
global.gc(); // Value should NOT be GC'd because there is a strong ref
assert.strictEqual(test_reference.finalizeCount, 0);

assert.strictEqual(test_reference.decrementRefcount(), 1);
global.gc(); // Value should NOT be GC'd because there is a strong ref
assert.strictEqual(test_reference.finalizeCount, 0);

assert.strictEqual(test_reference.decrementRefcount(), 0);
global.gc(); // Value should be GC'd because the ref is now weak!
assert.strictEqual(test_reference.finalizeCount, 1);

test_reference.deleteReference();
global.gc(); // Value was already GC'd
assert.strictEqual(test_reference.finalizeCount, 1);
}
153 changes: 153 additions & 0 deletions test/addons-napi/test_reference/test_reference.c
@@ -0,0 +1,153 @@
#include <node_api.h>
#include "../common.h"
#include <stdlib.h>

static int test_value = 1;
static int finalize_count = 0;
static napi_ref test_reference = NULL;

napi_value GetFinalizeCount(napi_env env, napi_callback_info info) {
napi_value result;
NAPI_CALL(env, napi_create_number(env, finalize_count, &result));
return result;
}

void FinalizeExternal(napi_env env, void* data, void* hint) {
free(data);
finalize_count++;
}

napi_value CreateExternal(napi_env env, napi_callback_info info) {
int* data = &test_value;

napi_value result;
NAPI_CALL(env,
napi_create_external(env,
data,
NULL, /* finalize_cb */
NULL, /* finalize_hint */
&result));

finalize_count = 0;
return result;
}

napi_value CreateExternalWithFinalize(napi_env env, napi_callback_info info) {
int* data = malloc(sizeof(int));
*data = test_value;

napi_value result;
NAPI_CALL(env,
napi_create_external(env,
data,
FinalizeExternal,
NULL, /* finalize_hint */
&result));

finalize_count = 0;
return result;
}

napi_value CheckExternal(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value arg;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL));

NAPI_ASSERT(env, argc == 1, "Expected one argument.");

napi_valuetype argtype;
NAPI_CALL(env, napi_typeof(env, arg, &argtype));

NAPI_ASSERT(env, argtype == napi_external, "Expected an external value.")

int* data;
NAPI_CALL(env, napi_get_value_external(env, arg, &data));

NAPI_ASSERT(env, data != NULL && *data == test_value,
"An external data value of 1 was expected.")

return NULL;
}

napi_value CreateReference(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, test_reference == NULL,
"The test allows only one reference at a time.");

size_t argc = 2;
napi_value args[2];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NAPI_ASSERT(env, argc == 2, "Expected two arguments.");

uint32_t initial_refcount;
NAPI_CALL(env, napi_get_value_uint32(env, args[1], &initial_refcount));

NAPI_CALL(env,
napi_create_reference(env, args[0], initial_refcount, &test_reference));

NAPI_ASSERT(env, test_reference != NULL,
"A reference should have been created.");

return NULL;
}

napi_value DeleteReference(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, test_reference != NULL,
"A reference must have been created.");

NAPI_CALL(env, napi_delete_reference(env, test_reference));
test_reference = NULL;
return NULL;
}

napi_value IncrementRefcount(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, test_reference != NULL,
"A reference must have been created.");

uint32_t refcount;
NAPI_CALL(env, napi_reference_ref(env, test_reference, &refcount));

napi_value result;
NAPI_CALL(env, napi_create_number(env, refcount, &result));
return result;
}

napi_value DecrementRefcount(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, test_reference != NULL,
"A reference must have been created.");

uint32_t refcount;
NAPI_CALL(env, napi_reference_unref(env, test_reference, &refcount));

napi_value result;
NAPI_CALL(env, napi_create_number(env, refcount, &result));
return result;
}

napi_value GetReferenceValue(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, test_reference != NULL,
"A reference must have been created.");

napi_value result;
NAPI_CALL(env, napi_get_reference_value(env, test_reference, &result));
return result;
}

void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_GETTER("finalizeCount", GetFinalizeCount),
DECLARE_NAPI_PROPERTY("createExternal", CreateExternal),
DECLARE_NAPI_PROPERTY("createExternalWithFinalize",
CreateExternalWithFinalize),
DECLARE_NAPI_PROPERTY("checkExternal", CheckExternal),
DECLARE_NAPI_PROPERTY("createReference", CreateReference),
DECLARE_NAPI_PROPERTY("deleteReference", DeleteReference),
DECLARE_NAPI_PROPERTY("incrementRefcount", IncrementRefcount),
DECLARE_NAPI_PROPERTY("decrementRefcount", DecrementRefcount),
DECLARE_NAPI_GETTER("referenceValue", GetReferenceValue),
};

NAPI_CALL_RETURN_VOID(env, napi_define_properties(
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
}

NAPI_MODULE(addon, Init)

0 comments on commit bc25250

Please sign in to comment.