From 9553965ad661b2753d13fa9b414f529ad440000f Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Tue, 10 Jan 2023 17:38:01 -0500
Subject: [PATCH] DRTVWR-558: Add tests for LLDispatchListener functionality.

Refine the special case of calling a nullary target function from an (event)
method, notably via LLDispatchListener.

(cherry picked from commit edcc52a9f60b1ec9b8f53603d6e2676558d41294)
---
 indra/llcommon/lleventdispatcher.cpp          |   8 +-
 .../llcommon/tests/lleventdispatcher_test.cpp | 152 +++++++++++++-----
 2 files changed, 123 insertions(+), 37 deletions(-)

diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index 7e5723c5036..7abdc8f57a3 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -461,7 +461,13 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
         LLSD args{ event };
         if (fromMap)
         {
-            if (mArity)
+            if (! mArity)
+            {
+                // When the target function is nullary, and we're called from
+                // an (event) method, just ignore the rest of the map entries.
+                args.clear();
+            }
+            else
             {
                 // We only require/retrieve argskey if the target function
                 // isn't nullary. For all others, since we require an LLSD
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index 00bdff89e53..179fab9fad1 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -23,6 +23,7 @@
 #include "llsdutil.h"
 #include "llevents.h"
 #include "stringize.h"
+#include "StringVec.h"
 #include "tests/wrapllerrs.h"
 #include "../test/catch_and_store_what_in.h"
 #include "../test/debug.h"
@@ -315,6 +316,31 @@ void freenb(NPARAMSb)
 *****************************************************************************/
 namespace tut
 {
+    void ensure_has(const std::string& outer, const std::string& inner)
+    {
+        ensure(stringize("'", outer, "' does not contain '", inner, "'"),
+               outer.find(inner) != std::string::npos);
+    }
+
+    template <typename CALLABLE>
+    std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
+    {
+        std::string what =
+            catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
+        ensure_has(what, exc_frag);
+        return what;
+    }
+
+    template <typename CALLABLE>
+    void call_logerr(CALLABLE&& func, const std::string& frag)
+    {
+        CaptureLog capture;
+        // the error should be logged; we just need to stop the exception
+        // propagating
+        catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
+        capture.messageWith(frag);
+    }
+
     struct lleventdispatcher_data
     {
         Debug debug{"test"};
@@ -633,47 +659,26 @@ namespace tut
             return found->second;
         }
 
-        void ensure_has(const std::string& outer, const std::string& inner)
-        {
-            ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(),
-                   outer.find(inner) != std::string::npos);
-        }
-
         std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
         {
-            std::string what;
-            try
-            {
-                if (func.empty())
+            return tut::call_exc(
+                [this, func, args]()
                 {
-                    work(args);
-                }
-                else
-                {
-                    work(func, args);
-                }
-            }
-            catch (const LLEventDispatcher::DispatchError& err)
-            {
-                what = err.what();
-            }
-            ensure_has(what, exc_frag);
-            return what;
+                    if (func.empty())
+                    {
+                        work(args);
+                    }
+                    else
+                    {
+                        work(func, args);
+                    }
+                },
+                exc_frag);
         }
 
         void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
         {
-            CaptureLog capture;
-            try
-            {
-                work(func, args);
-            }
-            catch (const LLEventDispatcher::DispatchError& err)
-            {
-                // the error should also have been logged; we just need to
-                // stop the exception propagating
-            }
-            capture.messageWith(frag);
+            tut::call_logerr([this, func, args](){ work(func, args); }, frag);
         }
 
         LLSD getMetadata(const std::string& name)
@@ -1328,11 +1333,11 @@ namespace tut
         }
     }
 
-    struct DispatchResult: public LLEventDispatcher
+    struct DispatchResult: public LLDispatchListener
     {
         using DR = DispatchResult;
 
-        DispatchResult(): LLEventDispatcher("expect result", "op")
+        DispatchResult(): LLDispatchListener("results", "op")
         {
             // As of 2022-12-22, LLEventDispatcher's shorthand add() methods
             // for pointer-to-method of same instance only support methods
@@ -1340,6 +1345,7 @@ namespace tut
             // method) requires an instance getter.
             add("strfunc",   "return string",       &DR::strfunc,   [this](){ return this; });
             add("voidfunc",  "void function",       &DR::voidfunc,  [this](){ return this; });
+            add("emptyfunc", "return empty LLSD",   &DR::emptyfunc, [this](){ return this; });
             add("intfunc",   "return Integer LLSD", &DR::intfunc,   [this](){ return this; });
             add("mapfunc",   "return map LLSD",     &DR::mapfunc,   [this](){ return this; });
             add("arrayfunc", "return array LLSD",   &DR::arrayfunc, [this](){ return this; });
@@ -1347,6 +1353,7 @@ namespace tut
 
         std::string strfunc(const LLSD&) const { return "a string"; }
         void voidfunc()                  const {}
+        LLSD emptyfunc()                 const { return {}; }
         int  intfunc(const LLSD&)        const { return 17; }
         LLSD mapfunc(const LLSD&)        const { return llsd::map("key", "value"); }
         LLSD arrayfunc(const LLSD&)      const { return llsd::array("a", "b", "c"); }
@@ -1396,4 +1403,77 @@ namespace tut
         LLSD result{ service("arrayfunc", "ignored") };
         ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c"));
     }
+
+    template<> template<>
+    void object::test<28>()
+    {
+        set_test_name("listener error, no reply");
+        DispatchResult service;
+        tut::call_exc(
+            [&service]()
+            { service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
+            "nosuchfunc");
+    }
+
+    template<> template<>
+    void object::test<29>()
+    {
+        set_test_name("listener error with reply");
+        DispatchResult service;
+        LLCaptureListener<LLSD> result;
+        service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
+        LLSD reply{ result.get() };
+        ensure("no reply", reply.isDefined());
+        ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+        ensure_has(reply["error"].asString(), "nosuchfunc");
+    }
+
+    template<> template<>
+    void object::test<30>()
+    {
+        set_test_name("listener call to void function");
+        DispatchResult service;
+        LLCaptureListener<LLSD> result;
+        result.set("non-empty");
+        for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
+        {
+            service.post(llsd::map(
+                             "op", func,
+                             "reqid", 17,
+                             "reply", result.getName()));
+            ensure_equals("reply from " + func, result.get().asString(), "non-empty");
+        }
+    }
+
+    template<> template<>
+    void object::test<31>()
+    {
+        set_test_name("listener call to string function");
+        DispatchResult service;
+        LLCaptureListener<LLSD> result;
+        service.post(llsd::map(
+                         "op", "strfunc",
+                         "args", llsd::array(LLSD()),
+                         "reqid", 17,
+                         "reply", result.getName()));
+        LLSD reply{ result.get() };
+        ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+        ensure_equals("bad reply from strfunc", reply["data"].asString(), "a string");
+    }
+
+    template<> template<>
+    void object::test<32>()
+    {
+        set_test_name("listener call to map function");
+        DispatchResult service;
+        LLCaptureListener<LLSD> result;
+        service.post(llsd::map(
+                         "op", "mapfunc",
+                         "args", llsd::array(LLSD()),
+                         "reqid", 17,
+                         "reply", result.getName()));
+        LLSD reply{ result.get() };
+        ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+        ensure_equals("bad reply from mapfunc", reply["key"], "value");
+    }
 } // namespace tut
-- 
GitLab