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

Set up unit tests #24

Merged
merged 2 commits into from
Apr 18, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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;
}
40 changes: 40 additions & 0 deletions test/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

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;
});

let err = binding.error.catchError(
() => { throw new TypeError('test'); });
assert(err instanceof TypeError);
assert.equal(err.message, 'test');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just fyi, this is how we’d write this in Node core right now:

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

(const wherever possible, block scope + always using strict equality asserts)


let msg = binding.error.catchErrorMessage(
() => { throw new TypeError('test'); });
assert.equal(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;
}
52 changes: 52 additions & 0 deletions test/function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

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
19 changes: 16 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
'use strict';
const binding = require('./build/Release/binding.node');
const assert = require('assert');
global.buildType = process.config.target_defaults.default_configuration;
global.binding = require(`./build/${buildType}/binding.node`);
global.assert = require('assert');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that somebody looking at the test files themselves might need to look up where those “magic” globals come from … I’d be very okay with just copying these lines into the test files themselves :)

That would also have the advantage that they are are runnable as standalone node scripts, which might be nice when the test suite is bigger and takes longer to run.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I didn't really like this use of global either. Ability to run the test files individually is a good point.


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

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