diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/cases/floatundefinvoke_fastcomp.ll | 30 | ||||
-rw-r--r-- | tests/cases/floatundefinvoke_fastcomp.txt | 3 | ||||
-rw-r--r-- | tests/cases/i1tof_ta2.ll | 71 | ||||
-rw-r--r-- | tests/cases/i1tof_ta2.txt | 3 | ||||
-rw-r--r-- | tests/core/test_double_varargs.c | 34 | ||||
-rw-r--r-- | tests/core/test_double_varargs.out | 2 | ||||
-rw-r--r-- | tests/core/test_exceptions_white_list_empty.out | 0 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 424 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 285 | ||||
-rw-r--r-- | tests/emscripten_fs_api_browser.cpp | 5 | ||||
-rw-r--r-- | tests/life.c | 2 | ||||
-rw-r--r-- | tests/sdl_audio_mix.c | 2 | ||||
-rw-r--r-- | tests/test_benchmark.py | 37 | ||||
-rw-r--r-- | tests/test_core.py | 98 | ||||
-rw-r--r-- | tests/test_other.py | 102 | ||||
-rw-r--r-- | tests/test_sanity.py | 2 | ||||
-rw-r--r-- | tests/webidl/output.txt | 2 | ||||
-rw-r--r-- | tests/webidl/post.js | 3 | ||||
-rw-r--r-- | tests/webidl/test.h | 3 | ||||
-rw-r--r-- | tests/webidl/test.idl | 3 |
20 files changed, 1071 insertions, 40 deletions
diff --git a/tests/cases/floatundefinvoke_fastcomp.ll b/tests/cases/floatundefinvoke_fastcomp.ll new file mode 100644 index 00000000..215506ef --- /dev/null +++ b/tests/cases/floatundefinvoke_fastcomp.ll @@ -0,0 +1,30 @@ +; ModuleID = 'a.o' +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128" +target triple = "asmjs-unknown-emscripten" + +@.str = private unnamed_addr constant [11 x i8] c"float: %f\0A\00", align 1 + +define void @_Z10printFloatf(float %f) #0 { +entry: + %conv = fpext float %f to double + %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([11 x i8]* @.str, i32 0, i32 0), double %conv) + ret void +} + +define i32 @main() #1 { +entry: + tail call void @_Z10printFloatf(float 1.000000e+00) + call void @emscripten_preinvoke() + call void @_Z10printFloatf(float undef) + %last = call i32 @emscripten_postinvoke() + %lastf = sitofp i32 %last to float + tail call void @_Z10printFloatf(float %lastf) + ret i32 1 +} + +declare void @emscripten_preinvoke() +declare i32 @emscripten_postinvoke() +declare i32 @printf(i8* nocapture, ...) #1 + +attributes #0 = { noinline nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } diff --git a/tests/cases/floatundefinvoke_fastcomp.txt b/tests/cases/floatundefinvoke_fastcomp.txt new file mode 100644 index 00000000..5e19391e --- /dev/null +++ b/tests/cases/floatundefinvoke_fastcomp.txt @@ -0,0 +1,3 @@ +float: 1.000000 +float: 0.000000 +float: 0.000000 diff --git a/tests/cases/i1tof_ta2.ll b/tests/cases/i1tof_ta2.ll new file mode 100644 index 00000000..12940907 --- /dev/null +++ b/tests/cases/i1tof_ta2.ll @@ -0,0 +1,71 @@ +; ModuleID = 'bad/emcc-0-basebc.bc' +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128" +target triple = "asmjs-unknown-emscripten" + +@.str = private unnamed_addr constant [7 x i8] c"%0.1f\0A\00", align 1 +@.str2 = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 + +; Function Attrs: noinline +define float @_Z2v1v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FECCCCCC0000000, float 0.000000e+00 + ret float %cond +} + +define double @emscripten_get_now() #0 { + ret double 1.0 +} + +; Function Attrs: noinline +define float @_Z2v2v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FD99999A0000000, float 0.000000e+00 + ret float %cond +} + +; Function Attrs: noinline +define float @_Z2v3v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FB99999A0000000, float 0.000000e+00 + ret float %cond +} + +define i32 @main() #1 { +entry: + %call = tail call float @_Z2v1v() + %call1 = tail call float @_Z2v2v() + %call2 = tail call float @_Z2v3v() + %sub = fsub float %call1, %call + %cmp.i = fcmp ogt float %sub, 0.000000e+00 + br i1 %cmp.i, label %_ZL5signff.exit, label %cond.false.i + +cond.false.i: ; preds = %entry + %cmp1.i = fcmp olt float %sub, 0.000000e+00 + %phitmp = sitofp i1 %cmp1.i to float + %phitmpd = fpext float %phitmp to double + %call1115a = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %phitmpd) + %phitmpi = sext i1 %cmp1.i to i32 + %call1115b = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str2, i32 0, i32 0), i32 %phitmpi) + br label %_ZL5signff.exit + +_ZL5signff.exit: ; preds = %cond.false.i, %entry + %cond2.i = phi float [ %phitmp, %cond.false.i ], [ 1.000000e+00, %entry ] + %mul = fmul float %call2, %cond2.i + %add = fadd float %call, %mul + %conv4 = fpext float %add to double + %call5 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %conv4) + ret i32 0 +} + +; Function Attrs: nounwind +declare i32 @printf(i8* nocapture, ...) #2 + +attributes #0 = { noinline "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } diff --git a/tests/cases/i1tof_ta2.txt b/tests/cases/i1tof_ta2.txt new file mode 100644 index 00000000..5d3943cd --- /dev/null +++ b/tests/cases/i1tof_ta2.txt @@ -0,0 +1,3 @@ +-1.0 +-1 +0.8 diff --git a/tests/core/test_double_varargs.c b/tests/core/test_double_varargs.c new file mode 100644 index 00000000..d3eb3eed --- /dev/null +++ b/tests/core/test_double_varargs.c @@ -0,0 +1,34 @@ +#include <stdio.h> +#include <stdarg.h> + +double func_int_double_1(int unused1, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused1); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +double func_int_double_2(int unused1, int unused2, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused2); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +int main() { + double ret = func_int_double_1(0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 + ret = func_int_double_2(0, 0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 +} + diff --git a/tests/core/test_double_varargs.out b/tests/core/test_double_varargs.out new file mode 100644 index 00000000..c907dece --- /dev/null +++ b/tests/core/test_double_varargs.out @@ -0,0 +1,2 @@ +15.000000 +15.000000 diff --git a/tests/core/test_exceptions_white_list_empty.out b/tests/core/test_exceptions_white_list_empty.out new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/core/test_exceptions_white_list_empty.out diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 3ded811a..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)); @@ -2030,6 +2379,49 @@ module({ assert.equal(65538, instance.c); }); }); + + 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)); + assert.equal("object", cm.getTypeOfVal({})); + assert.equal("function", cm.getTypeOfVal(function(){})); + assert.equal("number", cm.getTypeOfVal(1)); + assert.equal("string", cm.getTypeOfVal("hi")); + }); + }); }); /* global run_all_tests */ diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 5a83903a..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> @@ -2368,3 +2464,180 @@ EMSCRIPTEN_BINDINGS(val_new_) { function("construct_with_memory_view", &construct_with_memory_view); 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>(); +} + +EMSCRIPTEN_BINDINGS(typeof) { + function("getTypeOfVal", &getTypeOfVal); +} diff --git a/tests/emscripten_fs_api_browser.cpp b/tests/emscripten_fs_api_browser.cpp index 0355287a..1410ba3c 100644 --- a/tests/emscripten_fs_api_browser.cpp +++ b/tests/emscripten_fs_api_browser.cpp @@ -107,11 +107,14 @@ int main() { onLoaded, onError); + char name[40]; + strcpy(name, "/tmp/screen_shot.png"); // test for issue #2349, name being free'd emscripten_async_wget( "http://localhost:8888/screenshot.png", - "/tmp/screen_shot.png", + name, onLoaded, onError); + memset(name, 0, 30); emscripten_set_main_loop(wait_wgets, 0, 0); diff --git a/tests/life.c b/tests/life.c index 4ce8d385..263fa0e6 100644 --- a/tests/life.c +++ b/tests/life.c @@ -67,7 +67,9 @@ void game(int w, int h, int i) i--; nudge(univ, w, h); // keep it interesting for benchmark } else { +#if !__EMSCRIPTEN__ usleep(20000); +#endif show(univ, w, h); } } diff --git a/tests/sdl_audio_mix.c b/tests/sdl_audio_mix.c index a1c0485d..422fc122 100644 --- a/tests/sdl_audio_mix.c +++ b/tests/sdl_audio_mix.c @@ -58,7 +58,9 @@ void one_iter() { Mix_HaltChannel(soundChannel); Mix_HaltMusic(); int result = 1; +#ifdef REPORT_RESULT REPORT_RESULT(); +#endif break; }; } diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 735f0feb..16abbfdd 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -112,6 +112,7 @@ process(sys.argv[1]) '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', #'-profiling', + #'--closure', '1', '-o', final] + shared_args + emcc_args + self.extra_args, stdout=PIPE, stderr=PIPE, env=self.env).communicate() assert os.path.exists(final), 'Failed to compile file: ' + output[0] self.filename = final @@ -407,6 +408,42 @@ class benchmark(RunnerCore): ''' self.do_benchmark('ifs', src, 'ok', reps=TEST_REPS*5) + def test_conditionals(self): + src = r''' + #include <stdio.h> + #include <stdlib.h> + + int main(int argc, char *argv[]) { + int arg = argc > 1 ? argv[1][0] - '0' : 3; + switch(arg) { + case 0: return 0; break; + case 1: arg = 3*75; break; + case 2: arg = 3*625; break; + case 3: arg = 3*1250; break; + case 4: arg = 3*5*1250; break; + case 5: arg = 3*10*1250; break; + default: printf("error: %d\\n", arg); return -1; + } + + int x = 0; + + for (int j = 0; j < 27000; j++) { + for (int i = 0; i < arg; i++) { + if (((x*x+11) % 3 == 0) | ((x*(x+2)+17) % 5 == 0)) { + x += 2; + } else { + x++; + } + } + } + + printf("ok %d\n", x); + + return x; + } + ''' + self.do_benchmark('conditionals', src, 'ok', reps=TEST_REPS*5) + def test_fannkuch(self): src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read().replace( 'int n = argc > 1 ? atoi(argv[1]) : 0;', diff --git a/tests/test_core.py b/tests/test_core.py index 6a920a7b..62a061e2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -425,6 +425,11 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output, 'waka fleefl asdfasdfasdfasdf'.split(' ')) + def test_double_varargs(self): + test_path = path_from_root('tests', 'core', 'test_double_varargs') + src, output = (test_path + s for s in ('.c', '.out')) + self.do_run_from_file(src, output) + def test_i32_mul_precise(self): if self.emcc_args == None: return self.skip('needs ta2') @@ -966,6 +971,7 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_strings(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') test_path = path_from_root('tests', 'core', 'test_strings') src, output = (test_path + s for s in ('.in', '.out')) @@ -1160,7 +1166,6 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_longjmp_repeat(self): - if os.environ.get('EMCC_FAST_COMPILER') == '0': Settings.MAX_SETJMPS = 1 # todo: do this more strict thing in fastcomp too test_path = path_from_root('tests', 'core', 'test_longjmp_repeat') src, output = (test_path + s for s in ('.in', '.out')) self.do_run_from_file(src, output) @@ -1184,8 +1189,6 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_setjmp_many(self): - if os.environ.get('EMCC_FAST_COMPILER') != '0': return self.skip('todo in fastcomp: make MAX_SETJMPS take effect') - src = r''' #include <stdio.h> #include <setjmp.h> @@ -1197,9 +1200,42 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co return 0; } ''' - for num in [Settings.MAX_SETJMPS, Settings.MAX_SETJMPS+1]: - print num - self.do_run(src.replace('NUM', str(num)), '0\n' * num if num <= Settings.MAX_SETJMPS or not Settings.ASM_JS else 'build with a higher value for MAX_SETJMPS') + for maxx in [Settings.MAX_SETJMPS/2, Settings.MAX_SETJMPS, 2*Settings.MAX_SETJMPS]: + Settings.MAX_SETJMPS = maxx + for num in [maxx, maxx+1]: + print maxx, num + self.do_run(src.replace('NUM', str(num)), '0\n' * num if num <= Settings.MAX_SETJMPS or not Settings.ASM_JS else 'build with a higher value for MAX_SETJMPS') + + def test_setjmp_many_2(self): + if os.environ.get('EMCC_FAST_COMPILER') == '0': return self.skip('non-fastcomp do not hit the limit.') + + src = r''' +#include <setjmp.h> +#include <stdio.h> + +jmp_buf env; + +void luaWork(int d){ + int x; + printf("d is at %d\n", d); + + longjmp(env, 1); +} + +int main() +{ + const int ITERATIONS=25; + for(int i = 0; i < ITERATIONS; i++){ + if(!setjmp(env)){ + luaWork(i); + } + } + return 0; +} +''' + + self.do_run(src, r'''d is at 19 +too many setjmps in a function call, build with a higher value for MAX_SETJMPS''') def test_exceptions(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") @@ -1311,6 +1347,33 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co test_path = path_from_root('tests', 'core', 'test_exceptions_white_list') src, output = (test_path + s for s in ('.in', '.out')) self.do_run_from_file(src, output) + size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'orig.js') + + if os.environ.get('EMCC_FAST_COMPILER') != '0': + # check that an empty whitelist works properly (as in, same as exceptions disabled) + empty_output = path_from_root('tests', 'core', 'test_exceptions_white_list_empty.out') + + Settings.EXCEPTION_CATCHING_WHITELIST = [] + self.do_run_from_file(src, empty_output) + empty_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'empty.js') + + Settings.EXCEPTION_CATCHING_WHITELIST = ['fake'] + self.do_run_from_file(src, empty_output) + fake_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'fake.js') + + Settings.DISABLE_EXCEPTION_CATCHING = 1 + self.do_run_from_file(src, empty_output) + disabled_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'disabled.js') + + assert size - empty_size > 2000, [empty_size, size] # big change when we disable entirely + assert size - fake_size > 2000, [fake_size, size] + assert empty_size == fake_size, [empty_size, fake_size] + assert empty_size - disabled_size < 100, [empty_size, disabled_size] # full disable removes a tiny bit more + assert fake_size - disabled_size < 100, [disabled_size, fake_size] def test_exceptions_white_list_2(self): Settings.DISABLE_EXCEPTION_CATCHING = 2 @@ -2032,10 +2095,10 @@ def process(filename): if self.emcc_args and '-O2' in self.emcc_args: # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized) - code_start = 'var TOTAL_MEMORY = ' + code_start = 'var TOTAL_MEMORY' fail = fail[fail.find(code_start):] win = win[win.find(code_start):] - assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)]) def test_ssr(self): # struct self-ref src = ''' @@ -2461,6 +2524,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_hex(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for hex strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_hex') src, output = (test_path + s for s in ('.in', '.out')) @@ -2468,6 +2532,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_dec(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for decimal strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_dec') src, output = (test_path + s for s in ('.in', '.out')) @@ -2475,6 +2540,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_bin(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for binary strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_bin') src, output = (test_path + s for s in ('.in', '.out')) @@ -2482,6 +2548,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_oct(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for decimal strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_oct') src, output = (test_path + s for s in ('.in', '.out')) @@ -3204,7 +3271,7 @@ def process(filename): break else: raise Exception('Could not find symbol table!') - table = table[table.find('{'):table.rfind('}')+1] + table = table[table.find('{'):table.find('}')+1] # ensure there aren't too many globals; we don't want unnamed_addr assert table.count(',') <= 4 @@ -3713,6 +3780,7 @@ int main() self.do_run(src, expected) def test_transtrcase(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') test_path = path_from_root('tests', 'core', 'test_transtrcase') src, output = (test_path + s for s in ('.in', '.out')) @@ -4608,6 +4676,7 @@ int main(void) { ### 'Medium' tests def test_fannkuch(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') try: if self.run_name == 'slow2' or self.run_name == 'slow2asm': old_target = os.environ.get('EMCC_LLVM_TARGET') or '' @@ -4652,8 +4721,7 @@ int main(void) { if self.emcc_args is None: self.emcc_args = [] # dlmalloc auto-inclusion is only done if we use emcc self.banned_js_engines = [NODE_JS] # slower, and fail on 64-bit - Settings.CORRECT_SIGNS = 2 - Settings.CORRECT_SIGNS_LINES = ['src.cpp:' + str(i+4) for i in [4816, 4191, 4246, 4199, 4205, 4235, 4227]] + Settings.CORRECT_SIGNS = 1 Settings.TOTAL_MEMORY = 128*1024*1024 # needed with typed arrays src = open(path_from_root('system', 'lib', 'dlmalloc.c'), 'r').read() + '\n\n\n' + open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() @@ -5412,9 +5480,6 @@ def process(filename): ### Integration tests def test_ccall(self): - if self.emcc_args is not None and '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here, to test we export things right - post = ''' def process(filename): src = \'\'\' @@ -5458,6 +5523,11 @@ def process(filename): self.do_run_from_file(src, output, post_build=post) + if self.emcc_args is not None and '-O2' in self.emcc_args: + print 'with closure' + self.emcc_args += ['--closure', '1'] + self.do_run_from_file(src, output, post_build=post) + def test_pgo(self): if Settings.ASM_JS: return self.skip('PGO does not work in asm mode') diff --git a/tests/test_other.py b/tests/test_other.py index 349a16b4..03859a4e 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -203,6 +203,10 @@ Options that are modified or new in %s include: (['--typed-arrays', '1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), (['--typed-arrays', '2'], lambda generated: 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 selected'), (['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'), + ([], lambda generated: '// The Module object' in generated, 'without opts, comments in shell code'), + (['-O2'], lambda generated: '// The Module object' not in generated, 'with opts, no comments in shell code'), + (['-O2', '-g2'], lambda generated: '// The Module object' not in generated, 'with -g2, no comments in shell code'), + (['-O2', '-g3'], lambda generated: '// The Module object' in generated, 'with -g3, yes comments in shell code'), ]: print params, text self.clear() @@ -1027,10 +1031,93 @@ This pointer might make sense in another type signature: i: 0 Building.emar('cr', lib_name, [a_name + '.o', b_name + '.o']) # libLIB.a with a and b # a is in the lib AND in an .o, so should be ignored in the lib. We do still need b from the lib though - Building.emcc(main_name, ['-L.', '-lLIB', a_name+'.o', c_name + '.o'], output_filename='a.out.js') + Building.emcc(main_name, [a_name+'.o', c_name + '.o', '-L.', '-lLIB'], output_filename='a.out.js') self.assertContained('result: 62', run_js(os.path.join(self.get_dir(), 'a.out.js'))) + def test_link_group_asserts(self): + lib_src_name = os.path.join(self.get_dir(), 'lib.c') + open(lib_src_name, 'w').write('int x() { return 42; }') + + main_name = os.path.join(self.get_dir(), 'main.c') + open(main_name, 'w').write(r''' + #include <stdio.h> + int x(); + int main() { + printf("result: %d\n", x()); + return 0; + } + ''') + + Building.emcc(lib_src_name) # lib.c.o + lib_name = os.path.join(self.get_dir(), 'libLIB.a') + Building.emar('cr', lib_name, [lib_src_name + '.o']) # libLIB.a with lib.c.o + + def test(lib_args, err_expected): + output = Popen([PYTHON, EMCC, main_name, '-o', 'a.out.js'] + lib_args, stdout=PIPE, stderr=PIPE).communicate() + if err_expected: + self.assertContained(err_expected, output[1]) + else: + out_js = os.path.join(self.get_dir(), 'a.out.js') + assert os.path.exists(out_js), '\n'.join(output) + self.assertContained('result: 42', run_js(out_js)) + + test(['-Wl,--start-group', lib_name], '--start-group without matching --end-group') + test(['-Wl,--start-group', lib_name, '-Wl,--start-group'], 'Nested --start-group, missing --end-group?') + test(['-Wl,--end-group', lib_name, '-Wl,--start-group'], '--end-group without --start-group') + test(['-Wl,--start-group', lib_name, '-Wl,--end-group'], None) + + def test_circular_libs(self): + def tmp_source(name, code): + file_name = os.path.join(self.get_dir(), name) + open(file_name, 'w').write(code) + return file_name + + a = tmp_source('a.c', 'int z(); int x() { return z(); }') + b = tmp_source('b.c', 'int x(); int y() { return x(); } int z() { return 42; }') + c = tmp_source('c.c', 'int q() { return 0; }') + main = tmp_source('main.c', r''' + #include <stdio.h> + int y(); + int main() { + printf("result: %d\n", y()); + return 0; + } + ''') + + Building.emcc(a) # a.c.o + Building.emcc(b) # b.c.o + Building.emcc(c) # c.c.o + lib_a = os.path.join(self.get_dir(), 'libA.a') + Building.emar('cr', lib_a, [a + '.o', c + '.o']) # libA.a with a.c.o,c.c.o + lib_b = os.path.join(self.get_dir(), 'libB.a') + Building.emar('cr', lib_b, [b + '.o', c + '.o']) # libB.a with b.c.o,c.c.o + + args = ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1', main, '-o', 'a.out.js'] + libs_list = [lib_a, lib_b] + + # lib_a does not satisfy any symbols from main, so it will not be included, + # and there will be an unresolved symbol. + output = Popen([PYTHON, EMCC] + args + libs_list, stdout=PIPE, stderr=PIPE).communicate() + self.assertContained('error: unresolved symbol: x', output[1]) + + # -Wl,--start-group and -Wl,--end-group around the libs will cause a rescan + # of lib_a after lib_b adds undefined symbol "x", so a.c.o will now be + # included (and the link will succeed). + libs = ['-Wl,--start-group'] + libs_list + ['-Wl,--end-group'] + output = Popen([PYTHON, EMCC] + args + libs, stdout=PIPE, stderr=PIPE).communicate() + out_js = os.path.join(self.get_dir(), 'a.out.js') + assert os.path.exists(out_js), '\n'.join(output) + self.assertContained('result: 42', run_js(out_js)) + + # -( and -) should also work. + args = ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1', main, '-o', 'a2.out.js'] + libs = ['-Wl,-('] + libs_list + ['-Wl,-)'] + output = Popen([PYTHON, EMCC] + args + libs, stdout=PIPE, stderr=PIPE).communicate() + out_js = os.path.join(self.get_dir(), 'a2.out.js') + assert os.path.exists(out_js), '\n'.join(output) + self.assertContained('result: 42', run_js(out_js)) + def test_redundant_link(self): lib = "int mult() { return 1; }" lib_name = os.path.join(self.get_dir(), 'libA.c') @@ -2135,7 +2222,7 @@ void wakaw::Cm::RasterBase<wakaw::watwat::Polocator?>(unsigned int*, unsigned in test_js_closure_0 = open(path_from_root('tests', 'Module-exports', 'test.js')).read() # Check that test.js compiled with --closure 0 contains "module['exports'] = Module;" - assert "module['exports'] = Module;" in test_js_closure_0 + assert ("module['exports'] = Module;" in test_js_closure_0) or ('module["exports"]=Module' in test_js_closure_0) # Check that main.js (which requires test.js) completes successfully when run in node.js # in order to check that the exports are indeed functioning correctly. @@ -2791,3 +2878,14 @@ int main(int argc, char **argv) { assert sizes[0] == 7 # no aliasing, all unique, fat tables assert sizes[1] == 3 # aliased once more + def test_bad_export(self): + for m in ['', ' ']: + self.clear() + cmd = [PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'EXPORTED_FUNCTIONS=["' + m + '_main"]'] + print cmd + stdout, stderr = Popen(cmd, stderr=PIPE).communicate() + if m: + assert 'function requested to be exported, but not implemented: " _main"' in stderr, stderr + else: + self.assertContained('hello, world!', run_js('a.out.js')) + diff --git a/tests/test_sanity.py b/tests/test_sanity.py index 3d3da523..3ebd49b6 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -240,7 +240,7 @@ class sanity(RunnerCore): os.chmod(path_from_root('tests', 'fake', 'bin', 'llc'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) os.chmod(path_from_root('tests', 'fake', 'bin', 'clang++'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) try_delete(SANITY_FILE) - output = self.check_working(EMCC, 'did not see a source tree above LLVM_DIR, could not verify version numbers match') + output = self.check_working(EMCC, 'did not see a source tree above the LLVM root directory') VERSION_WARNING = 'Emscripten, llvm and clang versions do not match, this is dangerous' diff --git a/tests/webidl/output.txt b/tests/webidl/output.txt index b874d928..c029935f 100644 --- a/tests/webidl/output.txt +++ b/tests/webidl/output.txt @@ -1,6 +1,7 @@ Parent:42 * 84 +object c1 Parent:7 Child1:7 @@ -10,6 +11,7 @@ Child1:7 588 14 28 +Child1::parentFunc(90) c1 v2 Parent:16 Child1:15 diff --git a/tests/webidl/post.js b/tests/webidl/post.js index 444efcd1..5376f27b 100644 --- a/tests/webidl/post.js +++ b/tests/webidl/post.js @@ -5,6 +5,8 @@ var sme = new Module.Parent(42); sme.mulVal(2); Module.print('*') Module.print(sme.getVal()); +sme.parentFunc(90); +Module.print(typeof sme.getAsConst()); Module.print('c1'); @@ -16,6 +18,7 @@ Module.print(c1.getValSqr()); Module.print(c1.getValSqr(3)); Module.print(c1.getValTimes()); // default argument should be 1 Module.print(c1.getValTimes(2)); +c1.parentFunc(90); Module.print('c1 v2'); diff --git a/tests/webidl/test.h b/tests/webidl/test.h index 903f8f78..5d13846c 100644 --- a/tests/webidl/test.h +++ b/tests/webidl/test.h @@ -10,6 +10,8 @@ public: Parent(Parent *p, Parent *q); // overload constructor int getVal() { return value; }; // inline should work just fine here, unlike Way 1 before void mulVal(int mul); + void parentFunc() {} + const Parent *getAsConst() { return NULL; } }; class Child1 : public Parent { @@ -19,6 +21,7 @@ public: int getValSqr() { return value*value; } int getValSqr(int more) { return value*value*more; } int getValTimes(int times=1) { return value*times; } + void parentFunc(int x) { printf("Child1::parentFunc(%d)\n", x); } }; // Child2 has vtable, parent does not. Checks we cast child->parent properly - (Parent*)child is not a no-op, must offset diff --git a/tests/webidl/test.idl b/tests/webidl/test.idl index 98ab5070..8ee82b76 100644 --- a/tests/webidl/test.idl +++ b/tests/webidl/test.idl @@ -5,12 +5,15 @@ interface Parent { void Parent(long val); long getVal(); void mulVal(long mul); + void parentFunc(); + [Const] Parent getAsConst(); }; interface Child1 { void Child1(optional long val); long getValSqr(optional long more); long getValTimes(optional long times=1); + void parentFunc(long x); // redefinition, name collides with parent }; Child1 implements Parent; |