#include #include "rbx/test/DataModelFixture.h" #include "rbx/test/Base.UnitTest.Lib.h" #include "rbx/test/ScopedFastFlagSetting.h" #include "script/ScriptContext.h" #include "script/Script.h" #include "util/ProtectedString.h" #include "util/udim.h" #include "util/axes.h" #include "v8datamodel/PartInstance.h" #include "v8datamodel/Surface.h" #include "v8datamodel/ModelInstance.h" #include "rbx/make_shared.h" #include "Util/CellID.h" #include "script/LuaArguments.h" enum TestEnum { NoReverb = 0, GenericReverb, PaddedCell, Room, Bathroom, LivingRoom, StoneRoom, Auditorium, ConcertHall, Cave, Arena, Hangar, }; namespace RBX { template<> bool RBX::StringConverter::convertToValue(const std::string& text, TestEnum& value) { return RBX::Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } namespace Reflection { template<> EnumDesc::EnumDesc() :EnumDescriptor("TestEnum") { addPair(NoReverb, "NoReverb"); addPair(GenericReverb, "GenericReverb"); addPair(PaddedCell, "PaddedCell"); addPair(Room, "Room"); addPair(Bathroom, "Bathroom"); addPair(LivingRoom, "LivingRoom"); addPair(StoneRoom, "StoneRoom"); addPair(Auditorium, "Auditorium"); addPair(ConcertHall, "ConcertHall"); addPair(Cave, "Cave"); addPair(Arena, "Arena"); addPair(Hangar, "Hangar"); } template<> TestEnum& Variant::convert(void) { return genericConvert(); } }} RBX_REGISTER_ENUM(TestEnum); using namespace RBX; extern const char* const sLuaReflectionTest; class LuaReflectionTest : public DescribedCreatable { static double hypot(double x, double y) { return sqrt(x*x + y*y); } public: rbx::signal sig; boost::function hypotCallback; int dummy(int x, int y, int z) { return 11; } std::string dummyString(std::string s) { return s; } shared_ptr instance(shared_ptr s) { return s; } shared_ptr tuple(int x, int y, int z) { shared_ptr result(new RBX::Reflection::Tuple(3)); result->values[0] = x; result->values[1] = y; result->values[2] = z; return result; } shared_ptr tupleTest(shared_ptr inout) { // We could just pass inout as the return value. However, that is unrealistic. // In most cases we'd return a new tuple #if 0 // TODO: Why won't this build on Mac? return rbx::make_shared(inout); #else shared_ptr result = rbx::make_shared(inout->values.size()); for (size_t i=0; ivalues.size(); ++i) result->values[i] = inout->values[i]; return result; #endif } shared_ptr enumtuple(int x, int y, int z) { shared_ptr result(new RBX::Reflection::Tuple(3)); result->values[0] = RBX::PartInstance::SYMETRIC; result->values[1] = RBX::PartInstance::SYMETRIC; result->values[2] = RBX::PartInstance::PLATE; return result; } void voidCall() { } TestEnum dummyEnum(TestEnum x, TestEnum y, TestEnum z) { return Cave; } double callHypot(double x, double y) { return hypotCallback(x, y); } LuaReflectionTest() { setName("LuaReflectionTest"); hypotCallback = hypot; } void yieldInt1(int arg1, boost::function resumeFunction, boost::function errorFunction) { resumeFunction(2 * arg1); } void yieldVoid1(int arg1, boost::function resumeFunction, boost::function errorFunction) { resumeFunction(); } void asyncDivide(double a, double b, boost::function resumeFunction, boost::function errorFunction) { if (b != 0.0) DataModel::get(this)->submitTask(boost::bind(resumeFunction, a/b), DataModelJob::Write); else DataModel::get(this)->submitTask(boost::bind(errorFunction, "failure"), DataModelJob::Write); } void asyncImmediateDivide(double a, double b, boost::function resumeFunction, boost::function errorFunction) { #if 1 if (b != 0.0) resumeFunction(a/b); else errorFunction("failure"); #else if (b == 0.0) throw std::runtime_error("failure"); resumeFunction(a/b); #endif } Reflection::Variant variant(Reflection::Variant value) { return value; } int add(lua_State* L) { // skip "this" Lua::LuaArguments args(L, 1); double x; if (!args.getDouble(1, x)) throw std::runtime_error("Argument 1 missing or nil"); double y; if (!args.getDouble(2, y)) throw std::runtime_error("Argument 2 missing or nil"); lua_pushnumber(L, x + y); return 1; } }; const char* const sLuaReflectionTest = "LuaReflectionTest"; static Reflection::BoundCallbackDesc hypotCallback("HypotCallback", &LuaReflectionTest::hypotCallback, "x", "y"); static Reflection::BoundFuncDesc callHypot(&LuaReflectionTest::callHypot, "CallHypot", "x", "y", Security::None); static Reflection::BoundFuncDesc callDummy(&LuaReflectionTest::dummy, "Dummy", "x", "y", "z", Security::None); static Reflection::BoundFuncDesc(shared_ptr)> func_TupleTest(&LuaReflectionTest::tupleTest, "TupleTest", "inout", Security::None); static Reflection::BoundFuncDesc(int, int, int)> callTuple(&LuaReflectionTest::tuple, "Tuple", "x", "y", "z", Security::None); static Reflection::BoundFuncDesc(int, int, int)> callEnumTuple(&LuaReflectionTest::enumtuple, "EnumTuple", "x", "y", "z", Security::None); static Reflection::BoundFuncDesc callVoid(&LuaReflectionTest::voidCall, "Void", Security::None); static Reflection::BoundFuncDesc callString(&LuaReflectionTest::dummyString, "String", "s", "HelloWorld", Security::None); static Reflection::BoundFuncDesc(shared_ptr)> callInstance(&LuaReflectionTest::instance, "Instance", "instance", Security::None); static Reflection::BoundFuncDesc callDummyEnum(&LuaReflectionTest::dummyEnum, "EnumDummy", "x", "y", "z", Security::None); static Reflection::BoundFuncDesc desc_Variant(&LuaReflectionTest::variant, "Variant", "value", Security::None); static Reflection::EventDesc event_Sig(&LuaReflectionTest::sig, "Sig", "arg"); static Reflection::BoundYieldFuncDesc func_YieldVoid1(&LuaReflectionTest::yieldVoid1, "YieldVoid1", "arg1", Security::None); static Reflection::BoundYieldFuncDesc func_YieldInt1(&LuaReflectionTest::yieldInt1, "YieldInt1", "arg1", Security::None); static Reflection::BoundYieldFuncDesc func_AsyncSuccess(&LuaReflectionTest::asyncDivide, "AsyncDivide", "a", "b", Security::None); static Reflection::BoundYieldFuncDesc func_AsyncImmediateSuccess(&LuaReflectionTest::asyncImmediateDivide, "AsyncImmediateDivide", "a", "b", Security::None); static Reflection::CustomBoundFuncDesc func_Add(&LuaReflectionTest::add, "Add", "x", "y", Security::None); RBX_REGISTER_CLASS(LuaReflectionTest); BOOST_AUTO_TEST_SUITE(LuaReflection) BOOST_FIXTURE_TEST_CASE(Inst, DataModelFixture) { Test::PerformanceTestFixture fixture; std::auto_ptr result = execute ( "l = Instance.new('LuaReflectionTest', workspace) \n" "return l:Instance(l) " ); BOOST_REQUIRE_EQUAL(result->values.size(), 1); BOOST_CHECK_NE(result->at(0).convert< shared_ptr >(), shared_ptr()); } BOOST_FIXTURE_TEST_CASE(ypcallSuccess, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 100; ++i) { std::auto_ptr result = execute ( "return ypcall(function(a, b) return a, b, a .. b end, 'a', 'b') " ); BOOST_REQUIRE_EQUAL(result->values.size(), 4); BOOST_CHECK_EQUAL(result->values.at(0).get(), true); BOOST_CHECK_EQUAL(result->values.at(1).get(), "a"); BOOST_CHECK_EQUAL(result->values.at(2).get(), "b"); BOOST_CHECK_EQUAL(result->values.at(3).get(), "ab"); } }; BOOST_FIXTURE_TEST_CASE(ypcallFailure, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 100; ++i) { std::auto_ptr result = execute ( "return ypcall(function() error('failure') end) " ); BOOST_REQUIRE_EQUAL(result->values.size(), 2); BOOST_CHECK_EQUAL(result->values.at(0).get(), false); BOOST_CHECK(result->values.at(1).get().find("failure") != std::string::npos); } }; static shared_ptr check_pcall_aux(RBX::Reflection::Tuple* args, double a, double b, shared_ptr event) { BOOST_REQUIRE_GE(args->values.size(), (size_t) 1); BOOST_CHECK_EQUAL(args->at(0).get(), b != 0.0); BOOST_REQUIRE_EQUAL(args->values.size(), (size_t) 2); if (b == 0.0) { std::string message = args->at(1).get(); BOOST_CHECK(message.find("failure") != std::string::npos); } else { double result = args->at(1).get(); BOOST_CHECK_EQUAL(result, a/b); } if (event) event->Set(); return shared_ptr(); }; static shared_ptr check_pcall(shared_ptr args, double a, double b, shared_ptr event) { BOOST_REQUIRE_GE(args->values.size(), (size_t) 1); BOOST_CHECK_EQUAL(args->at(0).get(), b != 0.0); BOOST_REQUIRE_EQUAL(args->values.size(), (size_t) 2); if (b == 0.0) { std::string message = args->at(1).get(); BOOST_CHECK(message.find("failure") != std::string::npos); } else { double result = args->at(1).get(); BOOST_CHECK_EQUAL(result, a/b); } if (event) event->Set(); return shared_ptr(); }; static shared_ptr check_old_style_failure(shared_ptr args, shared_ptr event) { BOOST_REQUIRE_EQUAL(args->values.size(), (size_t) 1); std::string message = args->at(0).get(); BOOST_CHECK(message.find("failure") != std::string::npos); if (event) event->Set(); return shared_ptr(); }; BOOST_FIXTURE_TEST_CASE(ypcallAsyncImmediateSuccess, DataModelFixture) { std::auto_ptr result = execute ( "l = Instance.new('LuaReflectionTest') \n" "l.Parent = Workspace \n" " return ypcall(function() l:AsyncImmediateDivide(2, 3) return l:AsyncImmediateDivide(1, 2) end) " ); check_pcall_aux(result.get(), 1, 2, shared_ptr()); }; BOOST_FIXTURE_TEST_CASE(ypcallAsyncImmediateFailure, DataModelFixture) { std::auto_ptr result = execute ( "l = Instance.new('LuaReflectionTest') \n" "l.Parent = Workspace \n" "return ypcall(function() l:AsyncImmediateDivide(4, 4) return l:AsyncImmediateDivide(2, 0) end) " ); check_pcall_aux(result.get(), 2, 0, shared_ptr()); }; BOOST_FIXTURE_TEST_CASE(ypcallAsyncSuccess, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 100; ++i) { shared_ptr event(rbx::make_shared(true)); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&check_pcall, _1, 1, 2, event))); execute ( "callback = ... \n" "callback(ypcall(function() l:AsyncDivide(2, 3) return l:AsyncDivide(1, 2) end)) ", args ); BOOST_REQUIRE(event->Wait(3000)); } }; BOOST_FIXTURE_TEST_CASE(ypcallAsyncSuccessNested, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 100; ++i) { shared_ptr event(rbx::make_shared(true)); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&check_pcall, _1, 1, 2, event))); execute ( "callback = ...\n" "callback(ypcall(function()\n" "s, two = ypcall(function() wait(0.01) return l:AsyncDivide(2, 1) end)\n" "return l:AsyncDivide(1, two)\n" "end)) ", args ); BOOST_REQUIRE(event->Wait(3000)); } }; BOOST_FIXTURE_TEST_CASE(ypcallBindableFunction, DataModelFixture) { execute("f = Instance.new('BindableFunction') f.Parent = Workspace"); for (int i = 0; i < 10; ++i) { shared_ptr event(rbx::make_shared(true)); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&check_pcall, _1, 1, 2, event))); execute( "callback = ...\n" "Delay(0.3, function() f.OnInvoke = function() end end)\n" "callback(ypcall(function (a , b)\n" " f:Invoke()\n" " f.OnInvoke = nil\n" " return a/b\n" "end, 1, 2))\n", args ); BOOST_CHECK(event->Wait(5000)); } }; BOOST_FIXTURE_TEST_CASE(ypcallAsyncFailure, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 100; ++i) { shared_ptr event(rbx::make_shared(true)); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&check_pcall, _1, 2, 0, event))); execute ( "callback = ... \n" "callback(ypcall(function() l:AsyncDivide(2, 3) return l:AsyncDivide(2, 0) end)) ", args ); BOOST_REQUIRE(event->Wait(3000)); } }; BOOST_FIXTURE_TEST_CASE(AsyncFailure, DataModelFixture) { shared_ptr event(rbx::make_shared(true)); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&check_old_style_failure, _1, event))); execute ( "callback = ... \n" "l = Instance.new('LuaReflectionTest', Workspace) " \ "callback(l:AsyncDivide(2, 0)) ", args ); BOOST_CHECK(!event->Wait(500)); // Make sure thread execution is halted because of failure }; static shared_ptr yieldResults(shared_ptr args, RBX::CEvent* event) { BOOST_REQUIRE_EQUAL(args->values.size(),2); BOOST_CHECK_EQUAL(args->at(1).get(), 6); BOOST_CHECK_EQUAL(args->at(0).type(), Reflection::Type::singleton()); event->Set(); return shared_ptr(); }; BOOST_FIXTURE_TEST_CASE(YieldFunctions, DataModelFixture) { RBX::CEvent event(true); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&yieldResults, _1, &event))); execute ( "result = ... \n" "l = Instance.new('LuaReflectionTest') \n" "l.Parent = Workspace \n" "a = l:YieldVoid1(7)\n" "b = l:YieldInt1(3) \n" "result(a, b)", args ); event.Wait(); } static shared_ptr yieldResults2(shared_ptr args, RBX::CEvent* event) { BOOST_REQUIRE_EQUAL(args->values.size(), 1); BOOST_CHECK_EQUAL(args->at(0).type(), Reflection::Type::singleton()); event->Set(); return shared_ptr(); }; BOOST_FIXTURE_TEST_CASE(YieldFunctions2, DataModelFixture) { RBX::CEvent event(true); RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(boost::bind(&yieldResults2, _1, &event))); execute ( "result = ... \n" "l = Instance.new('LuaReflectionTest') \n" "l.Parent = Workspace \n" "a = l:YieldVoid1(7)\n" "result(a)", args ); event.Wait(); } #ifndef _DEBUG BOOST_FIXTURE_TEST_CASE(PerfAsyncImmediateSuccess, DataModelFixture) { execute("l = Instance.new('LuaReflectionTest') l.Parent = Workspace"); for (int i = 0; i < 4000; ++i) { std::auto_ptr result = execute("return true, l:AsyncImmediateDivide(1, 2)"); check_pcall_aux(result.get(), 1, 2, shared_ptr()); } }; BOOST_FIXTURE_TEST_CASE(Perf_pcall, DataModelFixture) { std::auto_ptr result = execute ( "for i = 1, 1000000 do \n" " _, j = pcall(function(x) hello = 'hello' return x end, i) \n" "end \n" "return j, hello " ); BOOST_REQUIRE_EQUAL(result->values.size(), 2); BOOST_CHECK_EQUAL(result->values.at(0).convert(), 1000000); BOOST_CHECK_EQUAL(result->values.at(1).convert(), "hello"); }; // ypcall is slower than pcall. BOOST_FIXTURE_TEST_CASE(Perf_ypcall, DataModelFixture) { std::auto_ptr result = execute ( "for i = 1, 1000000 do \n" " _, j = ypcall(function(x) hello = 'hello' return x end, i) \n" "end \n" "return j, hello " ); BOOST_REQUIRE_EQUAL(result->values.size(), 2); BOOST_CHECK_EQUAL(result->values.at(0).convert(), 1000000); BOOST_CHECK_EQUAL(result->values.at(1).convert(), "hello"); }; #endif BOOST_FIXTURE_TEST_CASE(TupleTest, DataModelFixture) { std::auto_ptr result; try { result = execute("return Instance.new('LuaReflectionTest'):TupleTest('a','b','c',1,2,3)"); } catch (std::exception&) { BOOST_WARN_MESSAGE(false, "Tuples not properly supported"); BOOST_CHECK(true); // dummy test return; } BOOST_REQUIRE_EQUAL(result->values.size(), 6); BOOST_CHECK_EQUAL(result->values.at(0).convert(),"a"); BOOST_CHECK_EQUAL(result->values.at(1).convert(),"b"); BOOST_CHECK_EQUAL(result->values.at(2).convert(),"c"); BOOST_CHECK_EQUAL(result->values.at(3).convert(),1); BOOST_CHECK_EQUAL(result->values.at(4).convert(),2); BOOST_CHECK_EQUAL(result->values.at(5).convert(),3); } BOOST_FIXTURE_TEST_CASE(EmptyDataModel, DataModelFixture) { DataModel::LegacyLock lock(dataModel, DataModelJob::Write); run(); } BOOST_FIXTURE_TEST_CASE(Callbacks, DataModelFixture) { std::auto_ptr result = execute ( "l = Instance.new('LuaReflectionTest') \n" "l.Parent = workspace \n" "a = l:CallHypot(3, 4) \n" "l.HypotCallback = function(x,y) return x*x + y*y end \n" "b = l:CallHypot(3, 4) \n" "return a, b" ); BOOST_CHECK_CLOSE(result->at(0).convert(), 5.0, 0.001); BOOST_CHECK_CLOSE(result->at(1).convert(), 25.0, 0.001); result = execute ( "l = workspace.LuaReflectionTest \n" "b = l:CallHypot(3, 4) \n" "return b" ); BOOST_CHECK_CLOSE(result->at(0).convert(), 25.0, 0.001); } static int eventValue = 0; static shared_ptr check_Event(shared_ptr args) { eventValue = args->at(0).get(); return shared_ptr(); } BOOST_FIXTURE_TEST_CASE(Events, DataModelFixture) { RBX::Reflection::Tuple args(1); args.values[0] = shared_ptr(new RBX::Lua::GenericFunction(&check_Event)); std::auto_ptr result = execute ( "l = Instance.new('LuaReflectionTest', workspace)\n" "l.Sig:connect(...)\n" "return l\n", args ); shared_ptr i = shared_dynamic_cast(result->at(0).convert< shared_ptr >()); DataModel::LegacyLock lock(dataModel, DataModelJob::Write); i->sig(1); BOOST_CHECK_EQUAL(eventValue, 1); } static int event2Value = 0; static double check_Event2(double x, double) { event2Value = x; return 0; } BOOST_FIXTURE_TEST_CASE(Events2, DataModelFixture) { std::auto_ptr result = execute("return Instance.new('LuaReflectionTest', workspace)\n"); DataModel::LegacyLock lock(dataModel, DataModelJob::Write); run(); shared_ptr i = Creatable::create(); i->hypotCallback = check_Event2; i->setName("FooBar"); i->setParent(dataModel->getWorkspace()); shared_ptr script = Creatable::create