phoenix-firestorm/indra/llcommon/tests/lleventdispatcher_test.cpp

1674 lines
67 KiB
C++

/**
* @file lleventdispatcher_test.cpp
* @author Nat Goodspeed
* @date 2011-01-20
* @brief Test for lleventdispatcher.
*
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
* Copyright (c) 2011, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "lleventdispatcher.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "lleventfilter.h"
#include "llsd.h"
#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"
#include <map>
#include <string>
#include <stdexcept>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/range.hpp>
#include <boost/lambda/lambda.hpp>
#include <iostream>
#include <iomanip>
using boost::lambda::constant;
using boost::lambda::constant_ref;
using boost::lambda::var;
using namespace llsd;
/*****************************************************************************
* Example data, functions, classes
*****************************************************************************/
// We don't need a whole lot of different arbitrary-params methods, just (no |
// (const LLSD&) | arbitrary) args (function | static method | non-static
// method), where 'arbitrary' is (every LLSD datatype + (const char*)).
// But we need to register each one under different names for the different
// registration styles. Don't forget LLEventDispatcher subclass methods(const
// LLSD&).
// However, the number of target parameter conversions we want to try exceeds
// boost::fusion::invoke()'s supported parameter-list size. Break out two
// different lists.
#define NPARAMSa bool b, int i, float f, double d, const char* cp
#define NPARAMSb const std::string& s, const LLUUID& uuid, const LLDate& date, \
const LLURI& uri, const std::vector<U8>& bin
#define NARGSa b, i, f, d, cp
#define NARGSb s, uuid, date, uri, bin
// For some registration methods we need methods on a subclass of
// LLEventDispatcher. To simplify things, we'll use this Dispatcher subclass
// for all our testing, including testing its own methods.
class Dispatcher: public LLEventDispatcher
{
public:
Dispatcher(const std::string& name, const std::string& key):
LLEventDispatcher(name, key)
{}
// sensing member, mutable because we want to know when we've reached our
// const method too
mutable LLSD llsd;
void method1(const LLSD& obj) { llsd = obj; }
void cmethod1(const LLSD& obj) const { llsd = obj; }
};
// sensing vars, captured in a struct to make it convenient to clear them
struct Vars
{
LLSD llsd;
bool b;
int i;
float f;
double d;
// Capture param passed as char*. But merely storing a char* received from
// our caller, possibly the .c_str() from a concatenation expression,
// would be Bad: the pointer will be invalidated long before we can query
// it. We could allocate a new chunk of memory, copy the string data and
// point to that instead -- but hey, guess what, we already have a class
// that does that!
std::string cp;
std::string s;
LLUUID uuid;
LLDate date;
LLURI uri;
std::vector<U8> bin;
Vars():
// Only need to initialize the POD types, the rest should take care of
// default-constructing themselves.
b(false),
i(0),
f(0),
d(0)
{}
// Detect any non-default values for convenient testing
LLSD inspect() const
{
LLSD result;
if (llsd.isDefined())
result["llsd"] = llsd;
if (b)
result["b"] = b;
if (i)
result["i"] = i;
if (f)
result["f"] = f;
if (d)
result["d"] = d;
if (! cp.empty())
result["cp"] = cp;
if (! s.empty())
result["s"] = s;
if (uuid != LLUUID())
result["uuid"] = uuid;
if (date != LLDate())
result["date"] = date;
if (uri != LLURI())
result["uri"] = uri;
if (! bin.empty())
result["bin"] = bin;
return result;
}
/*------------- no-args (non-const, const, static) methods -------------*/
void method0()
{
debug()("method0()");
i = 17;
}
void cmethod0() const
{
debug()('c', NONL);
const_cast<Vars*>(this)->method0();
}
static void smethod0();
/*------------ Callable (non-const, const, static) methods -------------*/
void method1(const LLSD& obj)
{
debug()("method1(", obj, ")");
llsd = obj;
}
void cmethod1(const LLSD& obj) const
{
debug()('c', NONL);
const_cast<Vars*>(this)->method1(obj);
}
static void smethod1(const LLSD& obj);
/*-------- Arbitrary-params (non-const, const, static) methods ---------*/
void methodna(NPARAMSa)
{
DEBUG;
// Because our const char* param cp might be NULL, and because we
// intend to capture the value in a std::string, have to distinguish
// between the NULL value and any non-NULL value. Use a convention
// easy for a human reader: enclose any non-NULL value in single
// quotes, reserving the unquoted string "NULL" to represent a NULL ptr.
std::string vcp;
if (cp == NULL)
vcp = "NULL";
else
vcp = std::string("'") + cp + "'";
this->debug()("methodna(", b,
", ", i,
", ", f,
", ", d,
", ", vcp,
")");
this->b = b;
this->i = i;
this->f = f;
this->d = d;
this->cp = vcp;
}
void methodnb(NPARAMSb)
{
std::ostringstream vbin;
for (U8 byte: bin)
{
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
debug()("methodnb(", "'", s, "'",
", ", uuid,
", ", date,
", '", uri, "'",
", ", vbin.str(),
")");
this->s = s;
this->uuid = uuid;
this->date = date;
this->uri = uri;
this->bin = bin;
}
void cmethodna(NPARAMSa) const
{
DEBUG;
this->debug()('c', NONL);
const_cast<Vars*>(this)->methodna(NARGSa);
}
void cmethodnb(NPARAMSb) const
{
debug()('c', NONL);
const_cast<Vars*>(this)->methodnb(NARGSb);
}
static void smethodna(NPARAMSa);
static void smethodnb(NPARAMSb);
static Debug& debug()
{
// Lazily initialize this Debug instance so it can notice if main()
// has forcibly set LOGTEST. If it were simply a static member, it
// would already have examined the environment variable by the time
// main() gets around to checking command-line switches. Since we have
// a global static Vars instance, the same would be true of a plain
// non-static member.
static Debug sDebug("Vars");
return sDebug;
}
};
/*------- Global Vars instance for free functions and static methods -------*/
static Vars g;
/*------------ Static Vars method implementations reference 'g' ------------*/
void Vars::smethod0()
{
debug()("smethod0() -> ", NONL);
g.method0();
}
void Vars::smethod1(const LLSD& obj)
{
debug()("smethod1(", obj, ") -> ", NONL);
g.method1(obj);
}
void Vars::smethodna(NPARAMSa)
{
debug()("smethodna(...) -> ", NONL);
g.methodna(NARGSa);
}
void Vars::smethodnb(NPARAMSb)
{
debug()("smethodnb(...) -> ", NONL);
g.methodnb(NARGSb);
}
/*--------------------------- Reset global Vars ----------------------------*/
void clear()
{
g = Vars();
}
/*------------------- Free functions also reference 'g' --------------------*/
void free0()
{
g.debug()("free0() -> ", NONL);
g.method0();
}
void free1(const LLSD& obj)
{
g.debug()("free1(", obj, ") -> ", NONL);
g.method1(obj);
}
void freena(NPARAMSa)
{
g.debug()("freena(...) -> ", NONL);
g.methodna(NARGSa);
}
void freenb(NPARAMSb)
{
g.debug()("freenb(...) -> ", NONL);
g.methodnb(NARGSb);
}
/*****************************************************************************
* TUT
*****************************************************************************/
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"};
WrapLLErrs redirect;
Dispatcher work;
Vars v;
std::string name, desc;
// Capture our own copy of all registered functions' descriptions
typedef std::map<std::string, std::string> DescMap;
DescMap descs;
// Capture the Vars instance on which we expect each function to operate
typedef std::map<std::string, Vars*> VarsMap;
VarsMap funcvars;
// Required structure for Callables with requirements
LLSD required;
// Parameter names for freena(), freenb()
LLSD params;
// Full, partial defaults arrays for params for freena(), freenb()
LLSD dft_array_full, dft_array_partial;
// Start index of partial defaults arrays
const size_t partial_offset;
// Full, partial defaults maps for params for freena(), freenb()
LLSD dft_map_full, dft_map_partial;
// Most of the above are indexed by "a" or "b". Useful to have an
// array containing those strings for iterating.
std::vector<LLSD::String> ab;
lleventdispatcher_data():
work("test dispatcher", "op"),
// map {d=double, array=[3 elements]}
required(LLSDMap("d", LLSD::Real(0))("array", llsd::array(LLSD(), LLSD(), LLSD()))),
// first several params are required, last couple optional
partial_offset(3)
{
// This object is reconstructed for every test<n> method. But
// clear global variables every time too.
::clear();
const char* abs[] = { "a", "b" };
ab.assign(boost::begin(abs), boost::end(abs));
// Registration cases:
// - (Callable | subclass const method | subclass non-const method |
// non-subclass method) (with | without) required
// - (Free function | static method | non-static method), (no | arbitrary) params,
// array style
// - (Free function | static method | non-static method), (no | arbitrary) params,
// map style, (empty | partial | full) (array | map) defaults
// - Map-style errors:
// - (scalar | map) param names
// - defaults scalar
// - defaults array longer than params array
// - defaults map with plural unknown param names
// I hate to have to write things twice, because of having to keep
// them consistent. If we had variadic functions, addf() would be
// a variadic method, capturing the name and desc and passing them
// plus "everything else" to work.add(). If I could return a pair
// and use that pair as the first two args to work.add(), I'd do
// that. But the best I can do with present C++ is to set two
// instance variables as a side effect of addf(), and pass those
// variables to each work.add() call. :-P
/*------------------------- Callables --------------------------*/
// Arbitrary Callable with/out required params
addf("free1", "free1", &g);
work.add(name, desc, free1);
addf("free1_req", "free1", &g);
work.add(name, desc, free1, required);
// Subclass non-const method with/out required params
addf("Dmethod1", "method1", NULL);
work.add(name, desc, &Dispatcher::method1);
addf("Dmethod1_req", "method1", NULL);
work.add(name, desc, &Dispatcher::method1, required);
// Subclass const method with/out required params
addf("Dcmethod1", "cmethod1", NULL);
work.add(name, desc, &Dispatcher::cmethod1);
addf("Dcmethod1_req", "cmethod1", NULL);
work.add(name, desc, &Dispatcher::cmethod1, required);
// Non-subclass method with/out required params
addf("method1", "method1", &v);
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
addf("method1_req", "method1", &v);
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
/*--------------- Arbitrary params, array style ----------------*/
// (Free function | static method) with (no | arbitrary) params, array style
addf("free0_array", "free0", &g);
work.add(name, desc, free0);
addf("freena_array", "freena", &g);
work.add(name, desc, freena);
addf("freenb_array", "freenb", &g);
work.add(name, desc, freenb);
addf("smethod0_array", "smethod0", &g);
work.add(name, desc, &Vars::smethod0);
addf("smethodna_array", "smethodna", &g);
work.add(name, desc, &Vars::smethodna);
addf("smethodnb_array", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb);
// Non-static method with (no | arbitrary) params, array style
addf("method0_array", "method0", &v);
work.add(name, desc, &Vars::method0, boost::lambda::var(v));
addf("methodna_array", "methodna", &v);
work.add(name, desc, &Vars::methodna, boost::lambda::var(v));
addf("methodnb_array", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, boost::lambda::var(v));
/*---------------- Arbitrary params, map style -----------------*/
// We lay out each params list as an array, also each array of
// default values we'll register. We'll zip these into
// (param=value) maps. Why not define them as maps and just
// extract the keys and values to arrays? Because that wouldn't
// give us the right params-list order.
// freena(), methodna(), cmethodna(), smethodna() all take same param list.
// Same for freenb() et al.
params = LLSDMap("a", llsd::array("b", "i", "f", "d", "cp"))
("b", llsd::array("s", "uuid", "date", "uri", "bin"));
debug("params:\n",
params, "\n"
"params[\"a\"]:\n",
params["a"], "\n"
"params[\"b\"]:\n",
params["b"]);
// default LLSD::Binary value
std::vector<U8> binary;
for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11)
{
binary.push_back((U8)h);
}
// Full defaults arrays. We actually don't care what the LLUUID or
// LLDate values are, as long as they're different from the
// LLUUID() and LLDate() default values so inspect() will report
// them.
dft_array_full = LLSDMap("a", llsd::array(true, 17, 3.14, 123456.78, "classic"))
("b", llsd::array("string",
LLUUID::generateNewID(),
LLDate::now(),
LLURI("http://www.ietf.org/rfc/rfc3986.txt"),
binary));
debug("dft_array_full:\n",
dft_array_full);
// Partial defaults arrays.
for (LLSD::String a: ab)
{
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
dft_array_partial[a] =
llsd_copy_array(dft_array_full[a].beginArray() + partition,
dft_array_full[a].endArray());
}
debug("dft_array_partial:\n",
dft_array_partial);
for(LLSD::String a: ab)
{
// Generate full defaults maps by zipping (params, dft_array_full).
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
// Generate partial defaults map by zipping alternate entries from
// (params, dft_array_full). Part of the point of using map-style
// defaults is to allow any subset of the target function's
// parameters to be optional, not just the rightmost.
for (LLSD::Integer ix = 0, ixend = params[a].size(); ix < ixend; ix += 2)
{
dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix];
}
}
debug("dft_map_full:\n",
dft_map_full, "\n"
"dft_map_partial:\n",
dft_map_partial);
// (Free function | static method) with (no | arbitrary) params,
// map style, no (empty array) defaults
addf("free0_map", "free0", &g);
work.add(name, desc, free0, LLSD::emptyArray());
addf("smethod0_map", "smethod0", &g);
work.add(name, desc, &Vars::smethod0, LLSD::emptyArray());
addf("freena_map_allreq", "freena", &g);
work.add(name, desc, freena, params["a"]);
addf("freenb_map_allreq", "freenb", &g);
work.add(name, desc, freenb, params["b"]);
addf("smethodna_map_allreq", "smethodna", &g);
work.add(name, desc, &Vars::smethodna, params["a"]);
addf("smethodnb_map_allreq", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb, params["b"]);
// Non-static method with (no | arbitrary) params, map style, no
// (empty array) defaults
addf("method0_map", "method0", &v);
work.add(name, desc, &Vars::method0, var(v), LLSD::emptyArray());
addf("methodna_map_allreq", "methodna", &v);
work.add(name, desc, &Vars::methodna, var(v), params["a"]);
addf("methodnb_map_allreq", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, var(v), params["b"]);
// Except for the "more (array | map) defaults than params" error
// cases, tested separately below, the (partial | full)(array |
// map) defaults cases don't apply to no-params functions/methods.
// So eliminate free0, smethod0, method0 from the cases below.
// (Free function | static method) with arbitrary params, map
// style, partial (array | map) defaults
addf("freena_map_leftreq", "freena", &g);
work.add(name, desc, freena, params["a"], dft_array_partial["a"]);
addf("freenb_map_leftreq", "freenb", &g);
work.add(name, desc, freenb, params["b"], dft_array_partial["b"]);
addf("smethodna_map_leftreq", "smethodna", &g);
work.add(name, desc, &Vars::smethodna, params["a"], dft_array_partial["a"]);
addf("smethodnb_map_leftreq", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_partial["b"]);
addf("freena_map_skipreq", "freena", &g);
work.add(name, desc, freena, params["a"], dft_map_partial["a"]);
addf("freenb_map_skipreq", "freenb", &g);
work.add(name, desc, freenb, params["b"], dft_map_partial["b"]);
addf("smethodna_map_skipreq", "smethodna", &g);
work.add(name, desc, &Vars::smethodna, params["a"], dft_map_partial["a"]);
addf("smethodnb_map_skipreq", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb, params["b"], dft_map_partial["b"]);
// Non-static method with arbitrary params, map style, partial
// (array | map) defaults
addf("methodna_map_leftreq", "methodna", &v);
work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_array_partial["a"]);
addf("methodnb_map_leftreq", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_array_partial["b"]);
addf("methodna_map_skipreq", "methodna", &v);
work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_map_partial["a"]);
addf("methodnb_map_skipreq", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_map_partial["b"]);
// (Free function | static method) with arbitrary params, map
// style, full (array | map) defaults
addf("freena_map_adft", "freena", &g);
work.add(name, desc, freena, params["a"], dft_array_full["a"]);
addf("freenb_map_adft", "freenb", &g);
work.add(name, desc, freenb, params["b"], dft_array_full["b"]);
addf("smethodna_map_adft", "smethodna", &g);
work.add(name, desc, &Vars::smethodna, params["a"], dft_array_full["a"]);
addf("smethodnb_map_adft", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_full["b"]);
addf("freena_map_mdft", "freena", &g);
work.add(name, desc, freena, params["a"], dft_map_full["a"]);
addf("freenb_map_mdft", "freenb", &g);
work.add(name, desc, freenb, params["b"], dft_map_full["b"]);
addf("smethodna_map_mdft", "smethodna", &g);
work.add(name, desc, &Vars::smethodna, params["a"], dft_map_full["a"]);
addf("smethodnb_map_mdft", "smethodnb", &g);
work.add(name, desc, &Vars::smethodnb, params["b"], dft_map_full["b"]);
// Non-static method with arbitrary params, map style, full
// (array | map) defaults
addf("methodna_map_adft", "methodna", &v);
work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_array_full["a"]);
addf("methodnb_map_adft", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_array_full["b"]);
addf("methodna_map_mdft", "methodna", &v);
work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_map_full["a"]);
addf("methodnb_map_mdft", "methodnb", &v);
work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_map_full["b"]);
// All the above are expected to succeed, and are setup for the
// tests to follow. Registration error cases are exercised as
// tests rather than as test setup.
}
void addf(const std::string& n, const std::string& d, Vars* v)
{
debug("addf('", n, "', '", d, "')");
// This method is to capture in our own DescMap the name and
// description of every registered function, for metadata query
// testing.
descs[n] = d;
// Also capture the Vars instance on which each function should operate.
funcvars[n] = v;
// See constructor for rationale for setting these instance vars.
this->name = n;
this->desc = d;
}
void verify_descs()
{
// Copy descs to a temp map of same type.
DescMap forgotten(descs.begin(), descs.end());
for (LLEventDispatcher::NameDesc nd: work)
{
DescMap::iterator found = forgotten.find(nd.first);
ensure(stringize("LLEventDispatcher records function '", nd.first,
"' we didn't enter"),
found != forgotten.end());
ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
"' doesn't match what we entered: '", found->second, "'"),
nd.second, found->second);
// found in our map the name from LLEventDispatcher, good, erase
// our map entry
forgotten.erase(found);
}
if (! forgotten.empty())
{
std::ostringstream out;
out << "LLEventDispatcher failed to report";
const char* delim = ": ";
for (const DescMap::value_type& fme: forgotten)
{
out << delim << fme.first;
delim = ", ";
}
throw failure(out.str());
}
}
Vars* varsfor(const std::string& name)
{
VarsMap::const_iterator found = funcvars.find(name);
ensure(stringize("No Vars* for ", name), found != funcvars.end());
ensure(stringize("NULL Vars* for ", name), found->second);
return found->second;
}
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
return tut::call_exc(
[this, func, args]()
{
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)
{
tut::call_logerr([this, func, args](){ work(func, args); }, frag);
}
LLSD getMetadata(const std::string& name)
{
LLSD meta(work.getMetadata(name));
ensure(stringize("No metadata for ", name), meta.isDefined());
return meta;
}
// From two related LLSD arrays, e.g. a param-names array and a values
// array, zip them together into an LLSD map.
LLSD zipmap(const LLSD& keys, const LLSD& values)
{
LLSD map;
for (LLSD::Integer i = 0, iend = keys.size(); i < iend; ++i)
{
// Have to select asString() since you can index an LLSD
// object with either String or Integer.
map[keys[i].asString()] = values[i];
}
return map;
}
// If I call this ensure_equals(), it blocks visibility of all other
// ensure_equals() overloads. Normally I could say 'using
// baseclass::ensure_equals;' and fix that, but I don't know what the
// base class is!
void ensure_llsd(const std::string& msg, const LLSD& actual, const LLSD& expected, U32 bits)
{
std::ostringstream out;
if (! msg.empty())
{
out << msg << ": ";
}
out << "expected " << expected << ", actual " << actual;
ensure(out.str(), llsd_equals(actual, expected, bits));
}
void ensure_llsd(const LLSD& actual, const LLSD& expected, U32 bits)
{
ensure_llsd("", actual, expected, bits);
}
};
typedef test_group<lleventdispatcher_data> lleventdispatcher_group;
typedef lleventdispatcher_group::object object;
lleventdispatcher_group lleventdispatchergrp("lleventdispatcher");
// Call cases:
// - (try_call | call) (explicit name | event key) (real | bogus) name
// - Callable with args that (do | do not) match required
// - (Free function | non-static method), no args, (array | map) style
// - (Free function | non-static method), arbitrary args,
// (array style with (scalar | map) | map style with scalar)
// - (Free function | non-static method), arbitrary args, array style with
// array (too short | too long | just right)
// [trap LL_WARNS for too-long case?]
// - (Free function | non-static method), arbitrary args, map style with
// (array | map) (all | too many | holes (with | without) defaults)
// - const char* param gets ("" | NULL)
// Query cases:
// - Iterate over all (with | without) remove()
// - getDispatchKey()
// - Callable style (with | without) required
// - (Free function | non-static method), array style, (no | arbitrary) params
// - (Free function | non-static method), map style, (no | arbitrary) params,
// (empty | full | partial (array | map)) defaults
template<> template<>
void object::test<1>()
{
set_test_name("map-style registration with non-array params");
// Pass "param names" as scalar or as map
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
for (LLSD ae: inArray(attempts))
{
std::string threw = catch_what<std::exception>([this, &ae](){
work.add("freena_err", "freena", freena, ae);
});
ensure_has(threw, "must be an array");
}
}
template<> template<>
void object::test<2>()
{
set_test_name("map-style registration with badly-formed defaults");
std::string threw = catch_what<std::exception>([this](){
work.add("freena_err", "freena", freena, llsd::array("a", "b"), 17);
});
ensure_has(threw, "must be a map or an array");
}
template<> template<>
void object::test<3>()
{
set_test_name("map-style registration with too many array defaults");
std::string threw = catch_what<std::exception>([this](){
work.add("freena_err", "freena", freena,
llsd::array("a", "b"),
llsd::array(17, 0.9, "gack"));
});
ensure_has(threw, "shorter than");
}
template<> template<>
void object::test<4>()
{
set_test_name("map-style registration with too many map defaults");
std::string threw = catch_what<std::exception>([this](){
work.add("freena_err", "freena", freena,
llsd::array("a", "b"),
LLSDMap("b", 17)("foo", 3.14)("bar", "sinister"));
});
ensure_has(threw, "nonexistent params");
ensure_has(threw, "foo");
ensure_has(threw, "bar");
}
template<> template<>
void object::test<5>()
{
set_test_name("query all");
verify_descs();
}
template<> template<>
void object::test<6>()
{
set_test_name("query all with remove()");
ensure("remove('bogus') returned true", ! work.remove("bogus"));
ensure("remove('real') returned false", work.remove("free1"));
// Of course, remove that from 'descs' too...
descs.erase("free1");
verify_descs();
}
template<> template<>
void object::test<7>()
{
set_test_name("getDispatchKey()");
ensure_equals(work.getDispatchKey(), "op");
}
template<> template<>
void object::test<8>()
{
set_test_name("query Callables with/out required params");
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
ensure("should not have required structure", metadata["required"].isUndefined());
ensure("should not have optional", metadata["optional"].isUndefined());
std::string name_req(nm.asString() + "_req");
metadata = getMetadata(name_req);
ensure_equals(metadata["name"].asString(), name_req);
ensure_equals(metadata["desc"].asString(), descs[name_req]);
ensure_equals("required mismatch", required, metadata["required"]);
ensure("should not have optional", metadata["optional"].isUndefined());
}
}
template<> template<>
void object::test<9>()
{
set_test_name("query array-style functions/methods");
// Associate each registered name with expected arity.
LLSD expected(llsd::array
(llsd::array
(0, llsd::array("free0_array", "smethod0_array", "method0_array")),
llsd::array
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
llsd::array
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
for (LLSD ae: inArray(expected))
{
LLSD::Integer arity(ae[0].asInteger());
LLSD names(ae[1]);
LLSD req(LLSD::emptyArray());
if (arity)
req[arity - 1] = LLSD();
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
ensure_equals(stringize("mismatched required for ", nm.asString()),
metadata["required"], req);
ensure("should not have optional", metadata["optional"].isUndefined());
}
}
}
template<> template<>
void object::test<10>()
{
set_test_name("query map-style no-params functions/methods");
// - (Free function | non-static method), map style, no params (ergo
// no defaults)
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
ensure("should not have required",
(metadata["required"].isUndefined() || metadata["required"].size() == 0));
ensure("should not have optional", metadata["optional"].isUndefined());
}
}
template<> template<>
void object::test<11>()
{
set_test_name("query map-style arbitrary-params functions/methods: "
"full array defaults vs. full map defaults");
// With functions registered with no defaults ("_allreq" suffixes),
// there is of course no difference between array defaults and map
// defaults. (We don't even bother registering with LLSD::emptyArray()
// vs. LLSD::emptyMap().) With functions registered with all defaults,
// there should (!) be no difference beween array defaults and map
// defaults. Verify, so we can ignore the distinction for all other
// tests.
LLSD equivalences(llsd::array
(llsd::array("freena_map_adft", "freena_map_mdft"),
llsd::array("freenb_map_adft", "freenb_map_mdft"),
llsd::array("smethodna_map_adft", "smethodna_map_mdft"),
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
llsd::array("methodna_map_adft", "methodna_map_mdft"),
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
for (LLSD eq: inArray(equivalences))
{
LLSD adft(eq[0]);
LLSD mdft(eq[1]);
// We can't just compare the results of the two getMetadata()
// calls, because they contain ["name"], which are different. So
// capture them, verify that each ["name"] is as expected, then
// remove for comparing the rest.
LLSD ameta(getMetadata(adft));
LLSD mmeta(getMetadata(mdft));
ensure_equals("adft name", adft, ameta["name"]);
ensure_equals("mdft name", mdft, mmeta["name"]);
ameta.erase("name");
mmeta.erase("name");
ensure_equals(stringize("metadata for ", adft.asString(),
" vs. ", mdft.asString()),
ameta, mmeta);
}
}
template<> template<>
void object::test<12>()
{
set_test_name("query map-style arbitrary-params functions/methods");
// - (Free function | non-static method), map style, arbitrary params,
// (empty | full | partial (array | map)) defaults
// Generate maps containing all parameter names for cases in which all
// params are required. Also maps containing left requirements for
// partial defaults arrays. Also defaults maps from defaults arrays.
LLSD allreq, leftreq, rightdft;
for (LLSD::String a: ab)
{
// The map in which all params are required uses params[a] as
// keys, with all isUndefined() as values. We can accomplish that
// by passing zipmap() an empty values array.
allreq[a] = zipmap(params[a], LLSD::emptyArray());
// Same for leftreq, save that we use the subset of the params not
// supplied by dft_array_partial[a].
LLSD::Integer partition(params[a].size() - dft_array_partial[a].size());
leftreq[a] = zipmap(llsd_copy_array(params[a].beginArray(),
params[a].beginArray() + partition),
LLSD::emptyArray());
// Generate map pairing dft_array_partial[a] values with their
// param names.
rightdft[a] = zipmap(llsd_copy_array(params[a].beginArray() + partition,
params[a].endArray()),
dft_array_partial[a]);
}
debug("allreq:\n",
allreq, "\n"
"leftreq:\n",
leftreq, "\n"
"rightdft:\n",
rightdft);
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
LLSD skipreq(allreq);
for (LLSD::String a: ab)
{
for (const MapEntry& me: inMap(dft_map_partial[a]))
{
skipreq[a].erase(me.first);
}
}
debug("skipreq:\n",
skipreq);
LLSD groups(llsd::array // array of groups
(llsd::array // group
(llsd::array("freena_map_allreq", "smethodna_map_allreq", "methodna_map_allreq"),
llsd::array(allreq["a"], LLSD())), // required, optional
llsd::array // group
(llsd::array("freenb_map_allreq", "smethodnb_map_allreq", "methodnb_map_allreq"),
llsd::array(allreq["b"], LLSD())), // required, optional
llsd::array // group
(llsd::array("freena_map_leftreq", "smethodna_map_leftreq", "methodna_map_leftreq"),
llsd::array(leftreq["a"], rightdft["a"])), // required, optional
llsd::array // group
(llsd::array("freenb_map_leftreq", "smethodnb_map_leftreq", "methodnb_map_leftreq"),
llsd::array(leftreq["b"], rightdft["b"])), // required, optional
llsd::array // group
(llsd::array("freena_map_skipreq", "smethodna_map_skipreq", "methodna_map_skipreq"),
llsd::array(skipreq["a"], dft_map_partial["a"])), // required, optional
llsd::array // group
(llsd::array("freenb_map_skipreq", "smethodnb_map_skipreq", "methodnb_map_skipreq"),
llsd::array(skipreq["b"], dft_map_partial["b"])), // required, optional
// We only need mention the full-map-defaults ("_mdft" suffix)
// registrations, having established their equivalence with the
// full-array-defaults ("_adft" suffix) registrations in another test.
llsd::array // group
(llsd::array("freena_map_mdft", "smethodna_map_mdft", "methodna_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["a"])), // required, optional
llsd::array // group
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
LLSD names(grp[0]);
LLSD required(grp[1][0]);
LLSD optional(grp[1][1]);
debug("For ", names, ",\n",
"required:\n",
required, "\n"
"optional:\n",
optional);
// Loop through 'names'
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
ensure_equals(stringize(nm, " required mismatch"),
metadata["required"], required);
ensure_equals(stringize(nm, " optional mismatch"),
metadata["optional"], optional);
}
}
}
template<> template<>
void object::test<13>()
{
set_test_name("try_call()");
ensure("try_call(bogus name, LLSD()) returned true", ! work.try_call("freek", LLSD()));
ensure("try_call(bogus name) returned true", ! work.try_call(LLSDMap("op", "freek")));
ensure("try_call(real name, LLSD()) returned false", work.try_call("free0_array", LLSD()));
ensure("try_call(real name) returned false", work.try_call(LLSDMap("op", "free0_map")));
}
template<> template<>
void object::test<14>()
{
set_test_name("call with bad name");
call_exc("freek", LLSD(), "not found");
std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
ensure_has(threw, "op");
ensure_has(threw, "freek");
}
template<> template<>
void object::test<15>()
{
set_test_name("call with event key");
// We don't need a separate test for operator()(string, LLSD) with
// valid name, because all the rest of the tests exercise that case.
// The one we don't exercise elsewhere is operator()(LLSD) with valid
// name, so here it is.
work(LLSDMap("op", "free0_map"));
ensure_equals(g.i, 17);
}
// Cannot be defined inside function body... remind me again why we use C++... :-P
struct CallablesTriple
{
std::string name, name_req;
LLSD& llsd;
};
template<> template<>
void object::test<16>()
{
set_test_name("call Callables");
CallablesTriple tests[] =
{
{ "free1", "free1_req", g.llsd },
{ "Dmethod1", "Dmethod1_req", work.llsd },
{ "Dcmethod1", "Dcmethod1_req", work.llsd },
{ "method1", "method1_req", v.llsd }
};
// Arbitrary LLSD value that we should be able to pass to Callables
// without 'required', but should not be able to pass to Callables
// with 'required'.
LLSD answer(42);
// LLSD value matching 'required' according to llsd_matches() rules.
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
// Okay, walk through 'tests'.
for (const CallablesTriple& tr: tests)
{
// Should be able to pass 'answer' to Callables registered
// without 'required'.
work(tr.name, answer);
ensure_equals("answer mismatch", tr.llsd, answer);
// Should NOT be able to pass 'answer' to Callables registered
// with 'required'.
call_logerr(tr.name_req, answer, "bad request");
// But SHOULD be able to pass 'matching' to Callables registered
// with 'required'.
work(tr.name_req, matching);
ensure_equals("matching mismatch", tr.llsd, matching);
}
}
template<> template<>
void object::test<17>()
{
set_test_name("passing wrong args to (map | array)-style registrations");
// Pass scalar/map to array-style functions, scalar/array to map-style
// functions. It seems pointless to repeat this with every variation:
// (free function | non-static method), (no | arbitrary) args. We
// should only need to engage it for one map-style registration and
// one array-style registration.
// Now that LLEventDispatcher has been extended to treat an LLSD
// scalar as a single-entry array, the error we expect in this case is
// that apply() is trying to pass that non-empty array to a nullary
// function.
call_logerr("free0_array", 17, "LL::apply");
// similarly, apply() doesn't accept an LLSD Map
call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
std::string map_exc("needs a map");
call_logerr("free0_map", 17, map_exc);
// Passing an array to a map-style function works now! No longer an
// error case!
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
}
template<> template<>
void object::test<18>()
{
set_test_name("call no-args functions");
LLSD names(llsd::array
("free0_array", "free0_map",
"smethod0_array", "smethod0_map",
"method0_array", "method0_map"));
for (LLSD name: inArray(names))
{
// Look up the Vars instance for this function.
Vars* vars(varsfor(name));
// Both the global and stack Vars instances are automatically
// cleared at the start of each test<n> method. But since we're
// calling these things several different times in the same
// test<n> method, manually reset the Vars between each.
*vars = Vars();
ensure_equals(vars->i, 0);
// call function with empty array (or LLSD(), should be equivalent)
work(name, LLSD());
ensure_equals(vars->i, 17);
}
}
// Break out this data because we use it in a couple different tests.
LLSD array_funcs(llsd::array
(LLSDMap("a", "freena_array") ("b", "freenb_array"),
LLSDMap("a", "smethodna_array")("b", "smethodnb_array"),
LLSDMap("a", "methodna_array") ("b", "methodnb_array")));
template<> template<>
void object::test<19>()
{
set_test_name("call array-style functions with wrong-length arrays");
// Could have different wrong-length arrays for *na and for *nb, but
// since they both take 5 params...
LLSD tooshort(llsd::array("this", "array", "too", "short"));
LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
LLSD badargs (llsd::array(tooshort, toolong));
for (const LLSD& toosomething: inArray(badargs))
{
for (const LLSD& funcsab: inArray(array_funcs))
{
for (const llsd::MapEntry& e: inMap(funcsab))
{
// apply() complains about wrong number of array entries
call_logerr(e.second, toosomething, "LL::apply");
}
}
}
}
template<> template<>
void object::test<20>()
{
set_test_name("call array-style functions with right-size arrays");
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
binary.push_back((U8)h);
}
LLSD args(LLSDMap("a", llsd::array(true, 17, 3.14, 123.456, "char*"))
("b", llsd::array("string",
LLUUID("01234567-89ab-cdef-0123-456789abcdef"),
LLDate("2011-02-03T15:07:00Z"),
LLURI("http://secondlife.com"),
binary)));
LLSD expect;
for (LLSD::String a: ab)
{
expect[a] = zipmap(params[a], args[a]);
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
debug("expect: ", expect);
for (const LLSD& funcsab: inArray(array_funcs))
{
for (LLSD::String a: ab)
{
// Reset the Vars instance before each call
Vars* vars(varsfor(funcsab[a]));
*vars = Vars();
work(funcsab[a], args[a]);
ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
}
}
}
template<> template<>
void object::test<21>()
{
set_test_name("verify that passing LLSD() to const char* sends NULL");
ensure_equals("Vars::cp init", v.cp, "");
work("methodna_map_mdft", LLSDMap("cp", LLSD()));
ensure_equals("passing LLSD()", v.cp, "NULL");
work("methodna_map_mdft", LLSDMap("cp", ""));
ensure_equals("passing \"\"", v.cp, "''");
work("methodna_map_mdft", LLSDMap("cp", "non-NULL"));
ensure_equals("passing \"non-NULL\"", v.cp, "'non-NULL'");
}
template<> template<>
void object::test<22>()
{
set_test_name("call map-style functions with (full | oversized) (arrays | maps)");
const char binary[] = "\x99\x88\x77\x66\x55";
LLSD array_full(LLSDMap
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
LLSD array_overfull(array_full);
for (LLSD::String a: ab)
{
array_overfull[a].append("bogus");
}
debug("array_full: ", array_full, "\n"
"array_overfull: ", array_overfull);
// We rather hope that LLDate::now() will generate a timestamp
// distinct from the one it generated in the constructor, moments ago.
ensure_not_equals("Timestamps too close",
array_full["b"][2].asDate(), dft_array_full["b"][2].asDate());
// We /insist/ that LLUUID::generateNewID() do so.
ensure_not_equals("UUID collision",
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
LLSD map_full, map_overfull;
for (LLSD::String a: ab)
{
map_full[a] = zipmap(params[a], array_full[a]);
map_overfull[a] = map_full[a];
map_overfull[a]["extra"] = "ignore";
}
debug("map_full: ", map_full, "\n"
"map_overfull: ", map_overfull);
LLSD expect(map_full);
// Twiddle the const char* param.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
// Another adjustment. For each data type, we're trying to distinguish
// three values: the Vars member's initial value (member wasn't
// stored; control never reached the set function), the registered
// default param value from dft_array_full, and the array_full value
// in this test. But bool can only distinguish two values. In this
// case, we want to differentiate the local array_full value from the
// dft_array_full value, so we use 'false'. However, that means
// Vars::inspect() doesn't differentiate it from the initial value,
// so won't bother returning it. Predict that behavior to match the
// LLSD values.
expect["a"].erase("b");
debug("expect: ", expect);
// For this test, calling functions registered with different sets of
// parameter defaults should make NO DIFFERENCE WHATSOEVER. Every call
// should pass all params.
LLSD names(LLSDMap
("a", llsd::array
("freena_map_allreq", "smethodna_map_allreq", "methodna_map_allreq",
"freena_map_leftreq", "smethodna_map_leftreq", "methodna_map_leftreq",
"freena_map_skipreq", "smethodna_map_skipreq", "methodna_map_skipreq",
"freena_map_adft", "smethodna_map_adft", "methodna_map_adft",
"freena_map_mdft", "smethodna_map_mdft", "methodna_map_mdft"))
("b", llsd::array
("freenb_map_allreq", "smethodnb_map_allreq", "methodnb_map_allreq",
"freenb_map_leftreq", "smethodnb_map_leftreq", "methodnb_map_leftreq",
"freenb_map_skipreq", "smethodnb_map_skipreq", "methodnb_map_skipreq",
"freenb_map_adft", "smethodnb_map_adft", "methodnb_map_adft",
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
// Treat (full | overfull) (array | map) the same.
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
for (const LLSD& args: inArray(argssets))
{
for (LLSD::String a: ab)
{
for (LLSD::String name: inArray(names[a]))
{
// Reset the Vars instance
Vars* vars(varsfor(name));
*vars = Vars();
work(name, args[a]);
ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
// intercept LL_WARNS for the two overfull cases?
}
}
}
}
struct DispatchResult: public LLDispatchListener
{
using DR = DispatchResult;
DispatchResult(): LLDispatchListener("results", "op")
{
add("strfunc", "return string", &DR::strfunc);
add("voidfunc", "void function", &DR::voidfunc);
add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
add("intfunc", "return Integer LLSD", &DR::intfunc);
add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
add("mapfunc", "return map LLSD", &DR::mapfunc);
add("arrayfunc", "return array LLSD", &DR::arrayfunc);
}
std::string strfunc(const std::string& str) const { return "got " + str; }
void voidfunc() const {}
LLSD emptyfunc() const { return {}; }
int intfunc(int i) const { return -i; }
LLSD llsdfunc(const LLSD& event) const
{
LLSD result{ event };
result["with"] = "string";
return result;
}
LLSD mapfunc(int i, const std::string& str) const
{
return llsd::map("i", intfunc(i), "str", strfunc(str));
}
LLSD arrayfunc(int i, const std::string& str) const
{
return llsd::array(intfunc(i), strfunc(str));
}
};
template<> template<>
void object::test<23>()
{
set_test_name("string result");
DispatchResult service;
LLSD result{ service("strfunc", "a string") };
ensure_equals("strfunc() mismatch", result.asString(), "got a string");
}
template<> template<>
void object::test<24>()
{
set_test_name("void result");
DispatchResult service;
LLSD result{ service("voidfunc", LLSD()) };
ensure("voidfunc() returned defined", result.isUndefined());
}
template<> template<>
void object::test<25>()
{
set_test_name("Integer result");
DispatchResult service;
LLSD result{ service("intfunc", -17) };
ensure_equals("intfunc() mismatch", result.asInteger(), 17);
}
template<> template<>
void object::test<26>()
{
set_test_name("LLSD echo");
DispatchResult service;
LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
ensure_equals("llsdfunc() mismatch", result,
llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
}
template<> template<>
void object::test<27>()
{
set_test_name("map LLSD result");
DispatchResult service;
LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
}
template<> template<>
void object::test<28>()
{
set_test_name("array LLSD result");
DispatchResult service;
LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
}
template<> template<>
void object::test<29>()
{
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<30>()
{
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<31>()
{
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<32>()
{
set_test_name("listener call to string function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "strfunc",
"args", llsd::array("a string"),
"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(), "got a string");
}
template<> template<>
void object::test<33>()
{
set_test_name("listener call to map function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "mapfunc",
"args", llsd::array(-7, "value"),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
ensure_equals("bad str from mapfunc", reply["str"], "got value");
}
template<> template<>
void object::test<34>()
{
set_test_name("batched map success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"strfunc", "some string",
"intfunc", 2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad map batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<35>()
{
set_test_name("batched map error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"].asString() };
reply.erase("error");
ensure_has(error, "badfunc");
ensure_has(error, "missing");
ensure_equals(
"bad partial batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<36>()
{
set_test_name("batched map exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17));
// no "reply"
},
"badfunc");
ensure_has(error, "missing");
}
template<> template<>
void object::test<37>()
{
set_test_name("batched array success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2),
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad array batch",
reply,
llsd::map(
"data", llsd::array(
"got some string",
-2,
llsd::array(5, "got other string"),
LLSD())));
}
template<> template<>
void object::test<38>()
{
set_test_name("batched array error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"] };
reply.erase("error");
ensure_has(error, "[1]");
ensure_has(error, "unsupported");
ensure_equals("bad array batch", reply,
llsd::map("data", llsd::array("got some string")));
}
template<> template<>
void object::test<39>()
{
set_test_name("batched array exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17));
// no "reply"
},
"[1]");
ensure_has(error, "unsupported");
}
} // namespace tut