text: var assert = require("assert"),
    ref = require("mt-node-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();
      });
    });
  });
  