Merge branch 'DRTVWR-587-maint-V' of https://github.com/secondlife/viewer

master
Ansariel 2023-07-18 19:14:19 +02:00
commit 05d35a062e
26 changed files with 2979 additions and 592 deletions

View File

@ -16,7 +16,9 @@ include(Tracy)
set(llcommon_SOURCE_FILES
apply.cpp
indra_constants.cpp
lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@ -114,11 +116,15 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
always_return.h
apply.h
chrono.h
classic_callback.h
ctype_workaround.h
fix_macros.h
function_types.h
indra_constants.h
lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@ -326,9 +332,11 @@ if (LL_TESTS)
#set(TEST_DEBUG on)
set(test_libs llcommon)
LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")

View File

@ -0,0 +1,124 @@
/**
* @file always_return.h
* @author Nat Goodspeed
* @date 2023-01-20
* @brief Call specified callable with arbitrary arguments, but always return
* specified type.
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_ALWAYS_RETURN_H)
#define LL_ALWAYS_RETURN_H
#include <type_traits> // std::enable_if, std::is_convertible
namespace LL
{
#if __cpp_lib_is_invocable >= 201703L // C++17
template <typename CALLABLE, typename... ARGS>
using invoke_result = std::invoke_result<CALLABLE, ARGS...>;
#else // C++14
template <typename CALLABLE, typename... ARGS>
using invoke_result = std::result_of<CALLABLE(ARGS...)>;
#endif // C++14
/**
* AlwaysReturn<T>()(some_function, some_args...) calls
* some_function(some_args...). It is guaranteed to return a value of type
* T, regardless of the return type of some_function(). If some_function()
* returns a type convertible to T, it will convert and return that value.
* Otherwise (notably if some_function() is void), AlwaysReturn returns
* T().
*
* When some_function() returns a type not convertible to T, if
* you want AlwaysReturn to return some T value other than
* default-constructed T(), pass that value to AlwaysReturn's constructor.
*/
template <typename DESIRED>
class AlwaysReturn
{
public:
/// pass explicit default value if other than default-constructed type
AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {}
// callable returns a type not convertible to DESIRED, return default
template <typename CALLABLE, typename... ARGS,
typename std::enable_if<
! std::is_convertible<
typename invoke_result<CALLABLE, ARGS...>::type,
DESIRED
>::value,
bool
>::type=true>
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
{
// discard whatever callable(args) returns
std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
return mDefault;
}
// callable returns a type convertible to DESIRED
template <typename CALLABLE, typename... ARGS,
typename std::enable_if<
std::is_convertible<
typename invoke_result<CALLABLE, ARGS...>::type,
DESIRED
>::value,
bool
>::type=true>
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
{
return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) };
}
private:
DESIRED mDefault;
};
/**
* always_return<T>(some_function, some_args...) calls
* some_function(some_args...). It is guaranteed to return a value of type
* T, regardless of the return type of some_function(). If some_function()
* returns a type convertible to T, it will convert and return that value.
* Otherwise (notably if some_function() is void), always_return() returns
* T().
*/
template <typename DESIRED, typename CALLABLE, typename... ARGS>
DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
{
return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable),
std::forward<ARGS>(args)...);
}
/**
* make_always_return<T>(some_function) returns a callable which, when
* called with appropriate some_function() arguments, always returns a
* value of type T, regardless of the return type of some_function(). If
* some_function() returns a type convertible to T, the returned callable
* will convert and return that value. Otherwise (notably if
* some_function() is void), the returned callable returns T().
*
* When some_function() returns a type not convertible to T, if
* you want the returned callable to return some T value other than
* default-constructed T(), pass that value to make_always_return() as its
* optional second argument.
*/
template <typename DESIRED, typename CALLABLE>
auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
{
return
[dft, callable = std::forward<CALLABLE>(callable)]
(auto&&... args)
{
return AlwaysReturn<DESIRED>(dft)(callable,
std::forward<decltype(args)>(args)...);
};
}
} // namespace LL
#endif /* ! defined(LL_ALWAYS_RETURN_H) */

29
indra/llcommon/apply.cpp Normal file
View File

@ -0,0 +1,29 @@
/**
* @file apply.cpp
* @author Nat Goodspeed
* @date 2022-12-21
* @brief Implementation for apply.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "apply.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "stringize.h"
void LL::apply_validate_size(size_t size, size_t arity)
{
if (size != arity)
{
LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
"std::vector(", size, " elements))")));
}
}

View File

@ -12,8 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
#include <functional> // std::mem_fn()
#include <tuple>
#include <type_traits> // std::is_member_pointer
namespace LL
{
@ -54,20 +57,67 @@ namespace LL
}, \
(ARGS))
#if __cplusplus >= 201703L
/*****************************************************************************
* invoke()
*****************************************************************************/
#if __cpp_lib_invoke >= 201411L
// C++17 implementation
using std::apply;
using std::invoke;
#else // no std::invoke
// Use invoke() to handle pointer-to-method:
// derived from https://stackoverflow.com/a/38288251
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...);
}
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::forward<Fn>(f)(std::forward<Args>(args)...);
}
#endif // no std::invoke
/*****************************************************************************
* apply(function, tuple); apply(function, array)
*****************************************************************************/
#if __cpp_lib_apply >= 201603L
// C++17 implementation
// We don't just say 'using std::apply;' because that template is too general:
// it also picks up the apply(function, vector) case, which we want to handle
// below.
template <typename CALLABLE, typename... ARGS>
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
{
return std::apply(std::forward<CALLABLE>(func), args);
}
#else // C++14
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
template <typename CALLABLE, typename TUPLE, std::size_t... I>
auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
template <typename CALLABLE, typename... ARGS, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// We accept const std::tuple& so a caller can construct an tuple on the
// fly. But std::get<I>(const tuple) adds a const qualifier to everything
// it extracts. Get a non-const ref to this tuple so we can extract
// without the extraneous const.
auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
// call func(unpacked args)
return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
return invoke(std::forward<CALLABLE>(func),
std::forward<ARGS>(std::get<I>(non_const_args))...);
}
template <typename CALLABLE, typename... ARGS>
@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
#endif // C++14
// per https://stackoverflow.com/a/57510428/5533635
template <typename CALLABLE, typename T, size_t SIZE>
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
/*****************************************************************************
* bind_front()
*****************************************************************************/
// To invoke a non-static member function with a tuple, you need a callable
// that binds your member function with an instance pointer or reference.
// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
// Unfortunately bind_front() only enters the standard library in C++20.
#if __cpp_lib_bind_front >= 201907L
// C++20 implementation
using std::bind_front;
#else // no std::bind_front()
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
// Don't use perfect forwarding for f or args: we must bind them for later.
return [f, pfx_args=std::make_tuple(args...)]
(auto&&... sfx_args)
{
// Use perfect forwarding for sfx_args because we use them as soon as
// we receive them.
return apply(
f,
std::tuple_cat(pfx_args,
std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
};
}
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
}
#endif // C++20 with std::bind_front()
/*****************************************************************************
* apply(function, std::vector)
*****************************************************************************/
// per https://stackoverflow.com/a/28411055/5533635
template <typename CALLABLE, typename T, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
{
return apply_impl(std::forward<CALLABLE>(func),
std::make_tuple(std::forward<T>(args[I])...),
I...);
return apply(std::forward<CALLABLE>(func),
std::make_tuple(args[I]...));
}
// this goes beyond C++17 std::apply()
// produce suitable error if apply(func, vector) is the wrong size for func()
void apply_validate_size(size_t size, size_t arity);
/// possible exception from apply() validation
struct apply_error: public LLException
{
apply_error(const std::string& what): LLException(what) {}
};
template <size_t ARITY, typename CALLABLE, typename T>
auto apply_n(CALLABLE&& func, const std::vector<T>& args)
{
apply_validate_size(args.size(), ARITY);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<ARITY>());
}
/**
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
* time how many arguments to pass. This isn't Python. (But see apply_n() to
* pass a specific number of args to a variadic function.)
*/
template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
// infer arity from the definition of func
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
assert(args.size() == arity);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<arity>());
// now that we have a compile-time arity, apply_n() works
return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
#endif // C++14
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */

View File

@ -0,0 +1,49 @@
/**
* @file function_types.h
* @author Nat Goodspeed
* @date 2023-01-20
* @brief Extend boost::function_types to examine boost::function and
* std::function
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_FUNCTION_TYPES_H)
#define LL_FUNCTION_TYPES_H
#include <boost/function.hpp>
#include <boost/function_types/function_arity.hpp>
#include <functional>
namespace LL
{
template <typename F>
struct function_arity_impl
{
static constexpr auto value = boost::function_types::function_arity<F>::value;
};
template <typename F>
struct function_arity_impl<std::function<F>>
{
static constexpr auto value = function_arity_impl<F>::value;
};
template <typename F>
struct function_arity_impl<boost::function<F>>
{
static constexpr auto value = function_arity_impl<F>::value;
};
template <typename F>
struct function_arity
{
static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value;
};
} // namespace LL
#endif /* ! defined(LL_FUNCTION_TYPES_H) */

View File

@ -0,0 +1,72 @@
/**
* @file lazyeventapi.cpp
* @author Nat Goodspeed
* @date 2022-06-17
* @brief Implementation for lazyeventapi.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "lazyeventapi.h"
// STL headers
// std headers
#include <algorithm> // std::find_if
// external library headers
// other Linden headers
#include "llevents.h"
#include "llsdutil.h"
LL::LazyEventAPIBase::LazyEventAPIBase(
const std::string& name, const std::string& desc, const std::string& field)
{
// populate embedded LazyEventAPIParams instance
mParams.name = name;
mParams.desc = desc;
mParams.field = field;
// mParams.init and mOperations are populated by subsequent add() calls.
// Our raison d'etre: register as an LLEventPumps::PumpFactory
// so obtain() will notice any request for this name and call us.
// Of course, our subclass constructor must finish running (making add()
// calls) before mParams will be fully populated, but we expect that to
// happen well before the first LLEventPumps::obtain(name) call.
mRegistered = LLEventPumps::instance().registerPumpFactory(
name,
[this](const std::string& name){ return construct(name); });
}
LL::LazyEventAPIBase::~LazyEventAPIBase()
{
// If our constructor's registerPumpFactory() call was unsuccessful, that
// probably means somebody else claimed the name first. If that's the
// case, do NOT unregister their name out from under them!
// If this is a static instance being destroyed at process shutdown,
// LLEventPumps will probably have been cleaned up already.
if (mRegistered && ! LLEventPumps::wasDeleted())
{
// unregister the callback to this doomed instance
LLEventPumps::instance().unregisterPumpFactory(mParams.name);
}
}
LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const
{
// Since mOperations is a vector rather than a map, just search.
auto found = std::find_if(mOperations.begin(), mOperations.end(),
[&name](const auto& namedesc)
{ return (namedesc.first == name); });
if (found == mOperations.end())
return {};
// LLEventDispatcher() supplements the returned metadata in different
// ways, depending on metadata provided to the specific add() method.
// Don't try to emulate all that. At some point we might consider more
// closely unifying LLEventDispatcher machinery with LazyEventAPI, but for
// now this will have to do.
return llsd::map("name", found->first, "desc", found->second);
}

View File

@ -0,0 +1,204 @@
/**
* @file lazyeventapi.h
* @author Nat Goodspeed
* @date 2022-06-16
* @brief Declaring a static module-scope LazyEventAPI registers a specific
* LLEventAPI for future on-demand instantiation.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LAZYEVENTAPI_H)
#define LL_LAZYEVENTAPI_H
#include "apply.h"
#include "lleventapi.h"
#include "llinstancetracker.h"
#include <boost/signals2/signal.hpp>
#include <string>
#include <tuple>
#include <utility> // std::pair
#include <vector>
namespace LL
{
/**
* Bundle params we want to pass to LLEventAPI's protected constructor. We
* package them this way so a subclass constructor can simply forward an
* opaque reference to the LLEventAPI constructor.
*/
// This is a class instead of a plain struct mostly so when we forward-
// declare it we don't have to remember the distinction.
class LazyEventAPIParams
{
public:
// package the parameters used by the normal LLEventAPI constructor
std::string name, desc, field;
// bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
// the special LLEventAPI constructor we engage can "play back" those
// add() calls
boost::signals2::signal<void(LLEventAPI*)> init;
};
/**
* LazyEventAPIBase implements most of the functionality of LazyEventAPI
* (q.v.), but we need the LazyEventAPI template subclass so we can accept
* the specific LLEventAPI subclass type.
*/
// No LLInstanceTracker key: we don't need to find a specific instance,
// LLLeapListener just needs to be able to enumerate all instances.
class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
{
public:
LazyEventAPIBase(const std::string& name, const std::string& desc,
const std::string& field);
virtual ~LazyEventAPIBase();
// Do not copy or move: once constructed, LazyEventAPIBase must stay
// put: we bind its instance pointer into a callback.
LazyEventAPIBase(const LazyEventAPIBase&) = delete;
LazyEventAPIBase(LazyEventAPIBase&&) = delete;
LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
// capture add() calls we want to play back on LLEventAPI construction
template <typename... ARGS>
void add(const std::string& name, const std::string& desc, ARGS&&... rest)
{
// capture the metadata separately
mOperations.push_back(std::make_pair(name, desc));
// Use connect_extended() so the lambda is passed its own
// connection.
// We can't bind an unexpanded parameter pack into a lambda --
// shame really. Instead, capture it as a std::tuple and then, in
// the lambda, use apply() to convert back to function args.
mParams.init.connect_extended(
[name, desc, rest = std::make_tuple(std::forward<ARGS>(rest)...)]
(const boost::signals2::connection& conn, LLEventAPI* instance)
{
// we only need this connection once
conn.disconnect();
// Our add() method distinguishes name and desc because we
// capture them separately. But now, because apply()
// expects a tuple specifying ALL the arguments, expand to
// a tuple including add_trampoline() arguments: instance,
// name, desc, rest.
// apply() can't accept a template per se; it needs a
// particular specialization.
apply(&LazyEventAPIBase::add_trampoline<const std::string&, const std::string&, ARGS...>,
std::tuple_cat(std::make_tuple(instance, name, desc),
rest));
});
}
// The following queries mimic the LLEventAPI / LLEventDispatcher
// query API.
// Get the string name of the subject LLEventAPI
std::string getName() const { return mParams.name; }
// Get the documentation string
std::string getDesc() const { return mParams.desc; }
// Retrieve the LLSD key we use for dispatching
std::string getDispatchKey() const { return mParams.field; }
// operations
using NameDesc = std::pair<std::string, std::string>;
private:
// metadata that might be queried by LLLeapListener
std::vector<NameDesc> mOperations;
public:
using const_iterator = decltype(mOperations)::const_iterator;
const_iterator begin() const { return mOperations.begin(); }
const_iterator end() const { return mOperations.end(); }
LLSD getMetadata(const std::string& name) const;
protected:
// Params with which to instantiate the companion LLEventAPI subclass
LazyEventAPIParams mParams;
private:
// true if we successfully registered our LLEventAPI on construction
bool mRegistered;
// actually instantiate the companion LLEventAPI subclass
virtual LLEventPump* construct(const std::string& name) = 0;
// Passing an overloaded function to any function that accepts an
// arbitrary callable is a PITB because you have to specify the
// correct overload. What we want is for the compiler to select the
// correct overload, based on the carefully-wrought enable_ifs in
// LLEventDispatcher. This (one and only) add_trampoline() method
// exists solely to pass to LL::apply(). Once add_trampoline() is
// called with the expanded arguments, we hope the compiler will Do
// The Right Thing in selecting the correct LLEventAPI::add()
// overload.
template <typename... ARGS>
static
void add_trampoline(LLEventAPI* instance, ARGS&&... args)
{
instance->add(std::forward<ARGS>(args)...);
}
};
/**
* LazyEventAPI provides a way to register a particular LLEventAPI to be
* instantiated on demand, that is, when its name is passed to
* LLEventPumps::obtain().
*
* Derive your listener from LLEventAPI as usual, with its various
* operation methods, but code your constructor to accept
* <tt>(const LL::LazyEventAPIParams& params)</tt>
* and forward that reference to (the protected)
* <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
*
* Then derive your listener registrar from
* <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
* look very like a traditional LLEventAPI constructor:
*
* * pass (name, desc [, field]) to LazyEventAPI's constructor
* * in the body, make a series of add() calls referencing your LLEventAPI
* subclass methods.
*
* You may use any LLEventAPI::add() methods, that is, any
* LLEventDispatcher::add() methods. But the target methods you pass to
* add() must belong to your LLEventAPI subclass, not the LazyEventAPI
* subclass.
*
* Declare a static instance of your LazyEventAPI listener registrar
* class. When it's constructed at static initialization time, it will
* register your LLEventAPI subclass with LLEventPumps. It will also
* collect metadata for the LLEventAPI and its operations to provide to
* LLLeapListener's introspection queries.
*
* When someone later calls LLEventPumps::obtain() to post an event to
* your LLEventAPI subclass, obtain() will instantiate it using
* LazyEventAPI's name, desc, field and add() calls.
*/
template <class EVENTAPI>
class LazyEventAPI: public LazyEventAPIBase
{
public:
// for subclass constructor to reference handler methods
using listener = EVENTAPI;
LazyEventAPI(const std::string& name, const std::string& desc,
const std::string& field="op"):
// Forward ctor params to LazyEventAPIBase
LazyEventAPIBase(name, desc, field)
{}
private:
LLEventPump* construct(const std::string& /*name*/) override
{
// base class has carefully assembled LazyEventAPIParams embedded
// in this instance, just pass to LLEventAPI subclass constructor
return new EVENTAPI(mParams);
}
};
} // namespace LL
#endif /* ! defined(LL_LAZYEVENTAPI_H) */

View File

@ -35,6 +35,7 @@
// external library headers
// other Linden headers
#include "llerror.h"
#include "lazyeventapi.h"
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
lbase(name, field),
@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s
{
}
LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params):
LLEventAPI(params.name, params.desc, params.field)
{
// call initialization functions with our brand-new instance pointer
params.init(this);
}
LLEventAPI::~LLEventAPI()
{
}

View File

@ -35,6 +35,11 @@
#include "llinstancetracker.h"
#include <string>
namespace LL
{
class LazyEventAPIParams;
}
/**
* LLEventAPI not only provides operation dispatch functionality, inherited
* from LLDispatchListener -- it also gives us event API introspection.
@ -64,19 +69,6 @@ public:
/// Get the documentation string
std::string getDesc() const { return mDesc; }
/**
* Publish only selected add() methods from LLEventDispatcher.
* Every LLEventAPI add() @em must have a description string.
*/
template <typename CALLABLE>
void add(const std::string& name,
const std::string& desc,
CALLABLE callable,
const LLSD& required=LLSD())
{
LLEventDispatcher::add(name, desc, callable, required);
}
/**
* Instantiate a Response object in any LLEventAPI subclass method that
* wants to guarantee a reply (if requested) will be sent on exit from the
@ -150,16 +142,20 @@ public:
* @endcode
*/
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
/**
* set the response to the given data
*/
void setResponse(LLSD const & response){ mResp = response; }
/**
* set the response to the given data
*/
void setResponse(LLSD const & response){ mResp = response; }
LLSD mResp, mReq;
LLSD::String mKey;
};
protected:
// constructor used only by subclasses registered by LazyEventAPI
LLEventAPI(const LL::LazyEventAPIParams&);
private:
std::string mDesc;
};

View File

@ -40,70 +40,12 @@
// other Linden headers
#include "llevents.h"
#include "llerror.h"
#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
#include <iomanip> // std::quoted()
#include <memory> // 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
*****************************************************************************/
@ -156,19 +98,26 @@ void LLSDArgsSource::done() const
* - Holes are filled with the default values.
* - Any remaining holes constitute an error.
*/
class LL_COMMON_API LLSDArgsMapper
class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper
{
public:
/// Accept description of function: function name, param names, param
/// default values
LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function,
const LLSD& names, const LLSD& defaults);
/// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
/// Given arguments map, return LLSD::Array of parameter values, or
/// trigger error.
LLSD map(const LLSD& argsmap) const;
private:
static std::string formatlist(const LLSD&);
template <typename... ARGS>
void callFail(ARGS&&... args) const;
// store a plain dumb back-pointer because we don't have to manage the
// parent LLEventDispatcher's lifespan
LLEventDispatcher* _parent;
// 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;
@ -187,15 +136,18 @@ private:
FilledVector _has_dft;
};
LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
const LLSD& names, const LLSD& defaults):
LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
const std::string& function,
const LLSD& names,
const LLSD& defaults):
_parent(parent),
_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;
callFail(" names must be an array, not ", names);
}
auto nparams(_names.size());
// From _names generate _indexes.
@ -218,8 +170,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// 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;
callFail(" names array ", names, " shorter than defaults array ", defaults);
}
// Offset by which we slide defaults array right to right-align with
@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
<< formatlist(bogus) << LL_ENDL;
callFail(" defaults specified for nonexistent params ", formatlist(bogus));
}
}
else
{
LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
<< defaults << LL_ENDL;
callFail(" defaults must be a map or an array, not ", defaults);
}
}
LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
<< argsmap << LL_ENDL;
callFail(" map() needs a map or array, not ", argsmap);
}
// 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
@ -369,15 +317,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
<< formatlist(unfilled) << " from " << argsmap << LL_ENDL;
callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap);
}
// done
return args;
}
std::string LLSDArgsMapper::formatlist(const LLSD& list)
std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list)
{
std::ostringstream out;
const char* delim = "";
@ -390,23 +337,44 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
mDesc(desc),
mKey(key)
template <typename... ARGS>
void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
{
_parent->callFail<LLEventDispatcher::DispatchError>
(_function, std::forward<ARGS>(args)...);
}
/*****************************************************************************
* LLEventDispatcher
*****************************************************************************/
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
LLEventDispatcher(desc, key, "args")
{}
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key,
const std::string& argskey):
mDesc(desc),
mKey(key),
mArgskey(argskey)
{}
LLEventDispatcher::~LLEventDispatcher()
{
}
LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc):
mParent(parent),
mDesc(desc)
{}
/**
* 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),
LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
const Callable& func, const LLSD& required):
DispatchEntry(parent, desc),
mFunc(func),
mRequired(required)
{}
@ -414,22 +382,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override
{
// 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;
return callFail(desc, ": bad request: ", mismatch);
}
// Event syntax looks good, go for it!
mFunc(event);
return mFunc(event);
}
virtual LLSD addMetadata(LLSD meta) const
LLSD getMetadata() const override
{
meta["required"] = mRequired;
return meta;
return llsd::map("required", mRequired);
}
};
@ -439,17 +406,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
*/
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
{
ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
DispatchEntry(desc),
ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
const std::string& desc, const invoker_function& func):
DispatchEntry(parent, desc),
mName(name),
mInvoker(func)
{}
std::string mName;
invoker_function mInvoker;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
{
LLSDArgsSource src(desc, event);
mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
try
{
return mInvoker(event);
}
catch (const LL::apply_error& err)
{
// could hit runtime errors with LL::apply()
return callFail(err.what());
}
}
};
@ -459,23 +436,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
*/
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
const std::string& desc, const invoker_function& func,
LLSD::Integer arity):
ParamsDispatchEntry(desc, func),
ParamsDispatchEntry(parent, name, desc, func),
mArity(arity)
{}
LLSD::Integer mArity;
virtual LLSD addMetadata(LLSD meta) const
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") };
// Whether we try to extract arguments from 'event' depends on whether
// the LLEventDispatcher consumer called one of the (name, event)
// methods (! fromMap) or one of the (event) methods (fromMap). If we
// were called with (name, event), the passed event must itself be
// suitable to pass to the registered callable, no args extraction
// required or even attempted. Only if called with plain (event) do we
// consider extracting args from that event. Initially assume 'event'
// itself contains the arguments.
LLSD args{ event };
if (fromMap)
{
if (! mArity)
{
// When the target function is nullary, and we're called from
// an (event) method, just ignore the rest of the map entries.
args.clear();
}
else
{
// We only require/retrieve argskey if the target function
// isn't nullary. For all others, since we require an LLSD
// array, we must have an argskey.
if (argskey.empty())
{
return callFail("LLEventDispatcher has no args key");
}
if ((! event.has(argskey)))
{
return callFail("missing required key ", std::quoted(argskey));
}
args = event[argskey];
}
}
return ParamsDispatchEntry::call(desc, args, fromMap, argskey);
}
LLSD getMetadata() const override
{
LLSD array(LLSD::emptyArray());
// Resize to number of arguments required
if (mArity)
array[mArity - 1] = LLSD();
llassert_always(array.size() == mArity);
meta["required"] = array;
return meta;
return llsd::map("required", array);
}
};
@ -485,11 +501,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
*/
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
const invoker_function& func,
MapParamsDispatchEntry(LLEventDispatcher* parent, 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),
ParamsDispatchEntry(parent, name, desc, func),
mMapper(parent, name, params, defaults),
mRequired(LLSD::emptyMap())
{
// Build the set of all param keys, then delete the ones that are
@ -532,18 +548,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
// to base-class call() method.
ParamsDispatchEntry::call(desc, mMapper.map(event));
// by default, pass the whole event as the arguments map
LLSD args{ event };
// Were we called by one of the (event) methods (instead of the (name,
// event) methods), do we have an argskey, and does the incoming event
// have that key?
if (fromMap && (! argskey.empty()) && event.has(argskey))
{
// if so, extract the value of argskey from the incoming event,
// and use that as the arguments map
args = event[argskey];
}
// Now convert args from LLSD map to LLSD array using mMapper, then
// pass to base-class call() method.
return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey);
}
virtual LLSD addMetadata(LLSD meta) const
LLSD getMetadata() const override
{
meta["required"] = mRequired;
meta["optional"] = mOptional;
return meta;
return llsd::map("required", mRequired, "optional", mOptional);
}
};
@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
const invoker_function& invoker,
LLSD::Integer arity)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new ArrayParamsDispatchEntry(desc, invoker, arity))));
mDispatch.emplace(
name,
new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));
}
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
const LLSD& params,
const LLSD& defaults)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
// Pass instance info as well as this entry name for error messages.
mDispatch.emplace(
name,
new MapParamsDispatchEntry(this, "", 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)
void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
const Callable& callable, const LLSD& required)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new LLSDDispatchEntry(desc, callable, required))));
mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
}
void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
<< "): " << classname << " is not a subclass "
<< "of LLEventDispatcher" << LL_ENDL;
<< "): " << LLError::Log::demangle(classname)
<< " is not a subclass of LLEventDispatcher"
<< LL_ENDL;
}
/// Unregister a callable
@ -596,48 +621,105 @@ bool LLEventDispatcher::remove(const std::string& name)
return true;
}
/// Call a registered callable with an explicitly-specified name. If no
/// such callable exists, die with LL_ERRS.
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
/// Call a registered callable with an explicitly-specified name. It is an
/// error if no such callable exists.
LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
if (! try_call(name, event))
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
<< "' not found" << LL_ENDL;
}
}
/// 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.
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 (! try_call(name, event))
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
<< " value '" << name << "'" << LL_ENDL;
}
}
bool LLEventDispatcher::try_call(const LLSD& event) const
{
return try_call(event[mKey], event);
return try_call(std::string(), name, event);
}
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
try
{
try_call(std::string(), name, event);
return true;
}
// Note that we don't catch the generic DispatchError, only the specific
// DispatchMissing. try_call() only promises to return false if the
// specified callable name isn't found -- not for general errors.
catch (const DispatchMissing&)
{
return false;
}
}
/// Extract the @a key value from the incoming @a event, and call the callable
/// whose name is specified by that map @a key. It is an error if no such
/// callable exists.
LLSD LLEventDispatcher::operator()(const LLSD& event) const
{
return try_call(mKey, event[mKey], event);
}
bool LLEventDispatcher::try_call(const LLSD& event) const
{
try
{
try_call(mKey, event[mKey], event);
return true;
}
catch (const DispatchMissing&)
{
return false;
}
}
LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
const LLSD& event) const
{
if (name.empty())
{
if (key.empty())
{
callFail<DispatchError>("attempting to call with no name");
}
else
{
callFail<DispatchError>("no ", key);
}
}
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
// Here we were passed a non-empty name, but there's no registered
// callable with that name. This is the one case in which we throw
// DispatchMissing instead of the generic DispatchError.
// Distinguish the public method by which our caller reached here:
// key.empty() means the name was passed explicitly, non-empty means
// we extracted the name from the incoming event using that key.
if (key.empty())
{
callFail<DispatchMissing>(std::quoted(name), " not found");
}
else
{
callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
}
}
// 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
const char* delim = (key.empty()? "" : "=");
// append either "[key=name]" or just "[name]"
SetState transient(this, '[', key, delim, name, ']');
return found->second->call("", event, (! key.empty()), mArgskey);
}
template <typename EXCEPTION, typename... ARGS>
//static
void LLEventDispatcher::sCallFail(ARGS&&... args)
{
auto error = stringize(std::forward<ARGS>(args)...);
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
LLTHROW(EXCEPTION(error));
}
template <typename EXCEPTION, typename... ARGS>
void LLEventDispatcher::callFail(ARGS&&... args) const
{
// Describe this instance in addition to the error itself.
sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@ -647,26 +729,243 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
{
return LLSD();
}
LLSD meta;
LLSD meta{ found->second->getMetadata() };
meta["name"] = name;
meta["desc"] = found->second->mDesc;
return found->second->addMetadata(meta);
return meta;
}
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
LLEventDispatcher(pumpname, key),
mPump(pumpname, true), // allow tweaking for uniqueness
mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
{
// If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
// Also report whatever transient state is active.
return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
<< self.getState();
}
bool LLDispatchListener::process(const LLSD& event)
std::string LLEventDispatcher::getState() const
{
(*this)(event);
// default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
// to that; infer empty string
if (! mState.get())
return {};
else
return *mState;
}
bool LLEventDispatcher::setState(SetState&, const std::string& state) const
{
// If SetState is instantiated at multiple levels of function call, ignore
// the lower-level call because the outer call presumably provides more
// context.
if (mState.get())
return false;
// Pass us empty string (a la ~SetState()) to reset to nullptr, else take
// a heap copy of the passed state string so we can delete it on
// subsequent reset().
mState.reset(state.empty()? nullptr : new std::string(state));
return true;
}
/*****************************************************************************
* LLDispatchListener
*****************************************************************************/
std::string LLDispatchListener::mReplyKey{ "reply" };
bool LLDispatchListener::process(const LLSD& event) const
{
// Decide what to do based on the incoming value of the specified dispatch
// key.
LLSD name{ event[getDispatchKey()] };
if (name.isMap())
{
call_map(name, event);
}
else if (name.isArray())
{
call_array(name, event);
}
else
{
call_one(name, event);
}
return false;
}
LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
mDesc(desc)
{}
void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
{
LLSD result;
try
{
result = (*this)(event);
}
catch (const DispatchError& err)
{
if (! event.has(mReplyKey))
{
// Without a reply key, let the exception propagate.
throw;
}
// Here there was an error and the incoming event has mReplyKey. Reply
// with a map containing an "error" key explaining the problem.
return reply(llsd::map("error", err.what()), event);
}
// We seem to have gotten a valid result. But we don't know whether the
// registered callable is void or non-void. If it's void,
// LLEventDispatcher returned isUndefined(). Otherwise, try to send it
// back to our invoker.
if (result.isDefined())
{
if (! result.isMap())
{
// wrap the result in a map as the "data" key
result = llsd::map("data", result);
}
reply(result, event);
}
}
void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
{
// LLSD map containing returned values
LLSD result;
// cache dispatch key
std::string key{ getDispatchKey() };
// collect any error messages here
std::ostringstream errors;
const char* delim = "";
for (const auto& pair : llsd::inMap(reqmap))
{
const LLSD::String& name{ pair.first };
const LLSD& args{ pair.second };
try
{
// in case of errors, tell user the dispatch key, the fact that
// we're processing a request map and the current key in that map
SetState(this, '[', key, '[', name, "]]");
// With this form, capture return value even if undefined:
// presence of the key in the response map can be used to detect
// which request keys succeeded.
result[name] = (*this)(name, args);
}
catch (const std::exception& err)
{
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Collect exception name and message in
// 'errors'.
errors << delim << LLError::Log::classname(err) << ": " << err.what();
delim = "\n";
}
}
// so, were there any errors?
std::string error = errors.str();
if (! error.empty())
{
if (! event.has(mReplyKey))
{
// can't send reply, throw
sCallFail<DispatchError>(error);
}
else
{
// reply key present
result["error"] = error;
}
}
reply(result, event);
}
void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
{
// LLSD array containing returned values
LLSD results;
// cache the dispatch key
std::string key{ getDispatchKey() };
// arguments array, if present -- const because, if it's shorter than
// reqarray, we don't want to grow it
const LLSD argsarray{ event[getArgsKey()] };
// error message, if any
std::string error;
// classic index loop because we need the index
for (size_t i = 0, size = reqarray.size(); i < size; ++i)
{
const auto& reqentry{ reqarray[i] };
std::string name;
LLSD args;
if (reqentry.isString())
{
name = reqentry.asString();
args = argsarray[i];
}
else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
{
name = reqentry[0].asString();
args = reqentry[1];
}
else
{
// reqentry isn't in either of the documented forms
error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
reqentry, " unsupported");
break;
}
// reqentry is one of the valid forms, got name and args
try
{
// in case of errors, tell user the dispatch key, the fact that
// we're processing a request array, the current entry in that
// array and the corresponding callable name
SetState(this, '[', key, '[', i, "]=", name, ']');
// With this form, capture return value even if undefined
results.append((*this)(name, args));
}
catch (const std::exception& err)
{
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Report the exception class as well as the
// error string.
error = stringize(LLError::Log::classname(err), ": ", err.what());
break;
}
}
LLSD result;
// was there an error?
if (! error.empty())
{
if (! event.has(mReplyKey))
{
// can't send reply, throw
sCallFail<DispatchError>(error);
}
else
{
// reply key present
result["error"] = error;
}
}
// wrap the results array as response map "data" key, as promised
if (results.isDefined())
{
result["data"] = results;
}
reply(result, event);
}
void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
{
// Call sendReply() unconditionally: sendReply() itself tests whether the
// specified reply key is present in the incoming request, and does
// nothing if there's no such key.
sendReply(reply, request, mReplyKey);
}

File diff suppressed because it is too large Load Diff

View File

@ -435,16 +435,61 @@ public:
// generic type-appropriate store through mTarget, construct an
// LLSDParam<T> and store that, thus engaging LLSDParam's custom
// conversions.
mTarget = LLSDParam<T>(llsd::drill(event, mPath));
storeTarget(LLSDParam<T>(llsd::drill(event, mPath)));
return mConsume;
}
private:
// This method disambiguates LLStoreListener<LLSD>. Directly assigning
// some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value);
// is problematic because the compiler has too many choices: LLSD has
// multiple assignment operator overloads, and LLSDParam<LLSD> has a
// templated conversion operator. But LLSDParam<LLSD> can convert to a
// (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works.
void storeTarget(const T& value)
{
mTarget = value;
}
T& mTarget;
const LLSD mPath;
const bool mConsume;
};
/**
* LLVarHolder bundles a target variable of the specified type. We use it as a
* base class so the target variable will be fully constructed by the time a
* subclass constructor tries to pass a reference to some other base class.
*/
template <typename T>
struct LLVarHolder
{
T mVar;
};
/**
* LLCaptureListener isa LLStoreListener that bundles the target variable of
* interest.
*/
template <typename T>
class LLCaptureListener: public LLVarHolder<T>,
public LLStoreListener<T>
{
private:
using holder = LLVarHolder<T>;
using super = LLStoreListener<T>;
public:
LLCaptureListener(const LLSD& path=LLSD(), bool consume=false):
super(*this, holder::mVar, path, consume)
{}
void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); }
const T& get() const { return holder::mVar; }
operator const T&() { return holder::mVar; }
};
/*****************************************************************************
* LLEventLogProxy
*****************************************************************************/

View File

@ -68,19 +68,78 @@
LLEventPumps::LLEventPumps():
mFactories
{
{ "LLEventStream", [](const std::string& name, bool tweak)
{ "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
{ "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventMailDrop(name, tweak); } }
},
mTypes
{
// LLEventStream is the default for obtain(), so even if somebody DOES
// call obtain("placeholder"), this sample entry won't break anything.
{ "placeholder", "LLEventStream" }
// { "placeholder", "LLEventStream" }
}
{}
bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory)
{
auto found = mFactories.find(type);
// can't re-register a TypeFactory for a type name that's already registered
if (found != mFactories.end())
return false;
// doesn't already exist, go ahead and register
mFactories[type] = factory;
return true;
}
void LLEventPumps::unregisterTypeFactory(const std::string& type)
{
auto found = mFactories.find(type);
if (found != mFactories.end())
mFactories.erase(found);
}
bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory)
{
// Do we already have a pump by this name?
if (mPumpMap.find(name) != mPumpMap.end())
return false;
// Do we already have an override for this pump name?
if (mTypes.find(name) != mTypes.end())
return false;
// Leverage the two-level lookup implemented by mTypes (pump name -> type
// name) and mFactories (type name -> factory). We could instead create a
// whole separate (pump name -> factory) map, and look in both; or we
// could change mTypes to (pump name -> factory) and, for typical type-
// based lookups, use a "factory" that looks up the real factory in
// mFactories. But this works, and we don't expect many calls to make() -
// either explicit or implicit via obtain().
// Create a bogus type name extremely unlikely to collide with an actual type.
static std::string nul(1, '\0');
std::string type_name{ nul + name };
mTypes[name] = type_name;
// TypeFactory is called with (name, tweak, type), whereas PumpFactory
// accepts only name. We could adapt with std::bind(), but this lambda
// does the trick.
mFactories[type_name] =
[factory]
(const std::string& name, bool /*tweak*/, const std::string& /*type*/)
{ return factory(name); };
return true;
}
void LLEventPumps::unregisterPumpFactory(const std::string& name)
{
auto tfound = mTypes.find(name);
if (tfound != mTypes.end())
{
auto ffound = mFactories.find(tfound->second);
if (ffound != mFactories.end())
{
mFactories.erase(ffound);
}
mTypes.erase(tfound);
}
}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
PumpMap::iterator found = mPumpMap.find(name);
@ -114,7 +173,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
// Passing an unrecognized type name is a no-no
LLTHROW(BadType(type));
}
auto newInstance = (found->second)(name, tweak);
auto newInstance = (found->second)(name, tweak, type);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.

View File

@ -270,6 +270,45 @@ public:
LLEventPump& make(const std::string& name, bool tweak=false,
const std::string& type=std::string());
/// function passed to registerTypeFactory()
typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> TypeFactory;
/**
* Register a TypeFactory for use with make(). When make() is called with
* the specified @a type string, call @a factory(name, tweak, type) to
* instantiate it.
*
* Returns true if successfully registered, false if there already exists
* a TypeFactory for the specified @a type name.
*/
bool registerTypeFactory(const std::string& type, const TypeFactory& factory);
void unregisterTypeFactory(const std::string& type);
/// function passed to registerPumpFactory()
typedef std::function<LLEventPump*(const std::string&)> PumpFactory;
/**
* Register a PumpFactory for use with obtain(). When obtain() is called
* with the specified @a name string, if an LLEventPump with the specified
* @a name doesn't already exist, call @a factory(name) to instantiate it.
*
* Returns true if successfully registered, false if there already exists
* a factory override for the specified @a name.
*
* PumpFactory does not support @a tweak because it's only called when
* <i>that particular</i> @a name is passed to obtain(). Bear in mind that
* <tt>obtain(name)</tt> might still bypass the caller's PumpFactory for a
* couple different reasons:
*
* * registerPumpFactory() returns false because there's already a factory
* override for the specified @name
* * between a successful <tt>registerPumpFactory(name)</tt> call (returns
* true) and a call to <tt>obtain(name)</tt>, someone explicitly
* instantiated an LLEventPump(name), so obtain(name) returned that.
*/
bool registerPumpFactory(const std::string& name, const PumpFactory& factory);
void unregisterPumpFactory(const std::string& name);
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@ -327,13 +366,13 @@ testable:
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
// for make(), map string type name to LLEventPump subclass factory function
typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
typedef std::map<std::string, TypeFactory> TypeFactories;
// Data used by make().
// One might think mFactories and mTypes could reasonably be static. So
// they could -- if not for the fact that make() or obtain() might be
// called before this module's static variables have been initialized.
// This is why we use singletons in the first place.
PumpFactories mFactories;
TypeFactories mFactories;
// for obtain(), map desired string instance name to string type when
// obtain() must create the instance

View File

@ -327,11 +327,28 @@ public:
}
else
{
// The LLSD object we got from our stream contains the keys we
// need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
// Block calls to this method; resetting mBlocker unblocks calls
// to the other method.
try
{
// The LLSD object we got from our stream contains the
// keys we need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
}
catch (const std::exception& err)
{
// No plugin should be allowed to crash the viewer by
// driving an exception -- intentionally or not.
LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
// Whether or not the plugin added a "reply" key to the
// request, send a reply. We happen to know who originated
// this request, and the reply LLEventPump of interest.
// Not our problem if the plugin ignores the reply event.
data["reply"] = mReplyPump.getName();
sendReply(llsd::map("error",
stringize(LLError::Log::classname(err), ": ", err.what())),
data);
}
// Block calls to this method; resetting mBlocker unblocks
// calls to the other method.
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
// Go check for any more pending events in the buffer.
if (childout.size())

View File

@ -14,14 +14,16 @@
// associated header
#include "llleaplistener.h"
// STL headers
#include <map>
#include <algorithm> // std::find_if
#include <functional>
#include <map>
#include <set>
// std headers
// external library headers
#include <boost/foreach.hpp>
// other Linden headers
#include "lluuid.h"
#include "lazyeventapi.h"
#include "llsdutil.h"
#include "lluuid.h"
#include "stringize.h"
/*****************************************************************************
@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()
// value_type, and Bad Things would happen if you copied an
// LLTempBoundListener. (Destruction of the original would disconnect the
// listener, invalidating every stored connection.)
BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
for (ListenersMap::value_type& pair : mListeners)
{
pair.second.disconnect();
}
@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const
{
Response reply(LLSD(), request);
// first, traverse existing LLEventAPI instances
std::set<std::string> instances;
for (auto& ea : LLEventAPI::instance_snapshot())
{
LLSD info;
info["desc"] = ea.getDesc();
reply[ea.getName()] = info;
// remember which APIs are actually instantiated
instances.insert(ea.getName());
reply[ea.getName()] = llsd::map("desc", ea.getDesc());
}
// supplement that with *potential* instances: that is, instances of
// LazyEventAPI that can each instantiate an LLEventAPI on demand
for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot())
{
// skip any LazyEventAPI that's already instantiated its LLEventAPI
if (instances.find(lea.getName()) == instances.end())
{
reply[lea.getName()] = llsd::map("desc", lea.getDesc());
}
}
}
// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this
// function can be passed either -- even though they're unrelated types.
template <typename API>
void reportAPI(LLEventAPI::Response& reply, const API& api)
{
reply["name"] = api.getName();
reply["desc"] = api.getDesc();
reply["key"] = api.getDispatchKey();
LLSD ops;
for (const auto& namedesc : api)
{
ops.append(api.getMetadata(namedesc.first));
}
reply["ops"] = ops;
}
void LLLeapListener::getAPI(const LLSD& request) const
{
Response reply(LLSD(), request);
auto found = LLEventAPI::getInstance(request["api"]);
if (found)
// check first among existing LLEventAPI instances
auto foundea = LLEventAPI::getInstance(request["api"]);
if (foundea)
{
reply["name"] = found->getName();
reply["desc"] = found->getDesc();
reply["key"] = found->getDispatchKey();
LLSD ops;
for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
oi != oend; ++oi)
reportAPI(reply, *foundea);
}
else
{
// Here the requested LLEventAPI doesn't yet exist, but do we have a
// registered LazyEventAPI for it?
LL::LazyEventAPIBase::instance_snapshot snap;
auto foundlea = std::find_if(snap.begin(), snap.end(),
[api = request["api"].asString()]
(const auto& lea)
{ return (lea.getName() == api); });
if (foundlea != snap.end())
{
ops.append(found->getMetadata(oi->first));
reportAPI(reply, *foundlea);
}
reply["ops"] = ops;
}
}

View File

@ -33,9 +33,12 @@
#include "llpointer.h"
#include "llrefcount.h" // LLRefCount
#include <boost/intrusive_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/utility/enable_if.hpp>
#include <memory> // std::shared_ptr, std::unique_ptr
#include <type_traits>
/**
* LLPtrTo<TARGET>::type is either of two things:
@ -55,14 +58,14 @@ struct LLPtrTo
/// specialize for subclasses of LLRefCount
template <class T>
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
/// specialize for subclasses of LLThreadSafeRefCount
template <class T>
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
typedef SOMECLASS type;
};
namespace LL
{
/*****************************************************************************
* get_ref()
*****************************************************************************/
template <typename T>
struct GetRef
{
// return const ref or non-const ref, depending on whether we can bind
// a non-const lvalue ref to the argument
const auto& operator()(const T& obj) const { return obj; }
auto& operator()(T& obj) const { return obj; }
};
template <typename T>
struct GetRef<const T*>
{
const auto& operator()(const T* ptr) const { return *ptr; }
};
template <typename T>
struct GetRef<T*>
{
auto& operator()(T* ptr) const { return *ptr; }
};
template <typename T>
struct GetRef< LLPointer<T> >
{
auto& operator()(LLPointer<T> ptr) const { return *ptr; }
};
/// whether we're passed a pointer or a reference, return a reference
template <typename T>
auto& get_ref(T& ptr_or_ref)
{
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
}
template <typename T>
const auto& get_ref(const T& ptr_or_ref)
{
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
}
/*****************************************************************************
* get_ptr()
*****************************************************************************/
// if T is any pointer type we recognize, return it unchanged
template <typename T>
const T* get_ptr(const T* ptr) { return ptr; }
template <typename T>
T* get_ptr(T* ptr) { return ptr; }
template <typename T>
const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
template <typename T>
const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
template <typename T>
const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
template <typename T>
const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
template <typename T>
const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
// T is not any pointer type we recognize, take a pointer to the parameter
template <typename T>
const T* get_ptr(const T& obj) { return &obj; }
template <typename T>
T* get_ptr(T& obj) { return &obj; }
} // namespace LL
#endif /* ! defined(LL_LLPTRTO_H) */

View File

@ -1051,3 +1051,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
return shallow;
}
LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
{
// LLSD supports a number of types, two of which are aggregates: Map and
// Array. We don't try to support Map: supporting Map would seem to
// promise that we could somehow match the string key to 'func's parameter
// names. Uh sorry, maybe in some future version of C++ with reflection.
if (args.isMap())
{
LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
}
// We expect an LLSD array, but what the heck, treat isUndefined() as a
// zero-length array for calling a nullary 'func'.
if (args.isUndefined() || args.isArray())
{
// this works because LLSD().size() == 0
if (args.size() != arity)
{
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
args.size(), "-entry LLSD array)")));
}
return args;
}
// args is one of the scalar types
// scalar_LLSD.size() == 0, so don't test that here.
// You can pass a scalar LLSD only to a unary 'func'.
if (arity != 1)
{
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
"LLSD ", LLSD::typeString(args.type()), ")")));
}
// make an array of it
return llsd::array(args);
}

View File

@ -29,8 +29,12 @@
#ifndef LL_LLSDUTIL_H
#define LL_LLSDUTIL_H
#include "apply.h" // LL::invoke()
#include "function_types.h" // LL::function_arity
#include "llsd.h"
#include <boost/functional/hash.hpp>
#include <cassert>
#include <type_traits>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@ -332,6 +336,31 @@ private:
T _value;
};
/**
* LLSDParam<LLSD> is for when you don't already have the target parameter
* type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
* templated conversion operator will try to select a more specific LLSDParam
* specialization.
*/
template <>
class LLSDParam<LLSD>
{
private:
LLSD value_;
public:
LLSDParam(const LLSD& value): value_(value) {}
/// if we're literally being asked for an LLSD parameter, avoid infinite
/// recursion
operator LLSD() const { return value_; }
/// otherwise, instantiate a more specific LLSDParam<T> to convert; that
/// preserves the existing customization mechanism
template <typename T>
operator T() const { return LLSDParam<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
@ -350,7 +379,7 @@ class LLSDParam<T> \
{ \
public: \
LLSDParam(const LLSD& value): \
_value((T)value.AS()) \
_value((T)value.AS()) \
{} \
\
operator T() const { return _value; } \
@ -555,4 +584,56 @@ struct hash<LLSD>
}
};
}
namespace LL
{
/*****************************************************************************
* apply(function, LLSD array)
*****************************************************************************/
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
// apply_impl()
LLSD apply_llsd_fix(size_t arity, const LLSD& args);
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply .
// We can't simply make a tuple from the LLSD array and then apply() that
// tuple to the function -- how would make_tuple() deduce the correct
// parameter type for each entry? We must go directly to the target function.
template <typename CALLABLE, std::size_t... I>
auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
{
// call func(unpacked args), using generic LLSDParam<LLSD> to convert each
// entry in 'array' to the target parameter type
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
}
// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
// function with (that many) items from the passed LLSD array
template <size_t ARITY, typename CALLABLE>
auto apply_n(CALLABLE&& func, const LLSD& args)
{
return apply_impl(std::forward<CALLABLE>(func),
apply_llsd_fix(ARITY, args),
std::make_index_sequence<ARITY>());
}
/**
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
* time how many arguments to pass. This isn't Python. (But see apply_n() to
* pass a specific number of args to a variadic function.)
*/
template <typename CALLABLE>
auto apply(CALLABLE&& func, const LLSD& args)
{
// infer arity from the definition of func
constexpr auto arity = function_arity<
typename std::remove_reference<CALLABLE>::type>::value;
// now that we have a compile-time arity, apply_n() works
return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
} // namespace LL
#endif // LL_LLSDUTIL_H

View File

@ -0,0 +1,237 @@
/**
* @file apply_test.cpp
* @author Nat Goodspeed
* @date 2022-12-19
* @brief Test for apply.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "apply.h"
// STL headers
// std headers
#include <iomanip>
// external library headers
// other Linden headers
#include "llsd.h"
#include "llsdutil.h"
// for ensure_equals
std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
{
const char* delim = "[";
for (const auto& str : stringvec)
{
out << delim << std::quoted(str);
delim = ", ";
}
return out << ']';
}
// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
#include "../test/lltut.h"
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
namespace statics
{
/*------------------------------ data ------------------------------*/
// Although we're using types from the LLSD namespace, we're not
// constructing LLSD values, but rather instances of the C++ types
// supported by LLSD.
static LLSD::Boolean b{true};
static LLSD::Integer i{17};
static LLSD::Real f{3.14};
static LLSD::String s{ "hello" };
static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
static LLSD::Date dt{ "2022-12-19" };
static LLSD::URI uri{ "http://secondlife.com" };
static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
static std::vector<LLSD::String> quick
{
"The", "quick", "brown", "fox", "etc."
};
static std::array<int, 5> fibs
{
0, 1, 1, 2, 3
};
// ensure that apply() actually reaches the target method --
// lack of ensure_equals() failure could be due to no-op apply()
bool called{ false };
// capture calls from collect()
std::vector<std::string> collected;
/*------------------------- test functions -------------------------*/
void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
const LLSD::UUID& uu, const LLSD::Date& dt,
const LLSD::URI& uri, const LLSD::Binary& bin)
{
called = true;
ensure_equals( "b mismatch", b, statics::b);
ensure_equals( "i mismatch", i, statics::i);
ensure_equals( "f mismatch", f, statics::f);
ensure_equals( "s mismatch", s, statics::s);
ensure_equals( "uu mismatch", uu, statics::uu);
ensure_equals( "dt mismatch", dt, statics::dt);
ensure_equals("uri mismatch", uri, statics::uri);
ensure_equals("bin mismatch", bin, statics::bin);
}
void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
{
called = true;
ensure_equals("s0 mismatch", s0, statics::quick[0]);
ensure_equals("s1 mismatch", s1, statics::quick[1]);
ensure_equals("s2 mismatch", s2, statics::quick[2]);
ensure_equals("s3 mismatch", s3, statics::quick[3]);
ensure_equals("s4 mismatch", s4, statics::quick[4]);
}
void ints(int i0, int i1, int i2, int i3, int i4)
{
called = true;
ensure_equals("i0 mismatch", i0, statics::fibs[0]);
ensure_equals("i1 mismatch", i1, statics::fibs[1]);
ensure_equals("i2 mismatch", i2, statics::fibs[2]);
ensure_equals("i3 mismatch", i3, statics::fibs[3]);
ensure_equals("i4 mismatch", i4, statics::fibs[4]);
}
void sdfunc(const LLSD& sd)
{
called = true;
ensure_equals("sd mismatch", sd.asInteger(), statics::i);
}
void intfunc(int i)
{
called = true;
ensure_equals("i mismatch", i, statics::i);
}
void voidfunc()
{
called = true;
}
// recursion tail
void collect()
{
called = true;
}
// collect(arbitrary)
template <typename... ARGS>
void collect(const std::string& first, ARGS&&... rest)
{
statics::collected.push_back(first);
collect(std::forward<ARGS>(rest)...);
}
} // namespace statics
struct apply_data
{
apply_data()
{
// reset called before each test
statics::called = false;
statics::collected.clear();
}
};
typedef test_group<apply_data> apply_group;
typedef apply_group::object object;
apply_group applygrp("apply");
template<> template<>
void object::test<1>()
{
set_test_name("apply(tuple)");
LL::apply(statics::various,
std::make_tuple(statics::b, statics::i, statics::f, statics::s,
statics::uu, statics::dt, statics::uri, statics::bin));
ensure("apply(tuple) failed", statics::called);
}
template<> template<>
void object::test<2>()
{
set_test_name("apply(array)");
LL::apply(statics::ints, statics::fibs);
ensure("apply(array) failed", statics::called);
}
template<> template<>
void object::test<3>()
{
set_test_name("apply(vector)");
LL::apply(statics::strings, statics::quick);
ensure("apply(vector) failed", statics::called);
}
// The various apply(LLSD) tests exercise only the success cases because
// the failure cases trigger assert() fail, which is hard to catch.
template<> template<>
void object::test<4>()
{
set_test_name("apply(LLSD())");
LL::apply(statics::voidfunc, LLSD());
ensure("apply(LLSD()) failed", statics::called);
}
template<> template<>
void object::test<5>()
{
set_test_name("apply(fn(int), LLSD scalar)");
LL::apply(statics::intfunc, LLSD(statics::i));
ensure("apply(fn(int), LLSD scalar) failed", statics::called);
}
template<> template<>
void object::test<6>()
{
set_test_name("apply(fn(LLSD), LLSD scalar)");
// This test verifies that LLSDParam<LLSD> doesn't send the compiler
// into infinite recursion when the target is itself LLSD.
LL::apply(statics::sdfunc, LLSD(statics::i));
ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
}
template<> template<>
void object::test<7>()
{
set_test_name("apply(LLSD array)");
LL::apply(statics::various,
llsd::array(statics::b, statics::i, statics::f, statics::s,
statics::uu, statics::dt, statics::uri, statics::bin));
ensure("apply(LLSD array) failed", statics::called);
}
template<> template<>
void object::test<8>()
{
set_test_name("VAPPLY()");
// Make a std::array<std::string> from statics::quick. We can't call a
// variadic function with a data structure of dynamic length.
std::array<std::string, 5> strray;
for (size_t i = 0; i < strray.size(); ++i)
strray[i] = statics::quick[i];
// This doesn't work: the compiler doesn't know which overload of
// collect() to pass to LL::apply().
// LL::apply(statics::collect, strray);
// That's what VAPPLY() is for.
VAPPLY(statics::collect, strray);
ensure("VAPPLY() failed", statics::called);
ensure_equals("collected mismatch", statics::collected, statics::quick);
}
} // namespace tut

View File

@ -0,0 +1,136 @@
/**
* @file lazyeventapi_test.cpp
* @author Nat Goodspeed
* @date 2022-06-18
* @brief Test for lazyeventapi.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "lazyeventapi.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "llevents.h"
#include "llsdutil.h"
// observable side effect, solely for testing
static LLSD data;
// LLEventAPI listener subclass
class MyListener: public LLEventAPI
{
public:
// need this trivial forwarding constructor
// (of course do any other initialization your subclass requires)
MyListener(const LL::LazyEventAPIParams& params):
LLEventAPI(params)
{}
// example operation, registered by LazyEventAPI subclass below
void set_data(const LLSD& event)
{
data = event["data"];
}
};
// LazyEventAPI registrar subclass
class MyRegistrar: public LL::LazyEventAPI<MyListener>
{
using super = LL::LazyEventAPI<MyListener>;
using super::listener;
public:
// LazyEventAPI subclass initializes like a classic LLEventAPI subclass
// constructor, with API name and desc plus add() calls for the defined
// operations
MyRegistrar():
super("Test", "This is a test LLEventAPI")
{
add("set", "This is a set operation", &listener::set_data);
}
};
// Normally we'd declare a static instance of MyRegistrar -- but because we
// want to test both with and without, defer declaration to individual test
// methods.
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
struct lazyeventapi_data
{
lazyeventapi_data()
{
// before every test, reset 'data'
data.clear();
}
~lazyeventapi_data()
{
// after every test, reset LLEventPumps
LLEventPumps::deleteSingleton();
}
};
typedef test_group<lazyeventapi_data> lazyeventapi_group;
typedef lazyeventapi_group::object object;
lazyeventapi_group lazyeventapigrp("lazyeventapi");
template<> template<>
void object::test<1>()
{
set_test_name("LazyEventAPI");
// this is where the magic (should) happen
// 'register' still a keyword until C++17
MyRegistrar regster;
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey"));
ensure_equals("failed to set data", data.asString(), "hey");
}
template<> template<>
void object::test<2>()
{
set_test_name("No LazyEventAPI");
// Because the MyRegistrar declaration in test<1>() is local, because
// it has been destroyed, we fully expect NOT to reach a MyListener
// instance with this post.
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot"));
ensure("accidentally set data", ! data.isDefined());
}
template<> template<>
void object::test<3>()
{
set_test_name("LazyEventAPI metadata");
MyRegistrar regster;
// Of course we have 'regster' in hand; we don't need to search for
// it. But this next test verifies that we can find (all) LazyEventAPI
// instances using LazyEventAPIBase::instance_snapshot. Normally we
// wouldn't search; normally we'd just look at each instance in the
// loop body.
const MyRegistrar* found = nullptr;
for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot())
if ((found = dynamic_cast<const MyRegistrar*>(&registrar)))
break;
ensure("Failed to find MyRegistrar via LLInstanceTracker", found);
ensure_equals("wrong API name", found->getName(), "Test");
ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI");
ensure_equals("wrong API field", found->getDispatchKey(), "op");
// Normally we'd just iterate over *found. But for test purposes,
// actually capture the range of NameDesc pairs in a vector.
std::vector<LL::LazyEventAPIBase::NameDesc> ops{ found->begin(), found->end() };
ensure_equals("failed to find operations", ops.size(), 1);
ensure_equals("wrong operation name", ops[0].first, "set");
ensure_contains("wrong operation desc", ops[0].second, "set operation");
LLSD metadata{ found->getMetadata(ops[0].first) };
ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first);
ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second);
}
} // namespace tut

View File

@ -18,9 +18,12 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "lleventfilter.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "stringize.h"
#include "StringVec.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
#include "../test/debug.h"
@ -32,8 +35,6 @@
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/range.hpp>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH
#include <boost/lambda/lambda.hpp>
@ -205,7 +206,7 @@ struct Vars
void methodnb(NPARAMSb)
{
std::ostringstream vbin;
foreach(U8 byte, bin)
for (U8 byte: bin)
{
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
@ -315,6 +316,31 @@ void freenb(NPARAMSb)
*****************************************************************************/
namespace tut
{
void ensure_has(const std::string& outer, const std::string& inner)
{
ensure(stringize("'", outer, "' does not contain '", inner, "'"),
outer.find(inner) != std::string::npos);
}
template <typename CALLABLE>
std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
{
std::string what =
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
ensure_has(what, exc_frag);
return what;
}
template <typename CALLABLE>
void call_logerr(CALLABLE&& func, const std::string& frag)
{
CaptureLog capture;
// the error should be logged; we just need to stop the exception
// propagating
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
capture.messageWith(frag);
}
struct lleventdispatcher_data
{
Debug debug{"test"};
@ -397,9 +423,9 @@ namespace tut
work.add(name, desc, &Dispatcher::cmethod1, required);
// Non-subclass method with/out required params
addf("method1", "method1", &v);
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1));
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
addf("method1_req", "method1", &v);
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required);
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
/*--------------- Arbitrary params, array style ----------------*/
@ -461,7 +487,7 @@ namespace tut
debug("dft_array_full:\n",
dft_array_full);
// Partial defaults arrays.
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
dft_array_partial[a] =
@ -471,7 +497,7 @@ namespace tut
debug("dft_array_partial:\n",
dft_array_partial);
foreach(LLSD::String a, ab)
for(LLSD::String a: ab)
{
// Generate full defaults maps by zipping (params, dft_array_full).
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
@ -583,6 +609,7 @@ namespace tut
void addf(const std::string& n, const std::string& d, Vars* v)
{
debug("addf('", n, "', '", d, "')");
// This method is to capture in our own DescMap the name and
// description of every registered function, for metadata query
// testing.
@ -598,19 +625,14 @@ namespace tut
{
// 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
// '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<const Dispatcher&>(work))
for (LLEventDispatcher::NameDesc nd: work)
{
DescMap::iterator found = forgotten.find(nd.first);
ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
<< "' we didn't enter"),
ensure(stringize("LLEventDispatcher records function '", nd.first,
"' we didn't enter"),
found != forgotten.end());
ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
"' doesn't match what we entered: '" << found->second << "'"),
ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
"' doesn't match what we entered: '", found->second, "'"),
nd.second, found->second);
// found in our map the name from LLEventDispatcher, good, erase
// our map entry
@ -621,41 +643,49 @@ namespace tut
std::ostringstream out;
out << "LLEventDispatcher failed to report";
const char* delim = ": ";
foreach(const DescMap::value_type& fme, forgotten)
for (const DescMap::value_type& fme: forgotten)
{
out << delim << fme.first;
delim = ", ";
}
ensure(out.str(), false);
throw failure(out.str());
}
}
Vars* varsfor(const std::string& name)
{
VarsMap::const_iterator found = funcvars.find(name);
ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
ensure(STRINGIZE("NULL Vars* for " << name), found->second);
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)
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
outer.find(inner) != std::string::npos);
return tut::call_exc(
[this, func, args]()
{
if (func.empty())
{
work(args);
}
else
{
work(func, args);
}
},
exc_frag);
}
void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
{
std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
work(func, args);
});
ensure_has(threw, exc_frag);
tut::call_logerr([this, func, args](){ work(func, args); }, frag);
}
LLSD getMetadata(const std::string& name)
{
LLSD meta(work.getMetadata(name));
ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
ensure(stringize("No metadata for ", name), meta.isDefined());
return meta;
}
@ -724,7 +754,7 @@ namespace tut
set_test_name("map-style registration with non-array params");
// Pass "param names" as scalar or as map
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
foreach(LLSD ae, inArray(attempts))
for (LLSD ae: inArray(attempts))
{
std::string threw = catch_what<std::exception>([this, &ae](){
work.add("freena_err", "freena", freena, ae);
@ -799,7 +829,7 @@ namespace tut
{
set_test_name("query Callables with/out required params");
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@ -828,19 +858,19 @@ namespace tut
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
llsd::array
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
foreach(LLSD ae, inArray(expected))
for (LLSD ae: inArray(expected))
{
LLSD::Integer arity(ae[0].asInteger());
LLSD names(ae[1]);
LLSD req(LLSD::emptyArray());
if (arity)
req[arity - 1] = LLSD();
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
ensure_equals(stringize("mismatched required for ", nm.asString()),
metadata["required"], req);
ensure("should not have optional", metadata["optional"].isUndefined());
}
@ -854,7 +884,7 @@ namespace tut
// - (Free function | non-static method), map style, no params (ergo
// no defaults)
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@ -884,7 +914,7 @@ namespace tut
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
llsd::array("methodna_map_adft", "methodna_map_mdft"),
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
foreach(LLSD eq, inArray(equivalences))
for (LLSD eq: inArray(equivalences))
{
LLSD adft(eq[0]);
LLSD mdft(eq[1]);
@ -898,8 +928,8 @@ namespace tut
ensure_equals("mdft name", mdft, mmeta["name"]);
ameta.erase("name");
mmeta.erase("name");
ensure_equals(STRINGIZE("metadata for " << adft.asString()
<< " vs. " << mdft.asString()),
ensure_equals(stringize("metadata for ", adft.asString(),
" vs. ", mdft.asString()),
ameta, mmeta);
}
}
@ -915,7 +945,7 @@ namespace tut
// params are required. Also maps containing left requirements for
// partial defaults arrays. Also defaults maps from defaults arrays.
LLSD allreq, leftreq, rightdft;
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
// The map in which all params are required uses params[a] as
// keys, with all isUndefined() as values. We can accomplish that
@ -943,9 +973,9 @@ namespace tut
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
LLSD skipreq(allreq);
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
foreach(const MapEntry& me, inMap(dft_map_partial[a]))
for (const MapEntry& me: inMap(dft_map_partial[a]))
{
skipreq[a].erase(me.first);
}
@ -990,7 +1020,7 @@ namespace tut
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
foreach(LLSD grp, inArray(groups))
for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
LLSD names(grp[0]);
@ -1003,14 +1033,14 @@ namespace tut
optional);
// Loop through 'names'
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
ensure_equals(STRINGIZE(nm << " required mismatch"),
ensure_equals(stringize(nm, " required mismatch"),
metadata["required"], required);
ensure_equals(STRINGIZE(nm << " optional mismatch"),
ensure_equals(stringize(nm, " optional mismatch"),
metadata["optional"], optional);
}
}
@ -1031,13 +1061,7 @@ namespace tut
{
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 = catch_what<std::runtime_error>([this](){
work(LLSDMap("op", "freek"));
});
ensure_has(threw, "bad");
std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
ensure_has(threw, "op");
ensure_has(threw, "freek");
}
@ -1079,7 +1103,7 @@ namespace tut
// LLSD value matching 'required' according to llsd_matches() rules.
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
// Okay, walk through 'tests'.
foreach(const CallablesTriple& tr, tests)
for (const CallablesTriple& tr: tests)
{
// Should be able to pass 'answer' to Callables registered
// without 'required'.
@ -1087,7 +1111,7 @@ namespace tut
ensure_equals("answer mismatch", tr.llsd, answer);
// Should NOT be able to pass 'answer' to Callables registered
// with 'required'.
call_exc(tr.name_req, answer, "bad request");
call_logerr(tr.name_req, answer, "bad request");
// But SHOULD be able to pass 'matching' to Callables registered
// with 'required'.
work(tr.name_req, matching);
@ -1101,17 +1125,20 @@ namespace tut
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);
// functions. It seems pointless to repeat this with every variation:
// (free function | non-static method), (no | arbitrary) args. We
// should only need to engage it for one map-style registration and
// one array-style registration.
// Now that LLEventDispatcher has been extended to treat an LLSD
// scalar as a single-entry array, the error we expect in this case is
// that apply() is trying to pass that non-empty array to a nullary
// function.
call_logerr("free0_array", 17, "LL::apply");
// similarly, apply() doesn't accept an LLSD Map
call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
std::string map_exc("needs a map");
call_exc("free0_map", 17, map_exc);
call_logerr("free0_map", 17, map_exc);
// Passing an array to a map-style function works now! No longer an
// error case!
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
@ -1125,7 +1152,7 @@ namespace tut
("free0_array", "free0_map",
"smethod0_array", "smethod0_map",
"method0_array", "method0_map"));
foreach(LLSD name, inArray(names))
for (LLSD name: inArray(names))
{
// Look up the Vars instance for this function.
Vars* vars(varsfor(name));
@ -1150,15 +1177,21 @@ namespace tut
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...
set_test_name("call array-style functions with wrong-length arrays");
// Could have different wrong-length arrays for *na and for *nb, but
// since they both take 5 params...
LLSD tooshort(llsd::array("this", "array", "too", "short"));
foreach(const LLSD& funcsab, inArray(array_funcs))
LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
LLSD badargs (llsd::array(tooshort, toolong));
for (const LLSD& toosomething: inArray(badargs))
{
foreach(const llsd::MapEntry& e, inMap(funcsab))
for (const LLSD& funcsab: inArray(array_funcs))
{
call_exc(e.second, tooshort, "requires more arguments");
for (const llsd::MapEntry& e: inMap(funcsab))
{
// apply() complains about wrong number of array entries
call_logerr(e.second, toosomething, "LL::apply");
}
}
}
}
@ -1166,7 +1199,7 @@ namespace tut
template<> template<>
void object::test<20>()
{
set_test_name("call array-style functions with (just right | too long) arrays");
set_test_name("call array-style functions with right-size arrays");
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
@ -1178,40 +1211,25 @@ namespace tut
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)
for (LLSD::String a: ab)
{
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() + "'";
expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
debug("expect: ", expect);
// Use substantially the same logic for args and argsplus
LLSD argsarrays(llsd::array(args, argsplus));
// So i==0 selects 'args', i==1 selects argsplus
for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
for (const LLSD& funcsab: inArray(array_funcs))
{
foreach(const LLSD& funcsab, inArray(array_funcs))
for (LLSD::String a: ab)
{
foreach(LLSD::String a, ab)
{
// Reset the Vars instance before each call
Vars* vars(varsfor(funcsab[a]));
*vars = Vars();
work(funcsab[a], argsarrays[i][a]);
ensure_llsd(STRINGIZE(funcsab[a].asString() <<
": expect[\"" << a << "\"] mismatch"),
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.
}
// Reset the Vars instance before each call
Vars* vars(varsfor(funcsab[a]));
*vars = Vars();
work(funcsab[a], args[a]);
ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
}
}
}
@ -1239,7 +1257,7 @@ namespace tut
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
LLSD array_overfull(array_full);
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
array_overfull[a].append("bogus");
}
@ -1253,7 +1271,7 @@ namespace tut
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)
for (LLSD::String a: ab)
{
map_full[a] = zipmap(params[a], array_full[a]);
map_overfull[a] = map_full[a];
@ -1294,21 +1312,360 @@ namespace tut
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
// Treat (full | overfull) (array | map) the same.
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
foreach(const LLSD& args, inArray(argssets))
for (const LLSD& args: inArray(argssets))
{
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
foreach(LLSD::String name, inArray(names[a]))
for (LLSD::String name: inArray(names[a]))
{
// Reset the Vars instance
Vars* vars(varsfor(name));
*vars = Vars();
work(name, args[a]);
ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
// intercept LL_WARNS for the two overfull cases?
}
}
}
}
struct DispatchResult: public LLDispatchListener
{
using DR = DispatchResult;
DispatchResult(): LLDispatchListener("results", "op")
{
add("strfunc", "return string", &DR::strfunc);
add("voidfunc", "void function", &DR::voidfunc);
add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
add("intfunc", "return Integer LLSD", &DR::intfunc);
add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
add("mapfunc", "return map LLSD", &DR::mapfunc);
add("arrayfunc", "return array LLSD", &DR::arrayfunc);
}
std::string strfunc(const std::string& str) const { return "got " + str; }
void voidfunc() const {}
LLSD emptyfunc() const { return {}; }
int intfunc(int i) const { return -i; }
LLSD llsdfunc(const LLSD& event) const
{
LLSD result{ event };
result["with"] = "string";
return result;
}
LLSD mapfunc(int i, const std::string& str) const
{
return llsd::map("i", intfunc(i), "str", strfunc(str));
}
LLSD arrayfunc(int i, const std::string& str) const
{
return llsd::array(intfunc(i), strfunc(str));
}
};
template<> template<>
void object::test<23>()
{
set_test_name("string result");
DispatchResult service;
LLSD result{ service("strfunc", "a string") };
ensure_equals("strfunc() mismatch", result.asString(), "got a string");
}
template<> template<>
void object::test<24>()
{
set_test_name("void result");
DispatchResult service;
LLSD result{ service("voidfunc", LLSD()) };
ensure("voidfunc() returned defined", result.isUndefined());
}
template<> template<>
void object::test<25>()
{
set_test_name("Integer result");
DispatchResult service;
LLSD result{ service("intfunc", -17) };
ensure_equals("intfunc() mismatch", result.asInteger(), 17);
}
template<> template<>
void object::test<26>()
{
set_test_name("LLSD echo");
DispatchResult service;
LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
ensure_equals("llsdfunc() mismatch", result,
llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
}
template<> template<>
void object::test<27>()
{
set_test_name("map LLSD result");
DispatchResult service;
LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
}
template<> template<>
void object::test<28>()
{
set_test_name("array LLSD result");
DispatchResult service;
LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
}
template<> template<>
void object::test<29>()
{
set_test_name("listener error, no reply");
DispatchResult service;
tut::call_exc(
[&service]()
{ service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
"nosuchfunc");
}
template<> template<>
void object::test<30>()
{
set_test_name("listener error with reply");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
LLSD reply{ result.get() };
ensure("no reply", reply.isDefined());
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_has(reply["error"].asString(), "nosuchfunc");
}
template<> template<>
void object::test<31>()
{
set_test_name("listener call to void function");
DispatchResult service;
LLCaptureListener<LLSD> result;
result.set("non-empty");
for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
{
service.post(llsd::map(
"op", func,
"reqid", 17,
"reply", result.getName()));
ensure_equals("reply from " + func, result.get().asString(), "non-empty");
}
}
template<> template<>
void object::test<32>()
{
set_test_name("listener call to string function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "strfunc",
"args", llsd::array("a string"),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string");
}
template<> template<>
void object::test<33>()
{
set_test_name("listener call to map function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "mapfunc",
"args", llsd::array(-7, "value"),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
ensure_equals("bad str from mapfunc", reply["str"], "got value");
}
template<> template<>
void object::test<34>()
{
set_test_name("batched map success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"strfunc", "some string",
"intfunc", 2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad map batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<35>()
{
set_test_name("batched map error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"].asString() };
reply.erase("error");
ensure_has(error, "badfunc");
ensure_has(error, "missing");
ensure_equals(
"bad partial batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<36>()
{
set_test_name("batched map exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17));
// no "reply"
},
"badfunc");
ensure_has(error, "missing");
}
template<> template<>
void object::test<37>()
{
set_test_name("batched array success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2),
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad array batch",
reply,
llsd::map(
"data", llsd::array(
"got some string",
-2,
llsd::array(5, "got other string"),
LLSD())));
}
template<> template<>
void object::test<38>()
{
set_test_name("batched array error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"] };
reply.erase("error");
ensure_has(error, "[1]");
ensure_has(error, "unsupported");
ensure_equals("bad array batch", reply,
llsd::map("data", llsd::array("got some string")));
}
template<> template<>
void object::test<39>()
{
set_test_name("batched array exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17));
// no "reply"
},
"[1]");
ensure_has(error, "unsupported");
}
} // namespace tut

View File

@ -17,6 +17,7 @@
// std headers
#include <functional>
// external library headers
//#include <boost/algorithm/string/join.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/phoenix/core/argument.hpp>
// other Linden headers

View File

@ -226,6 +226,11 @@ public:
return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out);
}
friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self)
{
return self.streamto(out);
}
private:
LLError::FatalFunction mFatalFunction;
LLError::SettingsStoragePtr mOldSettings;

View File

@ -76,6 +76,13 @@ if (WINDOWS)
LINK_FLAGS "/NODEFAULTLIB:LIBCMT"
LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\""
)
elseif (DARWIN)
# Support our "@executable_path/../Resources" load path for our test
# executable. This SHOULD properly be "$<TARGET_FILE_DIR:lltest>/Resources",
# but the CMake $<TARGET_FILE_DIR> generator expression isn't evaluated by
# CREATE_LINK, so fudge it.
file(CREATE_LINK "../sharedlibs/Release/Resources" "${CMAKE_BINARY_DIR}/test/Resources"
SYMBOLIC)
endif (WINDOWS)
set(TEST_EXE $<TARGET_FILE:lltest>)

View File

@ -264,7 +264,7 @@ public:
break;
case tut::test_result::ex:
++mFailedTests;
out << "exception: " << tr.exception_typeid;
out << "exception: " << LLError::Log::demangle(tr.exception_typeid.c_str());
break;
case tut::test_result::warn:
++mFailedTests;