From f0d3663102bfab6a16693b9910a2021b10846777 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 8 Jul 2020 15:19:47 +0100 Subject: [PATCH] Nasal unit-testing: allow deep struct equality. Add a deep comparison of vecs and hashes, when deciding equality in test assertions. --- src/Scripting/NasalSys.cxx | 51 +++++++++++++++++ src/Scripting/NasalSys_private.hxx | 6 ++ src/Scripting/NasalUnitTesting.cxx | 14 ++++- .../FGTestApi/NasalUnitTesting_TestSuite.cxx | 22 +++++--- .../unit_tests/Scripting/testNasalSys.cxx | 55 ++++++++++++++++++- .../unit_tests/Scripting/testNasalSys.hxx | 4 +- 6 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index e3738e3b3..185a2c794 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -1956,3 +1956,54 @@ naRef NasalXMLVisitor::make_string(const char* s, int n) return naStr_fromdata(naNewString(_c), const_cast(s), n < 0 ? strlen(s) : n); } + +// like naEqual, but checks vector/hash recursively +// note this will not tolerate a recursively defined Nasal structure +// (such as globals.) +int nasalStructEqual(naContext ctx, naRef a, naRef b) +{ + if (naIsVector(a) && naIsVector(b)) { + const int aSz = naVec_size(a), + bSz = naVec_size(b); + + if (aSz != bSz) + return 0; + + for (int i = 0; i < aSz; ++i) { + int eq = nasalStructEqual(ctx, naVec_get(a, i), naVec_get(b, i)); + if (!eq) + return 0; + } + + // all elements equal, we're done + return 1; + } + + if (naIsHash(a) && naIsHash(b)) { + naRef keysVec = naNewVector(ctx); + naHash_keys(keysVec, a); + const auto aSz = naVec_size(keysVec); + + // first check key count, that's fast + if (aSz != naHash_size(b)) + return 0; + + for (int i = 0; i < aSz; i++) { + naRef key = naVec_get(keysVec, i); + naRef aValue, bValue; + if (!naHash_get(a, key, &aValue) || !naHash_get(b, key, &bValue)) { + return 0; + } + + int eq = nasalStructEqual(ctx, aValue, bValue); + if (!eq) { + return 0; + } + } + + // all values matched, we're good + return 1; + } + + return naEqual(a, b); +} diff --git a/src/Scripting/NasalSys_private.hxx b/src/Scripting/NasalSys_private.hxx index 86d593c68..646d6135e 100644 --- a/src/Scripting/NasalSys_private.hxx +++ b/src/Scripting/NasalSys_private.hxx @@ -20,6 +20,12 @@ #include #include +/** + @breif wrapper for naEqual which recursively checks vec/hash equality + Probably not very performant. + */ +int nasalStructEqual(naContext ctx, naRef a, naRef b); + class FGNasalListener : public SGPropertyChangeListener { public: FGNasalListener(SGPropertyNode* node, naRef code, FGNasalSys* nasal, diff --git a/src/Scripting/NasalUnitTesting.cxx b/src/Scripting/NasalUnitTesting.cxx index 0ddcc3856..f72a525a6 100644 --- a/src/Scripting/NasalUnitTesting.cxx +++ b/src/Scripting/NasalUnitTesting.cxx @@ -32,6 +32,8 @@ #include
#include
#include +#include + #include
#include @@ -106,7 +108,7 @@ static naRef f_assert_equal(const nasal::CallContext& ctx ) naRef argB = ctx.requireArg(1); auto msg = ctx.getArg(2, "assert_equal failed"); - bool same = naEqual(argA, argB); + bool same = nasalStructEqual(ctx.c_ctx(), argA, argB); if (!same) { string aStr = ctx.from_nasal(argA); string bStr = ctx.from_nasal(argB); @@ -142,6 +144,15 @@ static naRef f_assert_doubles_equal(const nasal::CallContext& ctx ) return naNil(); } +static naRef f_equal(const nasal::CallContext& ctx) +{ + naRef argA = ctx.requireArg(0); + naRef argB = ctx.requireArg(1); + + bool same = nasalStructEqual(ctx.c_ctx(), argA, argB); + return naNum(same); +} + //------------------------------------------------------------------------------ // commands @@ -187,6 +198,7 @@ naRef initNasalUnitTestInSim(naRef nasalGlobals, naContext c) unitTest.set("fail", f_fail); unitTest.set("assert_equal", f_assert_equal); unitTest.set("assert_doubles_equal", f_assert_doubles_equal); + unitTest.set("equal", f_equal); globals->get_commands()->addCommand("nasal-test", &command_executeNasalTest); globals->get_commands()->addCommand("nasal-test-dir", &command_executeNasalTestDir); diff --git a/test_suite/FGTestApi/NasalUnitTesting_TestSuite.cxx b/test_suite/FGTestApi/NasalUnitTesting_TestSuite.cxx index e4b651b03..4471aa255 100644 --- a/test_suite/FGTestApi/NasalUnitTesting_TestSuite.cxx +++ b/test_suite/FGTestApi/NasalUnitTesting_TestSuite.cxx @@ -30,6 +30,9 @@ #include
#include
+#include +#include + #include #include @@ -50,9 +53,6 @@ static naRef f_assert(const nasal::CallContext& ctx ) auto msg = ctx.getArg(1, "assert failed:"); CppUnit::Asserter::failIf(!pass, "assertion failed:" + msg, nasalSourceLine(ctx)); - if (!pass) { - ctx.runtimeError(msg.c_str()); - } return naNil(); } @@ -62,8 +62,6 @@ static naRef f_fail(const nasal::CallContext& ctx ) CppUnit::Asserter::fail("assertion failed:" + msg, nasalSourceLine(ctx)); - - ctx.runtimeError("Test failed: %s", msg.c_str()); return naNil(); } @@ -73,7 +71,7 @@ static naRef f_assert_equal(const nasal::CallContext& ctx ) naRef argB = ctx.requireArg(1); auto msg = ctx.getArg(2, "assert_equal failed"); - bool same = naEqual(argA, argB); + bool same = nasalStructEqual(ctx.c_ctx(), argA, argB); if (!same) { string aStr = ctx.from_nasal(argA); @@ -81,12 +79,20 @@ static naRef f_assert_equal(const nasal::CallContext& ctx ) msg += "; expected:" + aStr + ", actual:" + bStr; CppUnit::Asserter::fail(msg, nasalSourceLine(ctx)); - ctx.runtimeError(msg.c_str()); } return naNil(); } +static naRef f_equal(const nasal::CallContext& ctx) +{ + naRef argA = ctx.requireArg(0); + naRef argB = ctx.requireArg(1); + + bool same = nasalStructEqual(ctx.c_ctx(), argA, argB); + return naNum(same); +} + static naRef f_assert_doubles_equal(const nasal::CallContext& ctx ) { double argA = ctx.requireArg(0); @@ -99,7 +105,6 @@ static naRef f_assert_doubles_equal(const nasal::CallContext& ctx ) if (!same) { msg += "; expected:" + std::to_string(argA) + ", actual:" + std::to_string(argB); CppUnit::Asserter::fail(msg, nasalSourceLine(ctx)); - ctx.runtimeError(msg.c_str()); } return naNil(); @@ -114,6 +119,7 @@ naRef initNasalUnitTestCppUnit(naRef nasalGlobals, naContext c) unitTest.set("assert", f_assert); unitTest.set("fail", f_fail); unitTest.set("assert_equal", f_assert_equal); + unitTest.set("equal", f_equal); unitTest.set("assert_doubles_equal", f_assert_doubles_equal); return naNil(); diff --git a/test_suite/unit_tests/Scripting/testNasalSys.cxx b/test_suite/unit_tests/Scripting/testNasalSys.cxx index 4d2f3da53..aa67bdae2 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.cxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.cxx @@ -20,21 +20,72 @@ #include "testNasalSys.hxx" +#include "test_suite/FGTestApi/testGlobals.hxx" + +#include
+#include
+#include + +#include
// Set up function for each test. void NasalSysTests::setUp() { + FGTestApi::setUp::initTestGlobals("NasalSys"); + + fgInitAllowedPaths(); + globals->get_props()->getNode("nasal", true); + + globals->add_subsystem("prop-interpolator", new FGInterpolator, SGSubsystemMgr::INIT); + + globals->get_subsystem_mgr()->bind(); + globals->get_subsystem_mgr()->init(); + + globals->add_new_subsystem(SGSubsystemMgr::INIT); + + globals->get_subsystem_mgr()->postinit(); } // Clean up after each test. void NasalSysTests::tearDown() { + FGTestApi::tearDown::shutdownTestGlobals(); } // Test test -void NasalSysTests::testDummy() +void NasalSysTests::testStructEquality() { - CPPUNIT_ASSERT(1 != 2); + bool ok = FGTestApi::executeNasal(R"( + var foo = { + "name": "Bob", + "size": [512, 512], + "mipmapping": 1.9 + }; + + var bar = { + "name": "Bob", + "size": [512, 512], + "mipmapping": 1.9 + }; + + unitTest.assert_equal(foo, bar); + + append(bar.size, "Wowow"); + unitTest.assert(unitTest.equal(foo, bar) == 0); + + append(foo.size, "Wowow"); + unitTest.assert_equal(foo, bar); + + foo.wibble = 99.1; + unitTest.assert(unitTest.equal(foo, bar) == 0); + + bar.wibble = 99; + unitTest.assert(unitTest.equal(foo, bar) == 0); + bar.wibble = 99.1; + unitTest.assert_equal(foo, bar); + + )"); + CPPUNIT_ASSERT(ok); } diff --git a/test_suite/unit_tests/Scripting/testNasalSys.hxx b/test_suite/unit_tests/Scripting/testNasalSys.hxx index 19a562e80..cd18c2006 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.hxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.hxx @@ -31,7 +31,7 @@ class NasalSysTests : public CppUnit::TestFixture { // Set up the test suite. CPPUNIT_TEST_SUITE(NasalSysTests); - CPPUNIT_TEST(testDummy); + CPPUNIT_TEST(testStructEquality); CPPUNIT_TEST_SUITE_END(); public: @@ -42,7 +42,7 @@ public: void tearDown(); // The tests. - void testDummy(); + void testStructEquality(); }; #endif // _FG_NASALSYS_UNIT_TESTS_HXX