From 37d820120daba5f60376e950da360e57492f1760 Mon Sep 17 00:00:00 2001 From: James Turner <zakalawe@mac.com> Date: Wed, 26 Aug 2020 17:20:58 +0100 Subject: [PATCH] Unit-testing: tests for Nasal SGCommand API Test adding/removing/invoking commands, and error handles when duplicate adding and removing a command name. --- src/Scripting/NasalSys.cxx | 29 ++++++++- src/Scripting/NasalSys.hxx | 8 ++- .../unit_tests/Scripting/testNasalSys.cxx | 61 +++++++++++++++++++ .../unit_tests/Scripting/testNasalSys.hxx | 2 + 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index ac24f150d..5af03c492 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -821,7 +821,12 @@ static naRef f_addCommand(naContext c, naRef me, int argc, naRef* args) if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1])) naRuntimeError(c, "bad arguments to addcommand()"); - nasalSys->addCommand(args[1], naStr_data(args[0])); + const string commandName(naStr_data(args[0])); + bool ok = nasalSys->addCommand(args[1], commandName); + if (!ok) { + naRuntimeError(c, "Failed to add command:%s : likely a duplicate name ", commandName.c_str()); + } + return naNil(); } @@ -1423,17 +1428,34 @@ void FGNasalSys::loadPropertyScripts(SGPropertyNode* n) loaded->setBoolValue(is_loaded); } +#if defined(BUILDING_TESTSUITE) + +static string_list global_nasalErrors; + +string_list FGNasalSys::getAndClearErrorList() +{ + string_list r; + global_nasalErrors.swap(r); + return r; +} +#endif + // Logs a runtime error, with stack trace, to the FlightGear log stream void FGNasalSys::logError(naContext context) { string errorMessage = naGetError(context); +#if defined(BUILDING_TESTSUITE) + global_nasalErrors.push_back(errorMessage); +#else SG_LOG(SG_NASAL, SG_ALERT, "Nasal runtime error: " << errorMessage); string_list nasalStack; logNasalStack(context, nasalStack); flightgear::sentryReportNasalError(errorMessage, nasalStack); +#endif } + void FGNasalSys::logNasalStack(naContext context, string_list& stack) { const int stack_depth = naStackDepth(context); @@ -1770,15 +1792,16 @@ void FGNasalSys::registerToUnload(FGNasalModelData *data) _unloadList.push(data); } -void FGNasalSys::addCommand(naRef func, const std::string& name) +bool FGNasalSys::addCommand(naRef func, const std::string& name) { if (_commands.find(name) != _commands.end()) { SG_LOG(SG_NASAL, SG_WARN, "duplicate add of command:" << name); - return; + return false; } NasalCommand* cmd = new NasalCommand(this, func, name); _commands[name] = cmd; + return true; } bool FGNasalSys::removeCommand(const std::string& name) diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index 9e544743f..f59e1f33c 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -94,7 +94,7 @@ public: naRef getModule(const char* moduleName); - void addCommand(naRef func, const std::string& name); + bool addCommand(naRef func, const std::string& name); bool removeCommand(const std::string& name); /** @@ -153,6 +153,12 @@ public: simgear::BufferedLogCallback* log() const { return _log.get(); } +#if defined(BUILDING_TESTSUITE) + /// test-suite only API: retrieve all Nasal runtime errors which + /// occurred since we last called this + string_list getAndClearErrorList(); +#endif + private: void initLogLevelConstants(); diff --git a/test_suite/unit_tests/Scripting/testNasalSys.cxx b/test_suite/unit_tests/Scripting/testNasalSys.cxx index aa67bdae2..354d496e9 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.cxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.cxx @@ -22,8 +22,12 @@ #include "test_suite/FGTestApi/testGlobals.hxx" +#include <simgear/structure/commands.hxx> + +#include <Main/fg_props.hxx> #include <Main/globals.hxx> #include <Main/util.hxx> + #include <Scripting/NasalSys.hxx> #include <Main/FGInterpolator.hxx> @@ -89,3 +93,60 @@ void NasalSysTests::testStructEquality() )"); CPPUNIT_ASSERT(ok); } + +void NasalSysTests::testCommands() +{ + auto nasalSys = globals->get_subsystem<FGNasalSys>(); + nasalSys->getAndClearErrorList(); + + fgSetInt("/foo/test", 7); + bool ok = FGTestApi::executeNasal(R"( + var f = func { + var i = getprop('/foo/test'); + setprop('foo/test', i + 4); + }; + + addcommand('do-foo', f); + var ok = fgcommand('do-foo'); + unitTest.assert(ok); + )"); + CPPUNIT_ASSERT(ok); + + CPPUNIT_ASSERT_EQUAL(11, fgGetInt("/foo/test")); + + SGPropertyNode_ptr args(new SGPropertyNode); + ok = globals->get_commands()->execute("do-foo", args); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL(15, fgGetInt("/foo/test")); + + ok = FGTestApi::executeNasal(R"( + var g = func { print('fail'); }; + addcommand('do-foo', g); + )"); + CPPUNIT_ASSERT(ok); + auto errors = nasalSys->getAndClearErrorList(); + CPPUNIT_ASSERT_EQUAL(1UL, errors.size()); + + // old command shoudl still be registered and work + ok = globals->get_commands()->execute("do-foo", args); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL(19, fgGetInt("/foo/test")); + + ok = FGTestApi::executeNasal(R"( + removecommand('do-foo'); + )"); + CPPUNIT_ASSERT(ok); + + ok = FGTestApi::executeNasal(R"( + var ok = fgcommand('do-foo'); + unitTest.assert(!ok); + )"); + CPPUNIT_ASSERT(ok); + errors = nasalSys->getAndClearErrorList(); + CPPUNIT_ASSERT_EQUAL(0UL, errors.size()); + + // should fail, command is removed + ok = globals->get_commands()->execute("do-foo", args); + CPPUNIT_ASSERT(!ok); + CPPUNIT_ASSERT_EQUAL(19, fgGetInt("/foo/test")); +} diff --git a/test_suite/unit_tests/Scripting/testNasalSys.hxx b/test_suite/unit_tests/Scripting/testNasalSys.hxx index cd18c2006..e815f4fc6 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.hxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.hxx @@ -32,6 +32,7 @@ class NasalSysTests : public CppUnit::TestFixture // Set up the test suite. CPPUNIT_TEST_SUITE(NasalSysTests); CPPUNIT_TEST(testStructEquality); + CPPUNIT_TEST(testCommands); CPPUNIT_TEST_SUITE_END(); public: @@ -43,6 +44,7 @@ public: // The tests. void testStructEquality(); + void testCommands(); }; #endif // _FG_NASALSYS_UNIT_TESTS_HXX