diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/embind/embind.test.js | 414 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 277 |
2 files changed, 669 insertions, 22 deletions
diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 53d3988a..a6b2e98c 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -1548,7 +1548,7 @@ module({ }); }); - BaseFixture.extend("abstract methods", function() { + BaseFixture.extend("implementing abstract methods with JS objects", function() { test("can call abstract methods", function() { var obj = cm.getAbstractClass(); assert.equal("from concrete", obj.abstractMethod()); @@ -1580,7 +1580,8 @@ module({ }; var impl = cm.AbstractClass.implement(new MyImplementation); - assert.equal(expected, impl.optionalMethod(expected)); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal(expected, impl.optionalMethod(expected)); assert.equal(expected, cm.callOptionalMethod(impl, expected)); impl.delete(); }); @@ -1588,7 +1589,8 @@ module({ test("if not implemented then optional method runs default", function() { var impl = cm.AbstractClass.implement({}); assert.equal("optionalfoo", impl.optionalMethod("foo")); - assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); impl.delete(); }); @@ -1638,6 +1640,366 @@ module({ impl.delete(); }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var impl = cm.AbstractClass.implement({ + returnsSharedPtr: function() { + return derived; + } + }); + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + }); + + BaseFixture.extend("constructor prototype class inheritance", function() { + var Empty = cm.AbstractClass.extend("Empty", { + abstractMethod: function() { + } + }); + + test("can extend, construct, and delete", function() { + var instance = new Empty; + instance.delete(); + }); + + test("properties set in constructor are externally visible", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.property = x; + }, + abstractMethod: function() { + } + }); + var instance = new HasProperty(10); + assert.equal(10, instance.property); + instance.delete(); + }); + + test("pass derived object to c++", function() { + var Implementation = cm.AbstractClass.extend("Implementation", { + abstractMethod: function() { + return "abc"; + }, + }); + var instance = new Implementation; + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("abc", result); + }); + + test("properties set in constructor are visible in overridden methods", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.x = x; + }, + abstractMethod: function() { + return this.x; + }, + }); + var instance = new HasProperty("xyz"); + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("xyz", result); + }); + + test("interface methods are externally visible", function() { + var instance = new Empty; + var result = instance.concreteMethod(); + instance.delete(); + assert.equal("concrete", result); + }); + + test("optional methods are externally visible", function() { + var instance = new Empty; + var result = instance.optionalMethod("_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + test("optional methods: not defined", function() { + var instance = new Empty; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + // Calling C++ implementations of optional functions can be + // made to work, but requires an interface change on the C++ + // side, using a technique similar to the one described at + // https://wiki.python.org/moin/boost.python/OverridableVirtualFunctions + // + // The issue is that, in a standard binding, calling + // parent.prototype.optionalMethod invokes the wrapper + // function, which checks that the JS object implements + // 'optionalMethod', which it does. Thus, C++ calls back into + // JS, resulting in an infinite loop. + // + // The solution, for optional methods, is to bind a special + // concrete implementation that specifically calls the base + // class's implementation. See the binding of + // AbstractClass::optionalMethod in embind_test.cpp. + + test("can call parent implementation from within derived implementation", function() { + var parent = cm.AbstractClass; + var ExtendsOptionalMethod = parent.extend("ExtendsOptionalMethod", { + abstractMethod: function() { + }, + optionalMethod: function(s) { + return "optionaljs_" + parent.prototype.optionalMethod.call(this, s); + }, + }); + var instance = new ExtendsOptionalMethod; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optionaljs_optional_123", result); + }); + + // TODO: deriving from classes with constructors? + + test("instanceof", function() { + var instance = new Empty; + assert.instanceof(instance, Empty); + assert.instanceof(instance, cm.AbstractClass); + instance.delete(); + }); + + test("returning null shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return null; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("returning a new shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return cm.embind_test_return_smart_derived_ptr().deleteLater(); + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("void methods work", function() { + var saved = {}; + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + differentArguments: function(i, d, f, q, s) { + saved.i = i; + saved.d = d; + saved.f = f; + saved.q = q; + saved.s = s; + } + }); + var impl = new C; + + cm.callDifferentArguments(impl, 1, 2, 3, 4, "foo"); + + assert.deepEqual(saved, { + i: 1, + d: 2, + f: 3, + q: 4, + s: "foo", + }); + + impl.delete(); + }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return derived; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("calling pure virtual function gives good error message", function() { + var C = cm.AbstractClass.extend("C", {}); + var error = assert.throws(cm.PureVirtualError, function() { + new C; + }); + assert.equal('Pure virtual function abstractMethod must be implemented in JavaScript', error.message); + }); + + test("can extend from C++ class with constructor arguments", function() { + var parent = cm.AbstractClassWithConstructor; + var C = parent.extend("C", { + __construct: function(x) { + this.__parent.__construct.call(this, x); + }, + abstractMethod: function() { + return this.concreteMethod(); + } + }); + + var impl = new C("hi"); + var rv = cm.callAbstractMethod2(impl); + impl.delete(); + + assert.equal("hi", rv); + }); + + test("__destruct is called when object is destroyed", function() { + var parent = cm.HeldAbstractClass; + var calls = []; + var C = parent.extend("C", { + method: function() { + }, + __destruct: function() { + calls.push("__destruct"); + this.__parent.__destruct.call(this); + } + }); + var impl = new C; + var copy = impl.clone(); + impl.delete(); + assert.deepEqual([], calls); + copy.delete(); + assert.deepEqual(["__destruct"], calls); + }); + + test("if JavaScript implementation of interface is returned, don't wrap in new handle", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.delete(); + assert.equal(impl, rv); + rv.delete(); + }); + + test("can instantiate two wrappers with constructors", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct.call(this); + }, + method: function() { + } + }); + var a = new C; + var b = new C; + a.delete(); + b.delete(); + }); + + test("incorrectly calling parent is an error", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct(); + }, + method: function() { + } + }); + assert.throws(cm.BindingError, function() { + new C; + }); + }); + + test("deleteLater() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.deleteLater(); + cm.flushPendingDeletes(); + }); + + test("deleteLater() combined with delete() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.delete(); + cm.flushPendingDeletes(); + }); + + test("method arguments with pointer ownership semantics are cleaned up after call", function() { + var parent = cm.AbstractClass; + var C = parent.extend("C", { + abstractMethod: function() { + }, + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + }); + + test("method arguments with pointer ownership semantics can be cloned", function() { + var parent = cm.AbstractClass; + var owned; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passShared: function(p) { + owned = p.clone(); + } + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + + assert.equal("Derived", owned.getClassName()); + owned.delete(); + }); + + test("emscripten::val method arguments don't leak", function() { + var parent = cm.AbstractClass; + var got; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passVal: function(g) { + got = g; + } + }); + var impl = new C; + var v = {}; + cm.passVal(impl, v); + impl.delete(); + + assert.equal(v, got); + }); }); BaseFixture.extend("registration order", function() { @@ -1945,19 +2307,6 @@ module({ }); }); - test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { - var derived = cm.embind_test_return_smart_derived_ptr(); - var impl = cm.AbstractClass.implement({ - returnsSharedPtr: function() { - return derived; - } - }); - cm.callReturnsSharedPtrMethod(impl); - impl.delete(); - derived.delete(); - // Let the memory leak test superfixture check that no leaks occurred. - }); - BaseFixture.extend("val::as", function() { test("built-ins", function() { assert.equal(true, cm.val_as_bool(true)); @@ -2031,6 +2380,39 @@ module({ }); }); + BaseFixture.extend("intrusive pointers", function() { + test("can pass intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var d = cm.passThroughIntrusiveClass(ic); + assert.true(ic.isAliasOf(d)); + ic.delete(); + d.delete(); + }); + + test("can hold intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var holder = new cm.IntrusiveClassHolder; + holder.set(ic); + ic.delete(); + var d = holder.get(); + d.delete(); + holder.delete(); + }); + + test("can extend from intrusive pointer class and still preserve reference in JavaScript", function() { + var C = cm.IntrusiveClass.extend("C", { + }); + var instance = new C; + var holder = new cm.IntrusiveClassHolder; + holder.set(instance); + instance.delete(); + + var back = holder.get(); + assert.equal(back, instance); + holder.delete(); + }); + }); + BaseFixture.extend("typeof", function() { test("typeof", function() { assert.equal("object", cm.getTypeOfVal(null)); diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 52103bbb..30267994 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -1090,6 +1090,16 @@ public: virtual std::shared_ptr<Derived> returnsSharedPtr() = 0; virtual void differentArguments(int i, double d, unsigned char f, double q, std::string) = 0; + + std::string concreteMethod() const { + return "concrete"; + } + + virtual void passShared(const std::shared_ptr<Derived>&) { + } + + virtual void passVal(const val& v) { + } }; EMSCRIPTEN_SYMBOL(optionalMethod); @@ -1103,9 +1113,10 @@ public: } std::string optionalMethod(std::string s) const { - return optional_call<std::string>(optionalMethod_symbol, [&] { - return AbstractClass::optionalMethod(s); - }, s); + return call<std::string>("optionalMethod", s); + //return optional_call<std::string>(optionalMethod_symbol, [&] { + // return AbstractClass::optionalMethod(s); + //}, s); } std::shared_ptr<Derived> returnsSharedPtr() { @@ -1115,6 +1126,14 @@ public: void differentArguments(int i, double d, unsigned char f, double q, std::string s) { return call<void>("differentArguments", i, d, f, q, s); } + + virtual void passShared(const std::shared_ptr<Derived>& p) override { + return call<void>("passShared", p); + } + + virtual void passVal(const val& v) override { + return call<void>("passVal", v); + } }; class ConcreteClass : public AbstractClass { @@ -1122,7 +1141,6 @@ class ConcreteClass : public AbstractClass { return "from concrete"; } - void differentArguments(int i, double d, unsigned char f, double q, std::string s) { } @@ -1152,12 +1170,74 @@ void callDifferentArguments(AbstractClass& ac, int i, double d, unsigned char f, return ac.differentArguments(i, d, f, q, s); } +struct AbstractClassWithConstructor { + explicit AbstractClassWithConstructor(std::string s) + : s(s) + {} + + virtual std::string abstractMethod() = 0; + std::string concreteMethod() { + return s; + } + + std::string s; +}; + +struct AbstractClassWithConstructorWrapper : public wrapper<AbstractClassWithConstructor> { + EMSCRIPTEN_WRAPPER(AbstractClassWithConstructorWrapper); + + virtual std::string abstractMethod() override { + return call<std::string>("abstractMethod"); + } +}; + +std::string callAbstractMethod2(AbstractClassWithConstructor& ac) { + return ac.abstractMethod(); +} + +struct HeldAbstractClass : public PolyBase, public PolySecondBase { + virtual void method() = 0; +}; +struct HeldAbstractClassWrapper : wrapper<HeldAbstractClass> { + EMSCRIPTEN_WRAPPER(HeldAbstractClassWrapper); + + virtual void method() override { + return call<void>("method"); + } +}; + +std::shared_ptr<PolySecondBase> passHeldAbstractClass(std::shared_ptr<HeldAbstractClass> p) { + return p; +} + +void passShared(AbstractClass& ac) { + auto p = std::make_shared<Derived>(); + ac.passShared(p); +} + +void passVal(AbstractClass& ac, val v) { + return ac.passVal(v); +} + EMSCRIPTEN_BINDINGS(interface_tests) { class_<AbstractClass>("AbstractClass") .smart_ptr<std::shared_ptr<AbstractClass>>("shared_ptr<AbstractClass>") .allow_subclass<AbstractClassWrapper>("AbstractClassWrapper") - .function("abstractMethod", &AbstractClass::abstractMethod) - .function("optionalMethod", &AbstractClass::optionalMethod) + .function("abstractMethod", &AbstractClass::abstractMethod, pure_virtual()) + // The select_overload is necessary because, otherwise, the C++ compiler + // cannot deduce the signature of the lambda function. + .function("optionalMethod", optional_override( + [](AbstractClass& this_, std::string s) { + return this_.AbstractClass::optionalMethod(s); + } + )) + .function("concreteMethod", &AbstractClass::concreteMethod) + .function("passShared", select_overload<void(AbstractClass&, const std::shared_ptr<Derived>&)>([](AbstractClass& self, const std::shared_ptr<Derived>& derived) { + self.AbstractClass::passShared(derived); + })) + .function("passVal", select_overload<void(AbstractClass&, const val&)>([](AbstractClass& self, const val& v) { + self.AbstractClass::passVal(v); + })) ; function("getAbstractClass", &getAbstractClass); @@ -1165,6 +1245,22 @@ EMSCRIPTEN_BINDINGS(interface_tests) { function("callOptionalMethod", &callOptionalMethod); function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod); function("callDifferentArguments", &callDifferentArguments); + function("passShared", &passShared); + function("passVal", &passVal); + + class_<AbstractClassWithConstructor>("AbstractClassWithConstructor") + .allow_subclass<AbstractClassWithConstructorWrapper>("AbstractClassWithConstructorWrapper", constructor<std::string>()) + .function("abstractMethod", &AbstractClassWithConstructor::abstractMethod, pure_virtual()) + .function("concreteMethod", &AbstractClassWithConstructor::concreteMethod) + ; + function("callAbstractMethod2", &callAbstractMethod2); + + class_<HeldAbstractClass, base<PolySecondBase>>("HeldAbstractClass") + .smart_ptr<std::shared_ptr<HeldAbstractClass>>("shared_ptr<HeldAbstractClass>") + .allow_subclass<HeldAbstractClassWrapper, std::shared_ptr<HeldAbstractClassWrapper>>("HeldAbstractClassWrapper") + .function("method", &HeldAbstractClass::method, pure_virtual()) + ; + function("passHeldAbstractClass", &passHeldAbstractClass); } template<typename T, size_t sizeOfArray> @@ -2369,6 +2465,175 @@ EMSCRIPTEN_BINDINGS(val_new_) { function("construct_with_ints_and_float", &construct_with_ints_and_float); } +template <typename T> +class intrusive_ptr { +public: + typedef T element_type; + + intrusive_ptr(std::nullptr_t = nullptr) + : px(nullptr) + {} + + template <typename U> + explicit intrusive_ptr(U* px) + : px(px) + { + addRef(px); + } + + intrusive_ptr(const intrusive_ptr& that) + : px(that.px) + { + addRef(px); + } + + template<typename U> + intrusive_ptr(const intrusive_ptr<U>& that) + : px(that.get()) + { + addRef(px); + } + + intrusive_ptr& operator=(const intrusive_ptr& that) { + reset(that.get()); + return *this; + } + + intrusive_ptr& operator=(intrusive_ptr&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + template<typename U> + intrusive_ptr& operator=(const intrusive_ptr<U>& that) { + reset(that.get()); + return *this; + } + + template<typename U> + intrusive_ptr& operator=(intrusive_ptr<U>&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + ~intrusive_ptr() { + release(px); + } + + void reset(T* nx = nullptr) { + addRef(nx); + release(px); + px = nx; + } + + T* get() const { + return px; + } + + T& operator*() const { + return *px; + } + + T* operator->() const { + return px; + } + + explicit operator bool() const { + return px != nullptr; + } + + void swap(intrusive_ptr& rhs) { + std::swap(px, rhs.px); + } + +private: + void addRef(T* px) { + if (px) { + ++px->referenceCount; + } + } + + void release(T* px) { + if (--px->referenceCount == 0) { + delete px; + } + } + + T* px; + + template<typename U> + friend class intrusive_ptr; +}; + +namespace emscripten { + template<typename T> + struct smart_ptr_trait<intrusive_ptr<T>> { + typedef intrusive_ptr<T> pointer_type; + typedef T element_type; + + static sharing_policy get_sharing_policy() { + return sharing_policy::INTRUSIVE; + } + + static T* get(const intrusive_ptr<T>& p) { + return p.get(); + } + + static intrusive_ptr<T> share(const intrusive_ptr<T>& r, T* ptr) { + return intrusive_ptr<T>(ptr); + } + + static pointer_type* construct_null() { + return new pointer_type; + } + }; +} + +template<typename T> +intrusive_ptr<T> make_intrusive_ptr() { + return intrusive_ptr<T>(new T); +} + +struct IntrusiveClass { + virtual ~IntrusiveClass() {} + long referenceCount = 0; +}; + +struct IntrusiveClassWrapper : public wrapper<IntrusiveClass> { + EMSCRIPTEN_WRAPPER(IntrusiveClassWrapper); +}; + +template<typename T> +struct Holder { + void set(const T& v) { + value = v; + } + const T& get() const { + return value; + } + T value; +}; + +EMSCRIPTEN_BINDINGS(intrusive_pointers) { + class_<IntrusiveClass>("IntrusiveClass") + .smart_ptr_constructor("intrusive_ptr<IntrusiveClass>", &make_intrusive_ptr<IntrusiveClass>) + .allow_subclass<IntrusiveClassWrapper, intrusive_ptr<IntrusiveClassWrapper>>("IntrusiveClassWrapper") + ; + + typedef Holder<intrusive_ptr<IntrusiveClass>> IntrusiveClassHolder; + class_<IntrusiveClassHolder>("IntrusiveClassHolder") + .constructor<>() + .function("set", &IntrusiveClassHolder::set) + .function("get", &IntrusiveClassHolder::get) + ; + + function("passThroughIntrusiveClass", &passThrough<intrusive_ptr<IntrusiveClass>>); +} + std::string getTypeOfVal(const val& v) { return v.typeof().as<std::string>(); } |