var assert = require('assert') , ref = require('ref') , ffi = require('../') , int = ref.types.int , bindings = require('bindings')({ module_root: __dirname, bindings: 'ffi_tests' }) describe('Callback', function () { afterEach(gc) it('should create a C function pointer from a JS function', function () { var callback = ffi.Callback('void', [ ], function (val) { }) assert(Buffer.isBuffer(callback)) }) it('should be invokable by an ffi\'d ForeignFunction', function () { var funcPtr = ffi.Callback(int, [ int ], Math.abs) var func = ffi.ForeignFunction(funcPtr, int, [ int ]) assert.equal(1234, func(-1234)) }) it('should work with a "void" return type', function () { var funcPtr = ffi.Callback('void', [ ], function (val) { }) var func = ffi.ForeignFunction(funcPtr, 'void', [ ]) assert.strictEqual(null, func()) }) it('should not call "set()" of a pointer type', function () { var voidType = Object.create(ref.types.void) voidType.get = function () { throw new Error('"get()" should not be called') } voidType.set = function () { throw new Error('"set()" should not be called') } var voidPtr = ref.refType(voidType) var called = false var cb = ffi.Callback(voidPtr, [ voidPtr ], function (ptr) { called = true assert.equal(0, ptr.address()) return ptr }) var fn = ffi.ForeignFunction(cb, voidPtr, [ voidPtr ]) assert(!called) var nul = fn(ref.NULL) assert(called) assert(Buffer.isBuffer(nul)) assert.equal(0, nul.address()) }) it('should throw an Error when invoked through a ForeignFunction and throws', function () { var cb = ffi.Callback('void', [ ], function () { throw new Error('callback threw') }) var fn = ffi.ForeignFunction(cb, 'void', [ ]) assert.throws(function () { fn() }, /callback threw/) }) it('should throw an Error with a meaningful message when a type\'s "set()" throws', function () { var cb = ffi.Callback('int', [ ], function () { // Changed, because returning string is not failing because of this // https://github.com/iojs/io.js/issues/1161 return 1111111111111111111111 }) var fn = ffi.ForeignFunction(cb, 'int', [ ]) assert.throws(function () { fn() }, /error setting return value/) }) it('should throw an Error when invoked after the callback gets garbage collected', function () { var cb = ffi.Callback('void', [ ], function () { }) // register the callback function bindings.set_cb(cb) // should be ok bindings.call_cb() cb = null // KILL!! gc() // should throw an Error synchronously assert.throws(function () { bindings.call_cb() }, /callback has been garbage collected/) }) /** * We should make sure that callbacks or errors gets propagated back to node's main thread * when it called on a non libuv native thread. * See: https://github.com/node-ffi/node-ffi/issues/199 */ it("should propagate callbacks and errors back from native threads", function(done) { var invokeCount = 0 var cb = ffi.Callback('void', [ ], function () { invokeCount++ }) var kill = (function (cb) { // register the callback function bindings.set_cb(cb) return function () { var c = cb cb = null // kill c = null // kill!!! } })(cb) // destroy the outer "cb". now "kill()" holds the "cb" reference cb = null // invoke the callback a couple times assert.equal(0, invokeCount) bindings.call_cb_from_thread() bindings.call_cb_from_thread() setTimeout(function () { assert.equal(2, invokeCount) gc() // ensure the outer "cb" Buffer is collected process.nextTick(finish) }, 100) function finish () { kill() gc() // now ensure the inner "cb" Buffer is collected // should throw an Error asynchronously!, // because the callback has been garbage collected. // hijack the "uncaughtException" event for this test var listeners = process.listeners('uncaughtException').slice() process.removeAllListeners('uncaughtException') process.once('uncaughtException', function (e) { var err try { assert(/ffi/.test(e.message)) } catch (ae) { err = ae } done(err) listeners.forEach(function (fn) { process.on('uncaughtException', fn) }) }) bindings.call_cb_from_thread() } }) describe('async', function () { it('should be invokable asynchronously by an ffi\'d ForeignFunction', function (done) { var funcPtr = ffi.Callback(int, [ int ], Math.abs) var func = ffi.ForeignFunction(funcPtr, int, [ int ]) func.async(-9999, function (err, res) { assert.equal(null, err) assert.equal(9999, res) done() }) }) /** * See https://github.com/rbranson/node-ffi/issues/153. */ it('multiple callback invocations from uv thread pool should be properly synchronized', function (done) { this.timeout(10000) var iterations = 30000 var cb = ffi.Callback('string', [ 'string' ], function (val) { if (val === "ping" && --iterations > 0) { return "pong" } return "end" }) var pingPongFn = ffi.ForeignFunction(bindings.play_ping_pong, 'void', [ 'pointer' ]) pingPongFn.async(cb, function (err, ret) { assert.equal(iterations, 0) done() }) }) /** * See https://github.com/rbranson/node-ffi/issues/72. * This is a tough issue. If we pass the ffi_closure Buffer to some foreign * C function, we really don't know *when* it's safe to dispose of the Buffer, * so it's left up to the developer. * * In this case, we wrap the responsibility in a simple "kill()" function * that, when called, destroys of its references to the ffi_closure Buffer. */ it('should work being invoked multiple times', function (done) { var invokeCount = 0 var cb = ffi.Callback('void', [ ], function () { invokeCount++ }) var kill = (function (cb) { // register the callback function bindings.set_cb(cb) return function () { var c = cb cb = null // kill c = null // kill!!! } })(cb) // destroy the outer "cb". now "kill()" holds the "cb" reference cb = null // invoke the callback a couple times assert.equal(0, invokeCount) bindings.call_cb() assert.equal(1, invokeCount) bindings.call_cb() assert.equal(2, invokeCount) setTimeout(function () { // invoke it once more for shits and giggles bindings.call_cb() assert.equal(3, invokeCount) gc() // ensure the outer "cb" Buffer is collected process.nextTick(finish) }, 25) function finish () { bindings.call_cb() assert.equal(4, invokeCount) kill() gc() // now ensure the inner "cb" Buffer is collected // should throw an Error synchronously try { bindings.call_cb() assert(false) // shouldn't get here } catch (e) { assert(/ffi/.test(e.message)) } done() } }) it('should throw an Error when invoked after the callback gets garbage collected', function (done) { var cb = ffi.Callback('void', [ ], function () { }) // register the callback function bindings.set_cb(cb) // should be ok bindings.call_cb() // hijack the "uncaughtException" event for this test var listeners = process.listeners('uncaughtException').slice() process.removeAllListeners('uncaughtException') process.once('uncaughtException', function (e) { var err try { assert(/ffi/.test(e.message)) } catch (ae) { err = ae } done(err) listeners.forEach(function (fn) { process.on('uncaughtException', fn) }) }) cb = null // KILL!! gc() // should generate an "uncaughtException" asynchronously bindings.call_cb_async() }) }) })