module({ Emscripten: '../../../../build/embind_test.js', }, function(imports) { var cm = imports.Emscripten; var CheckForLeaks = fixture("check for leaks", function() { this.setUp(function() { if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! cm._mallocDebug(2); assert.equal(0, cm.count_emval_handles()); cm._mallocAssertAllMemoryFree(); } }); this.tearDown(function() { if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! cm._mallocAssertAllMemoryFree(); assert.equal(0, cm.count_emval_handles()); } }); }); var BaseFixture = CheckForLeaks; BaseFixture.extend("temp jig", function() { test("temp test", function() { }); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! BaseFixture.extend("leak testing", function() { test("no memory allocated at start of test", function() { cm._mallocAssertAllMemoryFree(); }); test("assert when memory is allocated", function() { var ptr = cm._malloc(42); assert.throws(cm._MemoryAllocationError, function() { cm._mallocAssertAllMemoryFree(); }); cm._free(ptr); }); test("allocated memory counts down again for free", function() { var ptr = cm._malloc(42); cm._free(ptr); cm._mallocAssertAllMemoryFree(); }); test("free without malloc throws MemoryAllocationError", function() { var ptr = cm._malloc(42); cm._free(ptr); assert.throws(cm._MemoryAllocationError, function() { cm._free(ptr); }); }); }); } BaseFixture.extend("access to base class members", function() { test("method name in derived class silently overrides inherited name", function() { var derived = new cm.Derived(); assert.equal("Derived", derived.getClassName()); derived.delete(); }); test("can reference base method from derived class", function(){ var derived = new cm.Derived(); assert.equal("Base", derived.getClassNameFromBase()); derived.delete(); }); test("can reference base method from doubly derived class", function() { var derivedTwice = new cm.DerivedTwice(); assert.equal("Base", derivedTwice.getClassNameFromBase()); derivedTwice.delete(); }); test("can reference base method through unbound classes", function() { var derivedThrice = new cm.DerivedThrice(); assert.equal("Base", derivedThrice.getClassNameFromBase()); derivedThrice.delete(); }); test("property name in derived class hides identically named property in base class for set", function() { var derived = new cm.Derived(); derived.setMember(7); derived.member = 17; assert.equal(17, derived.getMember()); derived.delete(); }); test("can reference base property from derived class for get", function(){ var derived = new cm.Derived(); derived.setBaseMember(5); assert.equal(5, derived.baseMember); derived.delete(); }); test("can reference property of any base class for get when multiply derived", function(){ var derived = new cm.MultiplyDerived(); derived.setBaseMember(11); assert.equal(11, derived.baseMember); derived.delete(); }); test("can reference base property from derived class for set", function(){ var derived = new cm.Derived(); derived.baseMember = 32; assert.equal(32, derived.getBaseMember()); derived.delete(); }); test("can reference property of any base for set when multiply derived", function(){ var derived = new cm.MultiplyDerived(); derived.setBaseMember(97); derived.baseMember = 32; assert.equal(32, derived.getBaseMember()); derived.delete(); }); test("can reach around derived property to access base property with same name for get", function() { var derived = new cm.Derived(); derived.setMember(12); derived.delete(); }); test("if deriving from second base adjusts pointer", function() { var derived = new cm.HasTwoBases; assert.equal("Base2", derived.getField()); derived.delete(); }); test("properties adjust pointer", function() { var derived = new cm.HasTwoBases; derived.field = "Foo"; assert.equal("Foo", derived.getField()); assert.equal("Foo", derived.field); derived.delete(); }); test("calling method on unrelated class throws error", function() { var a = new cm.HasTwoBases; var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call(a, "foo"); }); assert.equal('Derived.setMember incompatible with "this" of type HasTwoBases', e.message); a.delete(); }); test("calling method with invalid this throws error", function() { var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call(undefined, "foo"); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! // got Error: expected: Derived.setMember with invalid "this": undefined, actual: Derived.setMember incompatible with "this" of type Object assert.equal('Derived.setMember with invalid "this": undefined', e.message); } var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call("this", "foo"); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! // TODO got 'Derived.setMember incompatible with "this" of type Object' assert.equal('Derived.setMember with invalid "this": this', e.message); } var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call({}, "foo"); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! assert.equal('Derived.setMember incompatible with "this" of type Object', e.message); } }); test("setting and getting property on unrelated class throws error", function() { var a = new cm.HasTwoBases; var e = assert.throws(cm.BindingError, function() { Object.getOwnPropertyDescriptor(cm.HeldBySmartPtr.prototype, 'i').set.call(a, 10); }); assert.equal('HeldBySmartPtr.i setter incompatible with "this" of type HasTwoBases', e.message); var e = assert.throws(cm.BindingError, function() { Object.getOwnPropertyDescriptor(cm.HeldBySmartPtr.prototype, 'i').get.call(a); }); assert.equal('HeldBySmartPtr.i getter incompatible with "this" of type HasTwoBases', e.message); a.delete(); }); }); BaseFixture.extend("automatic upcasting of parameters passed to C++", function() { // raw test("raw pointer argument is upcast to parameter type", function() { var derived = new cm.Derived(); var name = cm.embind_test_get_class_name_via_base_ptr(derived); assert.equal("Base", name); derived.delete(); }); test("automatic raw pointer upcasting works with multiple inheritance", function() { var derived = new cm.MultiplyDerived(); var name = cm.embind_test_get_class_name_via_base_ptr(derived); assert.equal("Base", name); derived.delete(); }); test("automatic raw pointer upcasting does not change local pointer", function() { var derived = new cm.MultiplyDerived(); cm.embind_test_get_class_name_via_base_ptr(derived); assert.equal("MultiplyDerived", derived.getClassName()); derived.delete(); }); test("passing incompatible raw pointer to method throws exception", function() { var base = new cm.Base(); assert.throws(cm.BindingError, function() { cm.embind_test_get_class_name_via_second_base_ptr(base); }); base.delete(); }); // raw polymorphic test("polymorphic raw pointer argument is upcast to parameter type", function() { var derived = new cm.PolyDerived(); var name = cm.embind_test_get_class_name_via_polymorphic_base_ptr(derived); assert.equal("PolyBase", name); derived.delete(); }); test("automatic polymorphic raw pointer upcasting works with multiple inheritance", function() { var derived = new cm.PolyMultiplyDerived(); var name = cm.embind_test_get_class_name_via_polymorphic_base_ptr(derived); assert.equal("PolyBase", name); derived.delete(); }); test("passing incompatible raw polymorphic pointer to method throws exception", function() { var base = new cm.PolyBase(); assert.throws(cm.BindingError, function() { cm.embind_test_get_class_name_via_polymorphic_second_base_ptr(base); }); base.delete(); }); // smart test("can pass smart pointer to raw pointer parameter", function() { var smartBase = cm.embind_test_return_smart_base_ptr(); assert.equal("Base", cm.embind_test_get_class_name_via_base_ptr(smartBase)); smartBase.delete(); }); test("can pass and upcast smart pointer to raw pointer parameter", function() { var smartDerived = cm.embind_test_return_smart_derived_ptr(); assert.equal("Base", cm.embind_test_get_class_name_via_base_ptr(smartDerived)); smartDerived.delete(); }); test("smart pointer argument is upcast to parameter type", function() { var derived = cm.embind_test_return_smart_derived_ptr(); assert.instanceof(derived, cm.Derived); assert.instanceof(derived, cm.Base); var name = cm.embind_test_get_class_name_via_smart_base_ptr(derived); assert.equal("Base", name); derived.delete(); }); test("return smart derived ptr as base", function() { var derived = cm.embind_test_return_smart_derived_ptr_as_base(); assert.equal("PolyDerived", cm.embind_test_get_virtual_class_name_via_smart_polymorphic_base_ptr(derived)); assert.equal("PolyDerived", derived.getClassName()); derived.delete(); }); test("return smart derived ptr as val", function() { var derived = cm.embind_test_return_smart_derived_ptr_as_val(); assert.equal("PolyDerived", cm.embind_test_get_virtual_class_name_via_smart_polymorphic_base_ptr(derived)); derived.delete(); }); test("automatic smart pointer upcasting works with multiple inheritance", function() { var derived = cm.embind_test_return_smart_multiply_derived_ptr(); var name = cm.embind_test_get_class_name_via_smart_base_ptr(derived); assert.equal("Base", name); derived.delete(); }); test("automatically upcasted smart pointer parameter shares ownership with original argument", function() { var derived = cm.embind_test_return_smart_multiply_derived_ptr(); assert.equal(1, cm.MultiplyDerived.getInstanceCount()); cm.embind_save_smart_base_pointer(derived); assert.equal(1, cm.MultiplyDerived.getInstanceCount()); derived.delete(); assert.equal(1, cm.MultiplyDerived.getInstanceCount()); cm.embind_save_smart_base_pointer(null); assert.equal(0, cm.MultiplyDerived.getInstanceCount()); }); // smart polymorphic test("smart polymorphic pointer argument is upcast to parameter type", function() { var derived = cm.embind_test_return_smart_polymorphic_derived_ptr(); var name = cm.embind_test_get_class_name_via_smart_polymorphic_base_ptr(derived); assert.equal("PolyBase", name); derived.delete(); }); test("automatic smart polymorphic pointer upcasting works with multiple inheritance", function() { var derived = cm.embind_test_return_smart_polymorphic_multiply_derived_ptr(); var name = cm.embind_test_get_class_name_via_smart_polymorphic_base_ptr(derived); assert.equal("PolyBase", name); derived.delete(); }); }); BaseFixture.extend("automatic downcasting of return values received from C++", function() { // raw test("non-polymorphic raw pointers are not downcast and do not break automatic casting mechanism", function() { var base = cm.embind_test_return_raw_derived_ptr_as_base(); assert.equal("Base", base.getClassName()); assert.instanceof(base, cm.Base); base.delete(); }); // raw polymorphic test("polymorphic raw pointer return value is downcast to allocated type (if that is bound)", function() { var derived = cm.embind_test_return_raw_polymorphic_derived_ptr_as_base(); assert.instanceof(derived, cm.PolyBase); assert.instanceof(derived, cm.PolyDerived); assert.equal("PolyDerived", derived.getClassName()); var siblingDerived = cm.embind_test_return_raw_polymorphic_sibling_derived_ptr_as_base(); assert.equal("PolySiblingDerived", siblingDerived.getClassName()); siblingDerived.delete(); derived.delete(); }); test("polymorphic raw pointer return value is downcast to the most derived bound type", function() { var derivedThrice = cm.embind_test_return_raw_polymorphic_derived_four_times_not_bound_as_base(); // if the actual returned type is not bound, then don't assume anything assert.equal("PolyBase", derivedThrice.getClassName()); // if we ever fix this, then reverse the assertion //assert.equal("PolyDerivedThrice", derivedThrice.getClassName()); derivedThrice.delete(); }); test("polymorphic smart pointer return value is downcast to the most derived type which has an associated smart pointer", function() { var derived = cm.embind_test_return_poly_derived_twice_without_smart_pointer_as_poly_base(); // if the actual returned type is not bound, then don't assume anything assert.equal("PolyBase", derived.getClassName()); // if we ever fix this, then remove the assertion //assert.equal("PolyDerived", derived.getClassName()); derived.delete(); }); test("automatic downcasting works with multiple inheritance", function() { var base = cm.embind_test_return_raw_polymorphic_multiply_derived_ptr_as_base(); var secondBase = cm.embind_test_return_raw_polymorphic_multiply_derived_ptr_as_second_base(); assert.equal("PolyMultiplyDerived", base.getClassName()); // embind does not support multiple inheritance //assert.equal("PolyMultiplyDerived", secondBase.getClassName()); secondBase.delete(); base.delete(); }); // smart test("non-polymorphic smart pointers do not break automatic casting mechanism", function() { }); // smart polymorphic test("automatically downcasting a smart pointer does not change the underlying pointer", function() { cm.PolyDerived.setPtrDerived(); assert.equal("PolyBase", cm.PolyDerived.getPtrClassName()); var derived = cm.PolyDerived.getPtr(); assert.equal("PolyDerived", derived.getClassName()); assert.equal("PolyBase", cm.PolyDerived.getPtrClassName()); derived.delete(); cm.PolyDerived.releasePtr(); }); test("polymorphic smart pointer return value is actual allocated type (when bound)", function() { var derived = cm.embind_test_return_smart_polymorphic_derived_ptr_as_base(); assert.equal("PolyDerived", derived.getClassName()); var siblingDerived = cm.embind_test_return_smart_polymorphic_sibling_derived_ptr_as_base(); assert.equal("PolySiblingDerived", siblingDerived.getClassName()); siblingDerived.delete(); derived.delete(); }); }); BaseFixture.extend("embind", function() { test("value creation", function() { assert.equal(15, cm.emval_test_new_integer()); assert.equal("Hello everyone", cm.emval_test_new_string()); assert.equal("Hello everyone", cm.emval_test_get_string_from_val({key: "Hello everyone"})); var object = cm.emval_test_new_object(); assert.equal('bar', object.foo); assert.equal(1, object.baz); }); test("pass const reference to primitive", function() { assert.equal(3, cm.const_ref_adder(1, 2)); }); test("passthrough", function() { var a = {foo: 'bar'}; var b = cm.emval_test_passthrough(a); a.bar = 'baz'; assert.equal('baz', b.bar); assert.equal(0, cm.count_emval_handles()); }); test("void return converts to undefined", function() { assert.equal(undefined, cm.emval_test_return_void()); }); test("booleans can be marshalled", function() { assert.equal(false, cm.emval_test_not(true)); assert.equal(true, cm.emval_test_not(false)); }); test("convert double to unsigned", function() { var rv = cm.emval_test_as_unsigned(1.5); assert.equal('number', typeof rv); assert.equal(1, rv); assert.equal(0, cm.count_emval_handles()); }); test("get length of array", function() { assert.equal(10, cm.emval_test_get_length([0, 1, 2, 3, 4, 5, 'a', 'b', 'c', 'd'])); assert.equal(0, cm.count_emval_handles()); }); test("add a bunch of things", function() { assert.equal(66.0, cm.emval_test_add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); assert.equal(0, cm.count_emval_handles()); }); test("sum array", function() { assert.equal(66, cm.emval_test_sum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); assert.equal(0, cm.count_emval_handles()); }); test("strings", function() { assert.equal("foobar", "foo" + "bar"); assert.equal("foobar", cm.emval_test_take_and_return_std_string("foobar")); assert.equal("foobar", cm.emval_test_take_and_return_std_string_const_ref("foobar")); }); test("nuls pass through strings", function() { assert.equal("foo\0bar", cm.emval_test_take_and_return_std_string("foo\0bar")); }); test("no memory leak when passing strings in by const reference", function() { cm.emval_test_take_and_return_std_string_const_ref("foobar"); }); test("can create new object", function() { assert.deepEqual({}, cm.embind_test_new_Object()); }); test("can invoke constructors with arguments", function() { function constructor(i, s, argument) { this.i = i; this.s = s; this.argument = argument; } constructor.prototype.method = function() { return this.argument; }; var x = {}; var instance = cm.embind_test_new_factory(constructor, x); assert.equal(10, instance.i); assert.equal("hello", instance.s); assert.equal(x, instance.argument); }); test("can return module property objects", function() { assert.equal(cm.HEAP8, cm.get_module_property("HEAP8")); }); test("can return big class instances", function() { var c = cm.embind_test_return_big_class_instance(); assert.equal(11, c.member); c.delete(); }); test("can return small class instances", function() { var c = cm.embind_test_return_small_class_instance(); assert.equal(7, c.member); c.delete(); }); test("can pass small class instances", function() { var c = new cm.SmallClass(); var m = cm.embind_test_accept_small_class_instance(c); assert.equal(7, m); c.delete(); }); test("can pass big class instances", function() { var c = new cm.BigClass(); var m = cm.embind_test_accept_big_class_instance(c); assert.equal(11, m); c.delete(); }); test("can get member classes then call its member functions", function() { var p = new cm.ParentClass(); var c = p.getBigClass(); var m = c.getMember(); assert.equal(11, m); c.delete(); p.delete(); }); test('C++ -> JS primitive type range checks', function() { // all types should have zero. assert.equal("0", cm.char_to_string(0)); assert.equal("0", cm.signed_char_to_string(0)); assert.equal("0", cm.unsigned_char_to_string(0)); assert.equal("0", cm.short_to_string(0)); assert.equal("0", cm.unsigned_short_to_string(0)); assert.equal("0", cm.int_to_string(0)); assert.equal("0", cm.unsigned_int_to_string(0)); assert.equal("0", cm.long_to_string(0)); assert.equal("0", cm.unsigned_long_to_string(0)); // all types should have positive values. assert.equal("5", cm.char_to_string(5)); assert.equal("5", cm.signed_char_to_string(5)); assert.equal("5", cm.unsigned_char_to_string(5)); assert.equal("5", cm.short_to_string(5)); assert.equal("5", cm.unsigned_short_to_string(5)); assert.equal("5", cm.int_to_string(5)); assert.equal("5", cm.unsigned_int_to_string(5)); assert.equal("5", cm.long_to_string(5)); assert.equal("5", cm.unsigned_long_to_string(5)); // signed types should have negative values. assert.equal("-5", cm.char_to_string(-5)); // Assuming char as signed. assert.equal("-5", cm.signed_char_to_string(-5)); assert.equal("-5", cm.short_to_string(-5)); assert.equal("-5", cm.int_to_string(-5)); assert.equal("-5", cm.long_to_string(-5)); // assumptions: char == signed char == 8 bits // unsigned char == 8 bits // short == 16 bits // int == long == 32 bits // all types should have their max positive values. assert.equal("127", cm.char_to_string(127)); assert.equal("127", cm.signed_char_to_string(127)); assert.equal("255", cm.unsigned_char_to_string(255)); assert.equal("32767", cm.short_to_string(32767)); assert.equal("65535", cm.unsigned_short_to_string(65535)); assert.equal("2147483647", cm.int_to_string(2147483647)); assert.equal("4294967295", cm.unsigned_int_to_string(4294967295)); assert.equal("2147483647", cm.long_to_string(2147483647)); assert.equal("4294967295", cm.unsigned_long_to_string(4294967295)); // signed types should have their min negative values. assert.equal("-128", cm.char_to_string(-128)); assert.equal("-128", cm.signed_char_to_string(-128)); assert.equal("-32768", cm.short_to_string(-32768)); assert.equal("-2147483648", cm.int_to_string(-2147483648)); assert.equal("-2147483648", cm.long_to_string(-2147483648)); // passing out of range values should fail. assert.throws(TypeError, function() { cm.char_to_string(-129); }); assert.throws(TypeError, function() { cm.char_to_string(128); }); assert.throws(TypeError, function() { cm.signed_char_to_string(-129); }); assert.throws(TypeError, function() { cm.signed_char_to_string(128); }); assert.throws(TypeError, function() { cm.unsigned_char_to_string(-1); }); assert.throws(TypeError, function() { cm.unsigned_char_to_string(256); }); assert.throws(TypeError, function() { cm.short_to_string(-32769); }); assert.throws(TypeError, function() { cm.short_to_string(32768); }); assert.throws(TypeError, function() { cm.unsigned_short_to_string(-1); }); assert.throws(TypeError, function() { cm.unsigned_short_to_string(65536); }); assert.throws(TypeError, function() { cm.int_to_string(-2147483649); }); assert.throws(TypeError, function() { cm.int_to_string(2147483648); }); assert.throws(TypeError, function() { cm.unsigned_int_to_string(-1); }); assert.throws(TypeError, function() { cm.unsigned_int_to_string(4294967296); }); assert.throws(TypeError, function() { cm.long_to_string(-2147483649); }); assert.throws(TypeError, function() { cm.long_to_string(2147483648); }); assert.throws(TypeError, function() { cm.unsigned_long_to_string(-1); }); assert.throws(TypeError, function() { cm.unsigned_long_to_string(4294967296); }); }); /* test("can get templated member classes then call its member functions", function() { var p = new cm.ContainsTemplatedMemberClass(); var c = p.getTestTemplate(); var m = c.getMember(1); assert.equal(87, m); c.delete(); p.delete(); }); */ }); BaseFixture.extend("vector", function() { test("std::vector returns as an native object", function() { var vec = cm.emval_test_return_vector(); assert.equal(3, vec.size()); assert.equal(10, vec.get(0)); assert.equal(20, vec.get(1)); assert.equal(30, vec.get(2)); vec.delete(); }); test("out of bounds std::vector access returns undefined", function() { var vec = cm.emval_test_return_vector(); assert.throws(TypeError, function() { vec.get(-1); }); assert.equal(undefined, vec.get(4)); vec.delete(); }); test("std::vector> can be passed back", function() { var vec = cm.emval_test_return_shared_ptr_vector(); assert.equal(2, vec.size()); var str0 = vec.get(0); var str1 = vec.get(1); assert.equal('string #1', str0.get()); assert.equal('string #2', str1.get()); str0.delete(); str1.delete(); vec.delete(); }); test("objects can be pushed back", function() { var vectorHolder = new cm.VectorHolder(); var vec = vectorHolder.get(); assert.equal(2, vec.size()); var str = new cm.StringHolder('abc'); vec.push_back(str); str.delete(); assert.equal(3, vec.size()); var str = vec.get(2); assert.equal('abc', str.get()); str.delete(); vec.delete(); vectorHolder.delete(); }); test("can get elements with array operator", function(){ var vec = cm.emval_test_return_vector(); assert.equal(10, vec.get(0)); vec.delete(); }); test("can set elements with array operator", function() { var vec = cm.emval_test_return_vector(); assert.equal(10, vec.get(0)); vec.set(2, 60); assert.equal(60, vec.get(2)); vec.delete(); }); test("can set and get objects", function() { var vec = cm.emval_test_return_shared_ptr_vector(); var str = vec.get(0); assert.equal('string #1', str.get()); str.delete(); vec.delete(); }); }); BaseFixture.extend("map", function() { test("std::map returns as native object", function() { var map = cm.embind_test_get_string_int_map(); assert.equal(2, map.size()); assert.equal(1, map.get("one")); assert.equal(2, map.get("two")); map.delete(); }); test("std::map can set keys and values", function() { var map = cm.embind_test_get_string_int_map(); assert.equal(2, map.size()); map.set("three", 3); assert.equal(3, map.size()); assert.equal(3, map.get("three")); map.set("three", 4); assert.equal(3, map.size()); assert.equal(4, map.get("three")); map.delete(); }); }); BaseFixture.extend("functors", function() { test("can get and call function ptrs", function() { var ptr = cm.emval_test_get_function_ptr(); assert.equal("foobar", ptr.opcall("foobar")); ptr.delete(); }); test("can pass functor to C++", function() { var ptr = cm.emval_test_get_function_ptr(); assert.equal("asdf", cm.emval_test_take_and_call_functor(ptr)); ptr.delete(); }); test("can clone handles", function() { var a = cm.emval_test_get_function_ptr(); assert.equal(1, a.$$.count.value); var b = a.clone(); assert.equal(2, a.$$.count.value); assert.equal(2, b.$$.count.value); a.delete(); assert.throws(cm.BindingError, function() { a.delete(); }); b.delete(); }); }); BaseFixture.extend("classes", function() { test("class instance", function() { var a = {foo: 'bar'}; assert.equal(0, cm.count_emval_handles()); var c = new cm.ValHolder(a); assert.equal(1, cm.count_emval_handles()); assert.equal('bar', c.getVal().foo); assert.equal(1, cm.count_emval_handles()); c.setVal('1234'); assert.equal('1234', c.getVal()); c.delete(); assert.equal(0, cm.count_emval_handles()); }); test("class instance $$ property is non-enumerable", function() { var c = new cm.ValHolder(undefined); assert.deepEqual([], Object.keys(c)); var d = c.clone(); c.delete(); assert.deepEqual([], Object.keys(d)); d.delete(); }); test("class methods", function() { assert.equal(10, cm.ValHolder.some_class_method(10)); var b = cm.ValHolder.makeValHolder("foo"); assert.equal("foo", b.getVal()); b.delete(); }); test("can't call methods on deleted class instances", function() { var c = new cm.ValHolder(undefined); c.delete(); assert.throws(cm.BindingError, function() { c.getVal(); }); assert.throws(cm.BindingError, function() { c.delete(); }); }); test("calling constructor without new raises BindingError", function() { var e = assert.throws(cm.BindingError, function() { cm.ValHolder(undefined); }); assert.equal("Use 'new' to construct ValHolder", e.message); }); test("can return class instances by value", function() { var c = cm.emval_test_return_ValHolder(); assert.deepEqual({}, c.getVal()); c.delete(); }); test("can pass class instances to functions by reference", function() { var a = {a:1}; var c = new cm.ValHolder(a); cm.emval_test_set_ValHolder_to_empty_object(c); assert.deepEqual({}, c.getVal()); c.delete(); }); test("can pass smart pointer by reference", function() { var base = cm.embind_test_return_smart_base_ptr(); var name = cm.embind_test_get_class_name_via_reference_to_smart_base_ptr(base); assert.equal("Base", name); base.delete(); }); test("can pass smart pointer by value", function() { var base = cm.embind_test_return_smart_base_ptr(); var name = cm.embind_test_get_class_name_via_smart_base_ptr(base); assert.equal("Base", name); base.delete(); }); // todo: fix this // This test does not work because we make no provision for argument values // having been changed after returning from a C++ routine invocation. In // this specific case, the original pointee of the smart pointer was // freed and replaced by a new one, but the ptr in our local handle // was never updated after returning from the call. test("can modify smart pointers passed by reference", function() { // var base = cm.embind_test_return_smart_base_ptr(); // cm.embind_modify_smart_pointer_passed_by_reference(base); // assert.equal("Changed", base.getClassName()); // base.delete(); }); test("can not modify smart pointers passed by value", function() { var base = cm.embind_test_return_smart_base_ptr(); cm.embind_attempt_to_modify_smart_pointer_when_passed_by_value(base); assert.equal("Base", base.getClassName()); base.delete(); }); test("const return value", function() { var c = new cm.ValHolder("foo"); assert.equal("foo", c.getConstVal()); c.delete(); }); test("return object by const ref", function() { var c = new cm.ValHolder("foo"); assert.equal("foo", c.getValConstRef()); c.delete(); }); test("instanceof", function() { var c = new cm.ValHolder("foo"); assert.instanceof(c, cm.ValHolder); c.delete(); }); test("can access struct fields", function() { var c = new cm.CustomStruct(); assert.equal(10, c.field); assert.equal(10, c.getField()); c.delete(); }); test("can set struct fields", function() { var c = new cm.CustomStruct(); c.field = 15; assert.equal(15, c.field); c.delete(); }); test("assignment returns value", function() { var c = new cm.CustomStruct(); assert.equal(15, c.field = 15); c.delete(); }); test("assigning string to integer raises TypeError", function() { var c = new cm.CustomStruct(); var e = assert.throws(TypeError, function() { c.field = "hi"; }); assert.equal('Cannot convert "hi" to int', e.message); var e = assert.throws(TypeError, function() { c.field = {foo:'bar'}; }); assert.equal('Cannot convert "[object Object]" to int', e.message); c.delete(); }); test("can return tuples by value", function() { var c = cm.emval_test_return_TupleVector(); assert.deepEqual([1, 2, 3], c); }); test("tuples can contain tuples", function() { var c = cm.emval_test_return_TupleVectorTuple(); assert.deepEqual([[1, 2, 3]], c); }); test("can pass tuples by value", function() { var c = cm.emval_test_take_and_return_TupleVector([4, 5, 6]); assert.deepEqual([4, 5, 6], c); }); test("can return structs by value", function() { var c = cm.emval_test_return_StructVector(); assert.deepEqual({x: 1, y: 2, z: 3}, c); }); test("can pass structs by value", function() { var c = cm.emval_test_take_and_return_StructVector({x: 4, y: 5, z: 6}); assert.deepEqual({x: 4, y: 5, z: 6}, c); }); test("can pass and return tuples in structs", function() { var d = cm.emval_test_take_and_return_TupleInStruct({field: [1, 2, 3]}); assert.deepEqual({field: [1, 2, 3]}, d); }); test("can clone handles", function() { var a = new cm.ValHolder({}); assert.equal(1, cm.count_emval_handles()); var b = a.clone(); a.delete(); assert.equal(1, cm.count_emval_handles()); assert.throws(cm.BindingError, function() { a.delete(); }); b.delete(); assert.equal(0, cm.count_emval_handles()); }); test("A shared pointer set/get point to the same underlying pointer", function() { var a = new cm.SharedPtrHolder(); var b = a.get(); a.set(b); var c = a.get(); assert.equal(b.$$.ptr, c.$$.ptr); b.delete(); c.delete(); a.delete(); }); test("can return shared ptrs from instance methods", function() { var a = new cm.SharedPtrHolder(); // returns the shared_ptr. var b = a.get(); assert.equal("a string", b.get()); b.delete(); a.delete(); }); test("smart ptrs clone correctly", function() { assert.equal(0, cm.count_emval_handles()); var a = cm.emval_test_return_shared_ptr(); var b = a.clone(); a.delete(); assert.equal(1, cm.count_emval_handles()); assert.throws(cm.BindingError, function() { a.delete(); }); b.delete(); assert.equal(0, cm.count_emval_handles()); }); test("can't clone if already deleted", function() { var a = new cm.ValHolder({}); a.delete(); assert.throws(cm.BindingError, function() { a.clone(); }); }); test("virtual calls work correctly", function() { var derived = cm.embind_test_return_raw_polymorphic_derived_ptr_as_base(); assert.equal("PolyDerived", derived.virtualGetClassName()); derived.delete(); }); test("virtual calls work correctly on smart ptrs", function() { var derived = cm.embind_test_return_smart_polymorphic_derived_ptr_as_base(); assert.equal("PolyDerived", derived.virtualGetClassName()); derived.delete(); }); test("Empty smart ptr is null", function() { var a = cm.emval_test_return_empty_shared_ptr(); assert.equal(null, a); }); test("string cannot be given as smart pointer argument", function() { assert.throws(cm.BindingError, function() { cm.emval_test_is_shared_ptr_null("hello world"); }); }); test("number cannot be given as smart pointer argument", function() { assert.throws(cm.BindingError, function() { cm.emval_test_is_shared_ptr_null(105); }); }); test("raw pointer cannot be given as smart pointer argument", function() { var p = new cm.ValHolder({}); assert.throws(cm.BindingError, function() { cm.emval_test_is_shared_ptr_null(p); }); p.delete(); }); test("null is passed as empty smart pointer", function() { assert.true(cm.emval_test_is_shared_ptr_null(null)); }); test("Deleting already deleted smart ptrs fails", function() { var a = cm.emval_test_return_shared_ptr(); a.delete(); assert.throws(cm.BindingError, function() { a.delete(); }); }); test("StringHolder", function() { var a = new cm.StringHolder("foobar"); assert.equal("foobar", a.get()); a.set("barfoo"); assert.equal("barfoo", a.get()); assert.equal("barfoo", a.get_const_ref()); a.delete(); }); test("can call methods on unique ptr", function() { var result = cm.emval_test_return_unique_ptr(); result.setVal('1234'); assert.equal('1234', result.getVal()); result.delete(); }); test("can call methods on shared ptr", function(){ var result = cm.emval_test_return_shared_ptr(); result.setVal('1234'); assert.equal('1234', result.getVal()); result.delete(); }); test("Non functors throw exception", function() { var a = {foo: 'bar'}; var c = new cm.ValHolder(a); assert.throws(TypeError, function() { c(); }); c.delete(); }); test("non-member methods", function() { var a = {foo: 'bar'}; var c = new cm.ValHolder(a); c.setEmpty(); // non-member method assert.deepEqual({}, c.getValNonMember()); c.delete(); }); test("instantiating class without constructor gives error", function() { var e = assert.throws(cm.BindingError, function() { cm.AbstractClass(); }); assert.equal("Use 'new' to construct AbstractClass", e.message); var e = assert.throws(cm.BindingError, function() { new cm.AbstractClass(); }); assert.equal("AbstractClass has no accessible constructor", e.message); }); test("can construct class with external constructor", function() { var e = new cm.HasExternalConstructor("foo"); assert.instanceof(e, cm.HasExternalConstructor); assert.equal("foo", e.getString()); e.delete(); }); }); BaseFixture.extend("const", function() { test("calling non-const method with const handle is error", function() { var vh = cm.ValHolder.makeConst({}); var e = assert.throws(cm.BindingError, function() { vh.setVal({}); }); assert.equal('Cannot convert argument of type ValHolder const* to parameter type ValHolder*', e.message); vh.delete(); }); test("passing const pointer to non-const pointer is error", function() { var vh = new cm.ValHolder.makeConst({}); var e = assert.throws(cm.BindingError, function() { cm.ValHolder.set_via_raw_pointer(vh, {}); }); assert.equal('Cannot convert argument of type ValHolder const* to parameter type ValHolder*', e.message); vh.delete(); }); }); BaseFixture.extend("smart pointers", function() { test("constructor can return smart pointer", function() { var e = new cm.HeldBySmartPtr(10, "foo"); assert.instanceof(e, cm.HeldBySmartPtr); assert.equal(10, e.i); assert.equal("foo", e.s); var f = cm.takesHeldBySmartPtr(e); f.delete(); e.delete(); }); test("cannot pass incorrect smart pointer type", function() { var e = cm.emval_test_return_shared_ptr(); assert.throws(cm.BindingError, function() { cm.takesHeldBySmartPtr(e); }); e.delete(); }); test("smart pointer object has no object keys", function() { var e = new cm.HeldBySmartPtr(10, "foo"); assert.deepEqual([], Object.keys(e)); var f = e.clone(); e.delete(); assert.deepEqual([], Object.keys(f)); f.delete(); }); test("smart pointer object has correct constructor name", function() { var e = new cm.HeldBySmartPtr(10, "foo"); assert.equal('HeldBySmartPtr', e.constructor.name); e.delete(); }); test("constructor can return smart pointer", function() { var e = new cm.HeldBySmartPtr(10, "foo"); assert.instanceof(e, cm.HeldBySmartPtr); assert.equal(10, e.i); assert.equal("foo", e.s); var f = cm.takesHeldBySmartPtr(e); assert.instanceof(f, cm.HeldBySmartPtr); f.delete(); e.delete(); }); test("custom smart pointer", function() { var e = new cm.HeldByCustomSmartPtr(20, "bar"); assert.instanceof(e, cm.HeldByCustomSmartPtr); assert.equal(20, e.i); assert.equal("bar", e.s); e.delete(); }); test("custom smart pointer passed through wiretype", function() { var e = new cm.HeldByCustomSmartPtr(20, "bar"); var f = cm.passThroughCustomSmartPtr(e); e.delete(); assert.instanceof(f, cm.HeldByCustomSmartPtr); assert.equal(20, f.i); assert.equal("bar", f.s); f.delete(); }); test("cannot give null to by-value argument", function() { var e = assert.throws(cm.BindingError, function() { cm.takesHeldBySmartPtr(null); }); assert.equal('null is not a valid HeldBySmartPtr', e.message); }); test("raw pointer can take and give null", function() { assert.equal(null, cm.passThroughRawPtr(null)); }); test("custom smart pointer can take and give null", function() { assert.equal(null, cm.passThroughCustomSmartPtr(null)); }); test("cannot pass shared_ptr to CustomSmartPtr", function() { var o = cm.HeldByCustomSmartPtr.createSharedPtr(10, "foo"); var e = assert.throws(cm.BindingError, function() { cm.passThroughCustomSmartPtr(o); }); assert.equal('Cannot convert argument of type NSt3__110shared_ptrI20HeldByCustomSmartPtrEE to parameter type 14CustomSmartPtrI20HeldByCustomSmartPtrE', e.message); o.delete(); }); test("custom smart pointers can be passed to shared_ptr parameter", function() { var e = cm.HeldBySmartPtr.newCustomPtr(10, "abc"); assert.equal(10, e.i); assert.equal("abc", e.s); cm.takesHeldBySmartPtrSharedPtr(e).delete(); e.delete(); }); test("can call non-member functions as methods", function() { var e = new cm.HeldBySmartPtr(20, "bar"); var f = e.returnThis(); e.delete(); assert.equal(20, f.i); assert.equal("bar", f.s); f.delete(); }); }); BaseFixture.extend("enumerations", function() { test("can compare enumeration values", function() { assert.equal(cm.Enum.ONE, cm.Enum.ONE); assert.notEqual(cm.Enum.ONE, cm.Enum.TWO); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! test("repr includes enum value", function() { assert.equal('<#Enum_ONE {}>', IMVU.repr(cm.Enum.ONE)); assert.equal('<#Enum_TWO {}>', IMVU.repr(cm.Enum.TWO)); }); } test("instanceof", function() { assert.instanceof(cm.Enum.ONE, cm.Enum); }); test("can pass and return enumeration values to functions", function() { assert.equal(cm.Enum.TWO, cm.emval_test_take_and_return_Enum(cm.Enum.TWO)); }); }); BaseFixture.extend("C++11 enum class", function() { test("can compare enumeration values", function() { assert.equal(cm.EnumClass.ONE, cm.EnumClass.ONE); assert.notEqual(cm.EnumClass.ONE, cm.EnumClass.TWO); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! test("repr includes enum value", function() { assert.equal('<#EnumClass_ONE {}>', IMVU.repr(cm.EnumClass.ONE)); assert.equal('<#EnumClass_TWO {}>', IMVU.repr(cm.EnumClass.TWO)); }); } test("instanceof", function() { assert.instanceof(cm.EnumClass.ONE, cm.EnumClass); }); test("can pass and return enumeration values to functions", function() { assert.equal(cm.EnumClass.TWO, cm.emval_test_take_and_return_EnumClass(cm.EnumClass.TWO)); }); }); BaseFixture.extend("emval call tests", function() { test("can call functions from C++", function() { var called = false; cm.emval_test_call_function(function(i, f, tv, sv) { called = true; assert.equal(10, i); assert.equal(1.5, f); assert.deepEqual([1.25, 2.5, 3.75], tv); assert.deepEqual({x: 1.25, y: 2.5, z: 3.75}, sv); }, 10, 1.5, [1.25, 2.5, 3.75], {x: 1.25, y: 2.5, z: 3.75}); assert.true(called); }); }); BaseFixture.extend("extending built-in classes", function() { // cm.ValHolder.prototype.patched = 10; // this sets instanceCounts.patched inside of Deletable module !?! test("can access patched value on new instances", function() { cm.ValHolder.prototype.patched = 10; var c = new cm.ValHolder(undefined); assert.equal(10, c.patched); c.delete(); cm.ValHolder.prototype.patched = undefined; }); test("can access patched value on returned instances", function() { cm.ValHolder.prototype.patched = 10; var c = cm.emval_test_return_ValHolder(); assert.equal(10, c.patched); c.delete(); cm.ValHolder.prototype.patched = undefined; }); }); BaseFixture.extend("raw pointers", function() { test("can pass raw pointers into functions if explicitly allowed", function() { var vh = new cm.ValHolder({}); cm.ValHolder.set_via_raw_pointer(vh, 10); assert.equal(10, cm.ValHolder.get_via_raw_pointer(vh)); vh.delete(); }); test("can return raw pointers from functions if explicitly allowed", function() { var p = cm.embind_test_return_raw_base_ptr(); assert.equal("Base", p.getClassName()); p.delete(); }); test("can pass multiple raw pointers to functions", function() { var target = new cm.ValHolder(undefined); var source = new cm.ValHolder("hi"); cm.ValHolder.transfer_via_raw_pointer(target, source); assert.equal("hi", target.getVal()); target.delete(); source.delete(); }); }); BaseFixture.extend("JavaScript interface", function() { this.setUp(function() { this.testobj = { "method1": function() { return 111; }, "method2": function() { return 222; } }; }); test("pass js object to c++ and call its method", function() { var obj = new cm.JSInterfaceHolder(this.testobj); assert.equal(111, obj.callMethod("method1")); assert.equal(222, obj.callMethod("method2")); assert.equal(111, obj.callMethodUsingSharedPtr("method1")); assert.equal(222, obj.callMethodUsingSharedPtr("method2")); obj.delete(); }); }); BaseFixture.extend("abstract methods", function() { test("can call abstract methods", function() { var obj = cm.getAbstractClass(); assert.equal("from concrete", obj.abstractMethod()); obj.delete(); }); test("can implement abstract methods in JavaScript", function() { var expected = "my JS string"; function MyImplementation() { this.rv = expected; } MyImplementation.prototype.abstractMethod = function() { return this.rv; }; var impl = cm.AbstractClass.implement(new MyImplementation); assert.equal(expected, impl.abstractMethod()); assert.equal(expected, cm.callAbstractMethod(impl)); impl.delete(); }); }); BaseFixture.extend("registration order", function() { test("registration of tuple elements out of order leaves them in order", function() { var ot = cm.getOrderedTuple(); assert.instanceof(ot[0], cm.FirstElement); assert.instanceof(ot[1], cm.SecondElement); ot[0].delete(); ot[1].delete(); }); test("registration of struct elements out of order", function() { var os = cm.getOrderedStruct(); assert.instanceof(os.first, cm.FirstElement); assert.instanceof(os.second, cm.SecondElement); os.first.delete(); os.second.delete(); }); }); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! BaseFixture.extend("unbound types", function() { function assertMessage(fn, message) { var e = assert.throws(cm.UnboundTypeError, fn); assert.equal(message, e.message); } test("calling function with unbound types produces error", function() { assertMessage( function() { cm.getUnboundClass(); }, 'Cannot call getUnboundClass due to unbound types: UnboundClass'); }); test("unbound base class produces error", function() { assertMessage( function() { cm.getHasUnboundBase(); }, 'Cannot call getHasUnboundBase due to unbound types: UnboundClass'); }); test("construct of class with unbound base", function() { assertMessage( function() { new cm.HasUnboundBase; }, 'Cannot construct HasUnboundBase due to unbound types: UnboundClass'); }); test("unbound constructor argument", function() { assertMessage( function() { new cm.HasConstructorUsingUnboundArgument; }, 'Cannot construct HasConstructorUsingUnboundArgument due to unbound types: UnboundClass'); }); test("unbound constructor argument of class with unbound base", function() { assertMessage( function() { new cm.HasConstructorUsingUnboundArgumentAndUnboundBase; }, 'Cannot construct HasConstructorUsingUnboundArgumentAndUnboundBase due to unbound types: SecondUnboundClass'); }); test('class function with unbound argument', function() { var x = new cm.BoundClass; assertMessage( function() { x.method(); }, 'Cannot call BoundClass.method due to unbound types: UnboundClass'); x.delete(); }); test('class class function with unbound argument', function() { assertMessage( function() { cm.BoundClass.classfunction(); }, 'Cannot call BoundClass.classfunction due to unbound types: UnboundClass'); }); test('class property of unbound type', function() { var x = new cm.BoundClass; var y; assertMessage( function() { y = x.property; }, 'Cannot access BoundClass.property due to unbound types: UnboundClass'); assertMessage( function() { x.property = 10; }, 'Cannot access BoundClass.property due to unbound types: UnboundClass'); x.delete(); }); // todo: tuple elements // todo: tuple element accessors // todo: struct fields }); } BaseFixture.extend("noncopyable", function() { test('can call method on noncopyable object', function() { var x = new cm.Noncopyable; assert.equal('foo', x.method()); x.delete(); }); }); BaseFixture.extend("function names", function() { assert.equal('ValHolder', cm.ValHolder.name); assert.equal('ValHolder$setVal', cm.ValHolder.prototype.setVal.name); assert.equal('ValHolder$makeConst', cm.ValHolder.makeConst.name); }); }); // If running as part of the emscripten test runner suite, and not as part of the IMVU suite, // we launch the test execution from here. IMVU suite uses its own dedicated mechanism instead of this. if (typeof run_all_tests !== "undefined") run_all_tests();