start PVD

This commit is contained in:
Michael Davidsaver
2019-11-20 10:53:29 -08:00
parent 1f6502aef7
commit 801d295c1f
17 changed files with 2617 additions and 11 deletions
+5
View File
@@ -37,12 +37,17 @@ INC += pvxs/version.h
INC += pvxs/versionNum.h
INC += pvxs/log.h
INC += pvxs/unittest.h
INC += pvxs/util.h
INC += pvxs/sharedArray.h
INC += pvxs/data.h
INC += pvxs/server.h
LIBRARY = pvxs
LIB_SRCS += log.cpp
LIB_SRCS += unittest.cpp
LIB_SRCS += util.cpp
LIB_SRCS += data.cpp
LIB_SRCS += evhelper.cpp
LIB_SRCS += udp_collector.cpp
LIB_SRCS += server.cpp
+1039
View File
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#ifndef DATAENCODE_H
#define DATAENCODE_H
#include <stdexcept>
#include <functional>
#include <ostream>
#include <list>
#include <map>
#include <utility>
#include <type_traits>
#include <memory>
#include <pvxs/data.h>
#include <pvxs/sharedArray.h>
#include "pvaproto.h"
#include "utilpvt.h"
#include "dataimpl.h"
namespace pvxs {
namespace impl {
template<typename Buf>
void to_wire(Buf& buf, const FieldDesc* cur)
{
// we assume FieldDesc* is valid (checked on creation)
to_wire(buf, cur->code.code);
// other than (array of) struct and union, encoding is simple
switch(cur->code.code) {
case TypeCode::StructA:
case TypeCode::UnionA:
to_wire(buf, cur+1);
break;
case TypeCode::Struct:
case TypeCode::Union:
to_wire(buf, cur->id);
to_wire(buf, Size{cur->miter.size()});
for(auto& pair : cur->miter) {
to_wire(buf, pair.first);
to_wire(buf, cur+pair.second); // jump forward in FieldDesc array and recurse!
}
break;
default:
break;
}
}
typedef std::map<uint16_t, std::vector<FieldDesc>> TypeStore;
struct TypeDeserContext {
std::vector<FieldDesc>& descs;
TypeStore& cache;
};
template<typename Buf>
void from_wire(Buf& buf, TypeDeserContext& ctxt, unsigned depth=0)
{
if(!buf.good() || depth>20) {
buf.fault();
return;
}
TypeCode code;
from_wire(buf, code.code);
const size_t index = ctxt.descs.size(); // index of first node we add to ctxt.descs[]
if(code.code==0xfd) {
// update cache
uint16_t key=0;
from_wire(buf, key);
from_wire(buf, ctxt, depth+1u);
if(!buf.good() || index==ctxt.descs.size()) {
buf.fault();
return;
} else {
auto& entry = ctxt.cache[key];
// copy new node, and any decendents into cache
entry.resize(ctxt.descs.size()-index);
std::copy(ctxt.descs.begin()+index,
ctxt.descs.end(),
entry.begin());
}
} else if(code.code==0xfe) {
// fetch cache
uint16_t key=0;
from_wire(buf, key);
auto it = ctxt.cache.find(key);
if(it==ctxt.cache.end()) {
buf.fault();
}
if(!buf.good() || it->second.empty()) {
buf.fault();
return;
} else {
// copy from cache
ctxt.descs.resize(index+it->second.size());
std::copy(it->second.begin(),
it->second.end(),
ctxt.descs.begin()+index);
}
} else if(code.code!=0xff && code.code&0x10) {
// fixed length is deprecated
buf.fault();
} else {
// actual field
ctxt.descs.emplace_back();
{
auto& fld = ctxt.descs.back();
fld.code = code;
fld.hash = code.code;
}
switch(code.code) {
case TypeCode::StructA:
case TypeCode::UnionA:
from_wire(buf, ctxt, depth+1);
if(!buf.good() || ctxt.descs.size()==index || ctxt.descs[index+1].code!=code.scalarOf()) {
buf.fault();
return;
}
break;
case TypeCode::Struct:
case TypeCode::Union: {
from_wire(buf, ctxt.descs.back().id);
Size nfld{0};
std::string name;
from_wire(buf, nfld); // number of children
{
auto& fld = ctxt.descs.back();
fld.miter.reserve(nfld.size);
fld.hash ^= std::hash<std::string>{}(fld.id);
}
for(auto i: range(nfld.size)) {
(void)i;
const size_t cindex = ctxt.descs.size(); // index of this child
from_wire(buf, name);
from_wire(buf, ctxt, depth+1);
if(!buf.good() || cindex>=ctxt.descs.size()) {
buf.fault();
return;
}
// descs may be re-allocated (invalidating previous refs.)
auto& fld = ctxt.descs[index];
auto& cfld = ctxt.descs[cindex];
// update hash
// TODO investigate better ways to combine hashes
fld.hash ^= std::hash<std::string>{}(name) ^ cfld.hash;
// update field refs.
fld.miter.emplace_back(name, cindex-index);
fld.mlookup[name] = cindex-index;
name+='.';
if(code.code==TypeCode::Struct && code==cfld.code) {
// copy decendent indicies for sub-struct
for(auto& pair : cfld.mlookup) {
fld.mlookup[name+pair.first] = cindex + pair.second;
}
}
}
}
break;
default:
// not handling fixed/bounded
// other types have simple/single node description
switch(code.code&~0x08) {
case TypeCode::Bool:
case TypeCode::Int8:
case TypeCode::Int16:
case TypeCode::Int32:
case TypeCode::Int64:
case TypeCode::UInt8:
case TypeCode::UInt16:
case TypeCode::UInt32:
case TypeCode::UInt64:
case TypeCode::Float32:
case TypeCode::Float64:
case TypeCode::String:
case TypeCode::Any:
break;
default:
buf.fault();
break;
}
}
ctxt.descs[index].num_index = ctxt.descs.size()-index;
}
}
}} // namespace pvxs::impl
#endif // DATAENCODE_H
+114
View File
@@ -0,0 +1,114 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#ifndef DATAIMPL_H
#define DATAIMPL_H
#include <string>
#include <map>
#include <pvxs/data.h>
#include <pvxs/sharedArray.h>
namespace pvxs {
namespace impl {
/** Describes a single field, leaf or otherwise, in a nested structure.
*
* FieldDesc are always stored depth first as a contigious array,
* with offset to decendent fields given as positive integers relative
* to the current field. (not possible to jump _back_)
*
* We deal with two different numeric values:
* 1. indicies in this FieldDesc array. found in FieldDesc::mlookup and FieldDesc::miter
* Relative to current position in FieldDesc array. (aka this+n)
* 2. offsets in associated FieldStorage array. found in FieldDesc::index
* Relative to current FieldDesc*.
*/
struct FieldDesc {
// type ID string (struct/union)
std::string id;
// Lookup of all decendent fields of this Structure or Union.
// "fld.sub.leaf" -> rel index
std::map<std::string, size_t> mlookup;
// child iteration. child# -> ("sub", rel index)
std::vector<std::pair<std::string, size_t>> miter;
// hash of this type (aggragating from children)
// created using the code ^ id ^ (child_name ^ child_hash)*N
size_t hash;
// abs. offset in enclosing top Structure/Union. (not abs. offset of FieldDesc array)
// used to navigate FieldStorage array
size_t offset=0, next_offset=0;
// number of FieldDesc nodes which describe this node and decendents. Inclusive. always >=1
uint16_t num_index=0;
TypeCode code{TypeCode::Null};
template<typename T>
FieldDesc* operator[](T key) {
auto it = mlookup.find(key);
if(it!=mlookup.end()) {
return this+it.second;
} else {
return nullptr;
}
}
// number of FieldDesc nodes which describe this node. Inclusive. always size()>=1
inline size_t size() const { return num_index; }
};
struct StructTop;
struct FieldStorage {
/* Storage for field value. depends on type code.
*
* All array types stored as shared_array<const void> which includes full type info
* Integers promoted to either int64_t or uint64_t.
* Bool promoted to uint64_t
* Reals promoted to double.
* String stored as std::string
* Compound (Struct, Union, Any) stored as shared_ptr<FieldStorage>
*/
std::aligned_union<8,
double, // Real
uint64_t, // Bool, Integer
std::string, // String
Value, // Union, Any
shared_array<const void> // array of POD, std::string, or std::shared_ptr<Value>
>::type store;
// index of this field in StructTop::members
StructTop *top;
StoreType code=StoreType::Null; // duplicates associated FieldDesc::code
void init(const FieldDesc* desc);
void deinit();
~FieldStorage();
size_t index() const;
template<typename T>
T& as() { return *reinterpret_cast<T*>(&store); }
template<typename T>
const T& as() const { return *reinterpret_cast<const T*>(&store); }
};
struct StructTop {
std::vector<bool> valid;
std::shared_ptr<const FieldDesc> desc;
std::vector<FieldStorage> members;
};
static_assert (std::is_standard_layout<StructTop>{}, "Needed for offsetof()");
using Type = std::shared_ptr<const FieldDesc>;
PVXS_API
void FieldDesc_calculate_offset(FieldDesc* top);
PVXS_API
std::ostream& operator<<(std::ostream& strm, const FieldDesc* desc);
}} // namespace pvxs::impl
#endif // DATAIMPL_H
-1
View File
@@ -43,7 +43,6 @@ struct default_delete<evbuffer> {
}
namespace pvxs {namespace impl {
using namespace pvxs;
//! unique_ptr which is never constructed with NULL
template<typename T>
+381
View File
@@ -0,0 +1,381 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#ifndef PVXS_DATA_H
#define PVXS_DATA_H
#include <stdexcept>
#include <vector>
#include <ostream>
#include <memory>
#include <typeinfo>
#include <tuple>
#include <pvxs/version.h>
#include <pvxs/sharedArray.h>
namespace pvxs {
class Value;
//! selector for union FieldStorage::store
enum struct StoreType : uint8_t {
Null, //!< no associate storage
UInteger, //!< uint64_t
Integer, //!< int64_t
Real, //!< double
String, //!< std::string
Compound, //!< Value
Array, //!< shared_array<const void>
};
namespace impl {
struct FieldStorage;
struct FieldDesc;
//! maps T to one of the types which can be stored in the FieldStorage::store union
//! typename StorageMap<T>::store_t is, if existant, is one such type.
//! Can store_t shall be cast-able to/from T.
//! StorageMap<T>::code is the associated StoreType.
template<typename T, typename Enable=void>
struct StorageMap;
// map signed integers to int64_t
template<typename T>
struct StorageMap<T, typename std::enable_if<std::is_integral<T>{} && std::is_signed<T>{}>::type>
{ typedef int64_t store_t; static constexpr StoreType code{StoreType::Integer}; };
// map unsigned integers, and bool, to uint64_t
template<typename T>
struct StorageMap<T, typename std::enable_if<std::is_integral<T>{} && !std::is_signed<T>{}>::type>
{ typedef uint64_t store_t; static constexpr StoreType code{StoreType::UInteger}; };
// map floating point to double. (truncates long double, but then PVA doesn't >8 byte primatives anyway support anyway)
template<typename T>
struct StorageMap<T, typename std::enable_if<std::is_floating_point<T>{}>::type>
{ typedef double store_t; static constexpr StoreType code{StoreType::Real}; };
template<>
struct StorageMap<std::string>
{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; };
template<>
struct StorageMap<char*>
{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; };
template<>
struct StorageMap<shared_array<const void>>
{ typedef shared_array<const void> store_t; static constexpr StoreType code{StoreType::Array}; };
template<>
struct StorageMap<Value>
{ typedef Value store_t; static constexpr StoreType code{StoreType::Compound}; };
} // namespace impl
//! Groups of related types
enum struct Kind : uint8_t {
Bool = 0x00,
Integer = 0x20,
Real = 0x40,
String = 0x60,
Compound = 0x80,
Null = 0xe0,
};
//! A particular type
struct TypeCode {
//! actual complete (scalar) type code.
enum code_t : uint8_t {
Bool = 0x00,
BoolA = 0x08,
Int8 = 0x20,
Int16 = 0x21,
Int32 = 0x22,
Int64 = 0x23,
UInt8 = 0x24,
UInt16 = 0x25,
UInt32 = 0x26,
UInt64 = 0x27,
Int8A = 0x28,
Int16A = 0x29,
Int32A = 0x2a,
Int64A = 0x2b,
UInt8A = 0x2c,
UInt16A = 0x2d,
UInt32A = 0x2e,
UInt64A = 0x2f,
Float32 = 0x42,
Float64 = 0x43,
Float32A= 0x4a,
Float64A= 0x4b,
String = 0x60,
StringA = 0x68,
Struct = 0x80,
Union = 0x81,
Any = 0x82,
StructA = 0x88,
UnionA = 0x89,
AnyA = 0x8a,
// 0xfd - cache update w/ full
// 0xfe - cache fetch
Null = 0xff,
};
//! the actual type code. eg. for switch()
code_t code;
bool valid() const;
Kind kind() const { return Kind(code&0xe0); }
//! size()==1<<order()
uint8_t order() const { return code&3; }
//! Size in bytes for simple kinds (Bool, Integer, Real)
uint8_t size() const { return 1u<<order(); }
//! For Integer kind
bool isunsigned() const { return code&0x04; }
//! For all
bool isarray() const { return code&0x08; }
constexpr TypeCode() :code(Null) {}
constexpr explicit TypeCode(uint8_t c) :code(code_t(c)) {}
constexpr TypeCode(code_t c) :code(c) {}
//! associated array of type
constexpr TypeCode arrayOf() const {return TypeCode{uint8_t(code|0x08)};}
//! associated not array of type
constexpr TypeCode scalarOf() const {return TypeCode{uint8_t(code&~0x08)};}
//! name string. eg. "bool" or "uint8_t"
PVXS_API const char* name() const;
};
inline bool operator==(TypeCode lhs, TypeCode rhs) {
return lhs.code==rhs.code;
}
inline bool operator!=(TypeCode lhs, TypeCode rhs) {
return lhs.code!=rhs.code;
}
inline std::ostream& operator<<(std::ostream& strm, TypeCode c) {
strm<<c.name();
return strm;
}
class PVXS_API TypeDef
{
public:
struct Node;
private:
struct NodeDeletor {
PVXS_API void operator()(Node *p);
};
std::unique_ptr<Node, NodeDeletor> root;
public:
TypeDef() = default;
TypeDef(TypeCode code, const char *id=nullptr);
// moveable, not copyable
TypeDef(const TypeDef&) = delete;
TypeDef(TypeDef&&) = default;
TypeDef& operator=(const TypeDef&) = delete;
TypeDef& operator=(TypeDef&&) = default;
explicit TypeDef(const Value&);
~TypeDef();
TypeDef clone() const;
class PVXS_API Cursor {
friend class TypeDef;
TypeDef* owner;
Node* parent;
size_t index; // [0, parent->children.size()] (index==size() appends)
public:
Cursor& seek(const char *name);
Cursor& change(const char *id, TypeCode code);
Cursor& insert(const char *name, const char *id, TypeCode code);
inline Cursor& insert(const char *name, TypeCode code) {
return insert(name, nullptr, code);
}
Cursor& add(const char *name, const TypeDef& def);
Cursor& up();
Cursor& reset();
TypeDef& end() { return *owner; }
//! shorthand for .end().create()
inline Value create();
};
friend class Cursor;
Cursor begin();
Value create() const;
friend
std::ostream& operator<<(std::ostream& strm, const TypeDef&);
};
PVXS_API
std::ostream& operator<<(std::ostream& strm, const TypeDef&);
struct PVXS_API NoConvert : public std::runtime_error
{
explicit NoConvert();
virtual ~NoConvert();
TypeCode source;
};
//! pointer-like reference to a single data field
class PVXS_API Value {
friend class TypeDef;
std::shared_ptr<impl::FieldStorage> store;
const impl::FieldDesc* desc; // owned thourgh StructTop (aliased as FieldStorage)
public:
constexpr Value() :desc(nullptr) {}
explicit Value(const std::shared_ptr<const impl::FieldDesc>& desc);
Value(const Value&) = default;
Value(Value&&) = default;
Value& operator=(const Value&) = default;
Value& operator=(Value&&) = default;
~Value();
//! allocate new storage, with default values
Value cloneEmpty() const;
//! allocate new storage and copy in our values
Value clone() const;
//! copy values from other. Must have matching types.
// Value& assign(const Value&);
Value allocMember() const;
inline bool valid() const { return desc; }
inline explicit operator bool() const { return desc; }
bool isMarked(bool parents=true, bool children=false) const;
void mark(bool v=true);
void unmark(bool parents=false, bool children=true);
TypeCode type() const;
StoreType storageType() const;
const std::string& id() const;
//! test for instance equality.
inline bool compareInst(const Value& o) { return store==o.store; }
// int compareValue(const Value&);
// int compareType(const Value&);
// access to Value's ... value
// not for Struct
// use with caution
void copyOut(void *ptr, StoreType type) const;
//bool tryCopyOut(void *ptr, impl::StoreType type) const;
void copyIn(const void *ptr, StoreType type);
//bool tryCopyIn(const void *ptr, impl::StoreType type);
// template<typename T>
// inline bool tryAs(T& val) const {
// return tryCopyOut(&val, std::type_index(typeid(std::decay<T>::type)));
// }
/** Extract value from field.
*/
template<typename T>
inline T as() const {
typedef impl::StorageMap<typename std::decay<T>::type> map_t;
typename map_t::store_t ret;
copyOut(&ret, map_t::code);
return ret;
}
// template<typename T>
// void as(T& val) const {
// copyOut(&val, std::type_index(typeid(typename std::decay<T>::type)));
// }
// template<typename T>
// inline bool tryFrom(const T& val) {
// return tryCopyIn(&val, std::type_index(typeid(std::decay<T>::type)));
// }
template<typename T>
void from(const T& val) {
typedef impl::StorageMap<typename std::decay<T>::type> map_t;
typename map_t::store_t norm(val);
copyIn(&norm, map_t::code);
}
// TODO T=Value is ambigious with previous assignment operator
template<typename T>
Value& operator=(const T& val) {
from<T>(val);
return *this;
}
// Struct/Union access
private:
void traverse(const std::string& expr, bool modify);
public:
//! attempt to decend into sub-structure
Value operator[](const char *name);
inline Value operator[](const std::string& name) { return (*this)[name.c_str()]; }
const Value operator[](const char *name) const;
inline const Value operator[](const std::string& name) const { return (*this)[name.c_str()]; }
private:
template<typename V> friend class _iterator;
// these cheat on const-ness
void _step(const Value& child, bool next) const;
void _first_child(const Value& child) const;
template<typename V>
class _iterator {
V *parent;
V child;
friend class Value;
public:
_iterator() :parent(nullptr) {}
explicit _iterator(V* parent) :parent(parent) {}
V& operator*() { return child; }
V* operator->() { return &child; }
// ++(*this)
inline _iterator& operator++() { parent->_step(child, true); return *this; }
// (*this)++
inline _iterator operator++(int) { _iterator ret(*this); parent->_step(child, true); return ret;}
// --(*this)
inline _iterator& operator--() { parent->_step(child, false); return *this; }
// (*this)--
inline _iterator operator--(int) { _iterator ret(*this); parent->_step(child, false); return ret;}
inline bool operator==(const _iterator& o) const { return child.compareInst(o.child)==0; }
inline bool operator!=(const _iterator& o) const { return !((*this)==o); }
};
public:
typedef _iterator<Value> iterator;
typedef _iterator<const Value> const_iterator;
inline iterator begin() { iterator ret(this); _first_child(ret.child); return ret;}
inline const_iterator cbegin() const { const_iterator ret(this); _first_child(ret.child); return ret;}
inline const_iterator begin() const { return cbegin(); }
inline iterator end() { iterator ret(this); return ret;}
inline const_iterator cend() const { const_iterator ret(this); return ret;}
inline const_iterator end() const { return cend(); }
// impl cheating
const impl::FieldDesc* _desc() const { return desc; }
impl::FieldStorage* _store() const { return store.get(); }
};
PVXS_API
std::ostream& operator<<(std::ostream& strm, const Value& val);
Value TypeDef::Cursor::create() {
return end().create();
}
} // namespace pvxs
#endif // PVXS_DATA_H
+3 -2
View File
@@ -97,7 +97,7 @@ testCase testEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS& r
ret<<") == "<<sRHS<<" (";
test_print<RHS>::op(ret, rhs);
ret<<") ";
return std::move(ret);
return ret;
}
template<typename LHS, typename RHS>
@@ -109,7 +109,7 @@ testCase testNotEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS
ret<<") != "<<sRHS<<" (";
test_print<RHS>::op(ret, rhs);
ret<<") ";
return std::move(ret);
return ret;
}
} // namespace detail
@@ -133,5 +133,6 @@ testCase testThrows(FN fn)
#define testEq(LHS, RHS) ::pvxs::detail::testEq(#LHS, LHS, #RHS, RHS)
#define testNotEq(LHS, RHS) ::pvxs::detail::testNotEq(#LHS, LHS, #RHS, RHS)
#define testShow() ::pvxs::testCase()
#endif // PVXS_UNITTEST_H
+1 -1
View File
@@ -84,7 +84,7 @@ class PVXS_API UDPListener
std::shared_ptr<UDPCollector> collector;
const SockAddr dest;
bool active;
friend struct UDPCollector;
friend class UDPCollector;
friend struct UDPManager;
public:
+72
View File
@@ -14,6 +14,8 @@
#include <epicsStdlib.h>
#include <pvxs/util.h>
#include <pvxs/sharedArray.h>
#include <pvxs/data.h>
#include "utilpvt.h"
#include "udp_collector.h"
@@ -37,6 +39,76 @@ void cleanup_for_valgrind()
impl::UDPManager::cleanup();
}
std::ostream& operator<<(std::ostream& strm, ArrayType code)
{
switch(code) {
#define CASE(CODE) case ArrayType::CODE : strm<<#CODE; break
CASE(Null);
CASE(Bool);
CASE(UInt8);
CASE(UInt16);
CASE(UInt32);
CASE(UInt64);
CASE(Int8);
CASE(Int16);
CASE(Int32);
CASE(Int64);
CASE(Float);
CASE(Double);
CASE(Value);
#undef CASE
default:
strm<<"<\?\?\?>";
}
return strm;
}
std::ostream& operator<<(std::ostream& strm, const shared_array<const void>& arr)
{
switch(arr.original_type()) {
case ArrayType::Null: strm<<"[null]"; break;
#define CASE(CODE, Type) case ArrayType::CODE: strm<<shared_array_static_cast<const Type>(arr); break
CASE(Bool, bool);
CASE(UInt8, uint8_t);
CASE(UInt16, uint16_t);
CASE(UInt32, uint32_t);
CASE(UInt64, uint64_t);
CASE(Int8, int8_t);
CASE(Int16, int16_t);
CASE(Int32, int32_t);
CASE(Int64, int64_t);
CASE(Float, float);
CASE(Double, double);
CASE(String, std::string);
CASE(Value, Value);
#undef CASE
}
return strm;
}
std::ostream& operator<<(std::ostream& strm, const shared_array<void>& arr)
{
switch(arr.original_type()) {
case ArrayType::Null: strm<<"[null]"; break;
#define CASE(CODE, Type) case ArrayType::CODE: strm<<shared_array_static_cast<Type>(arr); break
CASE(Bool, bool);
CASE(UInt8, uint8_t);
CASE(UInt16, uint16_t);
CASE(UInt32, uint32_t);
CASE(UInt64, uint64_t);
CASE(Int8, int8_t);
CASE(Int16, int16_t);
CASE(Int32, int32_t);
CASE(Int64, int64_t);
CASE(Float, float);
CASE(Double, double);
CASE(String, std::string);
CASE(Value, Value);
#undef CASE
}
return strm;
}
namespace detail {
Escaper::Escaper(const char* v)
+4 -3
View File
@@ -23,7 +23,6 @@
#include <pvxs/util.h>
namespace pvxs {namespace impl {
using namespace pvxs;
//! in-line string builder (eg. for exception messages)
//! eg. @code throw std::runtime_error(SB()<<"Some message"<<42); @endcode
@@ -32,7 +31,7 @@ struct SB {
SB() {}
operator std::string() const { return strm.str(); }
template<typename T>
SB& operator<<(T i) { strm<<i; return *this; }
SB& operator<<(const T& i) { strm<<i; return *this; }
};
namespace detail {
@@ -144,6 +143,8 @@ public:
void logger_shutdown();
}} // namespace pvxs::impl
} // namespace impl
using namespace impl;
} // namespace pvxs
#endif // UTILPVT_H
+8
View File
@@ -26,6 +26,14 @@ TESTPROD += testshared
testshared_SRCS += testshared.cpp
TESTS += testshared
TESTPROD += testxcode
testxcode_SRCS += testxcode.cpp
TESTS += testxcode
TESTPROD += testdata
testdata_SRCS += testdata.cpp
TESTS += testdata
TESTPROD += dummyserv
dummyserv_SRCS += dummyserv.cpp
# not a unittest
+313
View File
@@ -0,0 +1,313 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include <testMain.h>
#include <epicsUnitTest.h>
#include <pvxs/unittest.h>
#include <pvxs/data.h>
#include "utilpvt.h"
#include "dataimpl.h"
using namespace pvxs;
namespace {
void showSize()
{
testDiag("%s()", __func__);
#define CASE(TYPE) testDiag("sizeof(" #TYPE ") = %u", unsigned(sizeof(TYPE)))
CASE(Value);
CASE(impl::FieldDesc);
CASE(impl::FieldStorage);
CASE(impl::StructTop);
#undef CASE
}
void testBasic()
{
testDiag("%s()", __func__);
auto top = TypeDef(TypeCode::Struct, "simple_t")
.begin()
.insert("value", nullptr, TypeCode::Float64)
.create();
testOk1(top.valid());
testEq(top.type(), TypeCode::Struct);
{
auto val = top["missing"];
testOk1(!val.valid());
testOk1(!val.isMarked());
testThrows<NoConvert>([&val]() {
val.from(4.2);
});
}
{
auto val = top["value"];
testOk1(!!val.valid());
testOk1(!val.isMarked());
val.from(4.2);
testEq(val.as<double>(), 4.2);
testOk1(!!val.isMarked());
}
testEq(std::string(SB()<<top),
"struct \"simple_t\" {\n"
" double value = 4.2\n"
"}\n");
}
void testTypeDef()
{
testDiag("%s()", __func__);
testEq(std::string(SB()<<TypeDef()),
"<Empty>\n");
testEq(std::string(SB()<<TypeDef(TypeCode::Struct, "simple_t")),
"struct \"simple_t\"\n");
TypeDef def(TypeCode::Struct, "simple_t");
def.begin()
.insert("value", nullptr, TypeCode::Float64A)
.insert("timeStamp", "time_t", TypeCode::Struct)
.seek("timeStamp")
.insert("secondsPastEpoch", TypeCode::UInt64)
.insert("nanoseconds", TypeCode::UInt32)
.up() // up one level
.insert("arbitrary", TypeCode::Struct)
.seek("arbitrary")
.insert("sarr", TypeCode::StructA)
.seek("sarr")
.insert("value", TypeCode::Float64)
.reset() // back to top
.insert("any", TypeCode::Any)
.insert("anya", TypeCode::AnyA)
.insert("choice", TypeCode::Union)
.seek("choice")
.insert("a", TypeCode::Float32)
.insert("b", TypeCode::String)
.reset()
.insert("achoice", TypeCode::UnionA)
.seek("achoice")
.insert("x", TypeCode::Float32)
.insert("y", TypeCode::Float32)
;
testShow()<<def;
testEq(std::string(SB()<<def),
"struct \"simple_t\" {\n"
" double[] value\n"
" struct \"time_t\" {\n"
" uint64_t secondsPastEpoch\n"
" uint32_t nanoseconds\n"
" } timeStamp\n"
" struct {\n"
" struct[] {\n"
" double value\n"
" } sarr\n"
" } arbitrary\n"
" any any\n"
" any[] anya\n"
" union {\n"
" float a\n"
" string b\n"
" } choice\n"
" union[] {\n"
" float x\n"
" float y\n"
" } achoice\n"
"}\n"
);
auto val = def.create();
testOk1(!!val.valid());
testShow()<<val._desc();
testEq(std::string(SB()<<val._desc()),
"[0] struct simple_t <0:11> [0:18)\n"
" achoice -> 14 [14]\n"
" any -> 9 [9]\n"
" anya -> 10 [10]\n"
" arbitrary -> 5 [5]\n"
" arbitrary.sarr -> 6 [6]\n"
" choice -> 11 [11]\n"
" timeStamp -> 2 [2]\n"
" timeStamp.nanoseconds -> 4 [4]\n"
" timeStamp.secondsPastEpoch -> 3 [3]\n"
" value -> 1 [1]\n"
" value : 1 [1]\n"
" timeStamp : 2 [2]\n"
" arbitrary : 5 [5]\n"
" any : 9 [9]\n"
" anya : 10 [10]\n"
" choice : 11 [11]\n"
" achoice : 14 [14]\n"
"[1] double[] <1:2> [1:2)\n"
"[2] struct time_t <2:3> [2:5)\n"
" nanoseconds -> 2 [4]\n"
" secondsPastEpoch -> 1 [3]\n"
" secondsPastEpoch : 1 [3]\n"
" nanoseconds : 2 [4]\n"
"[3] uint64_t <3:4> [3:4)\n"
"[4] uint32_t <4:5> [4:5)\n"
"[5] struct <5:6> [5:9)\n"
" sarr -> 1 [6]\n"
" sarr : 1 [6]\n"
"[6] struct[] <6:7> [6:9)\n"
"[7] struct <0:2> [7:9)\n"
" value -> 1 [8]\n"
" value : 1 [8]\n"
"[8] double <1:2> [8:9)\n"
"[9] any <7:8> [9:10)\n"
"[10] any[] <8:9> [10:11)\n"
"[11] union <9:10> [11:14)\n"
" a -> 1 [12]\n"
" b -> 2 [13]\n"
" a : 1 [12]\n"
" b : 2 [13]\n"
"[12] float <0:1> [12:13)\n"
"[13] string <0:1> [13:14)\n"
"[14] union[] <10:11> [14:18)\n"
"[15] union <0:3> [15:18)\n"
" x -> 1 [16]\n"
" y -> 2 [17]\n"
" x : 1 [16]\n"
" y : 2 [17]\n"
"[16] float <1:2> [16:17)\n"
"[17] float <2:3> [17:18)\n"
"");
// try to access all field Kinds
// sub-struct and scalar
val["timeStamp.secondsPastEpoch"] = 0x123456789abcdef0ull;
// array of scalar
{
shared_array<double> arr({1.0, 2.0});
val["value"].from(shared_array_static_cast<const void>(freeze(std::move(arr))));
}
// Struct[]
{
auto fld = val["arbitrary.sarr"];
shared_array<Value> arr(3);
arr[0] = fld.allocMember();
arr[1] = fld.allocMember();
// leave [2] as null
arr[0]["value"] = 1.0;
arr[1]["value"] = 2.0;
// auto frozen = freeze(std::move(arr));
// auto varr = shared_array_static_cast<const void>(frozen);
// fld.from(varr);
fld.from(shared_array_static_cast<const void>(freeze(std::move(arr))));
testEq(val["arbitrary.sarr[1]value"].as<double>(), 2.0);
}
// Union
val["choice->b"] = "test";
// Union[]
{
auto fld = val["achoice"];
shared_array<Value> arr(3);
arr[0] = fld.allocMember();
arr[1] = fld.allocMember();
// leave [2] as null
arr[0]["->x"] = 4.0;
arr[1]["->y"] = 5.0;
fld.from(shared_array_static_cast<const void>(freeze(std::move(arr))));
testEq(fld["[1]"].as<double>(), 5.0);
testEq(val["achoice[1]"].as<double>(), 5.0);
testEq(val["achoice[1]->y"].as<double>(), 5.0);
}
// Any
{
auto v = TypeDef(TypeCode::UInt32).create();
v = 42u;
val["any"].from(v);
testEq(v.as<uint64_t>(), 42u);
}
// Any[]
{
auto fld = val["anya"];
shared_array<Value> arr(3);
arr[0] = TypeDef(TypeCode::UInt32).create();
arr[1] = TypeDef(TypeCode::Struct)
.begin()
.insert("q", TypeCode::String)
.create();
// leave [2] as null
arr[0] = 123;
arr[1]["q"] = "theq";
fld.from(shared_array_static_cast<const void>(freeze(std::move(arr))));
testEq(fld["[0]"].as<uint64_t>(), 123u);
testEq(fld["[1]q"].as<std::string>(), "theq");
}
testShow()<<val;
testEq(std::string(SB()<<val),
"struct \"simple_t\" {\n"
" double[] value = {2}[1, 2]\n"
" struct \"time_t\" {\n"
" uint64_t secondsPastEpoch = 1311768467463790320\n"
" uint32_t nanoseconds = 0\n"
" } timeStamp\n"
" struct {\n"
" struct[] sarr [\n"
" struct {\n"
" double value = 1\n"
" }\n"
" struct {\n"
" double value = 2\n"
" }\n"
" null\n"
" ]\n"
" } arbitrary\n"
" any any uint32_t = 42\n"
" any[] anya [\n"
" uint32_t = 123\n"
" struct {\n"
" string q = \"theq\"\n"
" }\n"
" null\n"
" ]\n"
" union choice.b string = \"test\"\n"
" union[] achoice [\n"
" union.x float = 4\n"
" union.y float = 5\n"
" null\n"
" ]\n"
"}\n");
}
} // namespace
MAIN(testdata)
{
testPlan(23);
showSize();
testBasic();
testTypeDef();
cleanup_for_valgrind();
return testDone();
}
+1 -1
View File
@@ -12,7 +12,7 @@
#include <pvxs/log.h>
#include <evhelper.h>
using namespace pvxs::impl;
using namespace pvxs;
namespace {
struct my_special_error : public std::runtime_error
+1 -1
View File
@@ -16,7 +16,7 @@
#include <pvxs/log.h>
namespace {
using namespace pvxs::impl;
using namespace pvxs;
void test_udp()
{
+1 -1
View File
@@ -20,7 +20,7 @@
#include <udp_collector.h>
namespace {
using namespace pvxs::impl;
using namespace pvxs;
void testBeacon(bool be)
{
+459
View File
@@ -0,0 +1,459 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include <epicsUnitTest.h>
#include <testMain.h>
#include <pvxs/util.h>
#include <pvxs/unittest.h>
#include "dataencode.h"
namespace {
using namespace pvxs;
using namespace pvxs::impl;
void testDecode1()
{
testDiag("%s", __func__);
/* From PVA proto doc
*
* timeStamp_t
* long secondsPastEpoch
* int nanoSeconds
* int userTag
*/
std::vector<uint8_t> msg({
// update cache with key 0
0xFD, 0x00, 0x01,
// structure
0x80,
// ID "timeStamp_t"
0x0B, 0x74, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x5F, 0x74,
// 3 members
0x03,
// "secondsPastEpoch"
0x10, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x50, 0x61, 0x73, 0x74, 0x45, 0x70, 0x6F, 0x63, 0x68,
// integer signed 8 bytes
0x23,
// "nanoSeconds"
0x0B, 0x6E, 0x61, 0x6E, 0x6F, 0x53, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73,
// integer signed 4 bytes
0x22,
// "userTag"
0x07, 0x75, 0x73, 0x65, 0x72, 0x54, 0x61, 0x67,
// integer signed 4 bytes
0x22
});
std::vector<FieldDesc> descs;
TypeStore cache;
{
FixedBuf<uint8_t> buf(true, msg);
TypeDeserContext ctxt{descs, cache};
from_wire(buf, ctxt);
if(testOk1(buf.good()))
FieldDesc_calculate_offset(descs.data());
testEq(buf.size(), 0u)<<"Of "<<msg.size();
}
testEq(cache.size(), 1u);
{
auto it = cache.find(1);
if(testOk1(it!=cache.end())) {
testEq(it->second.size(), 4u);
}
}
if(testOk1(!descs.empty())) {
testEq(descs.size(), descs.front().size());
}
// cat <<EOF | sed -e 's|"|\\"|g' -e 's|^# | "|' -e 's|$|\\n"|g'
// paste in Actual
testEq(std::string(SB()<<descs.data()),
"[0] struct timeStamp_t <0:4> [0:4)\n"
" nanoSeconds -> 2 [2]\n"
" secondsPastEpoch -> 1 [1]\n"
" userTag -> 3 [3]\n"
" secondsPastEpoch : 1 [1]\n"
" nanoSeconds : 2 [2]\n"
" userTag : 3 [3]\n"
"[1] int64_t <1:2> [1:2)\n"
"[2] int32_t <2:3> [2:3)\n"
"[3] int32_t <3:4> [3:4)\n"
)<<"Actual:\n"<<descs.data();
}
/* epics:nt/NTScalarArray:1.0
* double[] value
* alarm_t alarm
* int severity
* int status
* string message
* time_t timeStamp
* long secondsPastEpoch
* int nanoseconds
* int userTag
*/
const uint8_t NTScalar[] = "\x80\x1a""epics:nt/NTScalarArray:1.0\x03"
"\x05""valueK"
"\x05""alarm\x80\x07""alarm_t\x03"
"\x08""severity\""
"\x06""status\""
"\x07""message`"
"\ttimeStamp\x80\x06""time_t\x03"
"\x10""secondsPastEpoch#"
"\x0b""nanoseconds\""
"\x07""userTag\"";
void testXCodeNTScalar()
{
testDiag("%s", __func__);
std::vector<uint8_t> msg(NTScalar, NTScalar+sizeof(NTScalar)-1);
std::vector<FieldDesc> descs;
TypeStore cache;
{
FixedBuf<uint8_t> buf(true, msg);
TypeDeserContext ctxt{descs, cache};
from_wire(buf, ctxt);
if(testOk1(buf.good()))
FieldDesc_calculate_offset(descs.data());
testEq(buf.size(), 0u)<<"remaining of "<<msg.size();
}
if(testOk1(!descs.empty())) {
testEq(descs.size(), descs.front().size());
}
testEq(std::string(SB()<<descs.data()),
"[0] struct epics:nt/NTScalarArray:1.0 <0:10> [0:10)\n"
" alarm -> 2 [2]\n"
" alarm.message -> 5 [5]\n"
" alarm.severity -> 3 [3]\n"
" alarm.status -> 4 [4]\n"
" timeStamp -> 6 [6]\n"
" timeStamp.nanoseconds -> 8 [8]\n"
" timeStamp.secondsPastEpoch -> 7 [7]\n"
" timeStamp.userTag -> 9 [9]\n"
" value -> 1 [1]\n"
" value : 1 [1]\n"
" alarm : 2 [2]\n"
" timeStamp : 6 [6]\n"
"[1] double[] <1:2> [1:2)\n"
"[2] struct alarm_t <2:3> [2:6)\n"
" message -> 3 [5]\n"
" severity -> 1 [3]\n"
" status -> 2 [4]\n"
" severity : 1 [3]\n"
" status : 2 [4]\n"
" message : 3 [5]\n"
"[3] int32_t <3:4> [3:4)\n"
"[4] int32_t <4:5> [4:5)\n"
"[5] string <5:6> [5:6)\n"
"[6] struct time_t <6:7> [6:10)\n"
" nanoseconds -> 2 [8]\n"
" secondsPastEpoch -> 1 [7]\n"
" userTag -> 3 [9]\n"
" secondsPastEpoch : 1 [7]\n"
" nanoseconds : 2 [8]\n"
" userTag : 3 [9]\n"
"[7] int64_t <7:8> [7:8)\n"
"[8] int32_t <8:9> [8:9)\n"
"[9] int32_t <9:10> [9:10)\n"
)<<"Actual:\n"<<descs.data();
testDiag("Round trip back to bytes");
std::vector<uint8_t> out;
out.reserve(msg.size());
{
VectorOutBuf buf(true, out);
to_wire(buf, descs.data());
testOk1(buf.good());
out.resize(out.size()-buf.size());
}
testEq(msg.size(), out.size());
testEq(msg, out);
}
// has a bit of everything... (except array of union)
const uint8_t NTNDArray[] = "\x80\x16""epics:nt/NTNDArray:1.0\n"
"\x05value\x81\x00\x0b"
"\x0c""booleanValue\x08"
"\tbyteValue("
"\nshortValue)"
"\x08intValue*"
"\tlongValue+"
"\nubyteValue,"
"\x0bushortValue-"
"\tuintValue."
"\nulongValue/"
"\nfloatValueJ"
"\x0b""doubleValueK"
"\x05""codec\x80\x07""codec_t\x02"
"\x04name`"
"\nparameters\x82"
"\x0e""compressedSize#"
"\x10uncompressedSize#"
"\x08uniqueId\""
"\rdataTimeStamp\x80\x06time_t\x03"
"\x10secondsPastEpoch#"
"\x0bnanoseconds\""
"\x07userTag\""
"\x05""alarm\x80\x07""alarm_t\x03"
"\x08severity\""
"\x06status\""
"\x07message`"
"\ttimeStamp\x80\x06time_t\x03"
"\x10secondsPastEpoch#"
"\x0bnanoseconds\""
"\x07userTag\""
"\tdimension\x88\x80\x0b""dimension_t\x05"
"\x04size\""
"\x06offset\""
"\x08""fullSize\""
"\x07""binning\""
"\x07reverse\x00"
"\tattribute\x88\x80\x18""epics:nt/NTAttribute:1.0\x08"
"\x04name`"
"\x05value\x82"
"\x04tagsh"
"\ndescriptor`"
"\x05""alarm\x80\x07""alarm_t\x03"
"\x08severity\""
"\x06status\""
"\x07message`"
"\ttimestamp\x80\x06time_t\x03"
"\x10secondsPastEpoch#"
"\x0bnanoseconds\""
"\x07userTag\""
"\nsourceType\""
"\x06source`";
void testXCodeNTNDArray()
{
testDiag("%s", __func__);
std::vector<uint8_t> msg(NTNDArray, NTNDArray+sizeof(NTNDArray)-1);
std::vector<FieldDesc> descs;
TypeStore cache;
{
FixedBuf<uint8_t> buf(true, msg);
TypeDeserContext ctxt{descs, cache};
from_wire(buf, ctxt);
if(testOk1(buf.good()))
FieldDesc_calculate_offset(descs.data());
testEq(buf.size(), 0u)<<"remaining of "<<msg.size();
}
if(testOk1(!descs.empty())) {
testEq(descs.size(), descs.front().size());
}
testEq(std::string(SB()<<descs.data()),
"[0] struct epics:nt/NTNDArray:1.0 <0:22> [0:54)\n"
" alarm -> 23 [23]\n"
" alarm.message -> 26 [26]\n"
" alarm.severity -> 24 [24]\n"
" alarm.status -> 25 [25]\n"
" attribute -> 38 [38]\n"
" codec -> 13 [13]\n"
" codec.name -> 14 [14]\n"
" codec.parameters -> 15 [15]\n"
" compressedSize -> 16 [16]\n"
" dataTimeStamp -> 19 [19]\n"
" dataTimeStamp.nanoseconds -> 21 [21]\n"
" dataTimeStamp.secondsPastEpoch -> 20 [20]\n"
" dataTimeStamp.userTag -> 22 [22]\n"
" dimension -> 31 [31]\n"
" timeStamp -> 27 [27]\n"
" timeStamp.nanoseconds -> 29 [29]\n"
" timeStamp.secondsPastEpoch -> 28 [28]\n"
" timeStamp.userTag -> 30 [30]\n"
" uncompressedSize -> 17 [17]\n"
" uniqueId -> 18 [18]\n"
" value -> 1 [1]\n"
" value : 1 [1]\n"
" codec : 13 [13]\n"
" compressedSize : 16 [16]\n"
" uncompressedSize : 17 [17]\n"
" uniqueId : 18 [18]\n"
" dataTimeStamp : 19 [19]\n"
" alarm : 23 [23]\n"
" timeStamp : 27 [27]\n"
" dimension : 31 [31]\n"
" attribute : 38 [38]\n"
"[1] union <1:2> [1:13)\n"
" booleanValue -> 1 [2]\n"
" byteValue -> 2 [3]\n"
" doubleValue -> 11 [12]\n"
" floatValue -> 10 [11]\n"
" intValue -> 4 [5]\n"
" longValue -> 5 [6]\n"
" shortValue -> 3 [4]\n"
" ubyteValue -> 6 [7]\n"
" uintValue -> 8 [9]\n"
" ulongValue -> 9 [10]\n"
" ushortValue -> 7 [8]\n"
" booleanValue : 1 [2]\n"
" byteValue : 2 [3]\n"
" shortValue : 3 [4]\n"
" intValue : 4 [5]\n"
" longValue : 5 [6]\n"
" ubyteValue : 6 [7]\n"
" ushortValue : 7 [8]\n"
" uintValue : 8 [9]\n"
" ulongValue : 9 [10]\n"
" floatValue : 10 [11]\n"
" doubleValue : 11 [12]\n"
"[2] bool[] <0:1> [2:3)\n"
"[3] int8_t[] <0:1> [3:4)\n"
"[4] int16_t[] <0:1> [4:5)\n"
"[5] int32_t[] <0:1> [5:6)\n"
"[6] int64_t[] <0:1> [6:7)\n"
"[7] uint8_t[] <0:1> [7:8)\n"
"[8] uint16_t[] <0:1> [8:9)\n"
"[9] uint32_t[] <0:1> [9:10)\n"
"[10] uint64_t[] <0:1> [10:11)\n"
"[11] float[] <0:1> [11:12)\n"
"[12] double[] <0:1> [12:13)\n"
"[13] struct codec_t <2:3> [13:16)\n"
" name -> 1 [14]\n"
" parameters -> 2 [15]\n"
" name : 1 [14]\n"
" parameters : 2 [15]\n"
"[14] string <3:4> [14:15)\n"
"[15] any <4:5> [15:16)\n"
"[16] int64_t <5:6> [16:17)\n"
"[17] int64_t <6:7> [17:18)\n"
"[18] int32_t <7:8> [18:19)\n"
"[19] struct time_t <8:9> [19:23)\n"
" nanoseconds -> 2 [21]\n"
" secondsPastEpoch -> 1 [20]\n"
" userTag -> 3 [22]\n"
" secondsPastEpoch : 1 [20]\n"
" nanoseconds : 2 [21]\n"
" userTag : 3 [22]\n"
"[20] int64_t <9:10> [20:21)\n"
"[21] int32_t <10:11> [21:22)\n"
"[22] int32_t <11:12> [22:23)\n"
"[23] struct alarm_t <12:13> [23:27)\n"
" message -> 3 [26]\n"
" severity -> 1 [24]\n"
" status -> 2 [25]\n"
" severity : 1 [24]\n"
" status : 2 [25]\n"
" message : 3 [26]\n"
"[24] int32_t <13:14> [24:25)\n"
"[25] int32_t <14:15> [25:26)\n"
"[26] string <15:16> [26:27)\n"
"[27] struct time_t <16:17> [27:31)\n"
" nanoseconds -> 2 [29]\n"
" secondsPastEpoch -> 1 [28]\n"
" userTag -> 3 [30]\n"
" secondsPastEpoch : 1 [28]\n"
" nanoseconds : 2 [29]\n"
" userTag : 3 [30]\n"
"[28] int64_t <17:18> [28:29)\n"
"[29] int32_t <18:19> [29:30)\n"
"[30] int32_t <19:20> [30:31)\n"
"[31] struct[] <20:21> [31:38)\n"
"[32] struct dimension_t <0:6> [32:38)\n"
" binning -> 4 [36]\n"
" fullSize -> 3 [35]\n"
" offset -> 2 [34]\n"
" reverse -> 5 [37]\n"
" size -> 1 [33]\n"
" size : 1 [33]\n"
" offset : 2 [34]\n"
" fullSize : 3 [35]\n"
" binning : 4 [36]\n"
" reverse : 5 [37]\n"
"[33] int32_t <1:2> [33:34)\n"
"[34] int32_t <2:3> [34:35)\n"
"[35] int32_t <3:4> [35:36)\n"
"[36] int32_t <4:5> [36:37)\n"
"[37] bool <5:6> [37:38)\n"
"[38] struct[] <21:22> [38:54)\n"
"[39] struct epics:nt/NTAttribute:1.0 <0:15> [39:54)\n"
" alarm -> 5 [44]\n"
" alarm.message -> 47 [86]\n"
" alarm.severity -> 45 [84]\n"
" alarm.status -> 46 [85]\n"
" descriptor -> 4 [43]\n"
" name -> 1 [40]\n"
" source -> 14 [53]\n"
" sourceType -> 13 [52]\n"
" tags -> 3 [42]\n"
" timestamp -> 9 [48]\n"
" timestamp.nanoseconds -> 50 [89]\n"
" timestamp.secondsPastEpoch -> 49 [88]\n"
" timestamp.userTag -> 51 [90]\n"
" value -> 2 [41]\n"
" name : 1 [40]\n"
" value : 2 [41]\n"
" tags : 3 [42]\n"
" descriptor : 4 [43]\n"
" alarm : 5 [44]\n"
" timestamp : 9 [48]\n"
" sourceType : 13 [52]\n"
" source : 14 [53]\n"
"[40] string <1:2> [40:41)\n"
"[41] any <2:3> [41:42)\n"
"[42] string[] <3:4> [42:43)\n"
"[43] string <4:5> [43:44)\n"
"[44] struct alarm_t <5:6> [44:48)\n"
" message -> 3 [47]\n"
" severity -> 1 [45]\n"
" status -> 2 [46]\n"
" severity : 1 [45]\n"
" status : 2 [46]\n"
" message : 3 [47]\n"
"[45] int32_t <6:7> [45:46)\n"
"[46] int32_t <7:8> [46:47)\n"
"[47] string <8:9> [47:48)\n"
"[48] struct time_t <9:10> [48:52)\n"
" nanoseconds -> 2 [50]\n"
" secondsPastEpoch -> 1 [49]\n"
" userTag -> 3 [51]\n"
" secondsPastEpoch : 1 [49]\n"
" nanoseconds : 2 [50]\n"
" userTag : 3 [51]\n"
"[49] int64_t <10:11> [49:50)\n"
"[50] int32_t <11:12> [50:51)\n"
"[51] int32_t <12:13> [51:52)\n"
"[52] int32_t <13:14> [52:53)\n"
"[53] string <14:15> [53:54)\n"
)<<"Actual:\n"<<descs.data();
testDiag("Round trip back to bytes");
std::vector<uint8_t> out;
out.reserve(msg.size());
{
VectorOutBuf buf(true, out);
to_wire(buf, descs.data());
testOk1(buf.good());
out.resize(out.size()-buf.size());
}
testEq(msg.size(), out.size());
testEq(msg, out);
}
} // namespace
MAIN(testxcode)
{
testPlan(0);
testDecode1();
testXCodeNTScalar();
testXCodeNTNDArray();
return testDone();
}
+1 -1
View File
@@ -30,7 +30,7 @@
#include <utilpvt.h>
#include <pvaproto.h>
namespace pva = pvxs::impl;
namespace pva = pvxs;
namespace {
DEFINE_LOGGER(out, "pvxvct");