From 342b1bc8ef657107b153f13cd5bf0543b5afb403 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 30 Jul 2018 14:38:13 -0700 Subject: [PATCH] add PVRequestMapper utility to having pvRequest .field mangling Warn if requesting some non-existant fields. Error if no requested fields exist. PVRequestMapper mode enum to select between two "styles" of interpretation. --- src/copy/Makefile | 1 + src/copy/pv/createRequest.h | 164 +++++++++++++ src/copy/requestmapper.cpp | 328 +++++++++++++++++++++++++ testApp/copy/testCreateRequest.cpp | 368 ++++++++++++++++++++++++++++- 4 files changed, 860 insertions(+), 1 deletion(-) create mode 100644 src/copy/requestmapper.cpp diff --git a/src/copy/Makefile b/src/copy/Makefile index 4bb65a0..5a6cdfc 100644 --- a/src/copy/Makefile +++ b/src/copy/Makefile @@ -7,4 +7,5 @@ INC += pv/pvCopy.h LIBSRCS += createRequest.cpp LIBSRCS += requestmask.cpp +LIBSRCS += requestmapper.cpp LIBSRCS += pvCopy.cpp diff --git a/src/copy/pv/createRequest.h b/src/copy/pv/createRequest.h index f3b9aff..f465c68 100644 --- a/src/copy/pv/createRequest.h +++ b/src/copy/pv/createRequest.h @@ -8,9 +8,11 @@ #define CREATEREQUEST_H #include #include +#include #include #include +#include #include @@ -82,6 +84,168 @@ BitSet extractRequestMask(const PVStructure::const_shared_pointer& type, const PVStructure::const_shared_pointer& pvRequestMask, bool expand = true); +/** Helper for implementations of epics::pvAccess::ChannelProvider in interpreting the + * 'field' substructure of a pvRequest. + * Copies between an internal (base) Structure, and a client/user visible (requested) Structure. + * + * @note PVRequestMapper is not re-entrant. It is copyable and swap()able. + */ +class epicsShareClass PVRequestMapper { +public: + enum mode_t { + /** Masking mode. + * + * Requested Structure is identical to Base. + * The 'field' substructure of the provided pvRequest is used to construct a BitSet + * where the bits corresponding to the "selected" fields are set. This mask can be + * access via. requestedMask(). The copy* and mask* methods operate only + * on "selected" fields. + */ + Mask, + /** Slice mode + * + * The Requested Structure is a strict sub-set of the Base Structure containing + * those fields "selected" by the 'field' substructure of the provided pvRequest. + */ + Slice, + }; + + PVRequestMapper(); + //! @see compute() + PVRequestMapper(const PVStructure& base, + const PVStructure& pvRequest, + mode_t mode = Mask); + + //! return to state of default ctor + void reset(); + + //! @returns the Structure of the PVStructure previously passed to compute(). NULL if never computed()'d + inline const StructureConstPtr& base() const { return typeBase; } + //! @returns the Structure which is the selected sub-set of the base Structure. NULL if never computed()'d + inline const StructureConstPtr& requested() const { return typeRequested; } + + /** A mask of all fields in the base structure which are also in the requested structure, + * and any parent/structure "compress" bits. eg. bit 0 is always set. + * + @code + PVRequestMapper mapper(...); + ... + BitSet changed = ...; // a base changed mask + bool wouldcopy = changed.logical_and(mapper.requestedMask()); + // wouldcopy==false means that copyBaseToRequested(..., changed, ...) would be a no-op + @endcode + * + * eg. allows early detection of empty monitor updates. + */ + inline const BitSet& requestedMask() const { return maskRequested; } + + //! @returns A new instance of the requested() Structure + PVStructurePtr buildRequested() const; + //! @returns A new instance of the base() Structure + PVStructurePtr buildBase() const; + + /** (re)compute the selected subset of provided base structure. + * @param base A full base structure. + * Must be "top level" (field offset zero). + * @param pvRequest The user/client provided request modifier + * @param mode Control how the mapping is constructed. @see mode_t for a description of mapping modes. + * + * @post Updates warnings() + * @throws std::runtime_error For errors involving invalid pvRequest + * @throws std::logic_error if the provided base is not a "top level" PVStructure. + */ + void compute(const PVStructure& base, + const PVStructure& pvRequest, + mode_t mode = Mask); + + //! After compute(), check if !warnings().empty() + inline const std::string& warnings() const { return messages; } + + /** Copy field values from Base structure into Requested structure + * + * @param base An instance of the base Structure. Field values are copied from it. + * Need not be the same instance passed to compute(). + * @param baseMask A bit mask selecting those base fields to copy. + * @param request An instance of the requested() Structure. Field values are copied to it. + * @param requestMask A bit mask indicating which requested fields were copied. + * BitSet::clear() is not called. + */ + void copyBaseToRequested( + const PVStructure& base, + const BitSet& baseMask, + PVStructure& request, + BitSet& requestMask + ) const; + + /** Copy field values into Base structure from Requested structure + * + * @param base An instance of the base Structure. Field values are copied into it. + * Need not be the same instance passed to compute(). + * @param baseMask A bit mask indicating which base fields were copied. + * BitSet::clear() is not called. + * @param request An instance of the requested() Structure. Field values are copied from it. + * @param requestMask A bit mask selecting those requested fields to copy. + */ + void copyBaseFromRequested( + PVStructure& base, + BitSet& baseMask, + const PVStructure& request, + const BitSet& requestMask + ) const; + + //! Translate Base bit mask into requested bit mask. + //! BitSet::clear() is not called. + inline void maskBaseToRequested( + const BitSet& baseMask, + BitSet& requestMask + ) const + { _mapMask(baseMask, requestMask, false); } + + //! Translate requested bit mask into base bit mask. + //! BitSet::clear() is not called. + inline void maskBaseFromRequested( + BitSet& baseMask, + const BitSet& requestMask + ) const + { _mapMask(requestMask, baseMask, true); } + + //! Exchange contents of two mappers. O(0) and never throws. + void swap(PVRequestMapper& other); + +private: + bool _compute(const PVStructure& base, const PVStructure& pvReq, + FieldBuilderPtr& builder, bool keepids, unsigned depth); + + void _map(const PVStructure& src, + const BitSet& maskSrc, + PVStructure& dest, + BitSet& maskDest, + bool dir_r2b) const; + void _mapMask(const BitSet& maskSrc, + BitSet& maskDest, + bool dir_r2b) const; + + StructureConstPtr typeBase, typeRequested; + BitSet maskRequested; + // Map between field offsets of base and requested Structures. + // Include all fields, both leaf and sub-structure. + struct Mapping { + size_t to; // offset in destination Structure + BitSet tomask, // if !leaf these are the other bits in the destination mask to changed + frommask; // if !leaf these are the other bits in the source mask to be copied + bool valid; // only true in (sparse) base -> requested mapping + bool leaf; // not a (sub)Structure? + Mapping() :valid(false) {} + Mapping(size_t to, bool leaf) :to(to), valid(true), leaf(leaf) {} + }; + typedef std::vector mapping_t; + mapping_t base2req, req2base; + + std::string messages; + + mutable BitSet scratch; // avoid temporary allocs. (we aren't re-entrant!) +}; + }} #endif /* CREATEREQUEST_H */ diff --git a/src/copy/requestmapper.cpp b/src/copy/requestmapper.cpp new file mode 100644 index 0000000..bb11db1 --- /dev/null +++ b/src/copy/requestmapper.cpp @@ -0,0 +1,328 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define epicsExportSharedSymbols +#include +#include +#include + +// Our arbitrary limit on pvRequest structure depth to bound stack usage during recursion +static const unsigned maxDepth = 5; + +namespace epics{namespace pvData { + +PVRequestMapper::PVRequestMapper() {} + +PVRequestMapper::PVRequestMapper(const PVStructure &base, + const PVStructure &pvRequest, + mode_t mode) +{ + compute(base, pvRequest, mode); +} + +PVStructurePtr PVRequestMapper::buildRequested() const +{ + if(!typeRequested) + THROW_EXCEPTION2(std::logic_error, "No mapping compute()d"); + return getPVDataCreate()->createPVStructure(typeRequested); +} + +PVStructurePtr PVRequestMapper::buildBase() const +{ + if(!typeBase) + THROW_EXCEPTION2(std::logic_error, "No mapping compute()d"); + return getPVDataCreate()->createPVStructure(typeBase); +} + +void PVRequestMapper::compute(const PVStructure &base, + const PVStructure &pvRequest, + mode_t mode) +{ + if(base.getFieldOffset()!=0) + THROW_EXCEPTION2(std::logic_error, "Mapper must be used with top level PVStructure"); + + bool ok = true; + + // we want to be transactional, which requires a second copy of everything. + PVRequestMapper temp; + + // whether to preserve IDs of partial structures. + bool keepids = false; + PVScalar::const_shared_pointer pbp(pvRequest.getSubField("record._options.keepIDs")); + try { + if(pbp) keepids = pbp->getAs(); + }catch(std::runtime_error& e){ + std::ostringstream msg; + msg<<"Can't parse keepIDs : '"<("field")); + if(!fields || fields->getPVFields().empty()) { + // not selection, or empty selection, treated as select all + temp.typeBase = temp.typeRequested = base.getStructure(); + + for(size_t i=1, N=base.getNextFieldOffset(); icreateFieldBuilder()); + + if(keepids) + builder = builder->setId(base.getStructure()->getID()); + + ok &= temp._compute(base, *fields, builder, keepids, 0); // fills in builder + + temp.typeBase = base.getStructure(); + temp.typeRequested = builder->createStructure(); + // possible that typeBase==typeRequested if all fields explicitly selected + } + + if(mode==Mask) { + // short circuit use of masked Structure, but keep maskRequested + temp.typeRequested = temp.typeBase; + } + + { + PVStructurePtr proto(getPVDataCreate()->createPVStructure(temp.typeRequested)); + + // base -> request may be sparce mapping + temp.base2req.resize(base.getNextFieldOffset()); + // request -> base is dense mapping + temp.req2base.resize(proto->getNextFieldOffset()); + + // special handling for whole structure mapping. in part because getSubField(0) isn't allowed + temp.base2req[0] = Mapping(0, false); + temp.req2base[0] = Mapping(0, false); + + // Iterate prototype of requested to map with base field offsets. + // which is handled as a special case below. + // We also don't try to prevent redundant copies if both leaf and compress bits are set. + for(size_t r=1, N=proto->getNextFieldOffset(); rgetSubFieldT(r)), + fld_base(base.getSubFieldT(fld_req->getFullName())); + const size_t b = fld_base->getFieldOffset(); + + if(!temp.requestedMask().get(b)) + continue; + + bool leaf = fld_base->getField()->getType()!=structure; + + // initialize mapping when our bit is set + temp.base2req[b] = Mapping(r, leaf); + temp.req2base[r] = Mapping(b, leaf); + + // add ourself to all "compress" bit mappings of enclosing structures + for(const PVStructure *parent = fld_req->getParent(); parent; parent = parent->getParent()) { + temp.req2base[parent->getFieldOffset()].tomask .set(b); + temp.req2base[parent->getFieldOffset()].frommask.set(r); + } + + for(const PVStructure *parent = fld_base->getParent(); parent; parent = parent->getParent()) { + temp.base2req[parent->getFieldOffset()].tomask .set(r); + temp.base2req[parent->getFieldOffset()].frommask.set(b); + } + } + } + + temp.maskRequested.set(0); + + if(temp.maskRequested.nextSetBit(1)==-1) { + ok = false; + temp.messages+="Empty field selection"; + } + + if(!ok) + throw std::runtime_error(temp.messages); + + swap(temp); +} + +bool PVRequestMapper::_compute(const PVStructure& base, const PVStructure& pvReq, + FieldBuilderPtr& builder, bool keepids, unsigned depth) +{ + bool ok = true; + const StringArray& reqNames = pvReq.getStructure()->getFieldNames(); + + for(size_t i=0, N=reqNames.size(); igetFields()[i]; + + if(subReq->getType()!=structure) { + // pvRequest .field was not properly composed + std::ostringstream msg; + // not a great warning message as it doesn't distinguish 'a.value' from 'b.value', + // but getFullName() whould prefix with 'field.', which would probably cause + // more frequent confusion... + msg<<"request invalid '"<getFieldNames()[i]<<"' "; + messages+=msg.str(); + ok = false; + + } else if(!subtype) { + // requested field does not actually exist in base + std::ostringstream msg; + msg<<"No field '"<getFieldNames()[i]<<"' "; + messages+=msg.str(); + + } else if(depth>=maxDepth // exceeds max recursion depth + || subtype->getField()->getType()!=structure // requested field is a leaf + || static_cast(*subReq).getFieldNames().empty() // requests all sub-fields + ) + { + // just add the whole thing + builder = builder->add(reqNames[i], subtype->getField()); + for(size_t j=subtype->getFieldOffset(), N=subtype->getNextFieldOffset(); jgetField()->getType()!=structure + && !static_cast(*subReq).getFieldNames().empty()) + { + // attempt to select below a leaf field + std::ostringstream msg; + msg<<"Leaf field '"<=maxDepth) { + std::ostringstream msg; + msg<<"selection truncated at '"<(*subtype); + + builder = builder->addNestedStructure(reqNames[i]); + maskRequested.set(substruct.getFieldOffset()); + + if(keepids) + builder = builder->setId(substruct.getStructure()->getID()); + + _compute(substruct, + static_cast(*pvReq.getPVFields()[i]), + builder, keepids, depth+1u); + + builder = builder->endNested(); + } + } + return ok; +} + +void PVRequestMapper::copyBaseToRequested( + const PVStructure& base, + const BitSet& baseMask, + PVStructure& request, + BitSet& requestMask +) const { + assert(base.getStructure()==typeBase); + assert(request.getStructure()==typeRequested); + _map(base, baseMask, request, requestMask, false); +} + +void PVRequestMapper::copyBaseFromRequested( + PVStructure& base, + BitSet& baseMask, + const PVStructure& request, + const BitSet& requestMask +) const { + assert(base.getStructure()==typeBase); + assert(request.getStructure()==typeRequested); + _map(request, requestMask, base, baseMask, true); +} + +void PVRequestMapper::_map(const PVStructure& src, const BitSet& maskSrc, + PVStructure& dest, BitSet& maskDest, + bool dir_r2b) const +{ + { + scratch = maskSrc; + const mapping_t& map = dir_r2b ? req2base : base2req; + + assert(map.size()==src.getNumberFields()); + + for(int32 i=scratch.nextSetBit(0), N=map.size(); i>=0 && i requested mapping can have holes + + } else if(M.leaf) { + // just copy + dest.getSubFieldT(M.to)->copy(*src.getSubFieldT(i)); + maskDest.set(M.to); + + } else { + // set bits of all sub-fields (in requested structure) + // these indicies are always >i + scratch |= M.frommask; + + // we will also set the individual bits, but if a compress bit is set in the input, + // then set the corresponding bit in the output. + maskDest.set(M.to); + } + } + } +} + +void PVRequestMapper::_mapMask(const BitSet& maskSrc, + BitSet& maskDest, + bool dir_r2b) const +{ + if(maskSrc.isEmpty()) { + // no-op + + } else { + const mapping_t& map = dir_r2b ? req2base : base2req; + + for(int32 i=maskSrc.nextSetBit(0), N=map.size(); i>=0 && i requested mapping can have holes + + } else { + maskDest.set(M.to); + + if(!M.leaf) { + maskDest |= M.tomask; + } + } + } + } + +} + +void PVRequestMapper::swap(PVRequestMapper& other) +{ + typeBase.swap(other.typeBase); + typeRequested.swap(other.typeRequested); + maskRequested.swap(other.maskRequested); + base2req.swap(other.base2req); + req2base.swap(other.req2base); + messages.swap(other.messages); + scratch.swap(other.scratch); // paranoia +} + +void PVRequestMapper::reset() +{ + typeBase.reset(); + typeRequested.reset(); + maskRequested.clear(); + base2req.clear(); + req2base.clear(); + messages.clear(); + scratch.clear(); // paranoia +} + +}} //namespace epics::pvData diff --git a/testApp/copy/testCreateRequest.cpp b/testApp/copy/testCreateRequest.cpp index eaf8ec4..70667cf 100644 --- a/testApp/copy/testCreateRequest.cpp +++ b/testApp/copy/testCreateRequest.cpp @@ -434,14 +434,380 @@ static void testMask() .set(V->getSubField("A")->getFieldOffset())); } +static +void testMapper(PVRequestMapper::mode_t mode) +{ + testDiag("=== %s mode==%d", CURRENT_FUNCTION, (int)mode); + { + testDiag("Map full structure"); + + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + PVRequestMapper mapper(*base, *createRequest(""), mode); + + testEqual(mapper.requested(), maskingType); + + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("A")->getFieldOffset()) + .set(base->getSubFieldT("B")->getFieldOffset()) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.D")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + + PVStructurePtr req(getPVDataCreate()->createPVStructure(mapper.requested())); + + base->getSubFieldT("A")->put(1); + base->getSubFieldT("B")->put(42); + + BitSet output; + mapper.copyBaseToRequested(*base, BitSet().set(0), *req, output); + + testFieldEqual(req, "A", 1); + testFieldEqual(req, "B", 42); + + req->getSubFieldT("A")->put(2); + req->getSubFieldT("B")->put(43); + + mapper.copyBaseFromRequested(*base, output, *req, BitSet().set(0)); + + testFieldEqual(req, "A", 2); + testFieldEqual(req, "B", 43); + testEqual(mapper.requestedMask(), BitSet().set(0).set(1).set(2).set(3).set(4).set(5).set(6)); + } + { + testDiag("Map single leaf field"); + + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + PVRequestMapper mapper(*base, *createRequest("field(B)"), mode); + + if(mode==PVRequestMapper::Slice) + testNotEqual(mapper.requested(), maskingType); + else + testEqual(mapper.requested(), maskingType); + + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("B")->getFieldOffset())); + + PVStructurePtr req(getPVDataCreate()->createPVStructure(mapper.requested())); + + if(mode==PVRequestMapper::Slice) + testOk1(!req->getSubField("A")); + + base->getSubFieldT("A")->putFrom(11); + base->getSubFieldT("B")->putFrom(42); + + BitSet output; + mapper.copyBaseToRequested(*base, BitSet().set(0), *req, output); + + if(mode!=PVRequestMapper::Slice) + testFieldEqual(req, "A", 0); + testFieldEqual(req, "B", 42); + + req->getSubFieldT("B")->putFrom(43); + + mapper.copyBaseFromRequested(*base, output, *req, BitSet().set(0)); + + testFieldEqual(req, "B", 43); + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("B")->getFieldOffset())); + + BitSet cmp; + mapper.maskBaseToRequested(BitSet().set(base->getSubFieldT("B")->getFieldOffset()), cmp); + testEqual(cmp, BitSet() + .set(req->getSubFieldT("B")->getFieldOffset())); + + cmp.clear(); + mapper.maskBaseFromRequested(cmp, BitSet() + .set(req->getSubFieldT("B")->getFieldOffset())); + testEqual(cmp, BitSet() + .set(base->getSubFieldT("B")->getFieldOffset())); + } + { + testDiag("Map two sub-fields"); + + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + PVRequestMapper mapper(*base, *createRequest("B,C.D"), mode); + + if(mode==PVRequestMapper::Slice) + testNotEqual(mapper.requested(), maskingType); + else + testEqual(mapper.requested(), maskingType); + + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("B")->getFieldOffset()) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.D")->getFieldOffset())); + + PVStructurePtr req(getPVDataCreate()->createPVStructure(mapper.requested())); + + if(mode==PVRequestMapper::Slice) + testOk1(!req->getSubField("A")); + + base->getSubFieldT("A")->putFrom(11); + base->getSubFieldT("B")->putFrom(1); + base->getSubFieldT("C.D")->putFrom(42); + + BitSet output; + mapper.copyBaseToRequested(*base, BitSet().set(0), *req, output); + + if(mode!=PVRequestMapper::Slice) + testFieldEqual(req, "A", 0); + testFieldEqual(req, "B", 1); + testFieldEqual(req, "C.D", 42); + + req->getSubFieldT("B")->putFrom(2); + req->getSubFieldT("C.D")->putFrom(43); + + mapper.copyBaseFromRequested(*base, output, *req, BitSet().set(0)); + + testFieldEqual(req, "B", 2); + testFieldEqual(req, "C.D", 43); + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("B")->getFieldOffset()) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.D")->getFieldOffset())); + } + { + testDiag("Map entire sub-structure"); + + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + PVRequestMapper mapper(*base, *createRequest("field(C.E)"), mode); + + if(mode==PVRequestMapper::Slice) + testNotEqual(mapper.requested(), maskingType); + else + testEqual(mapper.requested(), maskingType); + + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + + + PVStructurePtr req(getPVDataCreate()->createPVStructure(mapper.requested())); + + if(mode==PVRequestMapper::Slice) + testOk1(!req->getSubField("A")); + + base->getSubFieldT("A")->putFrom(11); + base->getSubFieldT("C.E.F")->putFrom(42); + + BitSet output; + mapper.copyBaseToRequested(*base, BitSet().set(0), *req, output); + + if(mode!=PVRequestMapper::Slice) + testFieldEqual(req, "A", 0); + testFieldEqual(req, "C.E.F", 42); + + req->getSubFieldT("C.E.F")->putFrom(43); + + mapper.copyBaseFromRequested(*base, output, *req, BitSet().set(0)); + + testFieldEqual(req, "C.E.F", 43); + testEqual(mapper.requestedMask(), BitSet().set(0) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + + BitSet cmp; + mapper.maskBaseToRequested(BitSet() + .set(base->getSubFieldT("C")->getFieldOffset()), cmp); + testEqual(cmp, BitSet() + .set(req->getSubFieldT("C")->getFieldOffset()) + .set(req->getSubFieldT("C.E")->getFieldOffset()) + .set(req->getSubFieldT("C.E.F")->getFieldOffset())); + + cmp.clear(); + mapper.maskBaseFromRequested(cmp, BitSet() + .set(req->getSubFieldT("C")->getFieldOffset())); + testEqual(cmp, BitSet() + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + } +} + +struct MapperMask { + PVStructurePtr base, req; + BitSet bmask, rmask; + PVRequestMapper mapper; + + MapperMask(PVRequestMapper::mode_t mode) { + base = getPVDataCreate()->createPVStructure(maskingType); + mapper.compute(*base, *createRequest("field(B,C.E)"), mode); + req = getPVDataCreate()->createPVStructure(mapper.requested()); + reset(); + } + + void reset() { + base->getSubFieldT("B")->putFrom(1); + base->getSubFieldT("C.E.F")->putFrom(3); + req->getSubFieldT("B")->putFrom(11); + req->getSubFieldT("C.E.F")->putFrom(13); + } + + void check(int32 bB, int32 bCEF, int32 rB, int32 rCEF) { + testFieldEqual(base, "B", bB); + testFieldEqual(base, "C.E.F", bCEF); + testFieldEqual(req, "B", rB); + testFieldEqual(req, "C.E.F", rCEF); + } + + void testEmptyMaskB2R() { + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 11, 13); + testEqual(bmask, BitSet()); + testEqual(rmask, BitSet()); + } + + void testEmptyMaskR2B() { + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(1, 3, 11, 13); + testEqual(bmask, BitSet()); + testEqual(rmask, BitSet()); + } + + void testAllMaskB2R() { + bmask.set(0); + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 1, 3); + testEqual(rmask, BitSet() + .set(0) + .set(req->getSubFieldT("B")->getFieldOffset()) + .set(req->getSubFieldT("C")->getFieldOffset()) + .set(req->getSubFieldT("C.E")->getFieldOffset()) + .set(req->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testAllMaskR2B() { + rmask.set(0); + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(11, 13, 11, 13); + testEqual(bmask, BitSet() + .set(0) + .set(base->getSubFieldT("B")->getFieldOffset()) + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskOneB2R() { + bmask.set(base->getSubFieldT("B")->getFieldOffset()); + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 1, 13); + testEqual(rmask, BitSet() + .set(req->getSubFieldT("B")->getFieldOffset())); + } + + void testMaskOneR2B() { + rmask.set(req->getSubFieldT("B")->getFieldOffset()); + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(11, 3, 11, 13); + testEqual(bmask, BitSet() + .set(base->getSubFieldT("B")->getFieldOffset())); + } + + void testMaskOtherB2R() { + bmask.set(base->getSubFieldT("C.E.F")->getFieldOffset()); + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 11, 3); + testEqual(rmask, BitSet() + .set(req->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskOtherR2B() { + rmask.set(req->getSubFieldT("C.E.F")->getFieldOffset()); + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(1, 13, 11, 13); + testEqual(bmask, BitSet() + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskSub1B2R() { + bmask.set(base->getSubFieldT("C.E")->getFieldOffset()); + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 11, 3); + testEqual(rmask, BitSet() + .set(req->getSubFieldT("C.E")->getFieldOffset()) + .set(req->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskSub1R2B() { + rmask.set(req->getSubFieldT("C.E")->getFieldOffset()); + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(1, 13, 11, 13); + testEqual(bmask, BitSet() + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskSub2B2R() { + bmask.set(base->getSubFieldT("C")->getFieldOffset()); + mapper.copyBaseToRequested(*base, bmask, *req, rmask); + check(1, 3, 11, 3); + testEqual(rmask, BitSet() + .set(req->getSubFieldT("C")->getFieldOffset()) + .set(req->getSubFieldT("C.E")->getFieldOffset()) + .set(req->getSubFieldT("C.E.F")->getFieldOffset())); + } + + void testMaskSub2R2B() { + rmask.set(req->getSubFieldT("C")->getFieldOffset()); + mapper.copyBaseFromRequested(*base, bmask, *req, rmask); + check(1, 13, 11, 13); + testEqual(bmask, BitSet() + .set(base->getSubFieldT("C")->getFieldOffset()) + .set(base->getSubFieldT("C.E")->getFieldOffset()) + .set(base->getSubFieldT("C.E.F")->getFieldOffset())); + } +}; + +void testMaskWarn() +{ + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + PVRequestMapper mapper(*base, *createRequest("field(B,invalid)"), PVRequestMapper::Slice); + + testEqual(mapper.warnings(), "No field 'invalid' "); +} + +void testMaskErr() +{ + PVStructurePtr base(getPVDataCreate()->createPVStructure(maskingType)); + testThrows(std::runtime_error, PVRequestMapper mapper(*base, *createRequest("field(invalid)"), PVRequestMapper::Slice)); +} + } // namespace MAIN(testCreateRequest) { - testPlan(141); + testPlan(329); testCreateRequestInternal(); testBadRequest(); testMask(); + testMapper(PVRequestMapper::Slice); + testMapper(PVRequestMapper::Mask); +#undef TEST_METHOD +#define TEST_METHOD(KLASS, METHOD) \ + { \ + testDiag("------- %s::%s Mask --------", #KLASS, #METHOD); \ + { KLASS inst(PVRequestMapper::Mask); inst.METHOD(); } \ + testDiag("------- %s::%s Slice --------", #KLASS, #METHOD); \ + { KLASS inst(PVRequestMapper::Slice); inst.METHOD(); } \ + } + TEST_METHOD(MapperMask, testEmptyMaskB2R); + TEST_METHOD(MapperMask, testEmptyMaskR2B); + TEST_METHOD(MapperMask, testAllMaskB2R); + TEST_METHOD(MapperMask, testAllMaskR2B); + TEST_METHOD(MapperMask, testMaskOneB2R); + TEST_METHOD(MapperMask, testMaskOneR2B); + TEST_METHOD(MapperMask, testMaskOtherB2R); + TEST_METHOD(MapperMask, testMaskOtherR2B); + TEST_METHOD(MapperMask, testMaskSub1B2R); + TEST_METHOD(MapperMask, testMaskSub1R2B); + TEST_METHOD(MapperMask, testMaskSub2B2R); + TEST_METHOD(MapperMask, testMaskSub2R2B); + testMaskWarn(); + testMaskErr(); return testDone(); }