Skip to content

Commit

Permalink
Set up unit tests (#24)
Browse files Browse the repository at this point in the history
 - Set up a pattern for organizing .cc and .js test files
   roughly corresponding to each class to be tested
 - Add unit tests for the Function and Error classes
 - Update test binding.gyp file to build on Windows (fix quotes)
 - Update test binding.gyp and README to enable exceptions with MSVC
 - Fix type of CallbackInfo::This
  • Loading branch information
jasongin authored Apr 18, 2017
1 parent f938de1 commit 238f5d1
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 19 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
/build
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ backward-compatibility with use with older versions of Node.js that do
not have N-API built-in.

To use N-API in a native module:
1. Add a dependency on this package to `package.json`.
1. Add a dependency on this package to `package.json`.
It is not yet published to npm, so reference it directly from GitHub.
```json
"dependencies": {
Expand All @@ -27,6 +27,9 @@ To use N-API in a native module:
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' },
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
},
```

4. Include `napi.h` in the native module code.
Expand Down
2 changes: 1 addition & 1 deletion napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1682,7 +1682,7 @@ inline const Value CallbackInfo::operator [](size_t index) const {
return index < _argc ? Value(_env, _argv[index]) : Env().Undefined();
}

inline Object CallbackInfo::This() const {
inline Value CallbackInfo::This() const {
if (_this == nullptr) {
return Env().Undefined();
}
Expand Down
2 changes: 1 addition & 1 deletion napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ namespace Napi {
Napi::Env Env() const;
size_t Length() const;
const Value operator [](size_t index) const;
Object This() const;
Value This() const;
void* Data() const;
void SetData(void* data);

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {},
"description": "Node.js API (N-API)",
"devDependencies": {
"node-gyp": "^3.6.0"
},
"directories": {},
"homepage": "https://github.com/nodejs/node-api",
Expand All @@ -18,6 +19,8 @@
"url": "git://github.com/nodejs/node-api.git"
},
"scripts": {
"pretest": "node-gyp rebuild -C test",
"test": "node test"
},
"version": "0.1.0"
}
13 changes: 4 additions & 9 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@

using namespace Napi;

Value Test1(const CallbackInfo& info) {
auto env = info.Env();
Object obj = Object::New(env);

obj["foo"] = String::New(env, "bar");

return obj;
}
Object InitError(Env env);
Object InitFunction(Env env);

void Init(Env env, Object exports, Object module) {
exports.Set("test1", Function::New(env, Test1));
exports.Set("error", InitError(env));
exports.Set("function", InitFunction(env));
}

NODE_API_MODULE(addon, Init)
15 changes: 11 additions & 4 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'include_dirs': ['<!(node -p \'require("../").include\')'],
'dependencies': ['<!(node -p \'require("../").gyp\')'],
'sources': [
'binding.cc',
'error.cc',
'function.cc',
],
'include_dirs': ["<!(node -p \"require('../').include\")"],
'dependencies': ["<!(node -p \"require('../').gyp\")"],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' }
'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' },
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
},
}
]
}
66 changes: 66 additions & 0 deletions test/error.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "napi.h"

using namespace Napi;

void ThrowError(const CallbackInfo& info) {
std::string message = info[0].As<String>().Utf8Value();
throw Error::New(info.Env(), message);
}

void ThrowTypeError(const CallbackInfo& info) {
std::string message = info[0].As<String>().Utf8Value();
throw TypeError::New(info.Env(), message);
}

void ThrowRangeError(const CallbackInfo& info) {
std::string message = info[0].As<String>().Utf8Value();
throw RangeError::New(info.Env(), message);
}

Value CatchError(const CallbackInfo& info) {
Function thrower = info[0].As<Function>();
try {
thrower({});
} catch (const Error& e) {
return e;
}
return info.Env().Null();
}

Value CatchErrorMessage(const CallbackInfo& info) {
Function thrower = info[0].As<Function>();
try {
thrower({});
} catch (const Error& e) {
std::string message = e.Message();
return String::New(info.Env(), message);
}
return info.Env().Null();
}

void DoNotCatch(const CallbackInfo& info) {
Function thrower = info[0].As<Function>();
thrower({});
}

void CatchAndRethrowError(const CallbackInfo& info) {
Function thrower = info[0].As<Function>();
try {
thrower({});
} catch (Error& e) {
e["caught"] = Boolean::New(info.Env(), true);
throw e;
}
}

Object InitError(Env env) {
Object exports = Object::New(env);
exports["throwError"] = Function::New(env, ThrowError);
exports["throwTypeError"] = Function::New(env, ThrowTypeError);
exports["throwRangeError"] = Function::New(env, ThrowRangeError);
exports["catchError"] = Function::New(env, CatchError);
exports["catchErrorMessage"] = Function::New(env, CatchErrorMessage);
exports["doNotCatch"] = Function::New(env, DoNotCatch);
exports["catchAndRethrowError"] = Function::New(env, CatchAndRethrowError);
return exports;
}
43 changes: 43 additions & 0 deletions test/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';
const buildType = process.config.target_defaults.default_configuration;
const binding = require(`./build/${buildType}/binding.node`);
const assert = require('assert');

assert.throws(() => binding.error.throwError('test'), err => {
return err instanceof Error && err.message === 'test';
});

assert.throws(() => binding.error.throwTypeError('test'), err => {
return err instanceof TypeError && err.message === 'test';
});

assert.throws(() => binding.error.throwRangeError('test'), err => {
return err instanceof RangeError && err.message === 'test';
});

assert.throws(
() => binding.error.doNotCatch(
() => {
throw new TypeError('test');
}),
err => {
return err instanceof TypeError && err.message === 'test' && !err.caught;
});

assert.throws(
() => binding.error.catchAndRethrowError(
() => {
throw new TypeError('test');
}),
err => {
return err instanceof TypeError && err.message === 'test' && err.caught;
});

const err = binding.error.catchError(
() => { throw new TypeError('test'); });
assert(err instanceof TypeError);
assert.strictEqual(err.message, 'test');

const msg = binding.error.catchErrorMessage(
() => { throw new TypeError('test'); });
assert.strictEqual(msg, 'test');
79 changes: 79 additions & 0 deletions test/function.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "napi.h"

using namespace Napi;

void VoidCallback(const CallbackInfo& info) {
auto env = info.Env();
Object obj = info[0].As<Object>();

obj["foo"] = String::New(env, "bar");
}

Value ValueCallback(const CallbackInfo& info) {
auto env = info.Env();
Object obj = Object::New(env);

obj["foo"] = String::New(env, "bar");

return obj;
}

Value CallWithArgs(const CallbackInfo& info) {
Function func = info[0].As<Function>();
return func({ info[1], info[2], info[3] });
}

Value CallWithVector(const CallbackInfo& info) {
Function func = info[0].As<Function>();
std::vector<napi_value> args;
args.reserve(3);
args.push_back(info[1]);
args.push_back(info[2]);
args.push_back(info[3]);
return func.Call(args);
}

Value CallWithReceiverAndArgs(const CallbackInfo& info) {
Function func = info[0].As<Function>();
Value receiver = info[1];
return func.Call(receiver, { info[2], info[3], info[4] });
}

Value CallWithReceiverAndVector(const CallbackInfo& info) {
Function func = info[0].As<Function>();
Value receiver = info[1];
std::vector<napi_value> args;
args.reserve(3);
args.push_back(info[2]);
args.push_back(info[3]);
args.push_back(info[4]);
return func.Call(receiver, args);
}

Value CallConstructorWithArgs(const CallbackInfo& info) {
Function func = info[0].As<Function>();
return func.New({ info[1], info[2], info[3] });
}

Value CallConstructorWithVector(const CallbackInfo& info) {
Function func = info[0].As<Function>();
std::vector<napi_value> args;
args.reserve(3);
args.push_back(info[1]);
args.push_back(info[2]);
args.push_back(info[3]);
return func.New(args);
}

Object InitFunction(Env env) {
Object exports = Object::New(env);
exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback");
exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback"));
exports["callWithArgs"] = Function::New(env, CallWithArgs);
exports["callWithVector"] = Function::New(env, CallWithVector);
exports["callWithReceiverAndArgs"] = Function::New(env, CallWithReceiverAndArgs);
exports["callWithReceiverAndVector"] = Function::New(env, CallWithReceiverAndVector);
exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs);
exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector);
return exports;
}
55 changes: 55 additions & 0 deletions test/function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';
const buildType = process.config.target_defaults.default_configuration;
const binding = require(`./build/${buildType}/binding.node`);
const assert = require('assert');

let obj = {};
assert.deepStrictEqual(binding.function.voidCallback(obj), undefined);
assert.deepStrictEqual(obj, { "foo": "bar" });

assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" });

let args = null;
let ret = null;
let receiver = null;
function testFunction() {
receiver = this;
args = [].slice.call(arguments);
return ret;
}
function testConstructor() {
args = [].slice.call(arguments);
}

ret = 4;
assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4);
assert.strictEqual(receiver, undefined);
assert.deepStrictEqual(args, [ 1, 2, 3 ]);

ret = 5;
assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5);
assert.strictEqual(receiver, undefined);
assert.deepStrictEqual(args, [ 2, 3, 4 ]);

ret = 6;
assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6);
assert.deepStrictEqual(receiver, obj);
assert.deepStrictEqual(args, [ 3, 4, 5 ]);

ret = 7;
assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7);
assert.deepStrictEqual(receiver, obj);
assert.deepStrictEqual(args, [ 4, 5, 6 ]);

obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7);
assert(obj instanceof testConstructor);
assert.deepStrictEqual(args, [ 5, 6, 7 ]);

obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8);
assert(obj instanceof testConstructor);
assert.deepStrictEqual(args, [ 6, 7, 8 ]);

assert.equal(binding.function.voidCallback.name, 'voidCallback');
assert.equal(binding.function.valueCallback.name, 'valueCallback');

// TODO: Function::MakeCallback tests
16 changes: 13 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
'use strict';
const binding = require('./build/Release/binding.node');
const assert = require('assert');

assert.deepStrictEqual(binding.test1(), { "foo": "bar" });
let testModules = [
'error',
'function',
];

testModules.forEach(name => {
try {
require('./' + name);
}
catch (e) {
console.error(e);
}
});

0 comments on commit 238f5d1

Please sign in to comment.