Files
2025-09-18 17:55:52 -04:00

957 lines
31 KiB
C++

#include "stdafx.h"
#include "V8DataModel/Test.h"
#include "V8DataModel/PhysicsSettings.h"
#include "Network/../../NetworkSettings.h"
#include "V8DataModel/TimerService.h"
#include "Script/script.h"
#include "Script/ScriptContext.h"
#include "util/RunStateOwner.h"
#include "v8datamodel/DataModel.h"
#include <boost/format.hpp>
#include <ios>
#define BOOST_FILESYSTEM_NO_DEPRECATED
#include <boost/filesystem.hpp>
namespace RBX
{
const char* const sFunctionalTest = "FunctionalTest";
const char* const sTestService = "TestService";
namespace Reflection
{
template<>
EnumDesc<FunctionalTest::Result>::EnumDesc()
:EnumDescriptor("FunctionalTestResult")
{
addPair(FunctionalTest::Passed, "Passed");
addPair(FunctionalTest::Warning, "Warning");
addPair(FunctionalTest::Error, "Error");
}
template<>
FunctionalTest::Result& Variant::convert<FunctionalTest::Result>(void)
{
return genericConvert<FunctionalTest::Result>();
}
}
template<>
bool RBX::StringConverter<FunctionalTest::Result>::convertToValue(const std::string& text, FunctionalTest::Result& value)
{
return Reflection::EnumDesc<FunctionalTest::Result>::singleton().convertToValue(text.c_str(),value);
}
REFLECTION_BEGIN();
Reflection::BoundProp<std::string> descDescription("Description", "Data", &FunctionalTest::description);
Reflection::BoundProp<double> descTimeout("Timeout", "Settings", &FunctionalTest::timeout, Reflection::PropertyDescriptor::LEGACY);
Reflection::BoundProp<bool> descPhysicsEnvironmentalThrottle("PhysicsEnvironmentalThrottle", "Physics", &FunctionalTest::isPhysicsThrottled, Reflection::PropertyDescriptor::LEGACY);
Reflection::BoundProp<bool> descAllowSleep("AllowSleep", "Physics", &FunctionalTest::allowSleep, Reflection::PropertyDescriptor::LEGACY);
Reflection::BoundProp<bool> descIs30FpsThrottleEnabled("Is30FpsThrottleEnabled", "Physics", &FunctionalTest::is30FpsThrottleEnabled, Reflection::PropertyDescriptor::LEGACY);
Reflection::BoundProp<bool> prop_HasMigratedSettingsToTestService("HasMigratedSettingsToTestService", "Settings", &FunctionalTest::hasMigratedSettingsToTestService, Reflection::PropertyDescriptor::STREAMING);
static Reflection::BoundFuncDesc<FunctionalTest, void(std::string)> descWarn(&FunctionalTest::warn, "Warn", "message", "", Security::None);
static Reflection::BoundFuncDesc<FunctionalTest, void(std::string)> descPassed(&FunctionalTest::pass, "Pass", "message", "", Security::None);
static Reflection::BoundFuncDesc<FunctionalTest, void(std::string)> descFailed(&FunctionalTest::error, "Error", "message", "", Security::None);
static Reflection::BoundFuncDesc<FunctionalTest, void(std::string)> descPassedDeprecated(&FunctionalTest::pass, "Passed", "message", "", Security::None);
static Reflection::BoundFuncDesc<FunctionalTest, void(std::string)> descFailedDeprecated(&FunctionalTest::error, "Failed", "message", "", Security::None);
Reflection::BoundProp<std::string> prop_Description("Description", "Data", &TestService::description);
Reflection::BoundProp<double> prop_Timeout("Timeout", "Settings", &TestService::timeout);
Reflection::BoundProp<bool> prop_IsPhysicsEnvironmentalThrottled("IsPhysicsEnvironmentalThrottled", "Physics", &TestService::isPhysicsThrottled);
Reflection::BoundProp<bool> prop_IsSleepAllowed("IsSleepAllowed", "Physics", &TestService::allowSleep);
Reflection::BoundProp<bool> prop_Is30FpsThrottleEnabled("Is30FpsThrottleEnabled", "Physics", &TestService::is30FpsThrottleEnabled);
Reflection::BoundProp<bool> prop_AutoStart("AutoRuns", "Physics", &TestService::autoRuns);
Reflection::BoundProp<int> prop_NumberOfPlayers("NumberOfPlayers", "Settings", &TestService::numberOfPlayers);
Reflection::BoundProp<double> prop_SimulateLag("SimulateSecondsLag", "Settings", &TestService::lagSimulation);
#ifdef RBX_TEST_BUILD
Reflection::BoundProp<float> prop_PhysicsSendRate("PhysicsSendRate", "Settings", &TestService::physicsSendRate);
#endif
Reflection::BoundProp<int, Reflection::READONLY> TestService::TestCount("TestCount", "Results", &TestService::testCount);
Reflection::BoundProp<int, Reflection::READONLY> TestService::WarnCount("WarnCount", "Results", &TestService::warnCount);
Reflection::BoundProp<int, Reflection::READONLY> TestService::ErrorCount("ErrorCount", "Results", &TestService::errorCount);
static Reflection::BoundYieldFuncDesc<TestService, void()> func_Run(&TestService::run, "Run", Security::Plugin);
static Reflection::BoundFuncDesc<TestService, void(std::string, shared_ptr<Instance>, int)> func_Message(&TestService::message, "Message", "text", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(std::string, shared_ptr<Instance>, int)> func_Checkpoint(&TestService::checkpoint, "Checkpoint", "text", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(bool, std::string, shared_ptr<Instance>, int)> func_Warn(&TestService::warn, "Warn", "condition", "description", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(bool, std::string, shared_ptr<Instance>, int)> func_Check(&TestService::check, "Check", "condition", "description", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(bool, std::string, shared_ptr<Instance>, int)> func_Require(&TestService::require2, "Require", "condition", "description", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(std::string, shared_ptr<Instance>, int)> func_Error(&TestService::error, "Error", "description", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void(std::string, shared_ptr<Instance>, int)> func_Fails(&TestService::fail, "Fail", "description", "source", shared_ptr<Instance>(), "line", 0, Security::None);
static Reflection::BoundFuncDesc<TestService, void()> func_Done(&TestService::done, "Done", Security::None);
// DoCommand calls getVerb instead of getWhitelistVerb, and thus is an easy exploit.
#ifdef RBX_TEST_BUILD
static Reflection::BoundFuncDesc<TestService, shared_ptr<const Reflection::ValueArray>()> func_GetCommandNames(&TestService::getCommandNames, "GetCommandNames", Security::LocalUser);
static Reflection::BoundFuncDesc<TestService, void(std::string)> func_DoCommand(&TestService::doCommand, "DoCommand", "name", Security::LocalUser);
static Reflection::BoundFuncDesc<TestService, bool(std::string)> func_IsCommandEnabled(&TestService::isCommandEnabled, "IsCommandEnabled", "name", Security::LocalUser);
static Reflection::BoundFuncDesc<TestService, bool(std::string)> func_IsCommandChecked(&TestService::isCommandChecked, "IsCommandChecked", "name", Security::LocalUser);
#endif
static Reflection::RemoteEventDesc<TestService, void(std::string, shared_ptr<Instance>, int)> desc_serverCollectResult(&TestService::serverCollectResultSignal, "ServerCollectResult", "text", "script", "line", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
static Reflection::RemoteEventDesc<TestService, void(bool, std::string, shared_ptr<Instance>, int)> desc_serverCollectConditionalResult(&TestService::serverCollectConditionalResultSignal, "ServerCollectConditionalResult", "condition", "text", "script", "line", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
REFLECTION_END();
TestService::TestService()
:description("")
,timeout(10)
,isPhysicsThrottled(true)
,allowSleep(true)
,is30FpsThrottleEnabled(true)
,running(false)
,runCount(0)
,testCount(0)
,warnCount(0)
,errorCount(0)
,autoRuns(true)
,numberOfPlayers(0)
,lagSimulation(0.0)
,clientTestService(false)
,scriptCount(0)
{
setName(sTestService);
RBX::NetworkSettings &ns = RBX::NetworkSettings::singleton();
physicsSendRate = ns.getPhysicsSendRate();
}
TestService::~TestService()
{
}
void TestService::message( std::string text, shared_ptr<Instance> source, int line )
{
if(isAClient())
desc_serverCollectResult.fireAndReplicateEvent(this, text, source, line);
if (onMessage)
onMessage(text, source, line);
else
output(RBX::MESSAGE_INFO, source, line, text);
}
void TestService::onRemoteResult( std::string text, shared_ptr<Instance> source, int line )
{
if (!isAClient())
{
RBXASSERT(numberOfPlayers > 0);
onMessage(text, source, line);
}
}
void TestService::checkpoint( std::string text, shared_ptr<Instance> source, int line )
{
if (onCheckpoint)
onCheckpoint(text, source, line);
else
output(RBX::MESSAGE_INFO, source, line, "checkpoint " + text);
}
void TestService::warn( bool condition, std::string description, shared_ptr<Instance> source, int line )
{
incrementTest();
if (!condition)
incrementWarn();
if (onWarn)
onWarn(condition, description, source, line);
else if (!condition)
output(RBX::MESSAGE_WARNING, source, line, description);
}
void TestService::check( bool condition, std::string description, shared_ptr<Instance> source, int line )
{
if(isAClient())
desc_serverCollectConditionalResult.fireAndReplicateEvent(this, condition, description, source, line);
incrementTest();
if (!condition)
incrementError();
if (onCheck)
onCheck(condition, description, source, line);
else if (!condition)
output(RBX::MESSAGE_ERROR, source, line, description);
}
void TestService::onRemoteConditionalResult( bool condition, std::string description, shared_ptr<Instance> source, int line )
{
incrementTest();
if (!condition)
incrementError();
onCheck(condition, description, source, line);
}
void TestService::error( std::string description, shared_ptr<Instance> source, int line )
{
incrementTest();
incrementError();
if (onCheck)
onCheck(false, description, source, line);
else
output(RBX::MESSAGE_ERROR, source, line, description);
}
void TestService::require2( bool condition, std::string description, shared_ptr<Instance> source, int line )
{
incrementTest();
if (!condition)
incrementError();
if (onCheck)
onCheck(condition, description, source, line);
else if (!condition)
output(RBX::MESSAGE_ERROR, source, line, "fatal " + description);
if (!condition)
done();
}
void TestService::fail( std::string description, shared_ptr<Instance> source, int line )
{
incrementTest();
incrementError();
if (onCheck)
onCheck(false, description, source, line);
else
output(RBX::MESSAGE_ERROR, source, line, "fatal " + description);
done();
}
void TestService::done()
{
if (!running)
return;
running = false;
if (onDone)
onDone();
else if (testCount == 0)
RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "TestService: Doesn't include any assertions");
else
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "TestService: Run completed, tests: %u, warns: %u, errors: %u", testCount, warnCount, errorCount);
stop();
}
void TestService::stillWaiting(int runCount, double timeout)
{
if (runCount != this->runCount)
return;
if (!running)
return;
if (onStillWaiting)
onStillWaiting(timeout);
else
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "TestService: Waiting a total of %g seconds for the test to end", timeout);
}
void TestService::onTimeout(int runCount, double timeout)
{
if (runCount != this->runCount)
return;
if (!running)
return;
running = false;
incrementTest();
incrementError();
if (onCheck)
onCheck(false, RBX::format("Tests failed to complete in %g seconds", timeout), shared_ptr<Instance>(), 0);
else
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "TestService: Tests failed to complete in %g seconds", timeout);
if (onDone)
onDone();
stop();
}
void TestService::run(boost::function<void()> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (running)
throw std::runtime_error("Run still in progress");
// wire up the callback
this->resumeFunction = resumeFunction;
running = true;
runCount++;
testCount = 0;
warnCount = 0;
errorCount = 0;
raisePropertyChanged(TestCount);
raisePropertyChanged(WarnCount);
raisePropertyChanged(ErrorCount);
setConfiguration();
// wire up a timeout
{
TimerService* timerService = ServiceProvider::find<TimerService>(this);
if (!timerService)
throw std::runtime_error("Unable to get TimerService");
// Print out a message in case somebody is impatient
if (timeout > 10.0)
timerService->delay(boost::bind(&TestService::stillWaiting, shared_from(this), runCount, timeout), 5.0);
timerService->delay(boost::bind(&TestService::onTimeout, shared_from(this), runCount, timeout), this->timeout);
}
// run!
{
RunService* runService = ServiceProvider::find<RunService>(this);
if (!runService)
throw std::runtime_error("Unable to get RunService");
wasRunning = runService->isRunState();
if (autoRuns)
runService->run();
}
try
{
startScripts();
}
catch (...)
{
fail("Failed to start test scripts");
throw;
}
}
void TestService::incrementTest()
{
testCount++;
raisePropertyChanged(TestCount);
}
void TestService::incrementWarn()
{
warnCount++;
raisePropertyChanged(WarnCount);
}
void TestService::incrementError()
{
errorCount++;
raisePropertyChanged(ErrorCount);
}
void TestService::startScripts()
{
shared_ptr<const Instances> instances = getChildren().read();
if (!instances)
return;
scriptCount = 0;
std::for_each(instances->begin(), instances->end(), boost::bind(&TestService::countScript, this, _1));
std::for_each(instances->begin(), instances->end(), boost::bind(&TestService::startScript, this, _1));
}
void TestService::stopScripts()
{
shared_ptr<const Instances> instances = getChildren().read();
if (!instances)
return;
std::for_each(instances->begin(), instances->end(), boost::bind(&TestService::stopScript, this, _1));
}
void TestService::countScript( shared_ptr<Instance> instance )
{
if (shared_dynamic_cast<Script>(instance))
scriptCount++;
}
void TestService::startScript( shared_ptr<Instance> instance )
{
if (shared_ptr<Script> script = shared_dynamic_cast<Script>(instance))
{
RBX::ScriptContext* scriptContext = RBX::ServiceProvider::create<RBX::ScriptContext>(this);
if (!scriptContext)
throw std::runtime_error("Unable to start test script");
ScriptContext::ScriptStartOptions options;
options.continuations.successHandler = boost::bind(&TestService::onScriptEnded, shared_from(this), runCount);
options.continuations.errorHandler = boost::bind(&TestService::onScriptFailed, shared_from(this), runCount, _1, _2, _3, _4);
// elevate the identity of this script to the current caller's identity:
options.identity = RBX::Security::Context::current().identity;
options.filter = boost::bind(&TestService::filterScript, this, _1);
scriptContext->addScript(script, options);
}
}
void TestService::stopScript( shared_ptr<Instance> instance )
{
if (shared_ptr<Script> script = shared_dynamic_cast<Script>(instance))
{
RBX::ScriptContext* scriptContext = RBX::ServiceProvider::create<RBX::ScriptContext>(this);
if (!scriptContext)
throw std::runtime_error("Unable to stop test script");
scriptContext->removeScript(script);
}
}
void TestService::onScriptEnded(int runCount)
{
if (runCount != this->runCount)
return;
if (--scriptCount == 0)
done();
}
void TestService::onScriptFailed(int runCount, const char* message, const char* callStack, shared_ptr<BaseScript> source, int line)
{
if (runCount != this->runCount)
return;
fail(message, source, line);
}
bool TestService::askForbidChild( const Instance* instance ) const
{
// Only allow script
return (dynamic_cast<const Script*>(instance) == NULL);
}
void TestService::setConfiguration()
{
RBX::PhysicsSettings &ps = RBX::PhysicsSettings::singleton();
oldSettings.eThrottle = ps.getEThrottle();
oldSettings.allowSleep = ps.getAllowSleep();
oldSettings.throttleAt30Fps = ps.getThrottleAt30Fps();
oldSettings.wasLogAsserts = FLog::Asserts;
RBX::NetworkSettings &ns = RBX::NetworkSettings::singleton();
ns.setPhysicsSendRate(physicsSendRate);
if (isPerformanceTest())
{
this->message("Running performance test");
FLog::Asserts = 0;
}
if( isPhysicsThrottled )
ps.setEThrottle( EThrottle::ThrottleDefaultAuto );
else
ps.setEThrottle( EThrottle::ThrottleDisabled );
ps.setAllowSleep( allowSleep );
ps.setThrottleAt30Fps( is30FpsThrottleEnabled );
}
void TestService::restoreConfiguration()
{
FLog::Asserts = oldSettings.wasLogAsserts;
RBX::PhysicsSettings &ps = RBX::PhysicsSettings::singleton();
ps.setEThrottle( oldSettings.eThrottle );
ps.setAllowSleep( oldSettings.allowSleep );
ps.setThrottleAt30Fps( oldSettings.throttleAt30Fps );
}
void TestService::stop()
{
stopScripts();
if (!wasRunning)
if (RunService* runService = ServiceProvider::find<RunService>(this))
runService->pause();
restoreConfiguration();
if (resumeFunction)
resumeFunction();
}
class MacroSubstituter
{
std::stringstream str;
std::stringstream result;
public:
std::string getResult() const {
return result.str();
}
MacroSubstituter(const std::string& source)
:str(source)
{
int lineNumber = 0;
try
{
char buffer[1024];
while (str.getline(buffer, 1024))
{
lineNumber++;
//process the line
std::string line = buffer;
if (line.size() > 1000)
throw std::runtime_error("Line too long");
line += '\n'; // restore the newline character
processLine(lineNumber, line);
}
}
catch (std::exception& e)
{
throw ScriptContext::ScriptStartOptions::LuaSyntaxError(lineNumber, e);
}
}
private:
void processLine(int lineNumber, const std::string& line)
{
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_EQUAL", "Check", "==", "!="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_NE", "Check", "~=", "=="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_GE", "Check", ">=", "<"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_LE", "Check", "<=", ">"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_GT", "Check", ">", "<="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_CHECK_LT", "Check", "<", ">="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_EQUAL", "Warn", "==", "!="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_NE", "Warn", "!=", "=="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_GE", "Warn", ">=", "<"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_LE", "Warn", "<=", ">"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_GT", "Warn", ">", "<="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_WARN_LT", "Warn", "<", ">="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_EQUAL", "Require", "==", "!="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_NE", "Require", "!=", "=="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_GE", "Require", ">=", "<"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_LE", "Require", "<=", ">"))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_GT", "Require", ">", "<="))
return;
if (doRBX_Test_Equality(lineNumber, line, "RBX_REQUIRE_LT", "Require", "<", ">="))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_WARN_MESSAGE", "Warn"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_CHECK_MESSAGE", "Check"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_REQUIRE_MESSAGE", "Require"))
return;
if (doRBX_Test_Throw(lineNumber, line, "RBX_WARN_THROW", "Warn"))
return;
if (doRBX_Test_Throw(lineNumber, line, "RBX_CHECK_THROW", "Check"))
return;
if (doRBX_Test_Throw(lineNumber, line, "RBX_REQUIRE_THROW", "Require"))
return;
if (doRBX_Test_NoThrow(lineNumber, line, "RBX_WARN_NO_THROW", "Warn"))
return;
if (doRBX_Test_NoThrow(lineNumber, line, "RBX_CHECK_NO_THROW", "Check"))
return;
if (doRBX_Test_NoThrow(lineNumber, line, "RBX_REQUIRE_NO_THROW", "Require"))
return;
if (doRBX_Test(lineNumber, line, "RBX_WARN", "Warn"))
return;
if (doRBX_Test(lineNumber, line, "RBX_CHECK", "Check"))
return;
if (doRBX_Test(lineNumber, line, "RBX_REQUIRE", "Require"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_ERROR", "Error"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_FAIL", "Fail"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_MESSAGE", "Message"))
return;
if (doRBX_SimpleSubstitution(lineNumber, line, "RBX_CHECKPOINT", "Checkpoint"))
return;
result << line;
}
static void appendArg(std::vector<std::string>* args, std::string::const_iterator begin, std::string::const_iterator end)
{
std::string arg;
arg.assign(begin, end);
args->push_back(arg);
}
bool doRBX_Test_Equality(int lineNumber, const std::string& line, const char* macro, const char* f, const char* equality, const char* notEquality)
{
int i = line.find(macro);
if (i < 0)
return false;
// RBX_CHECK_EQUAL(a, b) ===> if a == b then Game:GetService('TestService'):Check(true, [==[a == b]==]) else Game:GetService('TestService'):Check(false,[==[test a == b failed [1 != 2]]==]) end
using namespace std;
string::const_iterator start = line.begin() + i + strlen(macro);
start = Lua::ArgumentParser::skipWhitespaces(start, line.end());
if (*start != '(')
throw std::runtime_error("Expected '('");
vector<string> args;
string::const_iterator stop = Lua::ArgumentParser::parseBracket(start, line.end(), boost::bind(&appendArg, &args, _1, _2));
if (args.size() != 2)
throw RBX::runtime_error("Expected 2 arguments but got %d", (int)args.size());
// start points to (
// stop points after )
RBXASSERT(*start == '(');
RBXASSERT(*(stop-1) == ')');
// First write everything before RBX_CHECK
result << line.substr(0, i);
// Now substitute the function call
result << "do ";
// picking aZZZZ and bZZZZ, which are unlikely to be used elsewhere
result << "local aZZZZ = " << args[0] << " local bZZZZ = " << args[1] << " ";
result << "if aZZZZ " << equality << " bZZZZ then ";
result << "Game:GetService('TestService'):" << f << "(true, [==[" << args[0] << " " << equality << " " << args[1] << "]==], script, " << lineNumber << ") ";
result << "else ";
result << "Game:GetService('TestService'):" << f << "(false, [==[test " << args[0] << " " << equality << " " << args[1] << " failed []==] .. tostring(aZZZZ) .. ' " << notEquality << " ' .. tostring(bZZZZ) .. ']', script, " << lineNumber << ") ";
result << "end ";
result << "end";
// Now the rest of the line
copy(stop, line.end(), std::ostream_iterator<char>(result));
return true;
}
bool doRBX_Test(int lineNumber, const std::string& line, const char* macro, const char* f)
{
int i = line.find(macro);
if (i < 0)
return false;
// RBX_CHECK(a) ===> Game:GetService('TestService'):Check(a,[==[test failed: a]==])
using namespace std;
string::const_iterator start = line.begin() + i + strlen(macro);
start = Lua::ArgumentParser::skipWhitespaces(start, line.end());
if (*start != '(')
throw std::runtime_error("Expected '('");
string::const_iterator stop = Lua::ArgumentParser::parseBracket(start, line.end());
// start points to (
// stop points after )
RBXASSERT(*start == '(');
RBXASSERT(*(stop-1) == ')');
// First write everything before RBX_CHECK
result << line.substr(0, i);
// Now substitute the function call
result << "Game:GetService('TestService'):" << f << "(";
// Now the test argument
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
// Now the stringized test
result << ", [==[test failed: ";
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
result << "]==], script, " << lineNumber << ")";
// Now the rest of the line
copy(stop, line.end(), std::ostream_iterator<char>(result));
return true;
}
bool doRBX_Test_Throw(int lineNumber, const std::string& line, const char* macro, const char* f)
{
int i = line.find(macro);
if (i < 0)
return false;
// RBX_CHECK_THROW(a) ===> Game:GetService('TestService'):Check(ypcall(function() a end) == false, 'Exception expected in ' .. [==[a]==])
using namespace std;
string::const_iterator start = line.begin() + i + strlen(macro);
start = Lua::ArgumentParser::skipWhitespaces(start, line.end());
if (*start != '(')
throw std::runtime_error("Expected '('");
string::const_iterator stop = Lua::ArgumentParser::parseBracket(start, line.end());
// start points to (
// stop points after )
RBXASSERT(*start == '(');
RBXASSERT(*(stop-1) == ')');
// First write everything before RBX_CHECK_THROW
result << line.substr(0, i);
// Now substitute the function call
result << "Game:GetService('TestService'):" << f << "(ypcall(function() ";
// Now the test argument
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
// Now the stringized test
result << " end) == false, 'Exception expected in ' .. [==[";
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
result << "]==], script, " << lineNumber << ")";
// Now the rest of the line
copy(stop, line.end(), std::ostream_iterator<char>(result));
return true;
}
bool doRBX_Test_NoThrow(int lineNumber, const std::string& line, const char* macro, const char* f)
{
int i = line.find(macro);
if (i < 0)
return false;
// RBX_CHECK_NO_THROW(a) ===> Game:GetService('TestService'):Check(ypcall(function() a end) == true, 'Exception thrown by ' .. [==[a]==])
using namespace std;
string::const_iterator start = line.begin() + i + strlen(macro);
start = Lua::ArgumentParser::skipWhitespaces(start, line.end());
if (*start != '(')
throw std::runtime_error("Expected '('");
string::const_iterator stop = Lua::ArgumentParser::parseBracket(start, line.end());
// start points to (
// stop points after )
RBXASSERT(*start == '(');
RBXASSERT(*(stop-1) == ')');
// First write everything before RBX_CHECK_THROW
result << line.substr(0, i);
// Now substitute the function call
result << "Game:GetService('TestService'):" << f << "(ypcall(function() ";
// Now the test argument
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
// Now the stringized test
result << " end) == true, 'Exception thrown by ' .. [==[";
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
result << "]==], script, " << lineNumber << ")";
// Now the rest of the line
copy(stop, line.end(), std::ostream_iterator<char>(result));
return true;
}
bool doRBX_SimpleSubstitution(int lineNumber, const std::string& line, const char* macro, const char* f)
{
int i = line.find(macro);
if (i < 0)
return false;
// RBX_CHECK(a) ===> Game:GetService('TestService'):Check(a,[==[a]==])
using namespace std;
string::const_iterator start = line.begin() + i + strlen(macro);
start = Lua::ArgumentParser::skipWhitespaces(start, line.end());
if (*start != '(')
throw std::runtime_error("Expected '('");
string::const_iterator stop = Lua::ArgumentParser::parseBracket(start, line.end());
// start points to (
// stop points after )
RBXASSERT(*start == '(');
RBXASSERT(*(stop-1) == ')');
// First write everything before RBX_CHECK
result << line.substr(0, i);
// Now substitute the function call
result << "Game:GetService('TestService'):" << f << "(";
// Now the message argument
copy(start + 1, stop - 1, std::ostream_iterator<char>(result));
// Now the source
result << ", script, " << lineNumber << ")";
// Now the rest of the line
copy(stop, line.end(), std::ostream_iterator<char>(result));
return true;
}
};
std::string TestService::filterScript( const std::string& source )
{
std::string result = MacroSubstituter(source).getResult();
return result;
}
void TestService::output( MessageType type, shared_ptr<Instance> source, int line, const std::string& message)
{
if (source)
RBX::StandardOut::singleton()->printf(type, "TestService.%s(%d): %s", source->getName().c_str(), line, message.c_str());
else
RBX::StandardOut::singleton()->printf(type, "TestService: %s", message.c_str());
}
bool TestService::isPerformanceTest() const
{
if (!is30FpsThrottleEnabled)
return true;
if (!isPhysicsThrottled)
return true;
return false;
}
#ifdef RBX_TEST_BUILD
void TestService::doCommand( std::string name )
{
getVerb(name)->doIt(DataModel::get(this));
}
bool TestService::isCommandEnabled( std::string name )
{
return getVerb(name)->isEnabled();
}
bool TestService::isCommandChecked( std::string name )
{
return getVerb(name)->isChecked();
}
Verb* TestService::getVerb( std::string name )
{
DataModel* dm = DataModel::get(this);
if (!dm)
throw std::runtime_error("Invalid operation");
Verb* verb = dm->getVerb(name);
if (!verb)
throw RBX::runtime_error("Verb %s does not exist", name.c_str());
return verb;
}
#endif
void TestService::onServiceProvider( ServiceProvider* oldProvider, ServiceProvider* newProvider )
{
Super::onServiceProvider(oldProvider, newProvider);
if (newProvider)
{
resultCollectConnection = serverCollectResultSignal.connect(boost::bind(&TestService::onRemoteResult,this,_1,_2,_3));
conditionalResultCollectConnection = serverCollectConditionalResultSignal.connect(boost::bind(&TestService::onRemoteConditionalResult,this,_1,_2,_3,_4));
}
else if (oldProvider)
{
resultCollectConnection.disconnect();
conditionalResultCollectConnection.disconnect();
}
}
static void addName(Reflection::ValueArray* array, const Name* name)
{
array->push_back(name->str);
}
#ifdef RBX_TEST_BUILD
shared_ptr<const Reflection::ValueArray> TestService::getCommandNames()
{
DataModel* dm = DataModel::get(this);
if (!dm)
throw std::runtime_error("Invalid operation");
shared_ptr<Reflection::ValueArray> result = rbx::make_shared<Reflection::ValueArray>();
dm->eachVerbName(boost::bind(&addName, result.get(), _1));
return result;
}
#endif
FunctionalTest::FunctionalTest()
:description("?")
,timeout(60)
,isPhysicsThrottled(true)
,allowSleep(true)
,is30FpsThrottleEnabled(true)
,hasMigratedSettingsToTestService(false)
{
}
void FunctionalTest::pass(std::string message)
{
if (!testService)
throw std::runtime_error("Can't find TestService");
testService->check(true, message);
testService->done();
}
void FunctionalTest::warn(std::string message)
{
if (!testService)
throw std::runtime_error("Can't find TestService");
testService->warn(false, message);
testService->done();
}
void FunctionalTest::error(std::string message)
{
if (!testService)
throw std::runtime_error("Can't find TestService");
testService->check(false, message);
testService->done();
}
void FunctionalTest::onServiceProvider( ServiceProvider* oldProvider, ServiceProvider* newProvider )
{
Super::onServiceProvider(oldProvider, newProvider);
if (newProvider)
{
testService = shared_from(newProvider->find<TestService>());
if (!testService)
{
testService = shared_from(newProvider->create<TestService>());
if (hasMigratedSettingsToTestService)
{
// This is an old file with no TestService. We'll create one and migrate the configuration data here:
testService->description = this->description;
testService->timeout = this->timeout;
testService->isPhysicsThrottled = this->isPhysicsThrottled;
testService->allowSleep = this->allowSleep;
testService->is30FpsThrottleEnabled = this->is30FpsThrottleEnabled;
this->hasMigratedSettingsToTestService = true;
}
}
}
}
}