From 2770d6f8813b6bfef89e80dfaf091287c8c0eb4d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jan 2011 20:14:38 -0500 Subject: Introduce LLSDArray, LLSDMap, LLSDParam. LLSDArray is a helper to construct an LLSD::Array value inline. LLSDMap is a helper to construct an LLSD::Map value inline. LLSDParam is a customization point, a way for generic code to support unforseen parameter types as conversion targets for LLSD values. --- indra/llcommon/llsdutil.h | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index bb8c0690b1..58ccc59f5e 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -138,4 +138,192 @@ template LLSD llsd_copy_array(Input iter, Input end) return dest; } +/***************************************************************************** +* LLSDArray +*****************************************************************************/ +/** + * Construct an LLSD::Array inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDArray("text")(17)(3.14)); + * @endcode + * + * For completeness, LLSDArray() with no args constructs an empty array, so + * LLSDArray()("text")(17)(3.14) produces an array equivalent to the + * above. But for most purposes, LLSD() is already equivalent to an empty + * array, and if you explicitly want an empty isArray(), there's + * LLSD::emptyArray(). However, supporting a no-args LLSDArray() constructor + * follows the principle of least astonishment. + */ +class LLSDArray +{ +public: + LLSDArray(): + _data(LLSD::emptyArray()) + {} + LLSDArray(const LLSD& value): + _data(LLSD::emptyArray()) + { + _data.append(value); + } + + LLSDArray& operator()(const LLSD& value) + { + _data.append(value); + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +/***************************************************************************** +* LLSDMap +*****************************************************************************/ +/** + * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); + * @endcode + * + * For completeness, LLSDMap() with no args constructs an empty map, so + * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) produces a map + * equivalent to the above. But for most purposes, LLSD() is already + * equivalent to an empty map, and if you explicitly want an empty isMap(), + * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() + * constructor follows the principle of least astonishment. + */ +class LLSDMap +{ +public: + LLSDMap(): + _data(LLSD::emptyMap()) + {} + LLSDMap(const LLSD::String& key, const LLSD& value): + _data(LLSD::emptyMap()) + { + _data[key] = value; + } + + LLSDMap& operator()(const LLSD::String& key, const LLSD& value) + { + _data[key] = value; + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +/***************************************************************************** +* LLSDParam +*****************************************************************************/ +/** + * LLSDParam is a customization point for passing LLSD values to function + * parameters of more or less arbitrary type. LLSD provides a small set of + * native conversions; but if a generic algorithm explicitly constructs an + * LLSDParam object in the function's argument list, a consumer can provide + * LLSDParam specializations to support more different parameter types than + * LLSD's native conversions. + * + * Usage: + * + * @code + * void somefunc(const paramtype&); + * ... + * somefunc(..., LLSDParam(someLLSD), ...); + * @endcode + */ +template +class LLSDParam +{ +public: + /** + * Default implementation converts to T on construction, saves converted + * value for later retrieval + */ + LLSDParam(const LLSD& value): + _value(value) + {} + + operator T() const { return _value; } + +private: + T _value; +}; + +/** + * LLSDParam is an example of the kind of conversion you can + * support with LLSDParam beyond native LLSD conversions. Normally you can't + * pass an LLSD object to a function accepting const char* -- but you can + * safely pass an LLSDParam(yourLLSD). + */ +template <> +class LLSDParam +{ +private: + // The difference here is that we store a std::string rather than a const + // char*. It's important that the LLSDParam object own the std::string. + std::string _value; + // We don't bother storing the incoming LLSD object, but we do have to + // distinguish whether _value is an empty string because the LLSD object + // contains an empty string or because it's isUndefined(). + bool _undefined; + +public: + LLSDParam(const LLSD& value): + _value(value), + _undefined(value.isUndefined()) + {} + + // The const char* we retrieve is for storage owned by our _value member. + // That's how we guarantee that the const char* is valid for the lifetime + // of this LLSDParam object. Constructing your LLSDParam in the argument + // list should ensure that the LLSDParam object will persist for the + // duration of the function call. + operator const char*() const + { + if (_undefined) + { + // By default, an isUndefined() LLSD object's asString() method + // will produce an empty string. But for a function accepting + // const char*, it's often important to be able to pass NULL, and + // isUndefined() seems like the best way. If you want to pass an + // empty string, you can still pass LLSD(""). Without this special + // case, though, no LLSD value could pass NULL. + return NULL; + } + return _value.c_str(); + } +}; + +/** + * LLSDParam resolves conversion ambiguity. g++ considers F64, S32 and + * bool equivalent candidates for implicit conversion to float. (/me rolls eyes) + */ +template <> +class LLSDParam +{ +private: + float _value; + +public: + LLSDParam(const LLSD& value): + _value(value.asReal()) + {} + + operator float() const { return _value; } +}; + #endif // LL_LLSDUTIL_H -- cgit v1.2.3 From 2bafe0dc8a2eb1d99516a4af96acc93c3541a1cd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jan 2011 20:18:10 -0500 Subject: Extend LLEventAPI to directly call other functions & methods. Until now, LLEventAPI has only been able to register functions specifically accepting(const LLSD&). Typically you add a wrapper method to your LLEventAPI subclass, register that, have it extract desired params from the incoming LLSD and then call the actual function of interest. With help from Alain, added new LLEventAPI::add() methods capable of registering functions/methods with arbitrary parameter signatures. The code uses boost::fusion magic to implicitly match incoming LLSD arguments to the function's formal parameter list, bypassing the need for an explicit helper method. New add() methods caused an ambiguity with a previous convenience overload. Removed that overload and fixed the one existing usage. Replaced LLEventDispatcher::get() with try_call() -- it's no longer easy to return a Callable for caller to call directly. But the one known use of that feature simply used it to avoid fatal LL_ERRS on unknown function-name string, hence the try_call() approach actually addresses that case more directly. Added indra/common/lleventdispatcher_test.cpp to exercise new functionality. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/lleventdispatcher.cpp | 535 ++++++++++++++++++++++-- indra/llcommon/lleventdispatcher.h | 374 +++++++++++++++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 436 +++++++++++++++++++ 4 files changed, 1289 insertions(+), 57 deletions(-) create mode 100644 indra/llcommon/tests/lleventdispatcher_test.cpp (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 9342a22d46..dc9f93df3b 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -313,6 +313,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}") LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}") LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index d6e820d793..2ab006a173 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -41,6 +41,326 @@ #include "llevents.h" #include "llerror.h" #include "llsdutil.h" +#include "stringize.h" +#include // std::auto_ptr + +/***************************************************************************** +* LLSDArgsSource +*****************************************************************************/ +/** + * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS + * if the consumer requests more elements than the array contains. + */ +class LL_COMMON_API LLSDArgsSource +{ +public: + LLSDArgsSource(const std::string function, const LLSD& args); + ~LLSDArgsSource(); + + LLSD next(); + + void done() const; + +private: + std::string _function; + LLSD _args; + LLSD::Integer _index; +}; + +LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): + _function(function), + _args(args), + _index(0) +{ + if (! (_args.isUndefined() || _args.isArray())) + { + LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " + << _args << LL_ENDL; + } +} + +LLSDArgsSource::~LLSDArgsSource() +{ + done(); +} + +LLSD LLSDArgsSource::next() +{ + if (_index >= _args.size()) + { + LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " + << _args.size() << " provided: " << _args << LL_ENDL; + } + return _args[_index++]; +} + +void LLSDArgsSource::done() const +{ + if (_index < _args.size()) + { + LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index + << " of the " << _args.size() << " arguments provided: " + << _args << LL_ENDL; + } +} + +/***************************************************************************** +* LLSDArgsMapper +*****************************************************************************/ +/** + * From a formal parameters description and a map of arguments, construct an + * arguments array. + * + * That is, given: + * - an LLSD array of length n containing parameter-name strings, + * corresponding to the arguments of a function of interest + * - an LLSD collection specifying default parameter values, either: + * - an LLSD array of length m <= n, matching the rightmost m params, or + * - an LLSD map explicitly stating default name=value pairs + * - an LLSD map of parameter names and actual values for a particular + * function call + * construct an LLSD array of actual argument values for this function call. + * + * The parameter-names array and the defaults collection describe the function + * being called. The map might vary with every call, providing argument values + * for the described parameters. + * + * The array of parameter names must match the number of parameters expected + * by the function of interest. + * + * If you pass a map of default parameter values, it provides default values + * as you might expect. It is an error to specify a default value for a name + * not listed in the parameters array. + * + * If you pass an array of default parameter values, it is mapped to the + * rightmost m of the n parameter names. It is an error if the default-values + * array is longer than the parameter-names array. Consider the following + * parameter names: ["a", "b", "c", "d"]. + * + * - An empty array of default values (or an isUndefined() value) asserts that + * every one of the above parameter names is required. + * - An array of four default values [1, 2, 3, 4] asserts that every one of + * the above parameters is optional. If the current parameter map is empty, + * they will be passed to the function as [1, 2, 3, 4]. + * - An array of two default values [11, 12] asserts that parameters "a" and + * "b" are required, while "c" and "d" are optional, having default values + * "c"=11 and "d"=12. + * + * The arguments array is constructed as follows: + * + * - Arguments-map keys not found in the parameter-names array are ignored. + * - Entries from the map provide values for an improper subset of the + * parameters named in the parameter-names array. This results in a + * tentative values array with "holes." (size of map) + (number of holes) = + * (size of names array) + * - Holes are filled with the default values. + * - Any remaining holes constitute an error. + */ +class LL_COMMON_API LLSDArgsMapper +{ +public: + /// Accept description of function: function name, param names, param + /// default values + LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); + + /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS. + LLSD map(const LLSD& argsmap) const; + +private: + static std::string formatlist(const LLSD&); + + // The function-name string is purely descriptive. We want error messages + // to be able to indicate which function's LLSDArgsMapper has the problem. + std::string _function; + // Store the names array pretty much as given. + LLSD _names; + // Though we're handed an array of name strings, it's more useful to us to + // store it as a map from name string to position index. Of course that's + // easy to generate from the incoming names array, but why do it more than + // once? + typedef std::map IndexMap; + IndexMap _indexes; + // Generated array of default values, aligned with the array of param names. + LLSD _defaults; + // Indicate whether we have a default value for each param. + typedef std::vector FilledVector; + FilledVector _has_dft; +}; + +LLSDArgsMapper::LLSDArgsMapper(const std::string& function, + const LLSD& names, const LLSD& defaults): + _function(function), + _names(names), + _has_dft(names.size()) +{ + if (! (_names.isUndefined() || _names.isArray())) + { + LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; + } + LLSD::Integer nparams(_names.size()); + // From _names generate _indexes. + for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni) + { + _indexes[_names[ni]] = ni; + } + + // Presize _defaults() array so we don't have to resize it more than once. + // All entries are initialized to LLSD(); but since _has_dft is still all + // 0, they're all "holes" for now. + if (nparams) + { + _defaults[nparams - 1] = LLSD(); + } + + if (defaults.isUndefined() || defaults.isArray()) + { + LLSD::Integer ndefaults = defaults.size(); + // defaults is a (possibly empty) array. Right-align it with names. + if (ndefaults > nparams) + { + LL_ERRS("LLSDArgsMapper") << function << " names array " << names + << " shorter than defaults array " << defaults << LL_ENDL; + } + + // Offset by which we slide defaults array right to right-align with + // _names array + LLSD::Integer offset = nparams - ndefaults; + // Fill rightmost _defaults entries from defaults, and mark them as + // filled + for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i) + { + _defaults[i + offset] = defaults[i]; + _has_dft[i + offset] = 1; + } + } + else if (defaults.isMap()) + { + // defaults is a map. Use it to populate the _defaults array. + LLSD bogus; + for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap()); + mi != mend; ++mi) + { + IndexMap::const_iterator ixit(_indexes.find(mi->first)); + if (ixit == _indexes.end()) + { + bogus.append(mi->first); + continue; + } + + LLSD::Integer pos = ixit->second; + // Store default value at that position in the _defaults array. + _defaults[pos] = mi->second; + // Don't forget to record the fact that we've filled this + // position. + _has_dft[pos] = 1; + } + if (bogus.size()) + { + LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " + << formatlist(bogus) << LL_ENDL; + } + } + else + { + LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " + << defaults << LL_ENDL; + } +} + +LLSD LLSDArgsMapper::map(const LLSD& argsmap) const +{ + if (! (argsmap.isUndefined() || argsmap.isMap())) + { + LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map, not " << argsmap << LL_ENDL; + } + // Initialize the args array. Indexing a non-const LLSD array grows it + // to appropriate size, but we don't want to resize this one on each + // new operation. Just make it as big as we need before we start + // stuffing values into it. + LLSD args(LLSD::emptyArray()); + if (_defaults.size() == 0) + { + // If this function requires no arguments, fast exit. (Don't try to + // assign to args[-1].) + return args; + } + args[_defaults.size() - 1] = LLSD(); + + // Get a vector of chars to indicate holes. It's tempting to just scan + // for LLSD::isUndefined() values after filling the args array from + // the map, but it's plausible for caller to explicitly pass + // isUndefined() as the value of some parameter name. That's legal + // since isUndefined() has well-defined conversions (default value) + // for LLSD data types. So use a whole separate array for detecting + // holes. (Avoid std::vector which is known to be odd -- can we + // iterate?) + FilledVector filled(args.size()); + // Walk the map. + for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); + mi != mend; ++mi) + { + // mi->first is a parameter-name string, with mi->second its + // value. Look up the name's position index in _indexes. + IndexMap::const_iterator ixit(_indexes.find(mi->first)); + if (ixit == _indexes.end()) + { + // Allow for a map containing more params than were passed in + // our names array. Caller typically receives a map containing + // the function name, cruft such as reqid, etc. Ignore keys + // not defined in _indexes. + LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " + << mi->first << "=" << mi->second << LL_ENDL; + continue; + } + LLSD::Integer pos = ixit->second; + // Store the value at that position in the args array. + args[pos] = mi->second; + // Don't forget to record the fact that we've filled this + // position. + filled[pos] = 1; + } + // Fill any remaining holes from _defaults. + LLSD unfilled(LLSD::emptyArray()); + for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i) + { + if (! filled[i]) + { + // If there's no default value for this parameter, that's an + // error. + if (! _has_dft[i]) + { + unfilled.append(_names[i]); + } + else + { + args[i] = _defaults[i]; + } + } + } + // If any required args -- args without defaults -- were left unfilled + // by argsmap, that's a problem. + if (unfilled.size()) + { + LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments " + << formatlist(unfilled) << " from " << argsmap << LL_ENDL; + } + + // done + return args; +} + +std::string LLSDArgsMapper::formatlist(const LLSD& list) +{ + std::ostringstream out; + const char* delim = ""; + for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray()); + li != lend; ++li) + { + out << delim << li->asString(); + delim = ", "; + } + return out.str(); +} LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): mDesc(desc), @@ -52,12 +372,178 @@ LLEventDispatcher::~LLEventDispatcher() { } +/** + * DispatchEntry subclass used for callables accepting(const LLSD&) + */ +struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry +{ + LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): + DispatchEntry(desc), + mFunc(func), + mRequired(required) + {} + + Callable mFunc; + LLSD mRequired; + + virtual void call(const std::string& desc, const LLSD& event) const + { + // Validate the syntax of the event itself. + std::string mismatch(llsd_matches(mRequired, event)); + if (! mismatch.empty()) + { + LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; + } + // Event syntax looks good, go for it! + mFunc(event); + } + + virtual LLSD addMetadata(LLSD meta) const + { + meta["required"] = mRequired; + return meta; + } +}; + +/** + * DispatchEntry subclass for passing LLSD to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry +{ + ParamsDispatchEntry(const std::string& desc, const invoker_function& func): + DispatchEntry(desc), + mInvoker(func) + {} + + invoker_function mInvoker; + + virtual void call(const std::string& desc, const LLSD& event) const + { + LLSDArgsSource src(desc, event); + mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); + } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Array to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ + ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, + LLSD::Integer arity): + ParamsDispatchEntry(desc, func), + mArity(arity) + {} + + LLSD::Integer mArity; + + virtual LLSD addMetadata(LLSD meta) const + { + LLSD array(LLSD::emptyArray()); + // Resize to number of arguments required + array[mArity - 1] = LLSD(); + llassert_always(array.size() == mArity); + meta["required"] = array; + return meta; + } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Map to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ + MapParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func, + const LLSD& params, const LLSD& defaults): + ParamsDispatchEntry(desc, func), + mMapper(name, params, defaults), + mRequired(LLSD::emptyMap()) + { + // Build the set of all param keys, then delete the ones that are + // optional. What's left are the ones that are required. + for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray()); + pi != pend; ++pi) + { + mRequired[pi->asString()] = LLSD(); + } + + if (defaults.isArray() || defaults.isUndefined()) + { + // Right-align the params and defaults arrays. + LLSD::Integer offset = params.size() - defaults.size(); + // Now the name of every defaults[i] is at params[i + offset]. + for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i) + { + // Erase this optional param from mRequired. + mRequired.erase(params[i + offset].asString()); + // Instead, make an entry in mOptional with the default + // param's name and value. + mOptional[params[i + offset].asString()] = defaults[i]; + } + } + else if (defaults.isMap()) + { + // if defaults is already a map, then it's already in the form we + // intend to deliver in metadata + mOptional = defaults; + // Just delete from mRequired every key appearing in mOptional. + for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap()); + mi != mend; ++mi) + { + mRequired.erase(mi->first); + } + } + } + + LLSDArgsMapper mMapper; + LLSD mRequired; + LLSD mOptional; + + virtual void call(const std::string& desc, const LLSD& event) const + { + // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass + // to base-class call() method. + ParamsDispatchEntry::call(desc, mMapper.map(event)); + } + + virtual LLSD addMetadata(LLSD meta) const + { + meta["required"] = mRequired; + meta["optional"] = mOptional; + return meta; + } +}; + +void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + LLSD::Integer arity) +{ + // Peculiar to me that boost::ptr_map() accepts std::auto_ptr but not dumb ptr + mDispatch.insert(name, std::auto_ptr( + new ArrayParamsDispatchEntry(desc, invoker, arity))); +} + +void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + const LLSD& params, + const LLSD& defaults) +{ + mDispatch.insert(name, std::auto_ptr( + new MapParamsDispatchEntry(name, desc, invoker, params, defaults))); +} + /// Register a callable by name void LLEventDispatcher::add(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.insert(DispatchMap::value_type(name, - DispatchMap::mapped_type(callable, desc, required))); + mDispatch.insert(name, std::auto_ptr( + new LLSDDispatchEntry(desc, callable, required))); } void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const @@ -83,7 +569,7 @@ bool LLEventDispatcher::remove(const std::string& name) /// such callable exists, die with LL_ERRS. void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - if (! attemptCall(name, event)) + if (! try_call(name, event)) { LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name << "' not found" << LL_ENDL; @@ -98,44 +584,29 @@ void LLEventDispatcher::operator()(const LLSD& event) const // This could/should be implemented in terms of the two-arg overload. // However -- we can produce a more informative error message. std::string name(event[mKey]); - if (! attemptCall(name, event)) + if (! try_call(name, event)) { LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey << " value '" << name << "'" << LL_ENDL; } } -bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +bool LLEventDispatcher::try_call(const LLSD& event) const { - DispatchMap::const_iterator found = mDispatch.find(name); - if (found == mDispatch.end()) - { - // The reason we only return false, leaving it up to our caller to die - // with LL_ERRS, is that different callers have different amounts of - // available information. - return false; - } - // Found the name, so it's plausible to even attempt the call. But first, - // validate the syntax of the event itself. - std::string mismatch(llsd_matches(found->second.mRequired, event)); - if (! mismatch.empty()) - { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name - << "': bad request: " << mismatch << LL_ENDL; - } - // Event syntax looks good, go for it! - (found->second.mFunc)(event); - return true; // tell caller we were able to call + return try_call(event[mKey], event); } -LLEventDispatcher::Callable LLEventDispatcher::get(const std::string& name) const +bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - return Callable(); + return false; } - return found->second.mFunc; + // Found the name, so it's plausible to even attempt the call. + found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"), + event); + return true; // tell caller we were able to call } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -147,9 +618,8 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const } LLSD meta; meta["name"] = name; - meta["desc"] = found->second.mDesc; - meta["required"] = found->second.mRequired; - return meta; + meta["desc"] = found->second->mDesc; + return found->second->addMetadata(meta); } LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): @@ -164,3 +634,8 @@ bool LLDispatchListener::process(const LLSD& event) (*this)(event); return false; } + +LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): + mDesc(desc) +{} + diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index dfffd59eb6..ce0fc7b585 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,18 +27,56 @@ * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ + * + * The invoker machinery that constructs a boost::fusion argument list for use + * with boost::fusion::invoke() is derived from + * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp + * whose license information is copied below: + * + * "(C) Copyright Tobias Schwinger + * + * Use modification and distribution are subject to the boost Software License, + * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)." */ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H +// nil is too generic a term to be allowed to be a global macro. In +// particular, boost::fusion defines a 'class nil' (properly encapsulated in a +// namespace) that a global 'nil' macro breaks badly. +#if defined(nil) +// Capture the value of the macro 'nil', hoping int is an appropriate type. +static const int nil_(nil); +// Now forget the macro. +#undef nil +// Finally, reintroduce 'nil' as a properly-scoped alias for the previously- +// defined const 'nil_'. Make it static since otherwise it produces duplicate- +// symbol link errors later. +static const int& nil(nil_); +#endif + #include -#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "llevents.h" +#include "llsdutil.h" class LLSD; @@ -54,12 +92,18 @@ public: LLEventDispatcher(const std::string& desc, const std::string& key); virtual ~LLEventDispatcher(); - /// Accept any C++ callable, typically a boost::bind() expression + /// @name Register functions accepting(const LLSD&) + //@{ + + /// Accept any C++ callable with the right signature, typically a + /// boost::bind() expression typedef boost::function Callable; /** - * Register a @a callable by @a name. The optional @a required parameter - * is used to validate the structure of each incoming event (see + * Register a @a callable by @a name. The passed @a callable accepts a + * single LLSD value and uses it in any way desired, e.g. extract + * parameters and call some other function. The optional @a required + * parameter is used to validate the structure of each incoming event (see * llsd_matches()). */ void add(const std::string& name, @@ -69,8 +113,9 @@ public: /** * Special case: a subclass of this class can pass an unbound member - * function pointer without explicitly specifying the - * boost::bind() expression. + * function pointer (of an LLEventDispatcher subclass) without explicitly + * specifying the boost::bind() expression. The passed @a method + * accepts a single LLSD value, presumably containing other parameters. */ template void add(const std::string& name, @@ -81,7 +126,8 @@ public: addMethod(name, desc, method, required); } - /// Overload for both const and non-const methods + /// Overload for both const and non-const methods. The passed @a method + /// accepts a single LLSD value, presumably containing other parameters. template void add(const std::string& name, const std::string& desc, @@ -91,8 +137,10 @@ public: addMethod(name, desc, method, required); } +/*==========================================================================*| /// Convenience: for LLEventDispatcher, not every callable needs a - /// documentation string. + /// documentation string. The passed @a callable accepts a single LLSD + /// value, presumably containing other parameters. template void add(const std::string& name, CALLABLE callable, @@ -100,6 +148,92 @@ public: { add(name, "", callable, required); } +|*==========================================================================*/ + + //@} + + /// @name Register functions with arbitrary param lists + //@{ + + /** + * Register a free function with arbitrary parameters. (This also works + * for static class methods.) + * + * When calling this name, pass an LLSD::Array. Each entry in turn will be + * converted to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f); + + /** + * Register a nonstatic class method with arbitrary parameters. + * + * To cover cases such as a method on an LLSingleton we don't yet want to + * instantiate, instead of directly storing an instance pointer, accept a + * nullary callable returning a pointer/reference to the desired class + * instance. If you already have an instance in hand, + * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) + * produce suitable callables. + * + * When calling this name, pass an LLSD::Array. Each entry in turn will be + * converted to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); + + /** + * Register a free function with arbitrary parameters. (This also works + * for static class methods.) + * + * Pass an LLSD::Array of parameter names, and optionally another + * LLSD::Array of default parameter values, a la LLSDArgsMapper. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn + * to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); + + /** + * Register a nonstatic class method with arbitrary parameters. + * + * To cover cases such as a method on an LLSingleton we don't yet want to + * instantiate, instead of directly storing an instance pointer, accept a + * nullary callable returning a pointer/reference to the desired class + * instance. If you already have an instance in hand, + * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) + * produce suitable callables. + * + * Pass an LLSD::Array of parameter names, and optionally another + * LLSD::Array of default parameter values, a la LLSDArgsMapper. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn + * to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); + + //@} /// Unregister a callable bool remove(const std::string& name); @@ -109,12 +243,25 @@ public: /// the @a required prototype specified at add() time, die with LL_ERRS. void operator()(const std::string& name, const LLSD& event) const; + /// Call a registered callable with an explicitly-specified name and + /// return true. If no such callable exists, return + /// false. If the @a event fails to match the @a required + /// prototype specified at add() time, die with LL_ERRS. + bool try_call(const std::string& name, const LLSD& event) const; + /// Extract the @a key value from the incoming @a event, and call the /// callable whose name is specified by that map @a key. If no such /// callable exists, die with LL_ERRS. If the @a event fails to match the /// @a required prototype specified at add() time, die with LL_ERRS. void operator()(const LLSD& event) const; + /// Extract the @a key value from the incoming @a event, call the callable + /// whose name is specified by that map @a key and return true. + /// If no such callable exists, return false. If the @a event + /// fails to match the @a required prototype specified at add() time, die + /// with LL_ERRS. + bool try_call(const LLSD& event) const; + /// @name Iterate over defined names //@{ typedef std::pair NameDesc; @@ -122,37 +269,44 @@ public: private: struct DispatchEntry { - DispatchEntry(const Callable& func, const std::string& desc, const LLSD& required): - mFunc(func), - mDesc(desc), - mRequired(required) - {} - Callable mFunc; + DispatchEntry(const std::string& desc); + std::string mDesc; - LLSD mRequired; + + virtual void call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD addMetadata(LLSD) const = 0; }; - typedef std::map DispatchMap; + typedef boost::ptr_map DispatchMap; public: /// We want the flexibility to redefine what data we store per name, /// therefore our public interface doesn't expose DispatchMap iterators, /// or DispatchMap itself, or DispatchEntry. Instead we explicitly /// transform each DispatchMap item to NameDesc on dereferencing. - typedef boost::transform_iterator const_iterator; + typedef boost::transform_iterator const_iterator; const_iterator begin() const { - return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc); + // Originally we used DispatchMap::const_iterator, which Just Worked + // when DispatchMap was a std::map. Now that it's a boost::ptr_map, + // using DispatchMap::const_iterator doesn't work so well: it + // dereferences to a pair, whereas + // DispatchMap::value_type is just pair. Trying to pass a + // dereferenced iterator to the value_type didn't work because the + // compiler won't let you convert from const T* to plain T*. Changing + // our const_iterator definition above to be based on non-const + // DispatchMap::iterator works better, but of course we have to cast + // away the constness of mDispatch to use non-const iterator. (Sigh.) + return boost::make_transform_iterator(const_cast(mDispatch).begin(), + makeNameDesc); } const_iterator end() const { - return boost::make_transform_iterator(mDispatch.end(), makeNameDesc); + // see begin() comments + return boost::make_transform_iterator(const_cast(mDispatch).end(), + makeNameDesc); } //@} - /// Fetch the Callable for the specified name. If no such name was - /// registered, return an empty() Callable. - Callable get(const std::string& name) const; - /// Get information about a specific Callable LLSD getMetadata(const std::string& name) const; @@ -175,18 +329,184 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; - /// try to dispatch, return @c true if success - bool attemptCall(const std::string& name, const LLSD& event) const; std::string mDesc, mKey; DispatchMap mDispatch; - static NameDesc makeNameDesc(const DispatchMap::value_type& item) + static NameDesc makeNameDesc(DispatchMap::value_type item) { - return NameDesc(item.first, item.second.mDesc); + return NameDesc(item.first, item.second->mDesc); } + + struct LLSDDispatchEntry; + struct ParamsDispatchEntry; + struct ArrayParamsDispatchEntry; + struct MapParamsDispatchEntry; + + // Step 2 of parameter analysis. Instantiating invoker + // implicitly sets its From and To parameters to the (compile time) begin + // and end iterators over that function's parameter types. + template< typename Function + , class From = typename boost::mpl::begin< boost::function_types::parameter_types >::type + , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type + > + struct invoker; + + // deliver LLSD arguments one at a time + typedef boost::function args_source; + // obtain args from an args_source to build param list and call target + // function + typedef boost::function invoker_function; + + template + invoker_function make_invoker(Function f); + template + invoker_function make_invoker(Method f, const InstanceGetter& getter); + void addArrayParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + LLSD::Integer arity); + void addMapParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + const LLSD& params, + const LLSD& defaults); }; +/***************************************************************************** +* LLEventDispatcher template implementation details +*****************************************************************************/ +// Step 3 of parameter analysis, the recursive case. +template +struct LLEventDispatcher::invoker +{ + template + struct remove_cv_ref + : boost::remove_cv< typename boost::remove_reference::type > + { }; + + // apply() accepts an arbitrary boost::fusion sequence as args. It + // examines the next parameter type in the parameter-types sequence + // bounded by From and To, obtains the next LLSD object from the passed + // args_source and constructs an LLSDParam of appropriate type to try + // to convert the value. It then recurs with the next parameter-types + // iterator, passing the args sequence thus far. + template + static inline + void apply(Function func, const args_source& argsrc, Args const & args) + { + typedef typename boost::mpl::deref::type arg_type; + typedef typename boost::mpl::next::type next_iter_type; + typedef typename remove_cv_ref::type plain_arg_type; + + invoker::apply + ( func, argsrc, boost::fusion::push_back(args, LLSDParam(argsrc()))); + } + + // Special treatment for instance (first) parameter of a non-static member + // function. Accept the instance-getter callable, calling that to produce + // the first args value. Since we know we're at the top of the recursion + // chain, we need not also require a partial args sequence from our caller. + template + static inline + void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) + { + typedef typename boost::mpl::next::type next_iter_type; + + // Instead of grabbing the first item from argsrc and making an + // LLSDParam of it, call getter() and pass that as the instance param. + invoker::apply + ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); + } +}; + +// Step 4 of parameter analysis, the leaf case. When the general +// invoker logic has advanced From until it matches To, +// the compiler will pick this template specialization. +template +struct LLEventDispatcher::invoker +{ + // the argument list is complete, now call the function + template + static inline + void apply(Function func, const args_source&, Args const & args) + { + boost::fusion::invoke(func, args); + } +}; + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +{ + // Construct an invoker_function, a callable accepting const args_source&. + // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the + // caller's LLSD::Array. + addArrayParamsDispatchEntry(name, desc, make_invoker(f), + boost::function_types::function_arity::value); +} + +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +{ + // Subtract 1 from the compile-time arity because the getter takes care of + // the first parameter. We only need (arity - 1) additional arguments. + addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), + boost::function_types::function_arity::value - 1); +} + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +{ + // See comments for previous is_nonmember_callable_builtin add(). + addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); +} + +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +{ + addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); +} + +template +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Function f) +{ + // Step 1 of parameter analysis, the top of the recursion. Passing a + // suitable f (see add()'s enable_if condition) to this method causes it + // to infer the function type; specifying that function type to invoker<> + // causes it to fill in the begin/end MPL iterators over the function's + // list of parameter types. + // While normally invoker::apply() could infer its template type from the + // boost::fusion::nil parameter value, here we must be explicit since + // we're boost::bind()ing it rather than calling it directly. + return boost::bind(&invoker::template apply, + f, + _1, + boost::fusion::nil()); +} + +template +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) +{ + // Use invoker::method_apply() to treat the instance (first) arg specially. + return boost::bind(&invoker::template method_apply, + f, + _1, + getter); +} + +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ /** * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class * that contains (or derives from) LLDispatchListener need only specify the diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp new file mode 100644 index 0000000000..a1d7cf9ead --- /dev/null +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -0,0 +1,436 @@ +/** + * @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 "llsd.h" +#include "llsdutil.h" +#include "stringize.h" +#include "tests/wrapllerrs.h" + +// http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp +// downloaded 2011-01-20 by NRG and adapted with example usage +// (C) Copyright Tobias Schwinger +// +// Use modification and distribution are subject to the boost Software License, +// Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt). + +//------------------------------------------------------------------------------ +// +// This example implements a simple batch-style dispatcher that is capable of +// calling functions previously registered with it. The parameter types of the +// functions are used to control the parsing of the input. +// +// Implementation description +// ========================== +// +// When a function is registered, an 'invoker' template is instantiated with +// the function's type. The 'invoker' fetches a value from the 'arg_source' +// for each parameter of the function into a tuple and finally invokes the the +// function with these values as arguments. The invoker's entrypoint, which +// is a function of the callable builtin that describes the function to call and +// a reference to the 'arg_source', is partially bound to the registered +// function and put into a map so it can be found by name during parsing. + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +using boost::lambda::constant; +using boost::lambda::constant_ref; +using boost::lambda::var; + +/***************************************************************************** +* Output control +*****************************************************************************/ +#ifdef DEBUG_ON +using std::cout; +#else +static std::ostringstream cout; +#endif + +/***************************************************************************** +* Example data, functions, classes +*****************************************************************************/ +// sensing globals +static std::string gs; +static float gf; +static int gi; +static LLSD gl; + +void clear() +{ + gs.clear(); + gf = 0; + gi = 0; + gl = LLSD(); +} + +void abc(const std::string& message) +{ + cout << "abc('" << message << "')\n"; + gs = message; +} + +void def(float value, std::string desc) +{ + cout << "def(" << value << ", '" << desc << "')\n"; + gf = value; + gs = desc; +} + +void ghi(const std::string& foo, int bar) +{ + cout << "ghi('" << foo << "', " << bar << ")\n"; + gs = foo; + gi = bar; +} + +void jkl(const char* message) +{ + cout << "jkl('" << message << "')\n"; + gs = message; +} + +void somefunc(const LLSD& value) +{ + cout << "somefunc(" << value << ")\n"; + gl = value; +} + +class Dummy +{ +public: + Dummy(): _id("Dummy") {} + + void mno(const std::string& message) + { + cout << _id << "::mno('" << message << "')\n"; + s = message; + } + + void pqr(float value, std::string desc) + { + cout << _id << "::pqr(" << value << ", '" << desc << "')\n"; + f = value; + s = desc; + } + + void stu(const std::string& foo, int bar) + { + cout << _id << "::stu('" << foo << "', " << bar << ")\n"; + s = foo; + i = bar; + } + + void vwx(const char* message) + { + cout << _id << "::vwx('" << message << "')\n"; + s = message; + } + + static void yz1(const std::string& message) + { + cout << "Dummy::yz1('" << message << "')\n"; + // can't access sensing members... + gs = message; + } + + // sensing members + std::string s; + float f; + int i; + +private: + std::string _id; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lleventdispatcher_data + { + WrapLL_ERRS redirect; + LLEventDispatcher work; + Dummy dummy; + + lleventdispatcher_data(): + work("test dispatcher", "op") + { + // This object is reconstructed for every test method. But + // clear global variables every time too. + ::clear(); + + work.add("abc", "abc", abc, LLSDArray("message")); + work.add("def", "def", def); + work.add("ghi", "ghi", ghi); + work.add("jkl", "jkl", jkl); + work.add("yz1", "yz1", &Dummy::yz1); + work.add("mno", "mno", &Dummy::mno, var(dummy), + LLSDArray("message"), LLSDArray("default message")); + work.add("mnoptr", "mno", &Dummy::mno, constant(&dummy)); + work.add("pqr", "pqr", &Dummy::pqr, var(dummy), + LLSDArray("value")("desc")); + work.add("stu", "stu", &Dummy::stu, var(dummy), + LLSDArray("foo")("bar"), LLSDArray(-1)); + work.add("vwx", "vwx", &Dummy::vwx, var(dummy), + LLSDArray("message")); + } + + 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); + } + + void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) + { + std::string threw; + try + { + work(func, args); + } + catch (const std::runtime_error& e) + { + cout << "*** " << e.what() << '\n'; + threw = e.what(); + } + ensure_has(threw, exc_frag); + } + }; + typedef test_group lleventdispatcher_group; + typedef lleventdispatcher_group::object object; + lleventdispatcher_group lleventdispatchergrp("lleventdispatcher"); + + template<> template<> + void object::test<1>() + { + LLSD hello("Hello test!"); +// cout << std::string(hello) << "\n"; + clear(); + jkl(LLSDParam(hello)); + ensure_equals(gs, hello.asString()); + } + + template<> template<> + void object::test<2>() + { + somefunc("abc"); + ensure_equals(gl.asString(), "abc"); + + somefunc(17); + ensure_equals(gl.asInteger(), 17); + + somefunc(3.14); + // 7 bits is 128, just enough to express two decimal places. + ensure_approximately_equals(gl.asReal(), 3.14, 7); + + somefunc(LLSD().with(0, "abc").with(1, 17).with(2, 3.14)); + ensure(gl.isArray()); + ensure_equals(gl.size(), 4); // !!! bug in LLSD::with(Integer, const LLSD&) !!! + ensure_equals(gl[1].asInteger(), 17); + + somefunc(LLSD().with("alpha", "abc").with("random", 17).with("pi", 3.14)); + ensure(gl.isMap()); + ensure_equals(gl.size(), 3); + ensure_equals(gl["random"].asInteger(), 17); + + somefunc(LLSDArray("abc")(17)(3.14)); + ensure(gl.isArray()); + ensure_equals(gl.size(), 3); + ensure_equals(gl[0].asString(), "abc"); + + somefunc(LLSDMap("alpha", "abc")("random", 17)("pi", 3.14)); + ensure(gl.isMap()); + ensure_equals(gl.size(), 3); + ensure_equals(gl["alpha"].asString(), "abc"); + } + + template<> template<> + void object::test<3>() + { + call_exc("gofish", LLSDArray(1), "not found"); + } + + template<> template<> + void object::test<4>() + { + call_exc("abc", LLSD(), "missing required"); + } + + template<> template<> + void object::test<5>() + { + work("abc", LLSDMap("message", "something")); + ensure_equals(gs, "something"); + } + + template<> template<> + void object::test<6>() + { + work("abc", LLSDMap("message", "something")("plus", "more")); + ensure_equals(gs, "something"); + } + + template<> template<> + void object::test<7>() + { + call_exc("def", LLSDMap("value", 20)("desc", "questions"), "needs an args array"); + } + + template<> template<> + void object::test<8>() + { + work("def", LLSDArray(20)("questions")); + ensure_equals(gf, 20); + ensure_equals(gs, "questions"); + } + + template<> template<> + void object::test<9>() + { + work("def", LLSDArray(3.14)("pies")); + ensure_approximately_equals(gf, 3.14, 7); + ensure_equals(gs, "pies"); + } + + template<> template<> + void object::test<10>() + { + work("ghi", LLSDArray("answer")(17)); + ensure_equals(gs, "answer"); + ensure_equals(gi, 17); + } + + template<> template<> + void object::test<11>() + { + work("ghi", LLSDArray("answer")(3.14)); + ensure_equals(gs, "answer"); + ensure_equals(gi, 3); + } + + template<> template<> + void object::test<12>() + { + work("jkl", LLSDArray("sample message")); + ensure_equals(gs, "sample message"); + } + + template<> template<> + void object::test<13>() + { + work("yz1", LLSDArray("w00t")); + ensure_equals(gs, "w00t"); + } + + template<> template<> + void object::test<14>() + { + std::string msg("nonstatic member function"); + work("mno", LLSDMap("message", msg)); + ensure_equals(dummy.s, msg); + } + + template<> template<> + void object::test<15>() + { + std::string msg("nonstatic member function reached by ptr"); + work("mnoptr", LLSDArray(msg)); + ensure_equals(dummy.s, msg); + } + + template<> template<> + void object::test<16>() + { + work("mno", LLSD()); + ensure_equals(dummy.s, "default message"); + } + + template<> template<> + void object::test<17>() + { + work("pqr", LLSDMap("value", 3.14)("desc", "pies")); + ensure_approximately_equals(dummy.f, 3.14, 7); + ensure_equals(dummy.s, "pies"); + } + + template<> template<> + void object::test<18>() + { + call_exc("pqr", LLSD(), "missing required"); + } + + template<> template<> + void object::test<19>() + { + call_exc("pqr", LLSDMap("value", 3.14), "missing required"); + } + + template<> template<> + void object::test<20>() + { + call_exc("pqr", LLSDMap("desc", "pies"), "missing required"); + } + + template<> template<> + void object::test<21>() + { + work("stu", LLSDMap("bar", 3.14)("foo", "pies")); + ensure_equals(dummy.s, "pies"); + ensure_equals(dummy.i, 3); + } + + template<> template<> + void object::test<22>() + { + call_exc("stu", LLSD(), "missing required"); + } + + template<> template<> + void object::test<23>() + { + call_exc("stu", LLSDMap("bar", 3.14), "missing required"); + } + + template<> template<> + void object::test<24>() + { + work("stu", LLSDMap("foo", "pies")); + ensure_equals(dummy.s, "pies"); + ensure_equals(dummy.i, -1); + } + + template<> template<> + void object::test<25>() + { + std::string msg("nonstatic method(const char*)"); + work("vwx", LLSDMap("message", msg)); + ensure_equals(dummy.s, msg); + } +} // namespace tut -- cgit v1.2.3 From 8b7c903e5cf5620deaab09ec39e978d62ddb45c3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 31 Jan 2011 18:00:58 -0500 Subject: Fix a couple gotchas in LLSDArray, LLSDParam, llsd_equals(). Nested LLSDArray expressions, e.g.: LLSD array_of_arrays(LLSDArray(LLSDArray(17)(34)) (LLSDArray("x")("y"))); would quietly produce bad results because the outermost LLSDArray was being constructed with the compiler's implicit LLSDArray(const LLSDArray&) rather than LLSDArray(const LLSD&) as the reader assumes. Fixed with an explicit copy constructor to Do The Right Thing. Generalized LLSDParam specialization into a macro to resolve similar conversion ambiguities for float, LLUUID, LLDate, LLURI and LLSD::Binary. Added optional bits= argument to llsd_equals() to permit comparing embedded Real values using is_approx_equal_fraction() rather than strictly bitwise. Omitting bits= retains current bitwise-comparison behavior. --- indra/llcommon/llsdutil.cpp | 27 ++++++++++----- indra/llcommon/llsdutil.h | 82 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 28 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index f8f9ece058..803417d368 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -41,6 +41,7 @@ #include "llsdserialize.h" #include "stringize.h" +#include "is_approx_equal_fraction.h" #include #include @@ -571,7 +572,7 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str return match_types(prototype.type(), TypeVector(), data.type(), pfx); } -bool llsd_equals(const LLSD& lhs, const LLSD& rhs) +bool llsd_equals(const LLSD& lhs, const LLSD& rhs, unsigned bits) { // We're comparing strict equality of LLSD representation rather than // performing any conversions. So if the types aren't equal, the LLSD @@ -588,6 +589,20 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) // Both are TypeUndefined. There's nothing more to know. return true; + case LLSD::TypeReal: + // This is where the 'bits' argument comes in handy. If passed + // explicitly, it means to use is_approx_equal_fraction() to compare. + if (bits >= 0) + { + return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); + } + // Otherwise we compare bit representations, and the usual caveats + // about comparing floating-point numbers apply. Omitting 'bits' when + // comparing Real values is only useful when we expect identical bit + // representation for a given Real value, e.g. for integer-valued + // Reals. + return (lhs.asReal() == rhs.asReal()); + #define COMPARE_SCALAR(type) \ case LLSD::Type##type: \ /* LLSD::URI has operator!=() but not operator==() */ \ @@ -596,10 +611,6 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) COMPARE_SCALAR(Boolean); COMPARE_SCALAR(Integer); - // The usual caveats about comparing floating-point numbers apply. This is - // only useful when we expect identical bit representation for a given - // Real value, e.g. for integer-valued Reals. - COMPARE_SCALAR(Real); COMPARE_SCALAR(String); COMPARE_SCALAR(UUID); COMPARE_SCALAR(Date); @@ -617,7 +628,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) for ( ; lai != laend && rai != raend; ++lai, ++rai) { // If any one array element is unequal, the arrays are unequal. - if (! llsd_equals(*lai, *rai)) + if (! llsd_equals(*lai, *rai, bits)) return false; } // Here we've reached the end of one or the other array. They're equal @@ -644,7 +655,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) if (rhskeys.erase(lmi->first) != 1) return false; // Both maps have the current key. Compare values. - if (! llsd_equals(lmi->second, rhs[lmi->first])) + if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) return false; } // We've now established that all the lhs keys have equal values in @@ -657,7 +668,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) // We expect that every possible type() value is specifically handled // above. Failing to extend this switch to support a new LLSD type is // an error that must be brought to the coder's attention. - LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): " + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " "unknown type " << lhs.type() << LL_ENDL; return false; // pacify the compiler } diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 58ccc59f5e..c873b17112 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -123,8 +123,10 @@ LL_COMMON_API BOOL compare_llsd_with_template( */ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); -/// Deep equality -LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs); +/// Deep equality. If you want to compare LLSD::Real values for approximate +/// equality rather than bitwise equality, pass @a bits as for +/// is_approx_equal_fraction(). +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, unsigned bits=-1); // Simple function to copy data out of input & output iterators if // there is no need for casting. @@ -163,6 +165,31 @@ public: LLSDArray(): _data(LLSD::emptyArray()) {} + + /** + * Need an explicit copy constructor. Consider the following: + * + * @code + * LLSD array_of_arrays(LLSDArray(LLSDArray(17)(34)) + * (LLSDArray("x")("y"))); + * @endcode + * + * The coder intends to construct [[17, 34], ["x", "y"]]. + * + * With the compiler's implicit copy constructor, s/he gets instead + * [17, 34, ["x", "y"]]. + * + * The expression LLSDArray(17)(34) constructs an LLSDArray with those two + * values. The reader assumes it should be converted to LLSD, as we always + * want with LLSDArray, before passing it to the @em outer LLSDArray + * constructor! This copy constructor makes that happen. + */ + LLSDArray(const LLSDArray& inner): + _data(LLSD::emptyArray()) + { + _data.append(inner); + } + LLSDArray(const LLSD& value): _data(LLSD::emptyArray()) { @@ -263,6 +290,39 @@ private: T _value; }; +/** + * Turns out that several target types could accept an LLSD param using any of + * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or + * std::string. Therefore, the compiler can't decide which LLSD conversion + * operator to choose, even though to us it seems obvious. But that's okay, we + * can specialize LLSDParam for such target types, explicitly specifying the + * desired conversion -- that's part of what LLSDParam is all about. Turns out + * we have to do that enough to make it worthwhile generalizing. Use a macro + * because I need to specify one of the asReal, etc., explicit conversion + * methods as well as a type. If I'm overlooking a clever way to implement + * that using a template instead, feel free to reimplement. + */ +#define LLSDParam_for(T, AS) \ +template <> \ +class LLSDParam \ +{ \ +public: \ + LLSDParam(const LLSD& value): \ + _value(value.AS()) \ + {} \ + \ + operator T() const { return _value; } \ + \ +private: \ + T _value; \ +} + +LLSDParam_for(float, asReal); +LLSDParam_for(LLUUID, asUUID); +LLSDParam_for(LLDate, asDate); +LLSDParam_for(LLURI, asURI); +LLSDParam_for(LLSD::Binary, asBinary); + /** * LLSDParam is an example of the kind of conversion you can * support with LLSDParam beyond native LLSD conversions. Normally you can't @@ -308,22 +368,4 @@ public: } }; -/** - * LLSDParam resolves conversion ambiguity. g++ considers F64, S32 and - * bool equivalent candidates for implicit conversion to float. (/me rolls eyes) - */ -template <> -class LLSDParam -{ -private: - float _value; - -public: - LLSDParam(const LLSD& value): - _value(value.asReal()) - {} - - operator float() const { return _value; } -}; - #endif // LL_LLSDUTIL_H -- cgit v1.2.3 From d6e95923294483a5044fb6c66f7530442d55d338 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 31 Jan 2011 18:06:39 -0500 Subject: Resolve LLEventDispatcher::add(function(const LLSD&)) ambiguity. A free function or static method accepting(const LLSD&) was being intercepted by the free-function-with-arbitrary-parameters overload instead of the original Callable overload. Added an overload that specifically redirects that case. Documented limit of ~6 arbitrary parameters for directly-called functions (5 for methods). Beyond that many, you have to write a Callable wrapper function and unpack the parameters from the LLSD by hand. --- indra/llcommon/lleventdispatcher.h | 42 ++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index ce0fc7b585..b37189b58b 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -111,6 +111,19 @@ public: const Callable& callable, const LLSD& required=LLSD()); + /** + * The case of a free function (or static method) accepting(const LLSD&) + * could also be intercepted by the arbitrary-args overload below. Ensure + * that it's directed to the Callable overload above instead. + */ + void add(const std::string& name, + const std::string& desc, + void (*f)(const LLSD&), + const LLSD& required=LLSD()) + { + add(name, desc, Callable(f), required); + } + /** * Special case: a subclass of this class can pass an unbound member * function pointer (of an LLEventDispatcher subclass) without explicitly @@ -137,19 +150,6 @@ public: addMethod(name, desc, method, required); } -/*==========================================================================*| - /// Convenience: for LLEventDispatcher, not every callable needs a - /// documentation string. The passed @a callable accepts a single LLSD - /// value, presumably containing other parameters. - template - void add(const std::string& name, - CALLABLE callable, - const LLSD& required=LLSD()) - { - add(name, "", callable, required); - } -|*==========================================================================*/ - //@} /// @name Register functions with arbitrary param lists @@ -159,6 +159,10 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ @@ -171,6 +175,10 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class @@ -192,6 +200,10 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * * Pass an LLSD::Array of parameter names, and optionally another * LLSD::Array of default parameter values, a la LLSDArgsMapper. * @@ -210,6 +222,10 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class -- cgit v1.2.3 From 1fda4964326f5552b47abfc8e9a01d9cb2fe89e7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 31 Jan 2011 22:58:56 -0500 Subject: Fix crash bug in array-style metadata query for nullary functions. The shortcut way to construct an LLSD array of size n is to assign LLSD() to array[n-1]. That's fine -- as long as you remember not to do it for n == 0. --- indra/llcommon/lleventdispatcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 2ab006a173..e00cca366f 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -443,7 +443,8 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required - array[mArity - 1] = LLSD(); + if (mArity) + array[mArity - 1] = LLSD(); llassert_always(array.size() == mArity); meta["required"] = array; return meta; -- cgit v1.2.3 From 66083f9e7a65367fad5bf17dec9a89f9b14c5985 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 1 Feb 2011 15:52:32 -0500 Subject: Replace ad-hoc test functions/methods with systematic enumeration. Previous tests involved a small handful of functions with only a couple different parameter types. Now we exhaustively invoke every registration case, plus every metadata query case. Call cases still pending. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 1023 +++++++++++++++++------ 1 file changed, 780 insertions(+), 243 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index a1d7cf9ead..35a3188507 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -23,30 +23,6 @@ #include "stringize.h" #include "tests/wrapllerrs.h" -// http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp -// downloaded 2011-01-20 by NRG and adapted with example usage -// (C) Copyright Tobias Schwinger -// -// Use modification and distribution are subject to the boost Software License, -// Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt). - -//------------------------------------------------------------------------------ -// -// This example implements a simple batch-style dispatcher that is capable of -// calling functions previously registered with it. The parameter types of the -// functions are used to control the parsing of the input. -// -// Implementation description -// ========================== -// -// When a function is registered, an 'invoker' template is instantiated with -// the function's type. The 'invoker' fetches a value from the 'arg_source' -// for each parameter of the function into a tuple and finally invokes the the -// function with these values as arguments. The invoker's entrypoint, which -// is a function of the callable builtin that describes the function to call and -// a reference to the 'arg_source', is partially bound to the registered -// function and put into a map so it can be found by name during parsing. - #include #include #include @@ -58,6 +34,7 @@ #include #include +#include using boost::lambda::constant; using boost::lambda::constant_ref; @@ -75,98 +52,244 @@ static std::ostringstream cout; /***************************************************************************** * Example data, functions, classes *****************************************************************************/ -// sensing globals -static std::string gs; -static float gf; -static int gi; -static LLSD gl; - -void clear() +// 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& 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 { - gs.clear(); - gf = 0; - gi = 0; - gl = LLSD(); -} +public: + Dispatcher(const std::string& name, const std::string& key): + LLEventDispatcher(name, key) + {} -void abc(const std::string& message) -{ - cout << "abc('" << message << "')\n"; - gs = message; -} + // sensing member, mutable because we want to know when we've reached our + // const method too + mutable LLSD llsd; -void def(float value, std::string desc) -{ - cout << "def(" << value << ", '" << desc << "')\n"; - gf = value; - gs = desc; -} + void method1(const LLSD& obj) { llsd = obj; } + void cmethod1(const LLSD& obj) const { llsd = obj; } +}; -void ghi(const std::string& foo, int bar) +// sensing vars, captured in a struct to make it convenient to clear them +struct Vars { - cout << "ghi('" << foo << "', " << bar << ")\n"; - gs = foo; - gi = bar; -} + LLSD llsd; + bool b; + int i; + float f; + double d; + const char* cp; + std::string s; + LLUUID uuid; + LLDate date; + LLURI uri; + std::vector 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), + cp(NULL) + {} + + // 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) + 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; + } -void jkl(const char* message) -{ - cout << "jkl('" << message << "')\n"; - gs = message; -} + /*------------- no-args (non-const, const, static) methods -------------*/ + void method0() + { + cout << "method0()\n"; + i = 17; + } -void somefunc(const LLSD& value) -{ - cout << "somefunc(" << value << ")\n"; - gl = value; -} + void cmethod0() const + { + cout << 'c'; + const_cast(this)->method0(); + } -class Dummy -{ -public: - Dummy(): _id("Dummy") {} + static void smethod0(); - void mno(const std::string& message) + /*------------ Callable (non-const, const, static) methods -------------*/ + void method1(const LLSD& obj) { - cout << _id << "::mno('" << message << "')\n"; - s = message; + cout << "method1(" << obj << ")\n"; + llsd = obj; } - void pqr(float value, std::string desc) + void cmethod1(const LLSD& obj) const { - cout << _id << "::pqr(" << value << ", '" << desc << "')\n"; - f = value; - s = desc; + cout << 'c'; + const_cast(this)->method1(obj); } - void stu(const std::string& foo, int bar) + static void smethod1(const LLSD& obj); + + /*-------- Arbitrary-params (non-const, const, static) methods ---------*/ + void methodna(NPARAMSa) { - cout << _id << "::stu('" << foo << "', " << bar << ")\n"; - s = foo; - i = bar; + std::string vcp; + if (cp == NULL) + vcp = "NULL"; + else + vcp = std::string("'") + cp + "'"; + + cout << "methodna(" << b + << ", " << i + << ", " << f + << ", " << d + << ", " << vcp + << ")\n"; + + this->b = b; + this->i = i; + this->f = f; + this->d = d; + this->cp = cp; } - void vwx(const char* message) + void methodnb(NPARAMSb) { - cout << _id << "::vwx('" << message << "')\n"; - s = message; + std::ostringstream vbin; + for (size_t ix = 0, ixend = bin.size(); ix < ixend; ++ix) + { + vbin << std::hex << std::setfill('0') << std::setw(2) << bin[ix]; + } + + cout << "methodnb(" << "'" << s << "'" + << ", " << uuid + << ", " << date + << ", '" << uri << "'" + << ", " << vbin.str() + << ")\n"; + + this->s = s; + this->uuid = uuid; + this->date = date; + this->uri = uri; + this->bin = bin; } - static void yz1(const std::string& message) + void cmethodna(NPARAMSa) const { - cout << "Dummy::yz1('" << message << "')\n"; - // can't access sensing members... - gs = message; + cout << 'c'; + const_cast(this)->methodna(NARGSa); } - // sensing members - std::string s; - float f; - int i; + void cmethodnb(NPARAMSb) const + { + cout << 'c'; + const_cast(this)->methodnb(NARGSb); + } -private: - std::string _id; + static void smethodna(NPARAMSa); + static void smethodnb(NPARAMSb); }; +/*------- Global Vars instance for free functions and static methods -------*/ +static Vars g; + +/*------------ Static Vars method implementations reference 'g' ------------*/ +void Vars::smethod0() +{ + cout << "smethod0() -> "; + g.method0(); +} + +void Vars::smethod1(const LLSD& obj) +{ + cout << "smethod1(" << obj << ") -> "; + g.method1(obj); +} + +void Vars::smethodna(NPARAMSa) +{ + cout << "smethodna(...) -> "; + g.methodna(NARGSa); +} + +void Vars::smethodnb(NPARAMSb) +{ + cout << "smethodnb(...) -> "; + g.methodnb(NARGSb); +} + +/*--------------------------- Reset global Vars ----------------------------*/ +void clear() +{ + g = Vars(); +} + +/*------------------- Free functions also reference 'g' --------------------*/ +void free0() +{ + cout << "free0() -> "; + g.method0(); +} + +void free1(const LLSD& obj) +{ + cout << "free1(" << obj << ") -> "; + g.method1(obj); +} + +void freena(NPARAMSa) +{ + cout << "freena(...) -> "; + g.methodna(NARGSa); +} + +void freenb(NPARAMSb) +{ + cout << "freenb(...) -> "; + g.methodnb(NARGSb); +} /***************************************************************************** * TUT @@ -176,30 +299,300 @@ namespace tut struct lleventdispatcher_data { WrapLL_ERRS redirect; - LLEventDispatcher work; - Dummy dummy; + Dispatcher work; + Vars v; + std::string name, desc; + typedef std::map FuncMap; + FuncMap funcs; + // Required structure for Callables with requirements + LLSD required; + // Parameter names for freena(), freenb() + LLSD paramsa, paramsb; + // Full defaults arrays for params for freena(), freenb() + LLSD dfta_array_full, dftb_array_full; + // Start index of partial defaults arrays + const LLSD::Integer partial_offset; + // Full defaults maps for params for freena(), freenb() + LLSD dfta_map_full, dftb_map_full; + // Partial defaults maps for params for freena(), freenb() + LLSD dfta_map_partial, dftb_map_partial; lleventdispatcher_data(): - work("test dispatcher", "op") + work("test dispatcher", "op"), + // map {d=double, array=[3 elements]} + required(LLSDMap("d", LLSD::Real(0))("array", LLSDArray(LLSD())(LLSD())(LLSD()))), + // first several params are required, last couple optional + partial_offset(3) { // This object is reconstructed for every test method. But // clear global variables every time too. ::clear(); - work.add("abc", "abc", abc, LLSDArray("message")); - work.add("def", "def", def); - work.add("ghi", "ghi", ghi); - work.add("jkl", "jkl", jkl); - work.add("yz1", "yz1", &Dummy::yz1); - work.add("mno", "mno", &Dummy::mno, var(dummy), - LLSDArray("message"), LLSDArray("default message")); - work.add("mnoptr", "mno", &Dummy::mno, constant(&dummy)); - work.add("pqr", "pqr", &Dummy::pqr, var(dummy), - LLSDArray("value")("desc")); - work.add("stu", "stu", &Dummy::stu, var(dummy), - LLSDArray("foo")("bar"), LLSDArray(-1)); - work.add("vwx", "vwx", &Dummy::vwx, var(dummy), - LLSDArray("message")); + // 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"); + work.add(name, desc, free1); + addf("free1_req", "free1"); + work.add(name, desc, free1, required); + // Subclass non-const method with/out required params + addf("Dmethod1", "method1"); + work.add(name, desc, &Dispatcher::method1); + addf("Dmethod1_req", "method1"); + work.add(name, desc, &Dispatcher::method1, required); + // Subclass const method with/out required params + addf("Dcmethod1", "cmethod1"); + work.add(name, desc, &Dispatcher::cmethod1); + addf("Dcmethod1_req", "cmethod1"); + work.add(name, desc, &Dispatcher::cmethod1, required); + // Non-subclass method with/out required params + addf("method1", "method1"); + work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1)); + addf("method1_req", "method1"); + work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required); + + /*--------------- Arbitrary params, array style ----------------*/ + + // (Free function | static method) with (no | arbitrary) params, array style + addf("free0_array", "free0"); + work.add(name, desc, free0); + addf("freena_array", "freena"); + work.add(name, desc, freena); + addf("freenb_array", "freenb"); + work.add(name, desc, freenb); + addf("smethod0_array", "smethod0"); + work.add(name, desc, &Vars::smethod0); + addf("smethodna_array", "smethodna"); + work.add(name, desc, &Vars::smethodna); + addf("smethodnb_array", "smethodnb"); + work.add(name, desc, &Vars::smethodnb); + // Non-static method with (no | arbitrary) params, array style + addf("method0_array", "method0"); + work.add(name, desc, &Vars::method0, boost::lambda::var(v)); + addf("methodna_array", "methodna"); + work.add(name, desc, &Vars::methodna, boost::lambda::var(v)); + addf("methodnb_array", "methodnb"); + work.add(name, desc, &Vars::methodnb, boost::lambda::var(v)); + + /*---------------- Arbitrary params, map style -----------------*/ + + // freena(), methodna(), cmethodna(), smethodna() all take same param list + paramsa = LLSDArray("b")("i")("f")("d")("cp"); + // same for freenb() et al. + paramsb = LLSDArray("s")("uuid")("date")("uri")("bin"); + // Full defaults arrays. + dfta_array_full = LLSDArray(true)(17)(3.14)(123456.78)("classic"); + // default LLSD::Binary value + std::vector binary; + for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11) + { + binary.push_back(h); + } + // We actually don't care what the LLUUID or LLDate values are, as + // long as they're non-default. + dftb_array_full = LLSDArray("string")(LLUUID::generateNewID())(LLDate::now()) + (LLURI("http://www.ietf.org/rfc/rfc3986.txt"))(binary); + // Partial defaults arrays. + LLSD dfta_array_partial(llsd_copy_array(dfta_array_full.beginArray() + partial_offset, + dfta_array_full.endArray())); + LLSD dftb_array_partial(llsd_copy_array(dftb_array_full.beginArray() + partial_offset, + dftb_array_full.endArray())); + + // Generate full defaults maps by zipping (params, dftx_array_full). + LLSD zipped(LLSDArray(LLSDArray(paramsa)(dfta_array_full)) + (LLSDArray(paramsb)(dftb_array_full))); +// std::cout << "zipped:\n" << zipped << '\n'; + LLSD dft_maps_full, dft_maps_partial; + for (LLSD::array_const_iterator ai(zipped.beginArray()), aend(zipped.endArray()); + ai != aend; ++ai) + { + LLSD dft_map_full; + LLSD params((*ai)[0]); + LLSD dft_array_full((*ai)[1]); +// std::cout << "params:\n" << params << "\ndft_array_full:\n" << dft_array_full << '\n'; + for (LLSD::Integer ix = 0, ixend = params.size(); ix < ixend; ++ix) + { + dft_map_full[params[ix].asString()] = dft_array_full[ix]; + } +// std::cout << "dft_map_full:\n" << dft_map_full << '\n'; + // 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. + LLSD dft_map_partial; + for (LLSD::Integer ix = 0, ixend = params.size(); ix < ixend; ix += 2) + { + dft_map_partial[params[ix].asString()] = dft_array_full[ix]; + } +// std::cout << "dft_map_partial:\n" << dft_map_partial << '\n'; + dft_maps_full.append(dft_map_full); + dft_maps_partial.append(dft_map_partial); + } +// std::cout << "dft_maps_full:\n" << dft_maps_full << "\ndft_maps_partial:\n" << dft_maps_partial << '\n'; + dfta_map_full = dft_maps_full[0]; + dftb_map_full = dft_maps_full[1]; + dfta_map_partial = dft_maps_partial[0]; + dftb_map_partial = dft_maps_partial[1]; +// std::cout << "dfta_map_full:\n" << dfta_map_full +// << "\ndftb_map_full:\n" << dftb_map_full +// << "\ndfta_map_partial:\n" << dfta_map_partial +// << "\ndftb_map_partial:\n" << dftb_map_partial << '\n'; + + // (Free function | static method) with (no | arbitrary) params, + // map style, no (empty array) defaults + addf("free0_map", "free0"); + work.add(name, desc, free0, LLSD::emptyArray()); + addf("smethod0_map", "smethod0"); + work.add(name, desc, &Vars::smethod0, LLSD::emptyArray()); + addf("freena_map_allreq", "freena"); + work.add(name, desc, freena, paramsa); + addf("freenb_map_allreq", "freenb"); + work.add(name, desc, freenb, paramsb); + addf("smethodna_map_allreq", "smethodna"); + work.add(name, desc, &Vars::smethodna, paramsa); + addf("smethodnb_map_allreq", "smethodnb"); + work.add(name, desc, &Vars::smethodnb, paramsb); + // Non-static method with (no | arbitrary) params, map style, no + // (empty array) defaults + addf("method0_map", "method0"); + work.add(name, desc, &Vars::method0, var(v), LLSD::emptyArray()); + addf("methodna_map_allreq", "methodna"); + work.add(name, desc, &Vars::methodna, var(v), paramsa); + addf("methodnb_map_allreq", "methodnb"); + work.add(name, desc, &Vars::methodnb, var(v), paramsb); + + // 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"); + work.add(name, desc, freena, paramsa, dfta_array_partial); + addf("freenb_map_leftreq", "freenb"); + work.add(name, desc, freenb, paramsb, dftb_array_partial); + addf("smethodna_map_leftreq", "smethodna"); + work.add(name, desc, &Vars::smethodna, paramsa, dfta_array_partial); + addf("smethodnb_map_leftreq", "smethodnb"); + work.add(name, desc, &Vars::smethodnb, paramsb, dftb_array_partial); + addf("freena_map_skipreq", "freena"); + work.add(name, desc, freena, paramsa, dfta_map_partial); + addf("freenb_map_skipreq", "freenb"); + work.add(name, desc, freenb, paramsb, dftb_map_partial); + addf("smethodna_map_skipreq", "smethodna"); + work.add(name, desc, &Vars::smethodna, paramsa, dfta_map_partial); + addf("smethodnb_map_skipreq", "smethodnb"); + work.add(name, desc, &Vars::smethodnb, paramsb, dftb_map_partial); + // Non-static method with arbitrary params, map style, partial + // (array | map) defaults + addf("methodna_map_leftreq", "methodna"); + work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_array_partial); + addf("methodnb_map_leftreq", "methodnb"); + work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_array_partial); + addf("methodna_map_skipreq", "methodna"); + work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_map_partial); + addf("methodnb_map_skipreq", "methodnb"); + work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_map_partial); + + // (Free function | static method) with arbitrary params, map + // style, full (array | map) defaults + addf("freena_map_adft", "freena"); + work.add(name, desc, freena, paramsa, dfta_array_full); + addf("freenb_map_adft", "freenb"); + work.add(name, desc, freenb, paramsb, dftb_array_full); + addf("smethodna_map_adft", "smethodna"); + work.add(name, desc, &Vars::smethodna, paramsa, dfta_array_full); + addf("smethodnb_map_adft", "smethodnb"); + work.add(name, desc, &Vars::smethodnb, paramsb, dftb_array_full); + addf("freena_map_mdft", "freena"); + work.add(name, desc, freena, paramsa, dfta_map_full); + addf("freenb_map_mdft", "freenb"); + work.add(name, desc, freenb, paramsb, dftb_map_full); + addf("smethodna_map_mdft", "smethodna"); + work.add(name, desc, &Vars::smethodna, paramsa, dfta_map_full); + addf("smethodnb_map_mdft", "smethodnb"); + work.add(name, desc, &Vars::smethodnb, paramsb, dftb_map_full); + // Non-static method with arbitrary params, map style, full + // (array | map) defaults + addf("methodna_map_adft", "methodna"); + work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_array_full); + addf("methodnb_map_adft", "methodnb"); + work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_array_full); + addf("methodna_map_mdft", "methodna"); + work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_map_full); + addf("methodnb_map_mdft", "methodnb"); + work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_map_full); + + // 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) + { + // This method is to capture in our own FuncMap the name and + // description of every registered function, for metadata query + // testing. + funcs[n] = d; + // See constructor for rationale for setting these instance vars. + this->name = n; + this->desc = d; + } + + void verify_funcs() + { + // Copy funcs to a temp map of same type. + FuncMap forgotten(funcs.begin(), funcs.end()); + for (LLEventDispatcher::const_iterator edi(work.begin()), edend(work.end()); + edi != edend; ++edi) + { + FuncMap::iterator found = forgotten.find(edi->first); + ensure(STRINGIZE("LLEventDispatcher records function '" << edi->first + << "' we didn't enter"), + found != forgotten.end()); + ensure_equals(STRINGIZE("LLEventDispatcher desc '" << edi->second << + "' doesn't match what we entered: '" << found->second << "'"), + edi->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 (FuncMap::const_iterator fmi(forgotten.begin()), fmend(forgotten.end()); + fmi != fmend; ++fmi) + { + out << delim << fmi->first; + delim = ", "; + } + ensure(out.str(), false); + } } void ensure_has(const std::string& outer, const std::string& inner) @@ -222,215 +615,359 @@ namespace tut } ensure_has(threw, exc_frag); } + + LLSD getMetadata(const std::string& name) + { + LLSD meta(work.getMetadata(name)); + ensure(STRINGIZE("No metadata for " << name), meta.isDefined()); + return meta; + } }; typedef test_group 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) array style with + // (scalar | map | array (too short | too long | just right)) + // [trap LL_WARNS for too-long case?] + // - (Free function | non-static method) map style with + // (scalar | 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>() { - LLSD hello("Hello test!"); -// cout << std::string(hello) << "\n"; - clear(); - jkl(LLSDParam(hello)); - ensure_equals(gs, hello.asString()); + set_test_name("map-style registration with non-array params"); + // Pass "param names" as scalar or as map + LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); + for (LLSD::array_const_iterator ai(attempts.beginArray()), aend(attempts.endArray()); + ai != aend; ++ai) + { + std::string threw; + try + { + work.add("freena_err", "freena", freena, *ai); + } + catch (const std::exception& e) + { + threw = e.what(); + } + ensure_has(threw, "must be an array"); + } } template<> template<> void object::test<2>() { - somefunc("abc"); - ensure_equals(gl.asString(), "abc"); - - somefunc(17); - ensure_equals(gl.asInteger(), 17); - - somefunc(3.14); - // 7 bits is 128, just enough to express two decimal places. - ensure_approximately_equals(gl.asReal(), 3.14, 7); - - somefunc(LLSD().with(0, "abc").with(1, 17).with(2, 3.14)); - ensure(gl.isArray()); - ensure_equals(gl.size(), 4); // !!! bug in LLSD::with(Integer, const LLSD&) !!! - ensure_equals(gl[1].asInteger(), 17); - - somefunc(LLSD().with("alpha", "abc").with("random", 17).with("pi", 3.14)); - ensure(gl.isMap()); - ensure_equals(gl.size(), 3); - ensure_equals(gl["random"].asInteger(), 17); - - somefunc(LLSDArray("abc")(17)(3.14)); - ensure(gl.isArray()); - ensure_equals(gl.size(), 3); - ensure_equals(gl[0].asString(), "abc"); - - somefunc(LLSDMap("alpha", "abc")("random", 17)("pi", 3.14)); - ensure(gl.isMap()); - ensure_equals(gl.size(), 3); - ensure_equals(gl["alpha"].asString(), "abc"); + set_test_name("map-style registration with badly-formed defaults"); + std::string threw; + try + { + work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); + } + catch (const std::exception& e) + { + threw = e.what(); + } + ensure_has(threw, "must be a map or an array"); } template<> template<> void object::test<3>() { - call_exc("gofish", LLSDArray(1), "not found"); + set_test_name("map-style registration with too many array defaults"); + std::string threw; + try + { + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDArray(17)(0.9)("gack")); + } + catch (const std::exception& e) + { + threw = e.what(); + } + ensure_has(threw, "shorter than"); } template<> template<> void object::test<4>() { - call_exc("abc", LLSD(), "missing required"); + set_test_name("map-style registration with too many map defaults"); + std::string threw; + try + { + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); + } + catch (const std::exception& e) + { + threw = e.what(); + } + ensure_has(threw, "nonexistent params"); + ensure_has(threw, "foo"); + ensure_has(threw, "bar"); } template<> template<> void object::test<5>() { - work("abc", LLSDMap("message", "something")); - ensure_equals(gs, "something"); + set_test_name("query all"); + verify_funcs(); } template<> template<> void object::test<6>() { - work("abc", LLSDMap("message", "something")("plus", "more")); - ensure_equals(gs, "something"); + 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 'funcs' too... + funcs.erase("free1"); + verify_funcs(); } template<> template<> void object::test<7>() { - call_exc("def", LLSDMap("value", 20)("desc", "questions"), "needs an args array"); + set_test_name("getDispatchKey()"); + ensure_equals(work.getDispatchKey(), "op"); } template<> template<> void object::test<8>() { - work("def", LLSDArray(20)("questions")); - ensure_equals(gf, 20); - ensure_equals(gs, "questions"); + set_test_name("query Callables with/out required params"); + LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); + for (LLSD::array_const_iterator ai(names.beginArray()), aend(names.endArray()); + ai != aend; ++ai) + { + LLSD metadata(getMetadata(*ai)); + ensure_equals("name mismatch", metadata["name"], *ai); + ensure_equals(metadata["desc"].asString(), funcs[*ai]); + ensure("should not have required structure", metadata["required"].isUndefined()); + ensure("should not have optional", metadata["optional"].isUndefined()); + + std::string name_req(ai->asString() + "_req"); + metadata = getMetadata(name_req); + ensure_equals(metadata["name"].asString(), name_req); + ensure_equals(metadata["desc"].asString(), funcs[name_req]); + ensure_equals("required mismatch", required, metadata["required"]); + ensure("should not have optional", metadata["optional"].isUndefined()); + } } template<> template<> void object::test<9>() { - work("def", LLSDArray(3.14)("pies")); - ensure_approximately_equals(gf, 3.14, 7); - ensure_equals(gs, "pies"); + set_test_name("query array-style functions/methods"); + // Associate each registered name with expected arity. + LLSD expected(LLSDArray + (LLSDArray + (0)(LLSDArray("free0_array")("smethod0_array")("method0_array"))) + (LLSDArray + (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array"))) + (LLSDArray + (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array")))); + for (LLSD::array_const_iterator ai(expected.beginArray()), aend(expected.endArray()); + ai != aend; ++ai) + { + LLSD::Integer arity((*ai)[0].asInteger()); + LLSD names((*ai)[1]); + LLSD req(LLSD::emptyArray()); + if (arity) + req[arity - 1] = LLSD(); + for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); + ni != nend; ++ni) + { + LLSD metadata(getMetadata(*ni)); + ensure_equals("name mismatch", metadata["name"], *ni); + ensure_equals(metadata["desc"].asString(), funcs[*ni]); + ensure_equals(STRINGIZE("mismatched required for " << ni->asString()), + metadata["required"], req); + ensure("should not have optional", metadata["optional"].isUndefined()); + } + } } template<> template<> void object::test<10>() { - work("ghi", LLSDArray("answer")(17)); - ensure_equals(gs, "answer"); - ensure_equals(gi, 17); + set_test_name("query map-style no-params functions/methods"); + // - (Free function | non-static method), map style, no params (ergo + // no defaults) + LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map")); + for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); + ni != nend; ++ni) + { + LLSD metadata(getMetadata(*ni)); + ensure_equals("name mismatch", metadata["name"], *ni); + ensure_equals(metadata["desc"].asString(), funcs[*ni]); + 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>() { - work("ghi", LLSDArray("answer")(3.14)); - ensure_equals(gs, "answer"); - ensure_equals(gi, 3); + 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(LLSDArray + (LLSDArray("freena_map_adft")("freena_map_mdft")) + (LLSDArray("freenb_map_adft")("freenb_map_mdft")) + (LLSDArray("smethodna_map_adft")("smethodna_map_mdft")) + (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft")) + (LLSDArray("methodna_map_adft")("methodna_map_mdft")) + (LLSDArray("methodnb_map_adft")("methodnb_map_mdft"))); + for (LLSD::array_const_iterator + ei(equivalences.beginArray()), eend(equivalences.endArray()); + ei != eend; ++ei) + { + LLSD adft((*ei)[0]); + LLSD mdft((*ei)[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>() { - work("jkl", LLSDArray("sample message")); - ensure_equals(gs, "sample message"); - } - - template<> template<> - void object::test<13>() - { - work("yz1", LLSDArray("w00t")); - ensure_equals(gs, "w00t"); - } - - template<> template<> - void object::test<14>() - { - std::string msg("nonstatic member function"); - work("mno", LLSDMap("message", msg)); - ensure_equals(dummy.s, msg); - } + 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 allreqa, allreqb, leftreqa, leftreqb, rightdfta, rightdftb; + for (LLSD::Integer pi(0), pend(std::min(partial_offset, paramsa.size())); + pi < pend; ++pi) + { + allreqa[paramsa[pi].asString()] = LLSD(); + leftreqa[paramsa[pi].asString()] = LLSD(); + } + for (LLSD::Integer pi(partial_offset), pend(paramsa.size()); pi < pend; ++pi) + { + allreqa[paramsa[pi].asString()] = LLSD(); + rightdfta[paramsa[pi].asString()] = dfta_array_full[pi]; + } + for (LLSD::Integer pi(0), pend(std::min(partial_offset, paramsb.size())); + pi < pend; ++pi) + { + allreqb[paramsb[pi].asString()] = LLSD(); + leftreqb[paramsb[pi].asString()] = LLSD(); + } + for (LLSD::Integer pi(partial_offset), pend(paramsb.size()); pi < pend; ++pi) + { + allreqb[paramsb[pi].asString()] = LLSD(); + rightdftb[paramsb[pi].asString()] = dftb_array_full[pi]; + } - template<> template<> - void object::test<15>() - { - std::string msg("nonstatic member function reached by ptr"); - work("mnoptr", LLSDArray(msg)); - ensure_equals(dummy.s, msg); - } + // Generate maps containing parameter names not provided by the + // dft[ab]_map_partial maps. + LLSD skipreqa(allreqa), skipreqb(allreqb); + for (LLSD::map_const_iterator mi(dfta_map_partial.beginMap()), + mend(dfta_map_partial.endMap()); + mi != mend; ++mi) + { + skipreqa.erase(mi->first); + } + for (LLSD::map_const_iterator mi(dftb_map_partial.beginMap()), + mend(dftb_map_partial.endMap()); + mi != mend; ++mi) + { + skipreqb.erase(mi->first); + } - template<> template<> - void object::test<16>() - { - work("mno", LLSD()); - ensure_equals(dummy.s, "default message"); - } + LLSD groups(LLSDArray // array of groups - template<> template<> - void object::test<17>() - { - work("pqr", LLSDMap("value", 3.14)("desc", "pies")); - ensure_approximately_equals(dummy.f, 3.14, 7); - ensure_equals(dummy.s, "pies"); - } + (LLSDArray // group + (LLSDArray("freena_map_allreq")("smethodna_map_allreq")("methodna_map_allreq")) + (LLSDArray(allreqa)(LLSD()))) // required, optional - template<> template<> - void object::test<18>() - { - call_exc("pqr", LLSD(), "missing required"); - } + (LLSDArray // group + (LLSDArray("freenb_map_allreq")("smethodnb_map_allreq")("methodnb_map_allreq")) + (LLSDArray(allreqb)(LLSD()))) // required, optional - template<> template<> - void object::test<19>() - { - call_exc("pqr", LLSDMap("value", 3.14), "missing required"); - } + (LLSDArray // group + (LLSDArray("freena_map_leftreq")("smethodna_map_leftreq")("methodna_map_leftreq")) + (LLSDArray(leftreqa)(rightdfta))) // required, optional - template<> template<> - void object::test<20>() - { - call_exc("pqr", LLSDMap("desc", "pies"), "missing required"); - } + (LLSDArray // group + (LLSDArray("freenb_map_leftreq")("smethodnb_map_leftreq")("methodnb_map_leftreq")) + (LLSDArray(leftreqb)(rightdftb))) // required, optional - template<> template<> - void object::test<21>() - { - work("stu", LLSDMap("bar", 3.14)("foo", "pies")); - ensure_equals(dummy.s, "pies"); - ensure_equals(dummy.i, 3); - } + (LLSDArray // group + (LLSDArray("freena_map_skipreq")("smethodna_map_skipreq")("methodna_map_skipreq")) + (LLSDArray(skipreqa)(dfta_map_partial))) // required, optional - template<> template<> - void object::test<22>() - { - call_exc("stu", LLSD(), "missing required"); - } + (LLSDArray // group + (LLSDArray("freenb_map_skipreq")("smethodnb_map_skipreq")("methodnb_map_skipreq")) + (LLSDArray(skipreqb)(dftb_map_partial))) // required, optional - template<> template<> - void object::test<23>() - { - call_exc("stu", LLSDMap("bar", 3.14), "missing required"); - } + // 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. + (LLSDArray // group + (LLSDArray("freena_map_mdft")("smethodna_map_mdft")("methodna_map_mdft")) + (LLSDArray(LLSD::emptyMap())(dfta_map_full))) // required, optional - template<> template<> - void object::test<24>() - { - work("stu", LLSDMap("foo", "pies")); - ensure_equals(dummy.s, "pies"); - ensure_equals(dummy.i, -1); - } + (LLSDArray // group + (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) + (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional - template<> template<> - void object::test<25>() - { - std::string msg("nonstatic method(const char*)"); - work("vwx", LLSDMap("message", msg)); - ensure_equals(dummy.s, msg); + for (LLSD::array_const_iterator gi(groups.beginArray()), gend(groups.endArray()); + gi != gend; ++gi) + { + // Internal structure of each group in 'groups': + LLSD names((*gi)[0]); + LLSD required((*gi)[1][0]); + LLSD optional((*gi)[1][1]); + std::cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; + + // Loop through 'names' + for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); + ni != nend; ++ni) + { + LLSD metadata(getMetadata(*ni)); + ensure_equals("name mismatch", metadata["name"], *ni); + ensure_equals(metadata["desc"].asString(), funcs[*ni]); + ensure_equals("required mismatch", metadata["required"], required); + ensure_equals("optional mismatch", metadata["optional"], optional); + } + } } } // namespace tut -- cgit v1.2.3 From 6dcbbaeb230a0e6c54b56905728e4ab7fe459fba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 1 Feb 2011 20:18:01 -0500 Subject: Replace boost::ptr_map with std::map. On Windows, unlike on Mac or Linux, boost::ptr_map<> started insisting on this concept of clonability. In other words, it wants to own a unique instance of the pointee; if you copy a value_type -- even to dereference an iterator! -- it wants to construct a whole new instance of the mapped_type. That's nuts. A std::map<..., boost::shared_ptr<>> has the property I want (the mapped_type goes away when the entry is erased), plus it's willing to pass around the shared_ptr to the same instance of the mapped_type. This change also permits simplifying a couple awkward kludges I'd already had to make to accommodate ptr_map's idiosyncracies. --- indra/llcommon/lleventdispatcher.cpp | 16 +++++++++------- indra/llcommon/lleventdispatcher.h | 33 ++++++++++++++------------------- 2 files changed, 23 insertions(+), 26 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index e00cca366f..52660105c4 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -524,9 +524,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - // Peculiar to me that boost::ptr_map() accepts std::auto_ptr but not dumb ptr - mDispatch.insert(name, std::auto_ptr( - new ArrayParamsDispatchEntry(desc, invoker, arity))); + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new ArrayParamsDispatchEntry(desc, invoker, arity)))); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -535,16 +535,18 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.insert(name, std::auto_ptr( - new MapParamsDispatchEntry(name, desc, invoker, params, defaults))); + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new MapParamsDispatchEntry(name, desc, invoker, params, defaults)))); } /// Register a callable by name void LLEventDispatcher::add(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.insert(name, std::auto_ptr( - new LLSDDispatchEntry(desc, callable, required))); + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new LLSDDispatchEntry(desc, callable, required)))); } void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index b37189b58b..7acc61de4e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -57,7 +57,7 @@ static const int& nil(nil_); #endif #include -#include +#include #include #include #include @@ -286,40 +286,35 @@ private: struct DispatchEntry { DispatchEntry(const std::string& desc); + virtual ~DispatchEntry() {} // suppress MSVC warning, sigh std::string mDesc; virtual void call(const std::string& desc, const LLSD& event) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; - typedef boost::ptr_map DispatchMap; + // Tried using boost::ptr_map, but ptr_map<> + // wants its value type to be "clonable," even just to dereference an + // iterator. I don't want to clone entries -- if I have to copy an entry + // around, I want it to continue pointing to the same DispatchEntry + // subclass object. However, I definitely want DispatchMap to destroy + // DispatchEntry if no references are outstanding at the time an entry is + // removed. This looks like a job for boost::shared_ptr. + typedef std::map > DispatchMap; public: /// We want the flexibility to redefine what data we store per name, /// therefore our public interface doesn't expose DispatchMap iterators, /// or DispatchMap itself, or DispatchEntry. Instead we explicitly /// transform each DispatchMap item to NameDesc on dereferencing. - typedef boost::transform_iterator const_iterator; + typedef boost::transform_iterator const_iterator; const_iterator begin() const { - // Originally we used DispatchMap::const_iterator, which Just Worked - // when DispatchMap was a std::map. Now that it's a boost::ptr_map, - // using DispatchMap::const_iterator doesn't work so well: it - // dereferences to a pair, whereas - // DispatchMap::value_type is just pair. Trying to pass a - // dereferenced iterator to the value_type didn't work because the - // compiler won't let you convert from const T* to plain T*. Changing - // our const_iterator definition above to be based on non-const - // DispatchMap::iterator works better, but of course we have to cast - // away the constness of mDispatch to use non-const iterator. (Sigh.) - return boost::make_transform_iterator(const_cast(mDispatch).begin(), - makeNameDesc); + return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc); } const_iterator end() const { - // see begin() comments - return boost::make_transform_iterator(const_cast(mDispatch).end(), - makeNameDesc); + return boost::make_transform_iterator(mDispatch.end(), makeNameDesc); } //@} @@ -349,7 +344,7 @@ private: std::string mDesc, mKey; DispatchMap mDispatch; - static NameDesc makeNameDesc(DispatchMap::value_type item) + static NameDesc makeNameDesc(const DispatchMap::value_type& item) { return NameDesc(item.first, item.second->mDesc); } -- cgit v1.2.3 From f1262c83fc9be45c4b5cd1678c127b601634fb29 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 2 Feb 2011 11:13:07 -0500 Subject: First few LLEventDispatcher call cases: try_call(), call Callables --- indra/llcommon/tests/lleventdispatcher_test.cpp | 88 ++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 35a3188507..30878beae4 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -956,7 +956,7 @@ namespace tut LLSD names((*gi)[0]); LLSD required((*gi)[1][0]); LLSD optional((*gi)[1][1]); - std::cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; + cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; // Loop through 'names' for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); @@ -970,4 +970,90 @@ namespace tut } } } + + 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"); + // We don't have a comparable helper function for the one-arg + // operator() method, and it's not worth building one just for this + // case. Write it out. + std::string threw; + try + { + work(LLSDMap("op", "freek")); + } + catch (const std::runtime_error& e) + { + cout << "*** " << e.what() << "\n"; + threw = e.what(); + } + ensure_has(threw, "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 Triple + { + std::string name, name_req; + LLSD& llsd; + }; + + template<> template<> + void object::test<16>() + { + set_test_name("call Callables"); + Triple 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", LLSDArray("answer")(true)(answer))); + // Okay, walk through 'tests'. + for (const Triple *ti(boost::begin(tests)), *tend(boost::end(tests)); ti != tend; ++ti) + { + // Should be able to pass 'answer' to Callables registered + // without 'required'. + work(ti->name, answer); + ensure_equals("answer mismatch", ti->llsd, answer); + // Should NOT be able to pass 'answer' to Callables registered + // with 'required'. + call_exc(ti->name_req, answer, "bad request"); + // But SHOULD be able to pass 'matching' to Callables registered + // with 'required'. + work(ti->name_req, matching); + ensure_equals("matching mismatch", ti->llsd, matching); + } + } } // namespace tut -- cgit v1.2.3 From 63f81d59f6e736c5fdd30d8297d64d1677987d3c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 2 Feb 2011 17:17:41 -0500 Subject: Add test to exercise map/array args mismatch validation. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 30878beae4..2f2188a121 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1056,4 +1056,24 @@ namespace tut ensure_equals("matching mismatch", ti->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. As that validation happens well before we engage the + // argument magic, 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. + std::string array_exc("needs an args array"); + call_exc("free0_array", 17, array_exc); + call_exc("free0_array", LLSDMap("pi", 3.14), array_exc); + + std::string map_exc("needs a map"); + call_exc("free0_map", 17, map_exc); + call_exc("free0_map", LLSDArray("a")("b"), map_exc); + } } // namespace tut -- cgit v1.2.3 From 84a402adf181165b4fef0ab8f1c0e63cc81a57ee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 2 Feb 2011 17:42:26 -0500 Subject: Add test to call no-args functions using (map | array)-style calls --- indra/llcommon/tests/lleventdispatcher_test.cpp | 43 +++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2f2188a121..63a130a94b 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1017,7 +1017,7 @@ namespace tut } // Cannot be defined inside function body... remind me again why we use C++... :-P - struct Triple + struct CallablesTriple { std::string name, name_req; LLSD& llsd; @@ -1027,7 +1027,7 @@ namespace tut void object::test<16>() { set_test_name("call Callables"); - Triple tests[] = + CallablesTriple tests[] = { { "free1", "free1_req", g.llsd }, { "Dmethod1", "Dmethod1_req", work.llsd }, @@ -1041,7 +1041,8 @@ namespace tut // LLSD value matching 'required' according to llsd_matches() rules. LLSD matching(LLSDMap("d", 3.14)("array", LLSDArray("answer")(true)(answer))); // Okay, walk through 'tests'. - for (const Triple *ti(boost::begin(tests)), *tend(boost::end(tests)); ti != tend; ++ti) + for (const CallablesTriple *ti(boost::begin(tests)), *tend(boost::end(tests)); + ti != tend; ++ti) { // Should be able to pass 'answer' to Callables registered // without 'required'. @@ -1076,4 +1077,40 @@ namespace tut call_exc("free0_map", 17, map_exc); call_exc("free0_map", LLSDArray("a")("b"), map_exc); } + + struct FunctionsTriple + { + std::string name_array, name_map; + Vars& vars; + }; + + template<> template<> + void object::test<18>() + { + set_test_name("call no-args functions"); + FunctionsTriple tests[] = + { + { "free0_array", "free0_map", g }, + { "smethod0_array", "smethod0_map", g }, + { "method0_array", "method0_map", v } + }; + for (const FunctionsTriple *ti(boost::begin(tests)), *tend(boost::end(tests)); + ti != tend; ++ti) + { + // Both the global and stack Vars instances are automatically + // cleared at the start of each test method. But since we're + // calling these things several different times in the same + // test method, manually reset the Vars between each. + ti->vars = Vars(); + ensure_equals(ti->vars.i, 0); + // array-style call with empty array (or LLSD(), should be equivalent) + work(ti->name_array, LLSD()); + ensure_equals(ti->vars.i, 17); + + ti->vars = Vars(); + // map-style call with empty map (or LLSD(), should be equivalent) + work(ti->name_map, LLSD()); + ensure_equals(ti->vars.i, 17); + } + } } // namespace tut -- cgit v1.2.3 From 3cdd1931c67ae3be26549d05889e19ba26d3db03 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 3 Feb 2011 19:38:41 -0500 Subject: Add test to call array-style functions with too-short array. Also, finally got sick of hand-writing the official iterator-loop idiom and dragged in BOOST_FOREACH(). Because LLSD has two completely different iteration styles, added inArray and inMap helper classes to be able to write: BOOST_FOREACH(LLSD item, inArray(someArray)) { ... } --- indra/llcommon/tests/lleventdispatcher_test.cpp | 218 +++++++++++++++--------- 1 file changed, 137 insertions(+), 81 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 63a130a94b..2975707860 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#define foreach BOOST_FOREACH #include @@ -49,6 +51,52 @@ using std::cout; static std::ostringstream cout; #endif +/***************************************************************************** +* BOOST_FOREACH() helpers for LLSD +*****************************************************************************/ +/// Usage: BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } +class inArray +{ +public: + inArray(const LLSD& array): + _array(array) + {} + + typedef LLSD::array_const_iterator const_iterator; + typedef LLSD::array_iterator iterator; + + iterator begin() { return _array.beginArray(); } + iterator end() { return _array.endArray(); } + const_iterator begin() const { return _array.beginArray(); } + const_iterator end() const { return _array.endArray(); } + +private: + LLSD _array; +}; + +/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. +typedef std::pair MapEntry; + +/// Usage: BOOST_FOREACH([const] MapEntry& e, inMap(someLLSDmap)) { ... } +class inMap +{ +public: + inMap(const LLSD& map): + _map(map) + {} + + typedef LLSD::map_const_iterator const_iterator; + typedef LLSD::map_iterator iterator; + + iterator begin() { return _map.beginMap(); } + iterator end() { return _map.endMap(); } + const_iterator begin() const { return _map.beginMap(); } + const_iterator end() const { return _map.endMap(); } + +private: + LLSD _map; +}; + /***************************************************************************** * Example data, functions, classes *****************************************************************************/ @@ -198,9 +246,9 @@ struct Vars void methodnb(NPARAMSb) { std::ostringstream vbin; - for (size_t ix = 0, ixend = bin.size(); ix < ixend; ++ix) + foreach(U8 byte, bin) { - vbin << std::hex << std::setfill('0') << std::setw(2) << bin[ix]; + vbin << std::hex << std::setfill('0') << std::setw(2) << byte; } cout << "methodnb(" << "'" << s << "'" @@ -425,12 +473,11 @@ namespace tut (LLSDArray(paramsb)(dftb_array_full))); // std::cout << "zipped:\n" << zipped << '\n'; LLSD dft_maps_full, dft_maps_partial; - for (LLSD::array_const_iterator ai(zipped.beginArray()), aend(zipped.endArray()); - ai != aend; ++ai) + foreach(LLSD ae, inArray(zipped)) { LLSD dft_map_full; - LLSD params((*ai)[0]); - LLSD dft_array_full((*ai)[1]); + LLSD params(ae[0]); + LLSD dft_array_full(ae[1]); // std::cout << "params:\n" << params << "\ndft_array_full:\n" << dft_array_full << '\n'; for (LLSD::Integer ix = 0, ixend = params.size(); ix < ixend; ++ix) { @@ -566,16 +613,20 @@ namespace tut { // Copy funcs to a temp map of same type. FuncMap forgotten(funcs.begin(), funcs.end()); - for (LLEventDispatcher::const_iterator edi(work.begin()), edend(work.end()); - edi != edend; ++edi) + // LLEventDispatcher intentionally provides only const_iterator: + // since dereferencing that iterator generates values on the fly, + // it's meaningless to have a modifiable iterator. But since our + // 'work' object isn't const, by default BOOST_FOREACH() wants to + // use non-const iterators. Persuade it to use the const_iterator. + foreach(LLEventDispatcher::NameDesc nd, const_cast(work)) { - FuncMap::iterator found = forgotten.find(edi->first); - ensure(STRINGIZE("LLEventDispatcher records function '" << edi->first + FuncMap::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 '" << edi->second << + ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second << "' doesn't match what we entered: '" << found->second << "'"), - edi->second, found->second); + nd.second, found->second); // found in our map the name from LLEventDispatcher, good, erase // our map entry forgotten.erase(found); @@ -585,10 +636,9 @@ namespace tut std::ostringstream out; out << "LLEventDispatcher failed to report"; const char* delim = ": "; - for (FuncMap::const_iterator fmi(forgotten.begin()), fmend(forgotten.end()); - fmi != fmend; ++fmi) + foreach(const FuncMap::value_type& fme, forgotten) { - out << delim << fmi->first; + out << delim << fme.first; delim = ", "; } ensure(out.str(), false); @@ -630,10 +680,11 @@ namespace tut // 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) array style with + // - (Free function | non-static method), no args, (array | map) style + // - (Free function | non-static method), arbitrary args, array style with // (scalar | map | array (too short | too long | just right)) // [trap LL_WARNS for too-long case?] - // - (Free function | non-static method) map style with + // - (Free function | non-static method), arbitrary args, map style with // (scalar | array | map (all | too many | holes (with | without) defaults)) // - const char* param gets ("" | NULL) @@ -651,13 +702,12 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); - for (LLSD::array_const_iterator ai(attempts.beginArray()), aend(attempts.endArray()); - ai != aend; ++ai) + foreach(LLSD ae, inArray(attempts)) { std::string threw; try { - work.add("freena_err", "freena", freena, *ai); + work.add("freena_err", "freena", freena, ae); } catch (const std::exception& e) { @@ -751,16 +801,15 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); - for (LLSD::array_const_iterator ai(names.beginArray()), aend(names.endArray()); - ai != aend; ++ai) + foreach(LLSD ae, inArray(names)) { - LLSD metadata(getMetadata(*ai)); - ensure_equals("name mismatch", metadata["name"], *ai); - ensure_equals(metadata["desc"].asString(), funcs[*ai]); + LLSD metadata(getMetadata(ae)); + ensure_equals("name mismatch", metadata["name"], ae); + ensure_equals(metadata["desc"].asString(), funcs[ae]); ensure("should not have required structure", metadata["required"].isUndefined()); ensure("should not have optional", metadata["optional"].isUndefined()); - std::string name_req(ai->asString() + "_req"); + std::string name_req(ae.asString() + "_req"); metadata = getMetadata(name_req); ensure_equals(metadata["name"].asString(), name_req); ensure_equals(metadata["desc"].asString(), funcs[name_req]); @@ -781,21 +830,19 @@ namespace tut (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array"))) (LLSDArray (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array")))); - for (LLSD::array_const_iterator ai(expected.beginArray()), aend(expected.endArray()); - ai != aend; ++ai) + foreach(LLSD ae, inArray(expected)) { - LLSD::Integer arity((*ai)[0].asInteger()); - LLSD names((*ai)[1]); + LLSD::Integer arity(ae[0].asInteger()); + LLSD names(ae[1]); LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); - ni != nend; ++ni) + foreach(LLSD nm, inArray(names)) { - LLSD metadata(getMetadata(*ni)); - ensure_equals("name mismatch", metadata["name"], *ni); - ensure_equals(metadata["desc"].asString(), funcs[*ni]); - ensure_equals(STRINGIZE("mismatched required for " << ni->asString()), + LLSD metadata(getMetadata(nm)); + ensure_equals("name mismatch", metadata["name"], nm); + ensure_equals(metadata["desc"].asString(), funcs[nm]); + ensure_equals(STRINGIZE("mismatched required for " << nm.asString()), metadata["required"], req); ensure("should not have optional", metadata["optional"].isUndefined()); } @@ -809,12 +856,11 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map")); - for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); - ni != nend; ++ni) + foreach(LLSD nm, inArray(names)) { - LLSD metadata(getMetadata(*ni)); - ensure_equals("name mismatch", metadata["name"], *ni); - ensure_equals(metadata["desc"].asString(), funcs[*ni]); + LLSD metadata(getMetadata(nm)); + ensure_equals("name mismatch", metadata["name"], nm); + ensure_equals(metadata["desc"].asString(), funcs[nm]); ensure("should not have required", (metadata["required"].isUndefined() || metadata["required"].size() == 0)); ensure("should not have optional", metadata["optional"].isUndefined()); @@ -840,12 +886,10 @@ namespace tut (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft")) (LLSDArray("methodna_map_adft")("methodna_map_mdft")) (LLSDArray("methodnb_map_adft")("methodnb_map_mdft"))); - for (LLSD::array_const_iterator - ei(equivalences.beginArray()), eend(equivalences.endArray()); - ei != eend; ++ei) + foreach(LLSD eq, inArray(equivalences)) { - LLSD adft((*ei)[0]); - LLSD mdft((*ei)[1]); + 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 @@ -899,17 +943,13 @@ namespace tut // Generate maps containing parameter names not provided by the // dft[ab]_map_partial maps. LLSD skipreqa(allreqa), skipreqb(allreqb); - for (LLSD::map_const_iterator mi(dfta_map_partial.beginMap()), - mend(dfta_map_partial.endMap()); - mi != mend; ++mi) + foreach(const MapEntry& me, inMap(dfta_map_partial)) { - skipreqa.erase(mi->first); + skipreqa.erase(me.first); } - for (LLSD::map_const_iterator mi(dftb_map_partial.beginMap()), - mend(dftb_map_partial.endMap()); - mi != mend; ++mi) + foreach(const MapEntry& me, inMap(dftb_map_partial)) { - skipreqb.erase(mi->first); + skipreqb.erase(me.first); } LLSD groups(LLSDArray // array of groups @@ -949,22 +989,20 @@ namespace tut (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional - for (LLSD::array_const_iterator gi(groups.beginArray()), gend(groups.endArray()); - gi != gend; ++gi) + foreach(LLSD grp, inArray(groups)) { // Internal structure of each group in 'groups': - LLSD names((*gi)[0]); - LLSD required((*gi)[1][0]); - LLSD optional((*gi)[1][1]); + LLSD names(grp[0]); + LLSD required(grp[1][0]); + LLSD optional(grp[1][1]); cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; // Loop through 'names' - for (LLSD::array_const_iterator ni(names.beginArray()), nend(names.endArray()); - ni != nend; ++ni) + foreach(LLSD nm, inArray(names)) { - LLSD metadata(getMetadata(*ni)); - ensure_equals("name mismatch", metadata["name"], *ni); - ensure_equals(metadata["desc"].asString(), funcs[*ni]); + LLSD metadata(getMetadata(nm)); + ensure_equals("name mismatch", metadata["name"], nm); + ensure_equals(metadata["desc"].asString(), funcs[nm]); ensure_equals("required mismatch", metadata["required"], required); ensure_equals("optional mismatch", metadata["optional"], optional); } @@ -1041,20 +1079,19 @@ namespace tut // LLSD value matching 'required' according to llsd_matches() rules. LLSD matching(LLSDMap("d", 3.14)("array", LLSDArray("answer")(true)(answer))); // Okay, walk through 'tests'. - for (const CallablesTriple *ti(boost::begin(tests)), *tend(boost::end(tests)); - ti != tend; ++ti) + foreach(const CallablesTriple& tr, tests) { // Should be able to pass 'answer' to Callables registered // without 'required'. - work(ti->name, answer); - ensure_equals("answer mismatch", ti->llsd, answer); + work(tr.name, answer); + ensure_equals("answer mismatch", tr.llsd, answer); // Should NOT be able to pass 'answer' to Callables registered // with 'required'. - call_exc(ti->name_req, answer, "bad request"); + call_exc(tr.name_req, answer, "bad request"); // But SHOULD be able to pass 'matching' to Callables registered // with 'required'. - work(ti->name_req, matching); - ensure_equals("matching mismatch", ti->llsd, matching); + work(tr.name_req, matching); + ensure_equals("matching mismatch", tr.llsd, matching); } } @@ -1080,7 +1117,7 @@ namespace tut struct FunctionsTriple { - std::string name_array, name_map; + std::string name1, name2; Vars& vars; }; @@ -1094,23 +1131,42 @@ namespace tut { "smethod0_array", "smethod0_map", g }, { "method0_array", "method0_map", v } }; - for (const FunctionsTriple *ti(boost::begin(tests)), *tend(boost::end(tests)); - ti != tend; ++ti) + foreach(const FunctionsTriple& tr, tests) { // Both the global and stack Vars instances are automatically // cleared at the start of each test method. But since we're // calling these things several different times in the same // test method, manually reset the Vars between each. - ti->vars = Vars(); - ensure_equals(ti->vars.i, 0); + tr.vars = Vars(); + ensure_equals(tr.vars.i, 0); // array-style call with empty array (or LLSD(), should be equivalent) - work(ti->name_array, LLSD()); - ensure_equals(ti->vars.i, 17); + work(tr.name1, LLSD()); + ensure_equals(tr.vars.i, 17); - ti->vars = Vars(); + tr.vars = Vars(); // map-style call with empty map (or LLSD(), should be equivalent) - work(ti->name_map, LLSD()); - ensure_equals(ti->vars.i, 17); + work(tr.name2, LLSD()); + ensure_equals(tr.vars.i, 17); + } + } + + template<> template<> + void object::test<19>() + { + set_test_name("call array-style functions with too-short arrays"); + FunctionsTriple tests[] = + { + { "freena_array", "freenb_array", g }, + { "smethodna_array", "smethodnb_array", g }, + { "methodna_array", "methodnb_array", v } + }; + // Could have two different too-short arrays, one for *na and one for + // *nb, but since they both take 5 params... + LLSD tooshort(LLSDArray("this")("array")("too")("short")); + foreach(const FunctionsTriple& tr, tests) + { + call_exc(tr.name1, tooshort, "requires more arguments"); + call_exc(tr.name2, tooshort, "requires more arguments"); } } } // namespace tut -- cgit v1.2.3 From d814e76cad49d6ebeb8f34b13d5d946d9f675b76 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 3 Feb 2011 22:54:16 -0500 Subject: Introduce BOOST_FOREACH() helpers for LLSD in llsdutil.h. You can't directly write: BOOST_FOREACH(LLSD item, someLLSDarray) { ... } because LLSD has two distinct iteration mechanisms, one for arrays and one for maps, neither using the standard [const_]iterator typedefs or begin()/end() methods. But with these helpers, you can write: BOOST_FOREACH(LLSD item, llsd::inArray(someLLSDarray)) { ... } or BOOST_FOREACH(const llsd::MapEntry& pair, llsd::inMap(someLLSDmap)) { ... } These are in namespace llsd instead of being (e.g.) llsd_inMap because with a namespace at least your .cpp file can have a local 'using': using namespace llsd; BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } It's namespace llsd rather than LLSD because LLSD can't be both a namespace and a class name. --- indra/llcommon/llsdutil.h | 51 +++++++++++++++++++ indra/llcommon/tests/lleventdispatcher_test.cpp | 68 ++++--------------------- 2 files changed, 62 insertions(+), 57 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index c873b17112..65c7297cbf 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -368,4 +368,55 @@ public: } }; +namespace llsd +{ + +/***************************************************************************** +* BOOST_FOREACH() helpers for LLSD +*****************************************************************************/ +/// Usage: BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } +class inArray +{ +public: + inArray(const LLSD& array): + _array(array) + {} + + typedef LLSD::array_const_iterator const_iterator; + typedef LLSD::array_iterator iterator; + + iterator begin() { return _array.beginArray(); } + iterator end() { return _array.endArray(); } + const_iterator begin() const { return _array.beginArray(); } + const_iterator end() const { return _array.endArray(); } + +private: + LLSD _array; +}; + +/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. +typedef std::map::value_type MapEntry; + +/// Usage: BOOST_FOREACH([const] MapEntry& e, inMap(someLLSDmap)) { ... } +class inMap +{ +public: + inMap(const LLSD& map): + _map(map) + {} + + typedef LLSD::map_const_iterator const_iterator; + typedef LLSD::map_iterator iterator; + + iterator begin() { return _map.beginMap(); } + iterator end() { return _map.endMap(); } + const_iterator begin() const { return _map.beginMap(); } + const_iterator end() const { return _map.endMap(); } + +private: + LLSD _map; +}; + +} // namespace llsd + #endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2975707860..2241dea4ba 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -51,52 +51,6 @@ using std::cout; static std::ostringstream cout; #endif -/***************************************************************************** -* BOOST_FOREACH() helpers for LLSD -*****************************************************************************/ -/// Usage: BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } -class inArray -{ -public: - inArray(const LLSD& array): - _array(array) - {} - - typedef LLSD::array_const_iterator const_iterator; - typedef LLSD::array_iterator iterator; - - iterator begin() { return _array.beginArray(); } - iterator end() { return _array.endArray(); } - const_iterator begin() const { return _array.beginArray(); } - const_iterator end() const { return _array.endArray(); } - -private: - LLSD _array; -}; - -/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. -typedef std::pair MapEntry; - -/// Usage: BOOST_FOREACH([const] MapEntry& e, inMap(someLLSDmap)) { ... } -class inMap -{ -public: - inMap(const LLSD& map): - _map(map) - {} - - typedef LLSD::map_const_iterator const_iterator; - typedef LLSD::map_iterator iterator; - - iterator begin() { return _map.beginMap(); } - iterator end() { return _map.endMap(); } - const_iterator begin() const { return _map.beginMap(); } - const_iterator end() const { return _map.endMap(); } - -private: - LLSD _map; -}; - /***************************************************************************** * Example data, functions, classes *****************************************************************************/ @@ -473,7 +427,7 @@ namespace tut (LLSDArray(paramsb)(dftb_array_full))); // std::cout << "zipped:\n" << zipped << '\n'; LLSD dft_maps_full, dft_maps_partial; - foreach(LLSD ae, inArray(zipped)) + foreach(LLSD ae, llsd::inArray(zipped)) { LLSD dft_map_full; LLSD params(ae[0]); @@ -702,7 +656,7 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); - foreach(LLSD ae, inArray(attempts)) + foreach(LLSD ae, llsd::inArray(attempts)) { std::string threw; try @@ -801,7 +755,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); - foreach(LLSD ae, inArray(names)) + foreach(LLSD ae, llsd::inArray(names)) { LLSD metadata(getMetadata(ae)); ensure_equals("name mismatch", metadata["name"], ae); @@ -830,14 +784,14 @@ namespace tut (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array"))) (LLSDArray (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array")))); - foreach(LLSD ae, inArray(expected)) + foreach(LLSD ae, llsd::inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -856,7 +810,7 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map")); - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -886,7 +840,7 @@ namespace tut (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft")) (LLSDArray("methodna_map_adft")("methodna_map_mdft")) (LLSDArray("methodnb_map_adft")("methodnb_map_mdft"))); - foreach(LLSD eq, inArray(equivalences)) + foreach(LLSD eq, llsd::inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -943,11 +897,11 @@ namespace tut // Generate maps containing parameter names not provided by the // dft[ab]_map_partial maps. LLSD skipreqa(allreqa), skipreqb(allreqb); - foreach(const MapEntry& me, inMap(dfta_map_partial)) + foreach(const llsd::MapEntry& me, llsd::inMap(dfta_map_partial)) { skipreqa.erase(me.first); } - foreach(const MapEntry& me, inMap(dftb_map_partial)) + foreach(const llsd::MapEntry& me, llsd::inMap(dftb_map_partial)) { skipreqb.erase(me.first); } @@ -989,7 +943,7 @@ namespace tut (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional - foreach(LLSD grp, inArray(groups)) + foreach(LLSD grp, llsd::inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -998,7 +952,7 @@ namespace tut cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; // Loop through 'names' - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); -- cgit v1.2.3 From f2bb1b451cc37bcc2c09687fd52515d8b791d97a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 3 Feb 2011 23:04:40 -0500 Subject: BOOST_FOREACH(LLSD) helpers more readable with 'using namespace'. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2241dea4ba..f9202307f7 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -42,6 +42,8 @@ using boost::lambda::constant; using boost::lambda::constant_ref; using boost::lambda::var; +using namespace llsd; + /***************************************************************************** * Output control *****************************************************************************/ @@ -427,7 +429,7 @@ namespace tut (LLSDArray(paramsb)(dftb_array_full))); // std::cout << "zipped:\n" << zipped << '\n'; LLSD dft_maps_full, dft_maps_partial; - foreach(LLSD ae, llsd::inArray(zipped)) + foreach(LLSD ae, inArray(zipped)) { LLSD dft_map_full; LLSD params(ae[0]); @@ -656,7 +658,7 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); - foreach(LLSD ae, llsd::inArray(attempts)) + foreach(LLSD ae, inArray(attempts)) { std::string threw; try @@ -755,7 +757,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); - foreach(LLSD ae, llsd::inArray(names)) + foreach(LLSD ae, inArray(names)) { LLSD metadata(getMetadata(ae)); ensure_equals("name mismatch", metadata["name"], ae); @@ -784,14 +786,14 @@ namespace tut (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array"))) (LLSDArray (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array")))); - foreach(LLSD ae, llsd::inArray(expected)) + foreach(LLSD ae, inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - foreach(LLSD nm, llsd::inArray(names)) + foreach(LLSD nm, inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -810,7 +812,7 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map")); - foreach(LLSD nm, llsd::inArray(names)) + foreach(LLSD nm, inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -840,7 +842,7 @@ namespace tut (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft")) (LLSDArray("methodna_map_adft")("methodna_map_mdft")) (LLSDArray("methodnb_map_adft")("methodnb_map_mdft"))); - foreach(LLSD eq, llsd::inArray(equivalences)) + foreach(LLSD eq, inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -897,11 +899,11 @@ namespace tut // Generate maps containing parameter names not provided by the // dft[ab]_map_partial maps. LLSD skipreqa(allreqa), skipreqb(allreqb); - foreach(const llsd::MapEntry& me, llsd::inMap(dfta_map_partial)) + foreach(const MapEntry& me, inMap(dfta_map_partial)) { skipreqa.erase(me.first); } - foreach(const llsd::MapEntry& me, llsd::inMap(dftb_map_partial)) + foreach(const MapEntry& me, inMap(dftb_map_partial)) { skipreqb.erase(me.first); } @@ -943,7 +945,7 @@ namespace tut (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional - foreach(LLSD grp, llsd::inArray(groups)) + foreach(LLSD grp, inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -952,7 +954,7 @@ namespace tut cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; // Loop through 'names' - foreach(LLSD nm, llsd::inArray(names)) + foreach(LLSD nm, inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); -- cgit v1.2.3 From f0c1c4f5b0b7ab37f0830e2b9e3dab09935c2700 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 4 Feb 2011 10:57:48 -0500 Subject: Move FunctionsTriple data to function returning vector. We want to break out a couple different test methods that both need the same data. While we could define a std::vector in the lleventdispatcher_data class and initialize it using a classic {} initializer as in array_funcs(), using a separate function puts it closer to the tests consuming that data, and helps reduce clutter in the central data class. Either way, it's cool that BOOST_FOREACH() handles the gory details of iterating over a std::vector vs. a classic-C array. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index f9202307f7..f8cf93f838 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1098,7 +1098,7 @@ namespace tut // array-style call with empty array (or LLSD(), should be equivalent) work(tr.name1, LLSD()); ensure_equals(tr.vars.i, 17); - + tr.vars = Vars(); // map-style call with empty map (or LLSD(), should be equivalent) work(tr.name2, LLSD()); @@ -1106,20 +1106,27 @@ namespace tut } } - template<> template<> - void object::test<19>() + // Break out function to return this data because we use it in a couple + // different tests. + std::vector array_funcs(Vars& v) { - set_test_name("call array-style functions with too-short arrays"); FunctionsTriple tests[] = { { "freena_array", "freenb_array", g }, { "smethodna_array", "smethodnb_array", g }, { "methodna_array", "methodnb_array", v } }; + return std::vector(boost::begin(tests), boost::end(tests)); + } + + template<> template<> + void object::test<19>() + { + set_test_name("call array-style functions with too-short arrays"); // Could have two different too-short arrays, one for *na and one for // *nb, but since they both take 5 params... LLSD tooshort(LLSDArray("this")("array")("too")("short")); - foreach(const FunctionsTriple& tr, tests) + foreach(const FunctionsTriple& tr, array_funcs(v)) { call_exc(tr.name1, tooshort, "requires more arguments"); call_exc(tr.name2, tooshort, "requires more arguments"); -- cgit v1.2.3 From f18885e55df025059e279605d997364575c1561a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 4 Feb 2011 14:54:20 -0500 Subject: Change FunctionsTriple refs to pointers to facilitate passing. A certain popular-but-dumb compiler seems to think that initializing a std::vector from a pair of iterators requires assignment. A struct containing a reference cannot be assigned. Pointers get us past this issue. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index f8cf93f838..9de0819b66 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1074,7 +1074,7 @@ namespace tut struct FunctionsTriple { std::string name1, name2; - Vars& vars; + Vars* vars; }; template<> template<> @@ -1083,9 +1083,9 @@ namespace tut set_test_name("call no-args functions"); FunctionsTriple tests[] = { - { "free0_array", "free0_map", g }, - { "smethod0_array", "smethod0_map", g }, - { "method0_array", "method0_map", v } + { "free0_array", "free0_map", &g }, + { "smethod0_array", "smethod0_map", &g }, + { "method0_array", "method0_map", &v } }; foreach(const FunctionsTriple& tr, tests) { @@ -1093,16 +1093,16 @@ namespace tut // cleared at the start of each test method. But since we're // calling these things several different times in the same // test method, manually reset the Vars between each. - tr.vars = Vars(); - ensure_equals(tr.vars.i, 0); + *tr.vars = Vars(); + ensure_equals(tr.vars->i, 0); // array-style call with empty array (or LLSD(), should be equivalent) work(tr.name1, LLSD()); - ensure_equals(tr.vars.i, 17); + ensure_equals(tr.vars->i, 17); - tr.vars = Vars(); + *tr.vars = Vars(); // map-style call with empty map (or LLSD(), should be equivalent) work(tr.name2, LLSD()); - ensure_equals(tr.vars.i, 17); + ensure_equals(tr.vars->i, 17); } } @@ -1112,9 +1112,9 @@ namespace tut { FunctionsTriple tests[] = { - { "freena_array", "freenb_array", g }, - { "smethodna_array", "smethodnb_array", g }, - { "methodna_array", "methodnb_array", v } + { "freena_array", "freenb_array", &g }, + { "smethodna_array", "smethodnb_array", &g }, + { "methodna_array", "methodnb_array", &v } }; return std::vector(boost::begin(tests), boost::end(tests)); } -- cgit v1.2.3 From 950cac24ccfe963b5af1dc4f7b07acda1e8bd062 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 5 Feb 2011 11:11:20 -0500 Subject: Add successful calls to array-style functions. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 9de0819b66..c8f45290d6 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -628,6 +628,26 @@ namespace tut ensure(STRINGIZE("No metadata for " << name), meta.isDefined()); return meta; } + + // 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_group; typedef lleventdispatcher_group::object object; @@ -1132,4 +1152,54 @@ namespace tut call_exc(tr.name2, tooshort, "requires more arguments"); } } + + template<> template<> + void object::test<20>() + { + set_test_name("call array-style functions with (just right | too long) arrays"); + std::vector binary; + for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) + { + binary.push_back(h); + } + LLSD argsa(LLSDArray(true)(17)(3.14)(123.456)("char*")); + LLSD argsb(LLSDArray("string")(LLUUID("01234567-89ab-cdef-0123-456789abcdef")) + (LLDate("2011-02-03T15:07:00Z"))(LLURI("http://secondlife.com"))(binary)); + LLSD argsaplus(argsa), argsbplus(argsb); + argsaplus.append("bogus"); + argsbplus.append("bogus"); + LLSD expecta, expectb; + for (LLSD::Integer i(0), iend(paramsa.size()); i < iend; ++i) + { + expecta[paramsa[i].asString()] = argsa[i]; + } + for (LLSD::Integer i(0), iend(paramsb.size()); i < iend; ++i) + { + expectb[paramsb[i].asString()] = argsb[i]; + } + cout << "expecta: " << expecta << "\nexpectb: " << expectb << '\n'; + foreach(const FunctionsTriple& tr, array_funcs(v)) + { + *tr.vars = Vars(); + work(tr.name1, argsa); + ensure_llsd(STRINGIZE(tr.name1 << ": expecta mismatch"), + tr.vars->inspect(), expecta, 7); + + *tr.vars = Vars(); + work(tr.name2, argsb); + ensure_llsd(STRINGIZE(tr.name2 << ": expectb mismatch"), + tr.vars->inspect(), expectb, 7); + + // TODO: argsaplus, argsbplus, check LL_WARNS output? + } + } + + // TODO: + // - refactor params-related data as {'a':arraya, 'b':arrayb} + // - function to build a map from keys array, values array (or + // vice-versa?) + // - Vars formatting for char* and for binary should be done in setter + // method, storing each as std::string. We should NOT NOT NOT store + // char*! The string data we receive through the param list will be gone + // by the time we try to inspect()! } // namespace tut -- cgit v1.2.3 From 230d22ceb2ccc9e82aec0a37f1647636fb5ee310 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 5 Feb 2011 11:30:52 -0500 Subject: Fix Vars::cp dangling-pointer problem. Naively storing a const char* param in a const char* data member ignores the fact that once the caller's done, the string data referenced by that pointer will probably be freed. Store the referenced string in a std::string instead. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index c8f45290d6..afc2d01729 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -98,7 +98,13 @@ struct Vars int i; float f; double d; - const char* cp; + // 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; @@ -111,8 +117,7 @@ struct Vars b(false), i(0), f(0), - d(0), - cp(NULL) + d(0) {} // Detect any non-default values for convenient testing @@ -130,7 +135,7 @@ struct Vars result["f"] = f; if (d) result["d"] = d; - if (cp) + if (! cp.empty()) result["cp"] = cp; if (! s.empty()) result["s"] = s; @@ -179,6 +184,11 @@ struct Vars /*-------- Arbitrary-params (non-const, const, static) methods ---------*/ void methodna(NPARAMSa) { + // 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"; @@ -196,7 +206,7 @@ struct Vars this->i = i; this->f = f; this->d = d; - this->cp = cp; + this->cp = vcp; } void methodnb(NPARAMSb) @@ -1177,6 +1187,8 @@ namespace tut { expectb[paramsb[i].asString()] = argsb[i]; } + // Adjust expecta["cp"] for special Vars::cp treatment. + expecta["cp"] = std::string("'") + expecta["cp"].asString() + "'"; cout << "expecta: " << expecta << "\nexpectb: " << expectb << '\n'; foreach(const FunctionsTriple& tr, array_funcs(v)) { @@ -1198,8 +1210,4 @@ namespace tut // - refactor params-related data as {'a':arraya, 'b':arrayb} // - function to build a map from keys array, values array (or // vice-versa?) - // - Vars formatting for char* and for binary should be done in setter - // method, storing each as std::string. We should NOT NOT NOT store - // char*! The string data we receive through the param list will be gone - // by the time we try to inspect()! } // namespace tut -- cgit v1.2.3 From 54b1db2f6587acd11254f30addd8c53e00e05518 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 5 Feb 2011 22:29:43 -0500 Subject: Consolidate paramsa, paramsb, et al., into ["a"], ["b"] arrays. Following the C++ convention of having two distinct somethigna, somethingb names, initially we introduced paramsa, paramsb LLSD arrays, following that convention all the way down the line. This led to two distinct loops every time we wanted to walk both arrays, since we didn't want to assume that they were both the same size. But leveraging the fact that distinct LLSD arrays stored in the same LLSD container can in fact be of different lengths, refactored all the pairs of vars into top-level LLSD maps keyed by ["a"] and ["b"]. That lets us perform nested loops rather than duplicating the logic, making test code much less messy. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 287 ++++++++++++------------ 1 file changed, 141 insertions(+), 146 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index afc2d01729..32ba0cd16f 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -321,15 +321,18 @@ namespace tut // Required structure for Callables with requirements LLSD required; // Parameter names for freena(), freenb() - LLSD paramsa, paramsb; + LLSD params; // Full defaults arrays for params for freena(), freenb() - LLSD dfta_array_full, dftb_array_full; + LLSD dft_array_full; // Start index of partial defaults arrays const LLSD::Integer partial_offset; // Full defaults maps for params for freena(), freenb() - LLSD dfta_map_full, dftb_map_full; + LLSD dft_map_full; // Partial defaults maps for params for freena(), freenb() - LLSD dfta_map_partial, dftb_map_partial; + LLSD 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 ab; lleventdispatcher_data(): work("test dispatcher", "op"), @@ -342,6 +345,9 @@ namespace tut // 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 @@ -412,66 +418,59 @@ namespace tut /*---------------- Arbitrary params, map style -----------------*/ - // freena(), methodna(), cmethodna(), smethodna() all take same param list - paramsa = LLSDArray("b")("i")("f")("d")("cp"); - // same for freenb() et al. - paramsb = LLSDArray("s")("uuid")("date")("uri")("bin"); - // Full defaults arrays. - dfta_array_full = LLSDArray(true)(17)(3.14)(123456.78)("classic"); + // 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", LLSDArray("b")("i")("f")("d")("cp")) + ("b", LLSDArray("s")("uuid")("date")("uri")("bin")); + cout << "params:\n" << params << "\nparams[\"a\"]:\n" << params["a"] << "\nparams[\"b\"]:\n" << params["b"] << std::endl; // default LLSD::Binary value std::vector binary; for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11) { binary.push_back(h); } - // We actually don't care what the LLUUID or LLDate values are, as - // long as they're non-default. - dftb_array_full = LLSDArray("string")(LLUUID::generateNewID())(LLDate::now()) - (LLURI("http://www.ietf.org/rfc/rfc3986.txt"))(binary); + // 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", LLSDArray(true)(17)(3.14)(123456.78)("classic")) + ("b", LLSDArray("string") + (LLUUID::generateNewID()) + (LLDate::now()) + (LLURI("http://www.ietf.org/rfc/rfc3986.txt")) + (binary)); + cout << "dft_array_full:\n" << dft_array_full << std::endl; // Partial defaults arrays. - LLSD dfta_array_partial(llsd_copy_array(dfta_array_full.beginArray() + partial_offset, - dfta_array_full.endArray())); - LLSD dftb_array_partial(llsd_copy_array(dftb_array_full.beginArray() + partial_offset, - dftb_array_full.endArray())); - - // Generate full defaults maps by zipping (params, dftx_array_full). - LLSD zipped(LLSDArray(LLSDArray(paramsa)(dfta_array_full)) - (LLSDArray(paramsb)(dftb_array_full))); -// std::cout << "zipped:\n" << zipped << '\n'; - LLSD dft_maps_full, dft_maps_partial; - foreach(LLSD ae, inArray(zipped)) + LLSD dft_array_partial( + LLSDMap("a", llsd_copy_array(dft_array_full["a"].beginArray() + partial_offset, + dft_array_full["a"].endArray())) + ("b", llsd_copy_array(dft_array_full["b"].beginArray() + partial_offset, + dft_array_full["b"].endArray()))); + cout << "dft_array_partial:\n" << dft_array_partial << std::endl; + + // Generate full defaults maps by zipping (params, dft_array_full). + foreach(LLSD::String a, ab) { - LLSD dft_map_full; - LLSD params(ae[0]); - LLSD dft_array_full(ae[1]); -// std::cout << "params:\n" << params << "\ndft_array_full:\n" << dft_array_full << '\n'; - for (LLSD::Integer ix = 0, ixend = params.size(); ix < ixend; ++ix) + for (LLSD::Integer ix = 0, ixend = params[a].size(); ix < ixend; ++ix) { - dft_map_full[params[ix].asString()] = dft_array_full[ix]; + dft_map_full[a][params[a][ix].asString()] = dft_array_full[a][ix]; } -// std::cout << "dft_map_full:\n" << dft_map_full << '\n'; // 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. - LLSD dft_map_partial; - for (LLSD::Integer ix = 0, ixend = params.size(); ix < ixend; ix += 2) + for (LLSD::Integer ix = 0, ixend = params[a].size(); ix < ixend; ix += 2) { - dft_map_partial[params[ix].asString()] = dft_array_full[ix]; + dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix]; } -// std::cout << "dft_map_partial:\n" << dft_map_partial << '\n'; - dft_maps_full.append(dft_map_full); - dft_maps_partial.append(dft_map_partial); } -// std::cout << "dft_maps_full:\n" << dft_maps_full << "\ndft_maps_partial:\n" << dft_maps_partial << '\n'; - dfta_map_full = dft_maps_full[0]; - dftb_map_full = dft_maps_full[1]; - dfta_map_partial = dft_maps_partial[0]; - dftb_map_partial = dft_maps_partial[1]; -// std::cout << "dfta_map_full:\n" << dfta_map_full -// << "\ndftb_map_full:\n" << dftb_map_full -// << "\ndfta_map_partial:\n" << dfta_map_partial -// << "\ndftb_map_partial:\n" << dftb_map_partial << '\n'; + cout << "dft_map_full:\n" << dft_map_full << "\ndft_map_partial:\n" << dft_map_partial << '\n'; // (Free function | static method) with (no | arbitrary) params, // map style, no (empty array) defaults @@ -480,21 +479,21 @@ namespace tut addf("smethod0_map", "smethod0"); work.add(name, desc, &Vars::smethod0, LLSD::emptyArray()); addf("freena_map_allreq", "freena"); - work.add(name, desc, freena, paramsa); + work.add(name, desc, freena, params["a"]); addf("freenb_map_allreq", "freenb"); - work.add(name, desc, freenb, paramsb); + work.add(name, desc, freenb, params["b"]); addf("smethodna_map_allreq", "smethodna"); - work.add(name, desc, &Vars::smethodna, paramsa); + work.add(name, desc, &Vars::smethodna, params["a"]); addf("smethodnb_map_allreq", "smethodnb"); - work.add(name, desc, &Vars::smethodnb, paramsb); + 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"); work.add(name, desc, &Vars::method0, var(v), LLSD::emptyArray()); addf("methodna_map_allreq", "methodna"); - work.add(name, desc, &Vars::methodna, var(v), paramsa); + work.add(name, desc, &Vars::methodna, var(v), params["a"]); addf("methodnb_map_allreq", "methodnb"); - work.add(name, desc, &Vars::methodnb, var(v), paramsb); + 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 | @@ -504,60 +503,60 @@ namespace tut // (Free function | static method) with arbitrary params, map // style, partial (array | map) defaults addf("freena_map_leftreq", "freena"); - work.add(name, desc, freena, paramsa, dfta_array_partial); + work.add(name, desc, freena, params["a"], dft_array_partial["a"]); addf("freenb_map_leftreq", "freenb"); - work.add(name, desc, freenb, paramsb, dftb_array_partial); + work.add(name, desc, freenb, params["b"], dft_array_partial["b"]); addf("smethodna_map_leftreq", "smethodna"); - work.add(name, desc, &Vars::smethodna, paramsa, dfta_array_partial); + work.add(name, desc, &Vars::smethodna, params["a"], dft_array_partial["a"]); addf("smethodnb_map_leftreq", "smethodnb"); - work.add(name, desc, &Vars::smethodnb, paramsb, dftb_array_partial); + work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_partial["b"]); addf("freena_map_skipreq", "freena"); - work.add(name, desc, freena, paramsa, dfta_map_partial); + work.add(name, desc, freena, params["a"], dft_map_partial["a"]); addf("freenb_map_skipreq", "freenb"); - work.add(name, desc, freenb, paramsb, dftb_map_partial); + work.add(name, desc, freenb, params["b"], dft_map_partial["b"]); addf("smethodna_map_skipreq", "smethodna"); - work.add(name, desc, &Vars::smethodna, paramsa, dfta_map_partial); + work.add(name, desc, &Vars::smethodna, params["a"], dft_map_partial["a"]); addf("smethodnb_map_skipreq", "smethodnb"); - work.add(name, desc, &Vars::smethodnb, paramsb, dftb_map_partial); + 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"); - work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_array_partial); + work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_array_partial["a"]); addf("methodnb_map_leftreq", "methodnb"); - work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_array_partial); + work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_array_partial["b"]); addf("methodna_map_skipreq", "methodna"); - work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_map_partial); + work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_map_partial["a"]); addf("methodnb_map_skipreq", "methodnb"); - work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_map_partial); + 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"); - work.add(name, desc, freena, paramsa, dfta_array_full); + work.add(name, desc, freena, params["a"], dft_array_full["a"]); addf("freenb_map_adft", "freenb"); - work.add(name, desc, freenb, paramsb, dftb_array_full); + work.add(name, desc, freenb, params["b"], dft_array_full["b"]); addf("smethodna_map_adft", "smethodna"); - work.add(name, desc, &Vars::smethodna, paramsa, dfta_array_full); + work.add(name, desc, &Vars::smethodna, params["a"], dft_array_full["a"]); addf("smethodnb_map_adft", "smethodnb"); - work.add(name, desc, &Vars::smethodnb, paramsb, dftb_array_full); + work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_full["b"]); addf("freena_map_mdft", "freena"); - work.add(name, desc, freena, paramsa, dfta_map_full); + work.add(name, desc, freena, params["a"], dft_map_full["a"]); addf("freenb_map_mdft", "freenb"); - work.add(name, desc, freenb, paramsb, dftb_map_full); + work.add(name, desc, freenb, params["b"], dft_map_full["b"]); addf("smethodna_map_mdft", "smethodna"); - work.add(name, desc, &Vars::smethodna, paramsa, dfta_map_full); + work.add(name, desc, &Vars::smethodna, params["a"], dft_map_full["a"]); addf("smethodnb_map_mdft", "smethodnb"); - work.add(name, desc, &Vars::smethodnb, paramsb, dftb_map_full); + 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"); - work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_array_full); + work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_array_full["a"]); addf("methodnb_map_adft", "methodnb"); - work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_array_full); + work.add(name, desc, &Vars::methodnb, var(v), params["b"], dft_array_full["b"]); addf("methodna_map_mdft", "methodna"); - work.add(name, desc, &Vars::methodna, var(v), paramsa, dfta_map_full); + work.add(name, desc, &Vars::methodna, var(v), params["a"], dft_map_full["a"]); addf("methodnb_map_mdft", "methodnb"); - work.add(name, desc, &Vars::methodnb, var(v), paramsb, dftb_map_full); + 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 @@ -787,15 +786,15 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); - foreach(LLSD ae, inArray(names)) + foreach(LLSD nm, inArray(names)) { - LLSD metadata(getMetadata(ae)); - ensure_equals("name mismatch", metadata["name"], ae); - ensure_equals(metadata["desc"].asString(), funcs[ae]); + LLSD metadata(getMetadata(nm)); + ensure_equals("name mismatch", metadata["name"], nm); + ensure_equals(metadata["desc"].asString(), funcs[nm]); ensure("should not have required structure", metadata["required"].isUndefined()); ensure("should not have optional", metadata["optional"].isUndefined()); - std::string name_req(ae.asString() + "_req"); + std::string name_req(nm.asString() + "_req"); metadata = getMetadata(name_req); ensure_equals(metadata["name"].asString(), name_req); ensure_equals(metadata["desc"].asString(), funcs[name_req]); @@ -902,78 +901,71 @@ namespace tut // 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 allreqa, allreqb, leftreqa, leftreqb, rightdfta, rightdftb; - for (LLSD::Integer pi(0), pend(std::min(partial_offset, paramsa.size())); - pi < pend; ++pi) - { - allreqa[paramsa[pi].asString()] = LLSD(); - leftreqa[paramsa[pi].asString()] = LLSD(); - } - for (LLSD::Integer pi(partial_offset), pend(paramsa.size()); pi < pend; ++pi) - { - allreqa[paramsa[pi].asString()] = LLSD(); - rightdfta[paramsa[pi].asString()] = dfta_array_full[pi]; - } - for (LLSD::Integer pi(0), pend(std::min(partial_offset, paramsb.size())); - pi < pend; ++pi) + LLSD allreq, leftreq, rightdft; + foreach(LLSD::String a, ab) { - allreqb[paramsb[pi].asString()] = LLSD(); - leftreqb[paramsb[pi].asString()] = LLSD(); - } - for (LLSD::Integer pi(partial_offset), pend(paramsb.size()); pi < pend; ++pi) - { - allreqb[paramsb[pi].asString()] = LLSD(); - rightdftb[paramsb[pi].asString()] = dftb_array_full[pi]; + for (LLSD::Integer pi(0), pend(std::min(partial_offset, params[a].size())); + pi < pend; ++pi) + { + allreq [a][params[a][pi].asString()] = LLSD(); + leftreq[a][params[a][pi].asString()] = LLSD(); + } + for (LLSD::Integer pi(partial_offset), pend(params[a].size()); pi < pend; ++pi) + { + allreq [a][params[a][pi].asString()] = LLSD(); + rightdft[a][params[a][pi].asString()] = dft_array_full[a][pi]; + } } + cout << "allreq:\n" << allreq << "\nleftreq:\n" << leftreq << "\nrightdft:\n" << rightdft << std::endl; // Generate maps containing parameter names not provided by the - // dft[ab]_map_partial maps. - LLSD skipreqa(allreqa), skipreqb(allreqb); - foreach(const MapEntry& me, inMap(dfta_map_partial)) + // dft_map_partial maps. + LLSD skipreq(allreq); + foreach(LLSD::String a, ab) { - skipreqa.erase(me.first); - } - foreach(const MapEntry& me, inMap(dftb_map_partial)) - { - skipreqb.erase(me.first); + foreach(const MapEntry& me, inMap(dft_map_partial[a])) + { + skipreq[a].erase(me.first); + } } + cout << "skipreq:\n" << skipreq << std::endl; LLSD groups(LLSDArray // array of groups (LLSDArray // group (LLSDArray("freena_map_allreq")("smethodna_map_allreq")("methodna_map_allreq")) - (LLSDArray(allreqa)(LLSD()))) // required, optional + (LLSDArray(allreq["a"])(LLSD()))) // required, optional (LLSDArray // group (LLSDArray("freenb_map_allreq")("smethodnb_map_allreq")("methodnb_map_allreq")) - (LLSDArray(allreqb)(LLSD()))) // required, optional + (LLSDArray(allreq["b"])(LLSD()))) // required, optional (LLSDArray // group (LLSDArray("freena_map_leftreq")("smethodna_map_leftreq")("methodna_map_leftreq")) - (LLSDArray(leftreqa)(rightdfta))) // required, optional + (LLSDArray(leftreq["a"])(rightdft["a"]))) // required, optional (LLSDArray // group (LLSDArray("freenb_map_leftreq")("smethodnb_map_leftreq")("methodnb_map_leftreq")) - (LLSDArray(leftreqb)(rightdftb))) // required, optional + (LLSDArray(leftreq["b"])(rightdft["b"]))) // required, optional (LLSDArray // group (LLSDArray("freena_map_skipreq")("smethodna_map_skipreq")("methodna_map_skipreq")) - (LLSDArray(skipreqa)(dfta_map_partial))) // required, optional + (LLSDArray(skipreq["a"])(dft_map_partial["a"]))) // required, optional (LLSDArray // group (LLSDArray("freenb_map_skipreq")("smethodnb_map_skipreq")("methodnb_map_skipreq")) - (LLSDArray(skipreqb)(dftb_map_partial))) // required, optional + (LLSDArray(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. (LLSDArray // group (LLSDArray("freena_map_mdft")("smethodna_map_mdft")("methodna_map_mdft")) - (LLSDArray(LLSD::emptyMap())(dfta_map_full))) // required, optional + (LLSDArray(LLSD::emptyMap())(dft_map_full["a"]))) // required, optional (LLSDArray // group (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) - (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional + (LLSDArray(LLSD::emptyMap())(dft_map_full["b"])))); // required, optional foreach(LLSD grp, inArray(groups)) { @@ -988,9 +980,11 @@ namespace tut { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), funcs[nm]); - ensure_equals("required mismatch", metadata["required"], required); - ensure_equals("optional mismatch", metadata["optional"], optional); + ensure_equals(nm.asString(), metadata["desc"].asString(), funcs[nm]); + ensure_equals(STRINGIZE(nm << " required mismatch"), + metadata["required"], required); + ensure_equals(STRINGIZE(nm << " optional mismatch"), + metadata["optional"], optional); } } } @@ -1172,42 +1166,43 @@ namespace tut { binary.push_back(h); } - LLSD argsa(LLSDArray(true)(17)(3.14)(123.456)("char*")); - LLSD argsb(LLSDArray("string")(LLUUID("01234567-89ab-cdef-0123-456789abcdef")) - (LLDate("2011-02-03T15:07:00Z"))(LLURI("http://secondlife.com"))(binary)); - LLSD argsaplus(argsa), argsbplus(argsb); - argsaplus.append("bogus"); - argsbplus.append("bogus"); - LLSD expecta, expectb; - for (LLSD::Integer i(0), iend(paramsa.size()); i < iend; ++i) - { - expecta[paramsa[i].asString()] = argsa[i]; - } - for (LLSD::Integer i(0), iend(paramsb.size()); i < iend; ++i) + LLSD args(LLSDMap("a", LLSDArray(true)(17)(3.14)(123.456)("char*")) + ("b", LLSDArray("string") + (LLUUID("01234567-89ab-cdef-0123-456789abcdef")) + (LLDate("2011-02-03T15:07:00Z")) + (LLURI("http://secondlife.com")) + (binary))); + LLSD argsplus(args); + argsplus["a"].append("bogus"); + argsplus["b"].append("bogus"); + LLSD expect; + foreach(LLSD::String a, ab) { - expectb[paramsb[i].asString()] = argsb[i]; + for (LLSD::Integer i(0), iend(params[a].size()); i < iend; ++i) + { + expect[a][params[a][i].asString()] = args[a][i]; + } } - // Adjust expecta["cp"] for special Vars::cp treatment. - expecta["cp"] = std::string("'") + expecta["cp"].asString() + "'"; - cout << "expecta: " << expecta << "\nexpectb: " << expectb << '\n'; + // Adjust expect["a"]["cp"] for special Vars::cp treatment. + expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; + cout << "expect: " << expect << '\n'; foreach(const FunctionsTriple& tr, array_funcs(v)) { *tr.vars = Vars(); - work(tr.name1, argsa); - ensure_llsd(STRINGIZE(tr.name1 << ": expecta mismatch"), - tr.vars->inspect(), expecta, 7); + work(tr.name1, args["a"]); + ensure_llsd(STRINGIZE(tr.name1 << ": expect[\"a\"] mismatch"), + tr.vars->inspect(), expect["a"], 7); // 7 bits ~= 2 decimal digits *tr.vars = Vars(); - work(tr.name2, argsb); - ensure_llsd(STRINGIZE(tr.name2 << ": expectb mismatch"), - tr.vars->inspect(), expectb, 7); + work(tr.name2, args["b"]); + ensure_llsd(STRINGIZE(tr.name2 << ": expect[\"b\"] mismatch"), + tr.vars->inspect(), expect["b"], 7); - // TODO: argsaplus, argsbplus, check LL_WARNS output? + // TODO: argsplus, check LL_WARNS output? } } // TODO: - // - refactor params-related data as {'a':arraya, 'b':arrayb} // - function to build a map from keys array, values array (or // vice-versa?) } // namespace tut -- cgit v1.2.3 From 934e8c39761bd4f4b7c04eb8b29e0a305182e4ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 5 Feb 2011 22:49:53 -0500 Subject: Make array-funcs success test exercise args-array-too-long case too. Streamline a bit more redundancy from the code in that test. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 32ba0cd16f..c31f037d7f 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1186,19 +1186,34 @@ namespace tut // Adjust expect["a"]["cp"] for special Vars::cp treatment. expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; cout << "expect: " << expect << '\n'; - foreach(const FunctionsTriple& tr, array_funcs(v)) - { - *tr.vars = Vars(); - work(tr.name1, args["a"]); - ensure_llsd(STRINGIZE(tr.name1 << ": expect[\"a\"] mismatch"), - tr.vars->inspect(), expect["a"], 7); // 7 bits ~= 2 decimal digits - *tr.vars = Vars(); - work(tr.name2, args["b"]); - ensure_llsd(STRINGIZE(tr.name2 << ": expect[\"b\"] mismatch"), - tr.vars->inspect(), expect["b"], 7); + // Use substantially the same logic for args and argsplus + LLSD argsarrays(LLSDArray(args)(argsplus)); + // So i==0 selects 'args', i==1 selects argsplus + for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i) + { + foreach(const FunctionsTriple& tr, array_funcs(v)) + { + // Get tr.name1 and tr.name2 into a map keyed by ["a"] and ["b"] + LLSD funcs(LLSDMap("a", tr.name1)("b", tr.name2)); - // TODO: argsplus, check LL_WARNS output? + // So now we can call tr.name1 (as funcs["a"]) with the "a" + // params, etc. + foreach(LLSD::String a, ab) + { + // Reset the Vars instance before each call + *tr.vars = Vars(); + work(funcs[a], argsarrays[i][a]); + ensure_llsd(STRINGIZE(funcs[a].asString() << + ": expect[\"" << a << "\"] mismatch"), + tr.vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits + + // TODO: in the i==1 or argsplus case, intercept LL_WARNS + // output? Even without that, using argsplus verifies that + // passing too many args isn't fatal; it works -- but + // would be nice to notice the warning too. + } + } } } -- cgit v1.2.3 From e51ccdac0e3595a476dbc2d8580a3fb1780cdb4c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 5 Feb 2011 23:31:11 -0500 Subject: Introduce zipmap() function and use it in place of frequent loops. One operation we often use is to take an LLSD array of param names, a corresponding LLSD array of values, and create from them a name=value LLSD map. Instead of doing that "by hand" every time, use a function. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 79 ++++++++++++++----------- 1 file changed, 44 insertions(+), 35 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index c31f037d7f..30d3c42554 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -322,14 +322,12 @@ namespace tut LLSD required; // Parameter names for freena(), freenb() LLSD params; - // Full defaults arrays for params for freena(), freenb() - LLSD dft_array_full; + // Full, partial defaults arrays for params for freena(), freenb() + LLSD dft_array_full, dft_array_partial; // Start index of partial defaults arrays const LLSD::Integer partial_offset; - // Full defaults maps for params for freena(), freenb() - LLSD dft_map_full; - // Partial defaults maps for params for freena(), freenb() - LLSD dft_map_partial; + // 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 ab; @@ -447,20 +445,20 @@ namespace tut (binary)); cout << "dft_array_full:\n" << dft_array_full << std::endl; // Partial defaults arrays. - LLSD dft_array_partial( - LLSDMap("a", llsd_copy_array(dft_array_full["a"].beginArray() + partial_offset, - dft_array_full["a"].endArray())) - ("b", llsd_copy_array(dft_array_full["b"].beginArray() + partial_offset, - dft_array_full["b"].endArray()))); + foreach(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()); + } cout << "dft_array_partial:\n" << dft_array_partial << std::endl; - // Generate full defaults maps by zipping (params, dft_array_full). foreach(LLSD::String a, ab) { - for (LLSD::Integer ix = 0, ixend = params[a].size(); ix < ixend; ++ix) - { - dft_map_full[a][params[a][ix].asString()] = dft_array_full[a][ix]; - } + // 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 @@ -638,6 +636,20 @@ namespace tut 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 @@ -904,17 +916,21 @@ namespace tut LLSD allreq, leftreq, rightdft; foreach(LLSD::String a, ab) { - for (LLSD::Integer pi(0), pend(std::min(partial_offset, params[a].size())); - pi < pend; ++pi) - { - allreq [a][params[a][pi].asString()] = LLSD(); - leftreq[a][params[a][pi].asString()] = LLSD(); - } - for (LLSD::Integer pi(partial_offset), pend(params[a].size()); pi < pend; ++pi) - { - allreq [a][params[a][pi].asString()] = LLSD(); - rightdft[a][params[a][pi].asString()] = dft_array_full[a][pi]; - } + // 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]); } cout << "allreq:\n" << allreq << "\nleftreq:\n" << leftreq << "\nrightdft:\n" << rightdft << std::endl; @@ -1178,10 +1194,7 @@ namespace tut LLSD expect; foreach(LLSD::String a, ab) { - for (LLSD::Integer i(0), iend(params[a].size()); i < iend; ++i) - { - expect[a][params[a][i].asString()] = args[a][i]; - } + expect[a] = zipmap(params[a], args[a]); } // Adjust expect["a"]["cp"] for special Vars::cp treatment. expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; @@ -1216,8 +1229,4 @@ namespace tut } } } - - // TODO: - // - function to build a map from keys array, values array (or - // vice-versa?) } // namespace tut -- cgit v1.2.3 From f4f3791a5ff1cc76b9d1054c269e69d44cdaf8d6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 6 Feb 2011 11:57:19 -0500 Subject: Add test verifying passing LLSD() to const char* parameter. LLSDParam is coded to pass NULL for an isUndefined() LLSD value, so event-based caller can choose whether to pass NULL, "" or whatever string value to such a parameter. Ensure this behavior. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 30d3c42554..bdd8c16cec 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1229,4 +1229,18 @@ namespace tut } } } + + 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'"); + } } // namespace tut -- cgit v1.2.3 From 1a1563bb154102c09a172235081d8db62e4fbec9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 6 Feb 2011 21:32:25 -0500 Subject: Untested support for passing array to map-registered function. An array-registered function has no param names, so you can only pass an array: a map would be meaningless. Initial implementation of map-registered functions assumed that since you CAN pass a map, you MUST pass a map. But in fact it's meaningful to pass an array as well -- for whatever reason -- and easy to implement, so there you are. Tests to follow. --- indra/llcommon/lleventdispatcher.cpp | 72 +++++++++++++++++-------- indra/llcommon/tests/lleventdispatcher_test.cpp | 6 ++- 2 files changed, 54 insertions(+), 24 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 52660105c4..5b6d4efbe9 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -269,9 +269,10 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { - if (! (argsmap.isUndefined() || argsmap.isMap())) + if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map, not " << argsmap << LL_ENDL; + LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not " + << argsmap << LL_ENDL; } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -295,30 +296,57 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // holes. (Avoid std::vector which is known to be odd -- can we // iterate?) FilledVector filled(args.size()); - // Walk the map. - for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); - mi != mend; ++mi) + + if (argsmap.isArray()) { - // mi->first is a parameter-name string, with mi->second its - // value. Look up the name's position index in _indexes. - IndexMap::const_iterator ixit(_indexes.find(mi->first)); - if (ixit == _indexes.end()) + // Fill args from array. If there are too many args in passed array, + // ignore the rest. + LLSD::Integer size(argsmap.size()); + if (size > args.size()) + { + // We don't just use std::min() because we want to sneak in this + // warning if caller passes too many args. + LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size() + << " params, ignoring last " << (size - args.size()) + << " of passed " << size << ": " << argsmap << LL_ENDL; + size = args.size(); + } + for (LLSD::Integer i(0); i < size; ++i) + { + // Copy the actual argument from argsmap + args[i] = argsmap[i]; + // Note that it's been filled + filled[i] = 1; + } + } + else + { + // argsmap is in fact a map. Walk the map. + for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); + mi != mend; ++mi) { - // Allow for a map containing more params than were passed in - // our names array. Caller typically receives a map containing - // the function name, cruft such as reqid, etc. Ignore keys - // not defined in _indexes. - LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " - << mi->first << "=" << mi->second << LL_ENDL; - continue; + // mi->first is a parameter-name string, with mi->second its + // value. Look up the name's position index in _indexes. + IndexMap::const_iterator ixit(_indexes.find(mi->first)); + if (ixit == _indexes.end()) + { + // Allow for a map containing more params than were passed in + // our names array. Caller typically receives a map containing + // the function name, cruft such as reqid, etc. Ignore keys + // not defined in _indexes. + LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " + << mi->first << "=" << mi->second << LL_ENDL; + continue; + } + LLSD::Integer pos = ixit->second; + // Store the value at that position in the args array. + args[pos] = mi->second; + // Don't forget to record the fact that we've filled this + // position. + filled[pos] = 1; } - LLSD::Integer pos = ixit->second; - // Store the value at that position in the args array. - args[pos] = mi->second; - // Don't forget to record the fact that we've filled this - // position. - filled[pos] = 1; } + // Fill any remaining holes from _defaults. LLSD unfilled(LLSD::emptyArray()); for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i) diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index bdd8c16cec..c599672bc5 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -682,7 +682,7 @@ namespace tut // (scalar | map | array (too short | too long | just right)) // [trap LL_WARNS for too-long case?] // - (Free function | non-static method), arbitrary args, map style with - // (scalar | array | map (all | too many | holes (with | without) defaults)) + // (scalar | (array | map) (all | too many | holes (with | without) defaults)) // - const char* param gets ("" | NULL) // Query cases: @@ -1108,7 +1108,9 @@ namespace tut std::string map_exc("needs a map"); call_exc("free0_map", 17, map_exc); - call_exc("free0_map", LLSDArray("a")("b"), map_exc); + // Passing an array to a map-style function works now! No longer an + // error case! +// call_exc("free0_map", LLSDArray("a")("b"), map_exc); } struct FunctionsTriple -- cgit v1.2.3 From a4d0a29c4942f9a4ba423358805e2dfe042144a1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 7 Feb 2011 11:53:58 -0500 Subject: For test purposes, capture at registration each function's Vars*. We'd introduced FunctionsTriple to associate a pair of registered function names with the Vars* on which those functions should operate. But with more different tests coming up, it became clear that restating the Vars* every time a given function name appeared in any such context was redundant. Instead, extended addf() to accept and store the relevant Vars* for each registered function, be it the global Vars for the free functions and static methods or the stack Vars for the non-static methods. Added varsfor() function to retrieve and validate the Vars* for a given function name. Eliminated array_funcs() function, restating aggregates of names to test as LLSD collections. Where before these were coerced into a separate LLSD map with ["a"] and ["b"] keys, that map can now be part of the original structure. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 228 ++++++++++++------------ 1 file changed, 111 insertions(+), 117 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index c599672bc5..157160ae3e 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -316,8 +316,12 @@ namespace tut Dispatcher work; Vars v; std::string name, desc; - typedef std::map FuncMap; - FuncMap funcs; + // Capture our own copy of all registered functions' descriptions + typedef std::map DescMap; + DescMap descs; + // Capture the Vars instance on which we expect each function to operate + typedef std::map VarsMap; + VarsMap funcvars; // Required structure for Callables with requirements LLSD required; // Parameter names for freena(), freenb() @@ -371,47 +375,47 @@ namespace tut /*------------------------- Callables --------------------------*/ // Arbitrary Callable with/out required params - addf("free1", "free1"); + addf("free1", "free1", &g); work.add(name, desc, free1); - addf("free1_req", "free1"); + addf("free1_req", "free1", &g); work.add(name, desc, free1, required); // Subclass non-const method with/out required params - addf("Dmethod1", "method1"); + addf("Dmethod1", "method1", NULL); work.add(name, desc, &Dispatcher::method1); - addf("Dmethod1_req", "method1"); + addf("Dmethod1_req", "method1", NULL); work.add(name, desc, &Dispatcher::method1, required); // Subclass const method with/out required params - addf("Dcmethod1", "cmethod1"); + addf("Dcmethod1", "cmethod1", NULL); work.add(name, desc, &Dispatcher::cmethod1); - addf("Dcmethod1_req", "cmethod1"); + addf("Dcmethod1_req", "cmethod1", NULL); work.add(name, desc, &Dispatcher::cmethod1, required); // Non-subclass method with/out required params - addf("method1", "method1"); + addf("method1", "method1", &v); work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1)); - addf("method1_req", "method1"); + addf("method1_req", "method1", &v); work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required); /*--------------- Arbitrary params, array style ----------------*/ // (Free function | static method) with (no | arbitrary) params, array style - addf("free0_array", "free0"); + addf("free0_array", "free0", &g); work.add(name, desc, free0); - addf("freena_array", "freena"); + addf("freena_array", "freena", &g); work.add(name, desc, freena); - addf("freenb_array", "freenb"); + addf("freenb_array", "freenb", &g); work.add(name, desc, freenb); - addf("smethod0_array", "smethod0"); + addf("smethod0_array", "smethod0", &g); work.add(name, desc, &Vars::smethod0); - addf("smethodna_array", "smethodna"); + addf("smethodna_array", "smethodna", &g); work.add(name, desc, &Vars::smethodna); - addf("smethodnb_array", "smethodnb"); + addf("smethodnb_array", "smethodnb", &g); work.add(name, desc, &Vars::smethodnb); // Non-static method with (no | arbitrary) params, array style - addf("method0_array", "method0"); + addf("method0_array", "method0", &v); work.add(name, desc, &Vars::method0, boost::lambda::var(v)); - addf("methodna_array", "methodna"); + addf("methodna_array", "methodna", &v); work.add(name, desc, &Vars::methodna, boost::lambda::var(v)); - addf("methodnb_array", "methodnb"); + addf("methodnb_array", "methodnb", &v); work.add(name, desc, &Vars::methodnb, boost::lambda::var(v)); /*---------------- Arbitrary params, map style -----------------*/ @@ -472,25 +476,25 @@ namespace tut // (Free function | static method) with (no | arbitrary) params, // map style, no (empty array) defaults - addf("free0_map", "free0"); + addf("free0_map", "free0", &g); work.add(name, desc, free0, LLSD::emptyArray()); - addf("smethod0_map", "smethod0"); + addf("smethod0_map", "smethod0", &g); work.add(name, desc, &Vars::smethod0, LLSD::emptyArray()); - addf("freena_map_allreq", "freena"); + addf("freena_map_allreq", "freena", &g); work.add(name, desc, freena, params["a"]); - addf("freenb_map_allreq", "freenb"); + addf("freenb_map_allreq", "freenb", &g); work.add(name, desc, freenb, params["b"]); - addf("smethodna_map_allreq", "smethodna"); + addf("smethodna_map_allreq", "smethodna", &g); work.add(name, desc, &Vars::smethodna, params["a"]); - addf("smethodnb_map_allreq", "smethodnb"); + 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"); + addf("method0_map", "method0", &v); work.add(name, desc, &Vars::method0, var(v), LLSD::emptyArray()); - addf("methodna_map_allreq", "methodna"); + addf("methodna_map_allreq", "methodna", &v); work.add(name, desc, &Vars::methodna, var(v), params["a"]); - addf("methodnb_map_allreq", "methodnb"); + 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 @@ -500,60 +504,60 @@ namespace tut // (Free function | static method) with arbitrary params, map // style, partial (array | map) defaults - addf("freena_map_leftreq", "freena"); + addf("freena_map_leftreq", "freena", &g); work.add(name, desc, freena, params["a"], dft_array_partial["a"]); - addf("freenb_map_leftreq", "freenb"); + addf("freenb_map_leftreq", "freenb", &g); work.add(name, desc, freenb, params["b"], dft_array_partial["b"]); - addf("smethodna_map_leftreq", "smethodna"); + addf("smethodna_map_leftreq", "smethodna", &g); work.add(name, desc, &Vars::smethodna, params["a"], dft_array_partial["a"]); - addf("smethodnb_map_leftreq", "smethodnb"); + addf("smethodnb_map_leftreq", "smethodnb", &g); work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_partial["b"]); - addf("freena_map_skipreq", "freena"); + addf("freena_map_skipreq", "freena", &g); work.add(name, desc, freena, params["a"], dft_map_partial["a"]); - addf("freenb_map_skipreq", "freenb"); + addf("freenb_map_skipreq", "freenb", &g); work.add(name, desc, freenb, params["b"], dft_map_partial["b"]); - addf("smethodna_map_skipreq", "smethodna"); + addf("smethodna_map_skipreq", "smethodna", &g); work.add(name, desc, &Vars::smethodna, params["a"], dft_map_partial["a"]); - addf("smethodnb_map_skipreq", "smethodnb"); + 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"); + 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"); + 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"); + 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"); + 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"); + addf("freena_map_adft", "freena", &g); work.add(name, desc, freena, params["a"], dft_array_full["a"]); - addf("freenb_map_adft", "freenb"); + addf("freenb_map_adft", "freenb", &g); work.add(name, desc, freenb, params["b"], dft_array_full["b"]); - addf("smethodna_map_adft", "smethodna"); + addf("smethodna_map_adft", "smethodna", &g); work.add(name, desc, &Vars::smethodna, params["a"], dft_array_full["a"]); - addf("smethodnb_map_adft", "smethodnb"); + addf("smethodnb_map_adft", "smethodnb", &g); work.add(name, desc, &Vars::smethodnb, params["b"], dft_array_full["b"]); - addf("freena_map_mdft", "freena"); + addf("freena_map_mdft", "freena", &g); work.add(name, desc, freena, params["a"], dft_map_full["a"]); - addf("freenb_map_mdft", "freenb"); + addf("freenb_map_mdft", "freenb", &g); work.add(name, desc, freenb, params["b"], dft_map_full["b"]); - addf("smethodna_map_mdft", "smethodna"); + addf("smethodna_map_mdft", "smethodna", &g); work.add(name, desc, &Vars::smethodna, params["a"], dft_map_full["a"]); - addf("smethodnb_map_mdft", "smethodnb"); + 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"); + 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"); + 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"); + 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"); + 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 @@ -561,21 +565,23 @@ namespace tut // tests rather than as test setup. } - void addf(const std::string& n, const std::string& d) + void addf(const std::string& n, const std::string& d, Vars* v) { - // This method is to capture in our own FuncMap the name and + // This method is to capture in our own DescMap the name and // description of every registered function, for metadata query // testing. - funcs[n] = d; + 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_funcs() + void verify_descs() { - // Copy funcs to a temp map of same type. - FuncMap forgotten(funcs.begin(), funcs.end()); + // Copy descs to a temp map of same type. + DescMap forgotten(descs.begin(), descs.end()); // LLEventDispatcher intentionally provides only const_iterator: // since dereferencing that iterator generates values on the fly, // it's meaningless to have a modifiable iterator. But since our @@ -583,7 +589,7 @@ namespace tut // use non-const iterators. Persuade it to use the const_iterator. foreach(LLEventDispatcher::NameDesc nd, const_cast(work)) { - FuncMap::iterator found = forgotten.find(nd.first); + DescMap::iterator found = forgotten.find(nd.first); ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first << "' we didn't enter"), found != forgotten.end()); @@ -599,7 +605,7 @@ namespace tut std::ostringstream out; out << "LLEventDispatcher failed to report"; const char* delim = ": "; - foreach(const FuncMap::value_type& fme, forgotten) + foreach(const DescMap::value_type& fme, forgotten) { out << delim << fme.first; delim = ", "; @@ -608,6 +614,14 @@ namespace tut } } + 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; + } + void ensure_has(const std::string& outer, const std::string& inner) { ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(), @@ -772,7 +786,7 @@ namespace tut void object::test<5>() { set_test_name("query all"); - verify_funcs(); + verify_descs(); } template<> template<> @@ -781,9 +795,9 @@ namespace tut 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 'funcs' too... - funcs.erase("free1"); - verify_funcs(); + // Of course, remove that from 'descs' too... + descs.erase("free1"); + verify_descs(); } template<> template<> @@ -802,14 +816,14 @@ namespace tut { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), funcs[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(), funcs[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()); } @@ -838,7 +852,7 @@ namespace tut { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), funcs[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()); @@ -857,7 +871,7 @@ namespace tut { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), funcs[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()); @@ -996,7 +1010,7 @@ namespace tut { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(nm.asString(), metadata["desc"].asString(), funcs[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"), @@ -1113,53 +1127,35 @@ namespace tut // call_exc("free0_map", LLSDArray("a")("b"), map_exc); } - struct FunctionsTriple - { - std::string name1, name2; - Vars* vars; - }; - template<> template<> void object::test<18>() { set_test_name("call no-args functions"); - FunctionsTriple tests[] = - { - { "free0_array", "free0_map", &g }, - { "smethod0_array", "smethod0_map", &g }, - { "method0_array", "method0_map", &v } - }; - foreach(const FunctionsTriple& tr, tests) + LLSD names(LLSDArray + ("free0_array")("free0_map") + ("smethod0_array")("smethod0_map") + ("method0_array")("method0_map")); + foreach(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 method. But since we're // calling these things several different times in the same // test method, manually reset the Vars between each. - *tr.vars = Vars(); - ensure_equals(tr.vars->i, 0); - // array-style call with empty array (or LLSD(), should be equivalent) - work(tr.name1, LLSD()); - ensure_equals(tr.vars->i, 17); - - *tr.vars = Vars(); - // map-style call with empty map (or LLSD(), should be equivalent) - work(tr.name2, LLSD()); - ensure_equals(tr.vars->i, 17); + *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 function to return this data because we use it in a couple - // different tests. - std::vector array_funcs(Vars& v) - { - FunctionsTriple tests[] = - { - { "freena_array", "freenb_array", &g }, - { "smethodna_array", "smethodnb_array", &g }, - { "methodna_array", "methodnb_array", &v } - }; - return std::vector(boost::begin(tests), boost::end(tests)); - } + // Break out this data because we use it in a couple different tests. + LLSD array_funcs(LLSDArray + (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>() @@ -1168,10 +1164,12 @@ namespace tut // Could have two different too-short arrays, one for *na and one for // *nb, but since they both take 5 params... LLSD tooshort(LLSDArray("this")("array")("too")("short")); - foreach(const FunctionsTriple& tr, array_funcs(v)) + foreach(const LLSD& funcsab, inArray(array_funcs)) { - call_exc(tr.name1, tooshort, "requires more arguments"); - call_exc(tr.name2, tooshort, "requires more arguments"); + foreach(const llsd::MapEntry& e, inMap(funcsab)) + { + call_exc(e.second, tooshort, "requires more arguments"); + } } } @@ -1207,21 +1205,17 @@ namespace tut // So i==0 selects 'args', i==1 selects argsplus for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i) { - foreach(const FunctionsTriple& tr, array_funcs(v)) + foreach(const LLSD& funcsab, inArray(array_funcs)) { - // Get tr.name1 and tr.name2 into a map keyed by ["a"] and ["b"] - LLSD funcs(LLSDMap("a", tr.name1)("b", tr.name2)); - - // So now we can call tr.name1 (as funcs["a"]) with the "a" - // params, etc. foreach(LLSD::String a, ab) { // Reset the Vars instance before each call - *tr.vars = Vars(); - work(funcs[a], argsarrays[i][a]); - ensure_llsd(STRINGIZE(funcs[a].asString() << + Vars* vars(varsfor(funcsab[a])); + *vars = Vars(); + work(funcsab[a], argsarrays[i][a]); + ensure_llsd(STRINGIZE(funcsab[a].asString() << ": expect[\"" << a << "\"] mismatch"), - tr.vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits + vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits // TODO: in the i==1 or argsplus case, intercept LL_WARNS // output? Even without that, using argsplus verifies that -- cgit v1.2.3 From dad558250f645ef9d2eea88e0ad18a54ad0402ee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 9 Feb 2011 23:28:10 -0500 Subject: Add test to call map-style functions with full map/array params. Test also passes overlong arrays and maps with extraneous keys; in all cases we expect the same set of values to be passed to the registered functions. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 88 ++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 157160ae3e..263c9b171f 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -214,7 +214,7 @@ struct Vars std::ostringstream vbin; foreach(U8 byte, bin) { - vbin << std::hex << std::setfill('0') << std::setw(2) << byte; + vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte); } cout << "methodnb(" << "'" << s << "'" @@ -692,11 +692,13 @@ namespace tut // - (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 - // (scalar | map | array (too short | too long | just right)) + // array (too short | too long | just right) // [trap LL_WARNS for too-long case?] // - (Free function | non-static method), arbitrary args, map style with - // (scalar | (array | map) (all | too many | holes (with | without) defaults)) + // (array | map) (all | too many | holes (with | without) defaults) // - const char* param gets ("" | NULL) // Query cases: @@ -1239,4 +1241,84 @@ namespace tut 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", LLSDArray(false)(255)(98.6)(1024.5)("pointer")) + ("b", LLSDArray("object")(LLUUID::generateNewID())(LLDate::now())(LLURI("http://wiki.lindenlab.com/wiki"))(LLSD::Binary(boost::begin(binary), boost::end(binary))))); + LLSD array_overfull(array_full); + foreach(LLSD::String a, ab) + { + array_overfull[a].append("bogus"); + } + cout << "array_full: " << array_full << "\narray_overfull: " << array_overfull << std::endl; + // 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; + foreach(LLSD::String a, ab) + { + map_full[a] = zipmap(params[a], array_full[a]); + map_overfull[a] = map_full[a]; + map_overfull[a]["extra"] = "ignore"; + } + cout << "map_full: " << map_full << "\nmap_overfull: " << map_overfull << std::endl; + 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"); + cout << "expect: " << expect << std::endl; + // 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", LLSDArray + ("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", LLSDArray + ("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(LLSDArray(array_full)(array_overfull)(map_full)(map_overfull)); + foreach(const LLSD& args, inArray(argssets)) + { + foreach(LLSD::String a, ab) + { + foreach(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? + } + } + } + } } // namespace tut -- cgit v1.2.3 From 4ef02bc1b6cf5e044d2cf57725eac1a4ccd7580d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 18 Feb 2011 10:56:26 -0500 Subject: Introduce and use new sendReply() function for LLEventAPI methods. Each LLEventAPI method that generates a reply needs to extract the name of the reply LLEventPump from the request, typically from a ["reply"] key, copy the ["reqid"] value from request to reply, locate the reply LLEventPump and send the enriched reply object. Encapsulate in sendReply() function before we proliferate doing all that by hand too many more times. --- indra/llcommon/llevents.cpp | 13 +++++++++++++ indra/llcommon/llevents.h | 14 ++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 97e2bdeb57..ff03506e84 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -588,3 +588,16 @@ void LLReqID::stamp(LLSD& response) const } response["reqid"] = mReqid; } + +bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey) +{ + // Copy 'reply' to modify it. + LLSD newreply(reply); + // Get the ["reqid"] element from request + LLReqID reqID(request); + // and copy it to 'newreply'. + reqID.stamp(newreply); + // Send reply on LLEventPump named in request[replyKey]. Don't forget to + // send the modified 'newreply' instead of the original 'reply'. + return LLEventPumps::instance().obtain(request[replyKey]).post(newreply); +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 2491cf1371..65b0fef354 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -691,6 +691,20 @@ private: LLSD mReqid; }; +/** + * Conventionally send a reply to a request event. + * + * @a reply is the LLSD reply event to send + * @a request is the corresponding LLSD request event + * @a replyKey is the key in the @a request event, conventionally ["reply"], + * whose value is the name of the LLEventPump on which to send the reply. + * + * Before sending the reply event, sendReply() copies the ["reqid"] item from + * the request to the reply. + */ +LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request, + const std::string& replyKey="reply"); + /** * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We * provide virtual @c accept_xxx() methods, customization points allowing a -- cgit v1.2.3 From b9a9b0017dd4714cbca4b0ddb69ed5e6b2f09528 Mon Sep 17 00:00:00 2001 From: brad kittenbrink Date: Wed, 9 Mar 2011 17:01:08 -0800 Subject: Fix for "doubleton" error using LLInstanceTracker across shared library boundaries. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llinstancetracker.cpp | 17 ++++++++++++++--- indra/llcommon/llinstancetracker.h | 30 ++++++++++++++++++------------ 3 files changed, 33 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index dc9f93df3b..4f7e2f4c0d 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -62,6 +62,7 @@ set(llcommon_SOURCE_FILES llformat.cpp llframetimer.cpp llheartbeat.cpp + llinstancetracker.cpp llliveappconfig.cpp lllivefile.cpp lllog.cpp diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp index 89bc6cca39..f576204511 100644 --- a/indra/llcommon/llinstancetracker.cpp +++ b/indra/llcommon/llinstancetracker.cpp @@ -32,6 +32,17 @@ // external library headers // other Linden headers -// llinstancetracker.h is presently header-only. This file exists only because our CMake -// test macro ADD_BUILD_TEST requires it. -int dummy = 0; +//static +void * & LLInstanceTrackerBase::getInstances(std::type_info const & info) +{ + static std::map instances; + + std::string k = info.name(); + if(instances.find(k) == instances.end()) + { + instances[k] = NULL; + } + + return instances[k]; +} + diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 4945461d62..b971b2f914 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -37,14 +37,21 @@ #include #include +class LL_COMMON_API LLInstanceTrackerBase : public boost::noncopyable +{ + protected: + static void * & getInstances(std::type_info const & info); +}; + /// This mix-in class adds support for tracking all instances of the specified class parameter T /// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup /// If KEY is not provided, then instances are stored in a simple set /// @NOTE: see explicit specialization below for default KEY==T* case template -class LLInstanceTracker : boost::noncopyable +class LLInstanceTracker : public LLInstanceTrackerBase { typedef typename std::map InstanceMap; + typedef LLInstanceTracker MyT; typedef boost::function KeyGetter; typedef boost::function InstancePtrGetter; public: @@ -99,25 +106,26 @@ private: static InstanceMap& getMap_() { - if (! sInstances) + void * & instances = getInstances(typeid(MyT)); + if (! instances) { - sInstances = new InstanceMap; + instances = new InstanceMap; } - return *sInstances; + return * static_cast(instances); } private: KEY mKey; - static InstanceMap* sInstances; }; /// explicit specialization for default case where KEY is T* /// use a simple std::set template -class LLInstanceTracker +class LLInstanceTracker : public LLInstanceTrackerBase { typedef typename std::set InstanceSet; + typedef LLInstanceTracker MyT; public: /// Dereferencing key_iter gives you a T* (since T* is the key) typedef typename InstanceSet::iterator key_iter; @@ -172,19 +180,17 @@ protected: static InstanceSet& getSet_() { - if (! sInstances) + void * & instances = getInstances(typeid(MyT)); + if (! instances) { - sInstances = new InstanceSet; + instances = new InstanceSet; } - return *sInstances; + return * static_cast(instances); } - static InstanceSet* sInstances; static S32 sIterationNestDepth; }; -template typename LLInstanceTracker::InstanceMap* LLInstanceTracker::sInstances = NULL; -template typename LLInstanceTracker::InstanceSet* LLInstanceTracker::sInstances = NULL; template S32 LLInstanceTracker::sIterationNestDepth = 0; #endif -- cgit v1.2.3