mirror of
https://github.com/zeromq/libzmq.git
synced 2025-10-17 11:05:06 +02:00
Merge pull request #2950 from zeromq/add-unittests-mtrie
Problem: no unit tests for mtrie
This commit is contained in:
@@ -611,6 +611,8 @@ set (cxx-sources
|
||||
fd.hpp
|
||||
fq.hpp
|
||||
gather.hpp
|
||||
generic_mtrie.hpp
|
||||
generic_mtrie_impl.hpp
|
||||
gssapi_client.hpp
|
||||
gssapi_mechanism_base.hpp
|
||||
gssapi_server.hpp
|
||||
|
30
Makefile.am
30
Makefile.am
@@ -64,6 +64,8 @@ src_libzmq_la_SOURCES = \
|
||||
src/fq.hpp \
|
||||
src/gather.cpp \
|
||||
src/gather.hpp \
|
||||
src/generic_mtrie.hpp \
|
||||
src/generic_mtrie_impl.hpp \
|
||||
src/gssapi_mechanism_base.cpp \
|
||||
src/gssapi_mechanism_base.hpp \
|
||||
src/gssapi_client.cpp \
|
||||
@@ -415,6 +417,7 @@ test_apps = \
|
||||
tests/test_xpub_nodrop \
|
||||
tests/test_xpub_manual \
|
||||
tests/test_xpub_welcome_msg \
|
||||
tests/test_xpub_verbose \
|
||||
tests/test_atomics \
|
||||
tests/test_sockopt_hwm \
|
||||
tests/test_heartbeats \
|
||||
@@ -621,6 +624,10 @@ tests_test_xpub_manual_LDADD = src/libzmq.la
|
||||
tests_test_xpub_welcome_msg_SOURCES = tests/test_xpub_welcome_msg.cpp
|
||||
tests_test_xpub_welcome_msg_LDADD = src/libzmq.la
|
||||
|
||||
tests_test_xpub_verbose_SOURCES = tests/test_xpub_verbose.cpp
|
||||
tests_test_xpub_verbose_LDADD = src/libzmq.la ${UNITY_LIBS}
|
||||
tests_test_xpub_verbose_CPPFLAGS = ${UNITY_CPPFLAGS}
|
||||
|
||||
tests_test_atomics_SOURCES = tests/test_atomics.cpp
|
||||
tests_test_atomics_LDADD = src/libzmq.la
|
||||
|
||||
@@ -865,19 +872,32 @@ if ENABLE_STATIC
|
||||
# unit tests - these include individual source files and test the internal functions
|
||||
test_apps += \
|
||||
unittests/unittest_poller \
|
||||
unittests/unittest_ypipe
|
||||
unittests/unittest_ypipe \
|
||||
unittests/unittest_mtrie
|
||||
|
||||
unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp
|
||||
unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS}
|
||||
unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
|
||||
unittests_unittest_poller_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
|
||||
unittests_unittest_poller_LDADD = $(top_builddir)/src/.libs/libzmq.a \
|
||||
${src_libzmq_la_LIBADD} \
|
||||
${UNITY_LIBS}
|
||||
${UNITY_LIBS} \
|
||||
$(CODE_COVERAGE_LDFLAGS)
|
||||
|
||||
unittests_unittest_ypipe_SOURCES = unittests/unittest_ypipe.cpp
|
||||
unittests_unittest_ypipe_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS}
|
||||
unittests_unittest_ypipe_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
|
||||
unittests_unittest_ypipe_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
|
||||
unittests_unittest_ypipe_LDADD = $(top_builddir)/src/.libs/libzmq.a \
|
||||
${src_libzmq_la_LIBADD} \
|
||||
${UNITY_LIBS}
|
||||
${UNITY_LIBS} \
|
||||
$(CODE_COVERAGE_LDFLAGS)
|
||||
|
||||
unittests_unittest_mtrie_SOURCES = unittests/unittest_mtrie.cpp
|
||||
unittests_unittest_mtrie_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
|
||||
unittests_unittest_mtrie_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
|
||||
unittests_unittest_mtrie_LDADD = $(top_builddir)/src/.libs/libzmq.a \
|
||||
${src_libzmq_la_LIBADD} \
|
||||
${UNITY_LIBS} \
|
||||
$(CODE_COVERAGE_LDFLAGS)
|
||||
endif
|
||||
|
||||
check_PROGRAMS = ${test_apps}
|
||||
|
3
NEWS
3
NEWS
@@ -1,6 +1,9 @@
|
||||
0MQ version 4.2.4 stable, released on 20xx/xx/xx
|
||||
================================================
|
||||
|
||||
* Fixed #2820 - further clarify ZMQ_XPUB_VERBOSE(R) documentation.
|
||||
|
||||
|
||||
0MQ version 4.2.3 stable, released on 2017/12/13
|
||||
================================================
|
||||
|
||||
|
@@ -1003,11 +1003,12 @@ Default value:: N/A
|
||||
Applicable socket types:: ZMQ_SUB
|
||||
|
||||
|
||||
ZMQ_XPUB_VERBOSE: pass subscribe messages on XPUB socket
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the 'XPUB' socket behaviour on new subscriptions. If enabled,
|
||||
ZMQ_XPUB_VERBOSE: pass duplicate subscribe messages on XPUB socket
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the 'XPUB' socket behaviour on new duplicated subscriptions. If enabled,
|
||||
the socket passes all subscribe messages to the caller. If disabled,
|
||||
these are not visible to the caller. The default is 0 (disabled).
|
||||
only the first subscription to each filter will be passed. The default is 0
|
||||
(disabled).
|
||||
|
||||
[horizontal]
|
||||
Option value type:: int
|
||||
@@ -1016,11 +1017,12 @@ Default value:: 0
|
||||
Applicable socket types:: ZMQ_XPUB
|
||||
|
||||
|
||||
ZMQ_XPUB_VERBOSER: pass subscribe and unsubscribe messages on XPUB socket
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the 'XPUB' socket behaviour on new subscriptions and ubsubscriptions.
|
||||
If enabled, the socket passes all subscribe and unsubscribe messages to the
|
||||
caller. If disabled, these are not visible to the caller. The default is 0
|
||||
ZMQ_XPUB_VERBOSER: pass duplicate subscribe and unsubscribe messages on XPUB socket
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the 'XPUB' socket behaviour on new duplicated subscriptions and
|
||||
unsubscriptions. If enabled, the socket passes all subscribe and unsubscribe
|
||||
messages to the caller. If disabled, only the first subscription to each filter and
|
||||
the last unsubscription from each filter will be passed. The default is 0
|
||||
(disabled).
|
||||
|
||||
[horizontal]
|
||||
|
116
src/generic_mtrie.hpp
Normal file
116
src/generic_mtrie.hpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of libzmq, the ZeroMQ core engine in C++.
|
||||
|
||||
libzmq is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License (LGPL) as published
|
||||
by the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
As a special exception, the Contributors give you permission to link
|
||||
this library with independent modules to produce an executable,
|
||||
regardless of the license terms of these independent modules, and to
|
||||
copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the
|
||||
terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library.
|
||||
If you modify this library, you must extend this exception to your
|
||||
version of the library.
|
||||
|
||||
libzmq is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ZMQ_GENERIC_MTRIE_HPP_INCLUDED__
|
||||
#define __ZMQ_GENERIC_MTRIE_HPP_INCLUDED__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <set>
|
||||
|
||||
#include "stdint.hpp"
|
||||
|
||||
namespace zmq
|
||||
{
|
||||
// Multi-trie (prefix tree). Each node in the trie is a set of pointers.
|
||||
template <typename T> class generic_mtrie_t
|
||||
{
|
||||
public:
|
||||
typedef T value_t;
|
||||
typedef const unsigned char *prefix_t;
|
||||
|
||||
enum rm_result
|
||||
{
|
||||
not_found,
|
||||
last_value_removed,
|
||||
values_remain
|
||||
};
|
||||
|
||||
generic_mtrie_t ();
|
||||
~generic_mtrie_t ();
|
||||
|
||||
// Add key to the trie. Returns true iff no entry with the same prefix_
|
||||
// and size_ existed before.
|
||||
bool add (prefix_t prefix_, size_t size_, value_t *value_);
|
||||
|
||||
// Remove all entries with a specific value from the trie.
|
||||
// The call_on_uniq_ flag controls if the callback is invoked
|
||||
// when there are no entries left on a prefix only (true)
|
||||
// or on every removal (false). The arg_ argument is passed
|
||||
// through to the callback function.
|
||||
template <typename Arg>
|
||||
void rm (value_t *value_,
|
||||
void (*func_) (const unsigned char *data_, size_t size_, Arg arg_),
|
||||
Arg arg_,
|
||||
bool call_on_uniq_);
|
||||
|
||||
// Removes a specific entry from the trie.
|
||||
// Returns the result of the operation.
|
||||
rm_result rm (prefix_t prefix_, size_t size_, value_t *value_);
|
||||
|
||||
// Calls a callback function for all matching entries, i.e. any node
|
||||
// corresponding to data_ or a prefix of it. The arg_ argument
|
||||
// is passed through to the callback function.
|
||||
template <typename Arg>
|
||||
void match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (value_t *value_, Arg arg_),
|
||||
Arg arg_);
|
||||
|
||||
private:
|
||||
bool add_helper (prefix_t prefix_, size_t size_, value_t *value_);
|
||||
template <typename Arg>
|
||||
void rm_helper (value_t *value_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_, size_t size_, Arg arg_),
|
||||
Arg arg_,
|
||||
bool call_on_uniq_);
|
||||
rm_result rm_helper (prefix_t prefix_, size_t size_, value_t *value_);
|
||||
bool is_redundant () const;
|
||||
|
||||
typedef std::set<value_t *> pipes_t;
|
||||
pipes_t *pipes;
|
||||
|
||||
unsigned char min;
|
||||
unsigned short count;
|
||||
unsigned short live_nodes;
|
||||
union
|
||||
{
|
||||
class generic_mtrie_t<value_t> *node;
|
||||
class generic_mtrie_t<value_t> **table;
|
||||
} next;
|
||||
|
||||
generic_mtrie_t (const generic_mtrie_t<value_t> &);
|
||||
const generic_mtrie_t<value_t> &
|
||||
operator= (const generic_mtrie_t<value_t> &);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
453
src/generic_mtrie_impl.hpp
Normal file
453
src/generic_mtrie_impl.hpp
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of libzmq, the ZeroMQ core engine in C++.
|
||||
|
||||
libzmq is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License (LGPL) as published
|
||||
by the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
As a special exception, the Contributors give you permission to link
|
||||
this library with independent modules to produce an executable,
|
||||
regardless of the license terms of these independent modules, and to
|
||||
copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the
|
||||
terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library.
|
||||
If you modify this library, you must extend this exception to your
|
||||
version of the library.
|
||||
|
||||
libzmq is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ZMQ_GENERIC_MTRIE_IMPL_HPP_INCLUDED__
|
||||
#define __ZMQ_GENERIC_MTRIE_IMPL_HPP_INCLUDED__
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
#include "err.hpp"
|
||||
#include "pipe.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "generic_mtrie.hpp"
|
||||
|
||||
template <typename T>
|
||||
zmq::generic_mtrie_t<T>::generic_mtrie_t () :
|
||||
pipes (0),
|
||||
min (0),
|
||||
count (0),
|
||||
live_nodes (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> zmq::generic_mtrie_t<T>::~generic_mtrie_t ()
|
||||
{
|
||||
LIBZMQ_DELETE (pipes);
|
||||
|
||||
if (count == 1) {
|
||||
zmq_assert (next.node);
|
||||
LIBZMQ_DELETE (next.node);
|
||||
} else if (count > 1) {
|
||||
for (unsigned short i = 0; i != count; ++i) {
|
||||
LIBZMQ_DELETE (next.table[i]);
|
||||
}
|
||||
free (next.table);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::add (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
return add_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool zmq::generic_mtrie_t<T>::add_helper (prefix_t prefix_,
|
||||
size_t size_,
|
||||
value_t *pipe_)
|
||||
{
|
||||
// We are at the node corresponding to the prefix. We are done.
|
||||
if (!size_) {
|
||||
bool result = !pipes;
|
||||
if (!pipes) {
|
||||
pipes = new (std::nothrow) pipes_t;
|
||||
alloc_assert (pipes);
|
||||
}
|
||||
pipes->insert (pipe_);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (c < min || c >= min + count) {
|
||||
// The character is out of range of currently handled
|
||||
// characters. We have to extend the table.
|
||||
if (!count) {
|
||||
min = c;
|
||||
count = 1;
|
||||
next.node = NULL;
|
||||
} else if (count == 1) {
|
||||
unsigned char oldc = min;
|
||||
generic_mtrie_t *oldp = next.node;
|
||||
count = (min < c ? c - min : min - c) + 1;
|
||||
next.table =
|
||||
(generic_mtrie_t **) malloc (sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = 0; i != count; ++i)
|
||||
next.table[i] = 0;
|
||||
min = std::min (min, c);
|
||||
next.table[oldc - min] = oldp;
|
||||
} else if (min < c) {
|
||||
// The new character is above the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = c - min + 1;
|
||||
next.table = (generic_mtrie_t **) realloc (
|
||||
next.table, sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = old_count; i != count; i++)
|
||||
next.table[i] = NULL;
|
||||
} else {
|
||||
// The new character is below the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = (min + old_count) - c;
|
||||
next.table = (generic_mtrie_t **) realloc (
|
||||
next.table, sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table + min - c, next.table,
|
||||
old_count * sizeof (generic_mtrie_t *));
|
||||
for (unsigned short i = 0; i != min - c; i++)
|
||||
next.table[i] = NULL;
|
||||
min = c;
|
||||
}
|
||||
}
|
||||
|
||||
// If next node does not exist, create one.
|
||||
if (count == 1) {
|
||||
if (!next.node) {
|
||||
next.node = new (std::nothrow) generic_mtrie_t;
|
||||
alloc_assert (next.node);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
} else {
|
||||
if (!next.table[c - min]) {
|
||||
next.table[c - min] = new (std::nothrow) generic_mtrie_t;
|
||||
alloc_assert (next.table[c - min]);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.table[c - min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
template <typename Arg>
|
||||
void zmq::generic_mtrie_t<T>::rm (value_t *pipe_,
|
||||
void (*func_) (prefix_t data_,
|
||||
size_t size_,
|
||||
Arg arg_),
|
||||
Arg arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
unsigned char *buff = NULL;
|
||||
rm_helper (pipe_, &buff, 0, 0, func_, arg_, call_on_uniq_);
|
||||
free (buff);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Arg>
|
||||
void zmq::generic_mtrie_t<T>::rm_helper(value_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (prefix_t data_,
|
||||
size_t size_,
|
||||
Arg arg_),
|
||||
Arg arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
// Remove the subscription from this node.
|
||||
if (pipes && pipes->erase (pipe_)) {
|
||||
if (!call_on_uniq_ || pipes->empty ()) {
|
||||
func_ (*buff_, buffsize_, arg_);
|
||||
}
|
||||
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the buffer.
|
||||
if (buffsize_ >= maxbuffsize_) {
|
||||
maxbuffsize_ = buffsize_ + 256;
|
||||
*buff_ = (unsigned char *) realloc (*buff_, maxbuffsize_);
|
||||
alloc_assert (*buff_);
|
||||
}
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (count == 1) {
|
||||
(*buff_)[buffsize_] = min;
|
||||
buffsize_++;
|
||||
next.node->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_, func_,
|
||||
arg_, call_on_uniq_);
|
||||
|
||||
// Prune the node if it was made redundant by the removal
|
||||
if (next.node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.node);
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
//
|
||||
// New min non-null character in the node table after the removal
|
||||
unsigned char new_min = min + count - 1;
|
||||
// New max non-null character in the node table after the removal
|
||||
unsigned char new_max = min;
|
||||
for (unsigned short c = 0; c != count; c++) {
|
||||
(*buff_)[buffsize_] = min + c;
|
||||
if (next.table[c]) {
|
||||
next.table[c]->rm_helper (pipe_, buff_, buffsize_ + 1, maxbuffsize_,
|
||||
func_, arg_, call_on_uniq_);
|
||||
|
||||
// Prune redundant nodes from the mtrie
|
||||
if (next.table[c]->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.table[c]);
|
||||
|
||||
zmq_assert (live_nodes > 0);
|
||||
--live_nodes;
|
||||
} else {
|
||||
// The node is not redundant, so it's a candidate for being
|
||||
// the new min/max node.
|
||||
//
|
||||
// We loop through the node array from left to right, so the
|
||||
// first non-null, non-redundant node encountered is the new
|
||||
// minimum index. Conversely, the last non-redundant, non-null
|
||||
// node encountered is the new maximum index.
|
||||
if (c + min < new_min)
|
||||
new_min = c + min;
|
||||
if (c + min > new_max)
|
||||
new_max = c + min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zmq_assert (count > 1);
|
||||
|
||||
// Free the node table if it's no longer used.
|
||||
if (live_nodes == 0) {
|
||||
free (next.table);
|
||||
next.table = NULL;
|
||||
count = 0;
|
||||
}
|
||||
// Compact the node table if possible
|
||||
else if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
zmq_assert (new_min == new_max);
|
||||
zmq_assert (new_min >= min && new_min < min + count);
|
||||
generic_mtrie_t *node = next.table[new_min - min];
|
||||
zmq_assert (node);
|
||||
free (next.table);
|
||||
next.node = node;
|
||||
count = 1;
|
||||
min = new_min;
|
||||
} else if (new_min > min || new_max < min + count - 1) {
|
||||
zmq_assert (new_max - new_min + 1 > 1);
|
||||
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
zmq_assert (new_min > min || new_max < min + count - 1);
|
||||
zmq_assert (new_min >= min);
|
||||
zmq_assert (new_max <= min + count - 1);
|
||||
zmq_assert (new_max - new_min + 1 < count);
|
||||
|
||||
count = new_max - new_min + 1;
|
||||
next.table =
|
||||
(generic_mtrie_t **) malloc (sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
|
||||
memmove (next.table, old_table + (new_min - min),
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
|
||||
min = new_min;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename zmq::generic_mtrie_t<T>::rm_result
|
||||
zmq::generic_mtrie_t<T>::rm (prefix_t prefix_, size_t size_, value_t *pipe_)
|
||||
{
|
||||
return rm_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename zmq::generic_mtrie_t<T>::rm_result zmq::generic_mtrie_t<T>::rm_helper (
|
||||
prefix_t prefix_, size_t size_, value_t *pipe_)
|
||||
{
|
||||
if (!size_) {
|
||||
if (!pipes)
|
||||
return not_found;
|
||||
|
||||
typename pipes_t::size_type erased = pipes->erase (pipe_);
|
||||
if (pipes->empty ()) {
|
||||
zmq_assert (erased == 1);
|
||||
LIBZMQ_DELETE (pipes);
|
||||
return last_value_removed;
|
||||
}
|
||||
return (erased == 1) ? values_remain : not_found;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (!count || c < min || c >= min + count)
|
||||
return not_found;
|
||||
|
||||
generic_mtrie_t *next_node = count == 1 ? next.node : next.table[c - min];
|
||||
|
||||
if (!next_node)
|
||||
return not_found;
|
||||
|
||||
rm_result ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
|
||||
if (next_node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next_node);
|
||||
zmq_assert (count > 0);
|
||||
|
||||
if (count == 1) {
|
||||
next.node = 0;
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
} else {
|
||||
next.table[c - min] = 0;
|
||||
zmq_assert (live_nodes > 1);
|
||||
--live_nodes;
|
||||
|
||||
// Compact the table if possible
|
||||
if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
unsigned short i;
|
||||
for (i = 0; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count = 1;
|
||||
generic_mtrie_t *oldp = next.table[i];
|
||||
free (next.table);
|
||||
next.node = oldp;
|
||||
} else if (c == min) {
|
||||
// We can compact the table "from the left"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count -= i;
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
next.table = (generic_mtrie_t **) malloc (
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table + i,
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
} else if (c == min + count - 1) {
|
||||
// We can compact the table "from the right"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[count - 1 - i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
count -= i;
|
||||
generic_mtrie_t **old_table = next.table;
|
||||
next.table = (generic_mtrie_t **) malloc (
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table,
|
||||
sizeof (generic_mtrie_t *) * count);
|
||||
free (old_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Arg>
|
||||
void zmq::generic_mtrie_t<T>::match (prefix_t data_,
|
||||
size_t size_,
|
||||
void (*func_) (value_t *pipe_, Arg arg_),
|
||||
Arg arg_)
|
||||
{
|
||||
generic_mtrie_t *current = this;
|
||||
while (true) {
|
||||
// Signal the pipes attached to this node.
|
||||
if (current->pipes) {
|
||||
for (typename pipes_t::iterator it = current->pipes->begin ();
|
||||
it != current->pipes->end (); ++it)
|
||||
func_ (*it, arg_);
|
||||
}
|
||||
|
||||
// If we are at the end of the message, there's nothing more to match.
|
||||
if (!size_)
|
||||
break;
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (current->count == 0)
|
||||
break;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (current->count == 1) {
|
||||
if (data_[0] != current->min)
|
||||
break;
|
||||
current = current->next.node;
|
||||
data_++;
|
||||
size_--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
if (data_[0] < current->min
|
||||
|| data_[0] >= current->min + current->count)
|
||||
break;
|
||||
if (!current->next.table[data_[0] - current->min])
|
||||
break;
|
||||
current = current->next.table[data_[0] - current->min];
|
||||
data_++;
|
||||
size_--;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> bool zmq::generic_mtrie_t<T>::is_redundant () const
|
||||
{
|
||||
return !pipes && live_nodes == 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
391
src/mtrie.cpp
391
src/mtrie.cpp
@@ -28,395 +28,10 @@
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
#include "err.hpp"
|
||||
#include "pipe.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "mtrie.hpp"
|
||||
#include "generic_mtrie_impl.hpp"
|
||||
|
||||
zmq::mtrie_t::mtrie_t () : pipes (0), min (0), count (0), live_nodes (0)
|
||||
namespace zmq
|
||||
{
|
||||
}
|
||||
|
||||
zmq::mtrie_t::~mtrie_t ()
|
||||
{
|
||||
LIBZMQ_DELETE (pipes);
|
||||
|
||||
if (count == 1) {
|
||||
zmq_assert (next.node);
|
||||
LIBZMQ_DELETE (next.node);
|
||||
} else if (count > 1) {
|
||||
for (unsigned short i = 0; i != count; ++i) {
|
||||
LIBZMQ_DELETE (next.table[i]);
|
||||
}
|
||||
free (next.table);
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::add (unsigned char *prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
return add_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::add_helper (unsigned char *prefix_,
|
||||
size_t size_,
|
||||
pipe_t *pipe_)
|
||||
{
|
||||
// We are at the node corresponding to the prefix. We are done.
|
||||
if (!size_) {
|
||||
bool result = !pipes;
|
||||
if (!pipes) {
|
||||
pipes = new (std::nothrow) pipes_t;
|
||||
alloc_assert (pipes);
|
||||
}
|
||||
pipes->insert (pipe_);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (c < min || c >= min + count) {
|
||||
// The character is out of range of currently handled
|
||||
// characters. We have to extend the table.
|
||||
if (!count) {
|
||||
min = c;
|
||||
count = 1;
|
||||
next.node = NULL;
|
||||
} else if (count == 1) {
|
||||
unsigned char oldc = min;
|
||||
mtrie_t *oldp = next.node;
|
||||
count = (min < c ? c - min : min - c) + 1;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = 0; i != count; ++i)
|
||||
next.table[i] = 0;
|
||||
min = std::min (min, c);
|
||||
next.table[oldc - min] = oldp;
|
||||
} else if (min < c) {
|
||||
// The new character is above the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = c - min + 1;
|
||||
next.table =
|
||||
(mtrie_t **) realloc (next.table, sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
for (unsigned short i = old_count; i != count; i++)
|
||||
next.table[i] = NULL;
|
||||
} else {
|
||||
// The new character is below the current character range.
|
||||
unsigned short old_count = count;
|
||||
count = (min + old_count) - c;
|
||||
next.table =
|
||||
(mtrie_t **) realloc (next.table, sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table + min - c, next.table,
|
||||
old_count * sizeof (mtrie_t *));
|
||||
for (unsigned short i = 0; i != min - c; i++)
|
||||
next.table[i] = NULL;
|
||||
min = c;
|
||||
}
|
||||
}
|
||||
|
||||
// If next node does not exist, create one.
|
||||
if (count == 1) {
|
||||
if (!next.node) {
|
||||
next.node = new (std::nothrow) mtrie_t;
|
||||
alloc_assert (next.node);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
} else {
|
||||
if (!next.table[c - min]) {
|
||||
next.table[c - min] = new (std::nothrow) mtrie_t;
|
||||
alloc_assert (next.table[c - min]);
|
||||
++live_nodes;
|
||||
}
|
||||
return next.table[c - min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void zmq::mtrie_t::rm (pipe_t *pipe_,
|
||||
void (*func_) (unsigned char *data_,
|
||||
size_t size_,
|
||||
void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
unsigned char *buff = NULL;
|
||||
rm_helper (pipe_, &buff, 0, 0, func_, arg_, call_on_uniq_);
|
||||
free (buff);
|
||||
}
|
||||
|
||||
void zmq::mtrie_t::rm_helper (pipe_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (unsigned char *data_,
|
||||
size_t size_,
|
||||
void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_)
|
||||
{
|
||||
// Remove the subscription from this node.
|
||||
if (pipes && pipes->erase (pipe_)) {
|
||||
if (!call_on_uniq_ || pipes->empty ()) {
|
||||
func_ (*buff_, buffsize_, arg_);
|
||||
}
|
||||
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the buffer.
|
||||
if (buffsize_ >= maxbuffsize_) {
|
||||
maxbuffsize_ = buffsize_ + 256;
|
||||
*buff_ = (unsigned char *) realloc (*buff_, maxbuffsize_);
|
||||
alloc_assert (*buff_);
|
||||
}
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (count == 1) {
|
||||
(*buff_)[buffsize_] = min;
|
||||
buffsize_++;
|
||||
next.node->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_, func_,
|
||||
arg_, call_on_uniq_);
|
||||
|
||||
// Prune the node if it was made redundant by the removal
|
||||
if (next.node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.node);
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
//
|
||||
// New min non-null character in the node table after the removal
|
||||
unsigned char new_min = min + count - 1;
|
||||
// New max non-null character in the node table after the removal
|
||||
unsigned char new_max = min;
|
||||
for (unsigned short c = 0; c != count; c++) {
|
||||
(*buff_)[buffsize_] = min + c;
|
||||
if (next.table[c]) {
|
||||
next.table[c]->rm_helper (pipe_, buff_, buffsize_ + 1, maxbuffsize_,
|
||||
func_, arg_, call_on_uniq_);
|
||||
|
||||
// Prune redundant nodes from the mtrie
|
||||
if (next.table[c]->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next.table[c]);
|
||||
|
||||
zmq_assert (live_nodes > 0);
|
||||
--live_nodes;
|
||||
} else {
|
||||
// The node is not redundant, so it's a candidate for being
|
||||
// the new min/max node.
|
||||
//
|
||||
// We loop through the node array from left to right, so the
|
||||
// first non-null, non-redundant node encountered is the new
|
||||
// minimum index. Conversely, the last non-redundant, non-null
|
||||
// node encountered is the new maximum index.
|
||||
if (c + min < new_min)
|
||||
new_min = c + min;
|
||||
if (c + min > new_max)
|
||||
new_max = c + min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zmq_assert (count > 1);
|
||||
|
||||
// Free the node table if it's no longer used.
|
||||
if (live_nodes == 0) {
|
||||
free (next.table);
|
||||
next.table = NULL;
|
||||
count = 0;
|
||||
}
|
||||
// Compact the node table if possible
|
||||
else if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
zmq_assert (new_min == new_max);
|
||||
zmq_assert (new_min >= min && new_min < min + count);
|
||||
mtrie_t *node = next.table[new_min - min];
|
||||
zmq_assert (node);
|
||||
free (next.table);
|
||||
next.node = node;
|
||||
count = 1;
|
||||
min = new_min;
|
||||
} else if (new_min > min || new_max < min + count - 1) {
|
||||
zmq_assert (new_max - new_min + 1 > 1);
|
||||
|
||||
mtrie_t **old_table = next.table;
|
||||
zmq_assert (new_min > min || new_max < min + count - 1);
|
||||
zmq_assert (new_min >= min);
|
||||
zmq_assert (new_max <= min + count - 1);
|
||||
zmq_assert (new_max - new_min + 1 < count);
|
||||
|
||||
count = new_max - new_min + 1;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
|
||||
memmove (next.table, old_table + (new_min - min),
|
||||
sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
|
||||
min = new_min;
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::rm (unsigned char *prefix_, size_t size_, pipe_t *pipe_)
|
||||
{
|
||||
return rm_helper (prefix_, size_, pipe_);
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::rm_helper (unsigned char *prefix_,
|
||||
size_t size_,
|
||||
pipe_t *pipe_)
|
||||
{
|
||||
if (!size_) {
|
||||
if (pipes) {
|
||||
pipes_t::size_type erased = pipes->erase (pipe_);
|
||||
zmq_assert (erased == 1);
|
||||
if (pipes->empty ()) {
|
||||
LIBZMQ_DELETE (pipes);
|
||||
}
|
||||
}
|
||||
return !pipes;
|
||||
}
|
||||
|
||||
unsigned char c = *prefix_;
|
||||
if (!count || c < min || c >= min + count)
|
||||
return false;
|
||||
|
||||
mtrie_t *next_node = count == 1 ? next.node : next.table[c - min];
|
||||
|
||||
if (!next_node)
|
||||
return false;
|
||||
|
||||
bool ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);
|
||||
|
||||
if (next_node->is_redundant ()) {
|
||||
LIBZMQ_DELETE (next_node);
|
||||
zmq_assert (count > 0);
|
||||
|
||||
if (count == 1) {
|
||||
next.node = 0;
|
||||
count = 0;
|
||||
--live_nodes;
|
||||
zmq_assert (live_nodes == 0);
|
||||
} else {
|
||||
next.table[c - min] = 0;
|
||||
zmq_assert (live_nodes > 1);
|
||||
--live_nodes;
|
||||
|
||||
// Compact the table if possible
|
||||
if (live_nodes == 1) {
|
||||
// If there's only one live node in the table we can
|
||||
// switch to using the more compact single-node
|
||||
// representation
|
||||
unsigned short i;
|
||||
for (i = 0; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count = 1;
|
||||
mtrie_t *oldp = next.table[i];
|
||||
free (next.table);
|
||||
next.node = oldp;
|
||||
} else if (c == min) {
|
||||
// We can compact the table "from the left"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
min += i;
|
||||
count -= i;
|
||||
mtrie_t **old_table = next.table;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table + i, sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
} else if (c == min + count - 1) {
|
||||
// We can compact the table "from the right"
|
||||
unsigned short i;
|
||||
for (i = 1; i < count; ++i)
|
||||
if (next.table[count - 1 - i])
|
||||
break;
|
||||
|
||||
zmq_assert (i < count);
|
||||
count -= i;
|
||||
mtrie_t **old_table = next.table;
|
||||
next.table = (mtrie_t **) malloc (sizeof (mtrie_t *) * count);
|
||||
alloc_assert (next.table);
|
||||
memmove (next.table, old_table, sizeof (mtrie_t *) * count);
|
||||
free (old_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void zmq::mtrie_t::match (unsigned char *data_,
|
||||
size_t size_,
|
||||
void (*func_) (pipe_t *pipe_, void *arg_),
|
||||
void *arg_)
|
||||
{
|
||||
mtrie_t *current = this;
|
||||
while (true) {
|
||||
// Signal the pipes attached to this node.
|
||||
if (current->pipes) {
|
||||
for (pipes_t::iterator it = current->pipes->begin ();
|
||||
it != current->pipes->end (); ++it)
|
||||
func_ (*it, arg_);
|
||||
}
|
||||
|
||||
// If we are at the end of the message, there's nothing more to match.
|
||||
if (!size_)
|
||||
break;
|
||||
|
||||
// If there are no subnodes in the trie, return.
|
||||
if (current->count == 0)
|
||||
break;
|
||||
|
||||
// If there's one subnode (optimisation).
|
||||
if (current->count == 1) {
|
||||
if (data_[0] != current->min)
|
||||
break;
|
||||
current = current->next.node;
|
||||
data_++;
|
||||
size_--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are multiple subnodes.
|
||||
if (data_[0] < current->min
|
||||
|| data_[0] >= current->min + current->count)
|
||||
break;
|
||||
if (!current->next.table[data_[0] - current->min])
|
||||
break;
|
||||
current = current->next.table[data_[0] - current->min];
|
||||
data_++;
|
||||
size_--;
|
||||
}
|
||||
}
|
||||
|
||||
bool zmq::mtrie_t::is_redundant () const
|
||||
{
|
||||
return !pipes && live_nodes == 0;
|
||||
template class generic_mtrie_t<pipe_t>;
|
||||
}
|
||||
|
@@ -30,74 +30,23 @@
|
||||
#ifndef __ZMQ_MTRIE_HPP_INCLUDED__
|
||||
#define __ZMQ_MTRIE_HPP_INCLUDED__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <set>
|
||||
#include "generic_mtrie.hpp"
|
||||
|
||||
#include "stdint.hpp"
|
||||
#if __cplusplus >= 201103L || defined(_MSC_VER)
|
||||
#define ZMQ_HAS_EXTERN_TEMPLATE 1
|
||||
#else
|
||||
#define ZMQ_HAS_EXTERN_TEMPLATE 0
|
||||
#endif
|
||||
|
||||
namespace zmq
|
||||
{
|
||||
class pipe_t;
|
||||
|
||||
// Multi-trie. Each node in the trie is a set of pointers to pipes.
|
||||
#if ZMQ_HAS_EXTERN_TEMPLATE
|
||||
extern template class generic_mtrie_t<pipe_t>;
|
||||
#endif
|
||||
|
||||
class mtrie_t
|
||||
{
|
||||
public:
|
||||
mtrie_t ();
|
||||
~mtrie_t ();
|
||||
|
||||
// Add key to the trie. Returns true if it's a new subscription
|
||||
// rather than a duplicate.
|
||||
bool add (unsigned char *prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
|
||||
// Remove all subscriptions for a specific peer from the trie.
|
||||
// The call_on_uniq_ flag controls if the callback is invoked
|
||||
// when there are no subscriptions left on some topics or on
|
||||
// every removal.
|
||||
void rm (zmq::pipe_t *pipe_,
|
||||
void (*func_) (unsigned char *data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
|
||||
// Remove specific subscription from the trie. Return true is it was
|
||||
// actually removed rather than de-duplicated.
|
||||
bool rm (unsigned char *prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
|
||||
// Signal all the matching pipes.
|
||||
void match (unsigned char *data_,
|
||||
size_t size_,
|
||||
void (*func_) (zmq::pipe_t *pipe_, void *arg_),
|
||||
void *arg_);
|
||||
|
||||
private:
|
||||
bool add_helper (unsigned char *prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
void
|
||||
rm_helper (zmq::pipe_t *pipe_,
|
||||
unsigned char **buff_,
|
||||
size_t buffsize_,
|
||||
size_t maxbuffsize_,
|
||||
void (*func_) (unsigned char *data_, size_t size_, void *arg_),
|
||||
void *arg_,
|
||||
bool call_on_uniq_);
|
||||
bool rm_helper (unsigned char *prefix_, size_t size_, zmq::pipe_t *pipe_);
|
||||
bool is_redundant () const;
|
||||
|
||||
typedef std::set<zmq::pipe_t *> pipes_t;
|
||||
pipes_t *pipes;
|
||||
|
||||
unsigned char min;
|
||||
unsigned short count;
|
||||
unsigned short live_nodes;
|
||||
union
|
||||
{
|
||||
class mtrie_t *node;
|
||||
class mtrie_t **table;
|
||||
} next;
|
||||
|
||||
mtrie_t (const mtrie_t &);
|
||||
const mtrie_t &operator= (const mtrie_t &);
|
||||
};
|
||||
typedef generic_mtrie_t<pipe_t> mtrie_t;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
57
src/xpub.cpp
57
src/xpub.cpp
@@ -35,6 +35,7 @@
|
||||
#include "err.hpp"
|
||||
#include "msg.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "generic_mtrie_impl.hpp"
|
||||
|
||||
zmq::xpub_t::xpub_t (class ctx_t *parent_, uint32_t tid_, int sid_) :
|
||||
socket_base_t (parent_, tid_, sid_),
|
||||
@@ -106,18 +107,23 @@ void zmq::xpub_t::xread_activated (pipe_t *pipe_)
|
||||
pending_metadata.push_back (metadata);
|
||||
pending_flags.push_back (0);
|
||||
} else {
|
||||
bool unique;
|
||||
if (*data == 0)
|
||||
unique = subscriptions.rm (data + 1, size - 1, pipe_);
|
||||
else
|
||||
unique = subscriptions.add (data + 1, size - 1, pipe_);
|
||||
bool notify;
|
||||
if (*data == 0) {
|
||||
mtrie_t::rm_result rm_result =
|
||||
subscriptions.rm (data + 1, size - 1, pipe_);
|
||||
// TODO reconsider what to do if rm_result == mtrie_t::not_found
|
||||
notify =
|
||||
rm_result != mtrie_t::values_remain || verbose_unsubs;
|
||||
} else {
|
||||
bool first_added =
|
||||
subscriptions.add (data + 1, size - 1, pipe_);
|
||||
notify = first_added || verbose_subs;
|
||||
}
|
||||
|
||||
// If the (un)subscription is not a duplicate store it so that it can be
|
||||
// passed to the user on next recv call unless verbose mode is enabled
|
||||
// which makes to pass always these messages.
|
||||
if (options.type == ZMQ_XPUB
|
||||
&& (unique || (*data == 1 && verbose_subs)
|
||||
|| (*data == 0 && verbose_unsubs && verbose_subs))) {
|
||||
// If the request was a new subscription, or the subscription
|
||||
// was removed, or verbose mode is enabled, store it so that
|
||||
// it can be passed to the user on next recv call.
|
||||
if (options.type == ZMQ_XPUB && notify) {
|
||||
pending_data.push_back (blob_t (data, size));
|
||||
if (metadata)
|
||||
metadata->add_ref ();
|
||||
@@ -188,7 +194,7 @@ int zmq::xpub_t::xsetsockopt (int option_,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stub (unsigned char *data_, size_t size_, void *arg_)
|
||||
static void stub (zmq::mtrie_t::prefix_t data_, size_t size_, void *arg_)
|
||||
{
|
||||
LIBZMQ_UNUSED (data_);
|
||||
LIBZMQ_UNUSED (size_);
|
||||
@@ -204,7 +210,7 @@ void zmq::xpub_t::xpipe_terminated (pipe_t *pipe_)
|
||||
// Remove pipe without actually sending the message as it was taken
|
||||
// care of by the manual call above. subscriptions is the real mtrie,
|
||||
// so the pipe must be removed from there or it will be left over.
|
||||
subscriptions.rm (pipe_, stub, NULL, false);
|
||||
subscriptions.rm (pipe_, stub, (void *) NULL, false);
|
||||
} else {
|
||||
// Remove the pipe from the trie. If there are topics that nobody
|
||||
// is interested in anymore, send corresponding unsubscriptions
|
||||
@@ -215,10 +221,9 @@ void zmq::xpub_t::xpipe_terminated (pipe_t *pipe_)
|
||||
dist.pipe_terminated (pipe_);
|
||||
}
|
||||
|
||||
void zmq::xpub_t::mark_as_matching (pipe_t *pipe_, void *arg_)
|
||||
void zmq::xpub_t::mark_as_matching (pipe_t *pipe_, xpub_t *self_)
|
||||
{
|
||||
xpub_t *self = (xpub_t *) arg_;
|
||||
self->dist.match (pipe_);
|
||||
self_->dist.match (pipe_);
|
||||
}
|
||||
|
||||
int zmq::xpub_t::xsend (msg_t *msg_)
|
||||
@@ -295,26 +300,24 @@ bool zmq::xpub_t::xhas_in ()
|
||||
return !pending_data.empty ();
|
||||
}
|
||||
|
||||
void zmq::xpub_t::send_unsubscription (unsigned char *data_,
|
||||
void zmq::xpub_t::send_unsubscription (zmq::mtrie_t::prefix_t data_,
|
||||
size_t size_,
|
||||
void *arg_)
|
||||
xpub_t *self_)
|
||||
{
|
||||
xpub_t *self = (xpub_t *) arg_;
|
||||
|
||||
if (self->options.type != ZMQ_PUB) {
|
||||
if (self_->options.type != ZMQ_PUB) {
|
||||
// Place the unsubscription to the queue of pending (un)subscriptions
|
||||
// to be retrieved by the user later on.
|
||||
blob_t unsub (size_ + 1);
|
||||
*unsub.data () = 0;
|
||||
if (size_ > 0)
|
||||
memcpy (unsub.data () + 1, data_, size_);
|
||||
self->pending_data.ZMQ_PUSH_OR_EMPLACE_BACK (ZMQ_MOVE (unsub));
|
||||
self->pending_metadata.push_back (NULL);
|
||||
self->pending_flags.push_back (0);
|
||||
self_->pending_data.ZMQ_PUSH_OR_EMPLACE_BACK (ZMQ_MOVE (unsub));
|
||||
self_->pending_metadata.push_back (NULL);
|
||||
self_->pending_flags.push_back (0);
|
||||
|
||||
if (self->manual) {
|
||||
self->last_pipe = NULL;
|
||||
self->pending_pipes.push_back (NULL);
|
||||
if (self_->manual) {
|
||||
self_->last_pipe = NULL;
|
||||
self_->pending_pipes.push_back (NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,11 +66,12 @@ class xpub_t : public socket_base_t
|
||||
private:
|
||||
// Function to be applied to the trie to send all the subscriptions
|
||||
// upstream.
|
||||
static void
|
||||
send_unsubscription (unsigned char *data_, size_t size_, void *arg_);
|
||||
static void send_unsubscription (zmq::mtrie_t::prefix_t data_,
|
||||
size_t size_,
|
||||
xpub_t *self_);
|
||||
|
||||
// Function to be applied to each matching pipes.
|
||||
static void mark_as_matching (zmq::pipe_t *pipe_, void *arg_);
|
||||
static void mark_as_matching (zmq::pipe_t *pipe_, xpub_t *arg_);
|
||||
|
||||
// List of all subscriptions mapped to corresponding pipes.
|
||||
mtrie_t subscriptions;
|
||||
|
@@ -65,6 +65,7 @@ set(tests
|
||||
test_stream_timeout
|
||||
test_xpub_manual
|
||||
test_xpub_welcome_msg
|
||||
test_xpub_verbose
|
||||
test_base85
|
||||
test_bind_after_connect_tcp
|
||||
test_sodium
|
||||
|
499
tests/test_xpub_verbose.cpp
Normal file
499
tests/test_xpub_verbose.cpp
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of libzmq, the ZeroMQ core engine in C++.
|
||||
|
||||
libzmq is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License (LGPL) as published
|
||||
by the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
As a special exception, the Contributors give you permission to link
|
||||
this library with independent modules to produce an executable,
|
||||
regardless of the license terms of these independent modules, and to
|
||||
copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the
|
||||
terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library.
|
||||
If you modify this library, you must extend this exception to your
|
||||
version of the library.
|
||||
|
||||
libzmq is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "testutil.hpp"
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
void setUp ()
|
||||
{
|
||||
}
|
||||
void tearDown ()
|
||||
{
|
||||
}
|
||||
|
||||
void test_xpub_verbose_one_sub ()
|
||||
{
|
||||
int rc;
|
||||
char buffer[2];
|
||||
void *ctx = zmq_ctx_new ();
|
||||
TEST_ASSERT_NOT_NULL (ctx);
|
||||
|
||||
void *pub = zmq_socket (ctx, ZMQ_XPUB);
|
||||
TEST_ASSERT_NOT_NULL (pub);
|
||||
rc = zmq_bind (pub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
void *sub = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub);
|
||||
rc = zmq_connect (sub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe for A
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Subscribe socket for B instead
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "B", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'B');
|
||||
|
||||
// Subscribe again for A again
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time it is duplicated, so it will be filtered out
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
int verbose = 1;
|
||||
rc = zmq_setsockopt (pub, ZMQ_XPUB_VERBOSE, &verbose, sizeof (int));
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe socket for A again
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time with VERBOSE the duplicated sub will be received
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Sending A message and B Message
|
||||
rc = zmq_send_const (pub, "A", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_send_const (pub, "B", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_recv (sub, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
rc = zmq_recv (sub, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'B');
|
||||
|
||||
// Clean up.
|
||||
rc = zmq_close (pub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_ctx_term (ctx);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
}
|
||||
|
||||
void test_xpub_verbose_two_subs ()
|
||||
{
|
||||
int rc;
|
||||
char buffer[2];
|
||||
void *ctx = zmq_ctx_new ();
|
||||
TEST_ASSERT_NOT_NULL (ctx);
|
||||
|
||||
void *pub = zmq_socket (ctx, ZMQ_XPUB);
|
||||
TEST_ASSERT_NOT_NULL (pub);
|
||||
rc = zmq_bind (pub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
void *sub0 = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub0);
|
||||
rc = zmq_connect (sub0, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
void *sub1 = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub1);
|
||||
rc = zmq_connect (sub1, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe for A on the first socket
|
||||
rc = zmq_setsockopt (sub0, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Subscribe for A on the second socket
|
||||
rc = zmq_setsockopt (sub1, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time it is duplicated, so it will be filtered out
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Subscribe socket for B instead
|
||||
rc = zmq_setsockopt (sub0, ZMQ_SUBSCRIBE, "B", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'B');
|
||||
|
||||
int verbose = 1;
|
||||
rc = zmq_setsockopt (pub, ZMQ_XPUB_VERBOSE, &verbose, sizeof (int));
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe socket for A again
|
||||
rc = zmq_setsockopt (sub1, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time with VERBOSE the duplicated sub will be received
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Sending A message and B Message
|
||||
rc = zmq_send_const (pub, "A", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_send_const (pub, "B", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_recv (sub0, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
rc = zmq_recv (sub1, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
rc = zmq_recv (sub0, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'B');
|
||||
|
||||
// Clean up.
|
||||
rc = zmq_close (pub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub0);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_ctx_term (ctx);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
}
|
||||
|
||||
void test_xpub_verboser_one_sub ()
|
||||
{
|
||||
int rc;
|
||||
char buffer[3];
|
||||
void *ctx = zmq_ctx_new ();
|
||||
TEST_ASSERT_NOT_NULL (ctx);
|
||||
|
||||
// Create a publisher
|
||||
void *pub = zmq_socket (ctx, ZMQ_XPUB);
|
||||
TEST_ASSERT_NOT_NULL (pub);
|
||||
rc = zmq_bind (pub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Create a subscriber
|
||||
void *sub = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub);
|
||||
rc = zmq_connect (sub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Unsubscribe for A, does not exist yet
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Does not exist, so it will be filtered out by XSUB
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Subscribe for A
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Subscribe again for A again, XSUB will increase refcount
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time it is duplicated, so it will be filtered out by XPUB
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Unsubscribe for A, this time it exists in XPUB
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// XSUB refcounts and will not actually send unsub to PUB until the number
|
||||
// of unsubs match the earlier subs
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive unsubscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 0);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// XSUB only sends the last and final unsub, so XPUB will only receive 1
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Unsubscribe for A, does not exist anymore
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Does not exist, so it will be filtered out by XSUB
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
int verbose = 1;
|
||||
rc = zmq_setsockopt (pub, ZMQ_XPUB_VERBOSER, &verbose, sizeof (int));
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe socket for A again
|
||||
rc = zmq_setsockopt (sub, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber, did not exist anymore
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Sending A message to make sure everything still works
|
||||
rc = zmq_send_const (pub, "A", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_recv (sub, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
// Unsubscribe for A, this time it exists
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive unsubscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 0);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Unsubscribe for A again, it does not exist anymore so XSUB will filter
|
||||
rc = zmq_setsockopt (sub, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// XSUB only sends unsub if it matched it in its trie, IOW: it will only
|
||||
// send it if it existed in the first place even with XPUB_VERBBOSER
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Clean up.
|
||||
rc = zmq_close (pub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_ctx_term (ctx);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
}
|
||||
|
||||
void test_xpub_verboser_two_subs ()
|
||||
{
|
||||
int rc;
|
||||
char buffer[3];
|
||||
void *ctx = zmq_ctx_new ();
|
||||
TEST_ASSERT_NOT_NULL (ctx);
|
||||
|
||||
void *pub = zmq_socket (ctx, ZMQ_XPUB);
|
||||
TEST_ASSERT_NOT_NULL (pub);
|
||||
rc = zmq_bind (pub, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
void *sub0 = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub0);
|
||||
rc = zmq_connect (sub0, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
void *sub1 = zmq_socket (ctx, ZMQ_SUB);
|
||||
TEST_ASSERT_NOT_NULL (sub1);
|
||||
rc = zmq_connect (sub1, "inproc://soname");
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe for A
|
||||
rc = zmq_setsockopt (sub0, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Subscribe again for A on the other socket
|
||||
rc = zmq_setsockopt (sub1, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// This time it is duplicated, so it will be filtered out by XPUB
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Unsubscribe for A, this time it exists in XPUB
|
||||
rc = zmq_setsockopt (sub0, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// sub1 is still subscribed, so no notification
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Unsubscribe the second socket to trigger the notification
|
||||
rc = zmq_setsockopt (sub1, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive unsubscriptions since all sockets are gone
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 0);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Make really sure there is only one notification
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
int verbose = 1;
|
||||
rc = zmq_setsockopt (pub, ZMQ_XPUB_VERBOSER, &verbose, sizeof (int));
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe socket for A again
|
||||
rc = zmq_setsockopt (sub0, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Subscribe socket for A again
|
||||
rc = zmq_setsockopt (sub1, ZMQ_SUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive subscriptions from subscriber, did not exist anymore
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// VERBOSER is set, so subs from both sockets are received
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 1);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Sending A message to make sure everything still works
|
||||
rc = zmq_send_const (pub, "A", 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
|
||||
rc = zmq_recv (sub0, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
rc = zmq_recv (sub1, buffer, 1, 0);
|
||||
TEST_ASSERT_EQUAL_INT (1, rc);
|
||||
assert (buffer[0] == 'A');
|
||||
|
||||
// Unsubscribe for A
|
||||
rc = zmq_setsockopt (sub1, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive unsubscriptions from first subscriber due to VERBOSER
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 0);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Unsubscribe for A again from the other socket
|
||||
rc = zmq_setsockopt (sub0, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Receive unsubscriptions from first subscriber due to VERBOSER
|
||||
rc = zmq_recv (pub, buffer, 2, 0);
|
||||
TEST_ASSERT_EQUAL_INT (2, rc);
|
||||
assert (buffer[0] == 0);
|
||||
assert (buffer[1] == 'A');
|
||||
|
||||
// Unsubscribe again to make sure it gets filtered now
|
||||
rc = zmq_setsockopt (sub1, ZMQ_UNSUBSCRIBE, "A", 1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
|
||||
// Unmatched, so XSUB filters even with VERBOSER
|
||||
rc = zmq_recv (pub, buffer, 1, ZMQ_DONTWAIT);
|
||||
TEST_ASSERT_EQUAL_INT (-1, rc);
|
||||
TEST_ASSERT_EQUAL_INT (EAGAIN, errno);
|
||||
|
||||
// Clean up.
|
||||
rc = zmq_close (pub);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub0);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_close (sub1);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
rc = zmq_ctx_term (ctx);
|
||||
TEST_ASSERT_EQUAL_INT (0, rc);
|
||||
}
|
||||
|
||||
int main (void)
|
||||
{
|
||||
setup_test_environment ();
|
||||
|
||||
UNITY_BEGIN ();
|
||||
RUN_TEST (test_xpub_verbose_one_sub);
|
||||
RUN_TEST (test_xpub_verbose_two_subs);
|
||||
RUN_TEST (test_xpub_verboser_one_sub);
|
||||
RUN_TEST (test_xpub_verboser_two_subs);
|
||||
|
||||
return 0;
|
||||
}
|
@@ -4,6 +4,7 @@ cmake_minimum_required(VERSION "2.8.1")
|
||||
set(unittests
|
||||
unittest_ypipe
|
||||
unittest_poller
|
||||
unittest_mtrie
|
||||
)
|
||||
|
||||
#IF (ENABLE_DRAFTS)
|
||||
|
457
unittests/unittest_mtrie.cpp
Normal file
457
unittests/unittest_mtrie.cpp
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
Copyright (c) 2018 Contributors as noted in the AUTHORS file
|
||||
|
||||
This file is part of 0MQ.
|
||||
|
||||
0MQ is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
0MQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../tests/testutil.hpp"
|
||||
|
||||
#if defined(min)
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <generic_mtrie_impl.hpp>
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
void setUp ()
|
||||
{
|
||||
}
|
||||
void tearDown ()
|
||||
{
|
||||
}
|
||||
|
||||
int getlen (const zmq::generic_mtrie_t<int>::prefix_t &data)
|
||||
{
|
||||
return (int) strlen (reinterpret_cast<const char *> (data));
|
||||
}
|
||||
|
||||
void test_create ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
}
|
||||
|
||||
void mtrie_count (int *pipe, int *count)
|
||||
{
|
||||
LIBZMQ_UNUSED (pipe);
|
||||
++*count;
|
||||
}
|
||||
|
||||
void test_check_empty_match_nonempty_data ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_check_empty_match_empty_data ()
|
||||
{
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (NULL, 0, mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_add_single_entry_match_exact ()
|
||||
{
|
||||
int pipe;
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
bool res = mtrie.add (test_name, getlen (test_name), &pipe);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
|
||||
void test_add_single_entry_twice_match_exact ()
|
||||
{
|
||||
int pipe;
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
bool res = mtrie.add (test_name, getlen (test_name), &pipe);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
res = mtrie.add (test_name, getlen (test_name), &pipe);
|
||||
TEST_ASSERT_FALSE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
|
||||
void test_add_two_entries_with_same_name_match_exact ()
|
||||
{
|
||||
int pipe_1, pipe_2;
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
bool res = mtrie.add (test_name, getlen (test_name), &pipe_1);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
res = mtrie.add (test_name, getlen (test_name), &pipe_2);
|
||||
TEST_ASSERT_FALSE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (2, count);
|
||||
}
|
||||
|
||||
void test_add_two_entries_match_prefix_and_exact ()
|
||||
{
|
||||
int pipe_1, pipe_2;
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name_prefix =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name_full =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foobar");
|
||||
|
||||
bool res = mtrie.add (test_name_prefix, getlen (test_name_prefix), &pipe_1);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
res = mtrie.add (test_name_full, getlen (test_name_full), &pipe_2);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name_full, getlen (test_name_full), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (2, count);
|
||||
}
|
||||
|
||||
void test_add_rm_single_entry_match_exact ()
|
||||
{
|
||||
int pipe;
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
mtrie.add (test_name, getlen (test_name), &pipe);
|
||||
zmq::generic_mtrie_t<int>::rm_result res =
|
||||
mtrie.rm (test_name, getlen (test_name), &pipe);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::last_value_removed, res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_0_size_empty ()
|
||||
{
|
||||
int pipe;
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
|
||||
zmq::generic_mtrie_t<int>::rm_result res = mtrie.rm (0, 0, &pipe);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::not_found, res);
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_empty ()
|
||||
{
|
||||
int pipe;
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t test_name =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> ("foo");
|
||||
|
||||
zmq::generic_mtrie_t<int>::rm_result res =
|
||||
mtrie.rm (test_name, getlen (test_name), &pipe);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::not_found, res);
|
||||
|
||||
int count = 0;
|
||||
mtrie.match (test_name, getlen (test_name), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
|
||||
void test_add_and_rm_other (const char *add_name, const char *rm_name)
|
||||
{
|
||||
int addpipe, rmpipe;
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
const zmq::generic_mtrie_t<int>::prefix_t add_name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (add_name);
|
||||
const zmq::generic_mtrie_t<int>::prefix_t rm_name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (rm_name);
|
||||
|
||||
mtrie.add (add_name_data, getlen (add_name_data), &addpipe);
|
||||
|
||||
zmq::generic_mtrie_t<int>::rm_result res =
|
||||
mtrie.rm (rm_name_data, getlen (rm_name_data), &rmpipe);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::not_found, res);
|
||||
|
||||
{
|
||||
int count = 0;
|
||||
mtrie.match (add_name_data, getlen (add_name_data), mtrie_count,
|
||||
&count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
|
||||
if (strncmp (add_name, rm_name,
|
||||
std::min (strlen (add_name), strlen (rm_name) + 1))
|
||||
!= 0) {
|
||||
int count = 0;
|
||||
mtrie.match (rm_name_data, getlen (rm_name_data), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (0, count);
|
||||
}
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_nonempty_samename ()
|
||||
{
|
||||
// TODO this triggers an assertion
|
||||
test_add_and_rm_other ("foo", "foo");
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_nonempty_differentname ()
|
||||
{
|
||||
test_add_and_rm_other ("foo", "bar");
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_nonempty_prefix ()
|
||||
{
|
||||
// TODO here, a test assertion fails
|
||||
test_add_and_rm_other ("foobar", "foo");
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_nonempty_prefixed ()
|
||||
{
|
||||
test_add_and_rm_other ("foo", "foobar");
|
||||
}
|
||||
|
||||
void add_indexed_expect_unique (zmq::generic_mtrie_t<int> &mtrie,
|
||||
int *pipes,
|
||||
const char **names,
|
||||
size_t i)
|
||||
{
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (names[i]);
|
||||
|
||||
bool res = mtrie.add (name_data, getlen (name_data), &pipes[i]);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::last_value_removed, res);
|
||||
}
|
||||
|
||||
void test_rm_nonexistent_between ()
|
||||
{
|
||||
int pipes[3];
|
||||
const char *names[] = {"foo1", "foo2", "foo3"};
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_indexed_expect_unique (mtrie, pipes, names, 0);
|
||||
add_indexed_expect_unique (mtrie, pipes, names, 2);
|
||||
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (names[1]);
|
||||
|
||||
zmq::generic_mtrie_t<int>::rm_result res =
|
||||
mtrie.rm (name_data, getlen (name_data), &pipes[1]);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::not_found, res);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void add_entries (zmq::generic_mtrie_t<int> &mtrie,
|
||||
int (&pipes)[N],
|
||||
const char *(&names)[N])
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
add_indexed_expect_unique (mtrie, pipes, names, i);
|
||||
}
|
||||
}
|
||||
|
||||
void test_add_multiple ()
|
||||
{
|
||||
int pipes[3];
|
||||
const char *names[] = {"foo1", "foo2", "foo3"};
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_entries (mtrie, pipes, names);
|
||||
|
||||
for (size_t i = 0; i < sizeof (names) / sizeof (names[0]); ++i) {
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (names[i]);
|
||||
int count = 0;
|
||||
mtrie.match (name_data, getlen (name_data), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
}
|
||||
|
||||
void test_add_multiple_reverse ()
|
||||
{
|
||||
int pipes[3];
|
||||
const char *names[] = {"foo1", "foo2", "foo3"};
|
||||
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
for (int i = 2; i >= 0; --i) {
|
||||
add_indexed_expect_unique (mtrie, pipes, names, (size_t) i);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (names[i]);
|
||||
int count = 0;
|
||||
mtrie.match (name_data, getlen (name_data), mtrie_count, &count);
|
||||
TEST_ASSERT_EQUAL_INT (1, count);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N> void add_and_rm_entries (const char *(&names)[N])
|
||||
{
|
||||
int pipes[N];
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_entries (mtrie, pipes, names);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (names[i]);
|
||||
|
||||
zmq::generic_mtrie_t<int>::rm_result res =
|
||||
mtrie.rm (name_data, getlen (name_data), &pipes[i]);
|
||||
TEST_ASSERT_EQUAL (zmq::generic_mtrie_t<int>::last_value_removed, res);
|
||||
}
|
||||
}
|
||||
|
||||
void test_rm_multiple_in_order ()
|
||||
{
|
||||
const char *names[] = {"foo1", "foo2", "foo3"};
|
||||
add_and_rm_entries (names);
|
||||
}
|
||||
|
||||
void test_rm_multiple_reverse_order ()
|
||||
{
|
||||
const char *names[] = {"foo3", "foo2", "foo1"};
|
||||
add_and_rm_entries (names);
|
||||
}
|
||||
|
||||
void check_name (zmq::generic_mtrie_t<int>::prefix_t data_,
|
||||
size_t len_,
|
||||
const char *name_)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_UINT (strlen (name_), len_);
|
||||
TEST_ASSERT_EQUAL_STRING_LEN (name_, data_, len_);
|
||||
}
|
||||
|
||||
template <size_t N> void add_entries_rm_pipes_unique (const char *(&names)[N])
|
||||
{
|
||||
int pipes[N];
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_entries (mtrie, pipes, names);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
mtrie.rm (&pipes[i], check_name, names[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
void test_rm_with_callback_multiple_in_order ()
|
||||
{
|
||||
const char *names[] = {"foo1", "foo2", "foo3"};
|
||||
|
||||
add_entries_rm_pipes_unique (names);
|
||||
}
|
||||
|
||||
void test_rm_with_callback_multiple_reverse_order ()
|
||||
{
|
||||
const char *names[] = {"foo3", "foo2", "foo1"};
|
||||
|
||||
add_entries_rm_pipes_unique (names);
|
||||
}
|
||||
|
||||
void check_count (zmq::generic_mtrie_t<int>::prefix_t data_,
|
||||
size_t len_,
|
||||
int *count_)
|
||||
{
|
||||
--count_;
|
||||
TEST_ASSERT_GREATER_OR_EQUAL (0, count_);
|
||||
}
|
||||
|
||||
void add_duplicate_entry (zmq::generic_mtrie_t<int> &mtrie, int (&pipes)[2])
|
||||
{
|
||||
const char *name = "foo";
|
||||
|
||||
const zmq::generic_mtrie_t<int>::prefix_t name_data =
|
||||
reinterpret_cast<zmq::generic_mtrie_t<int>::prefix_t> (name);
|
||||
|
||||
bool res = mtrie.add (name_data, getlen (name_data), &pipes[0]);
|
||||
TEST_ASSERT_TRUE (res);
|
||||
res = mtrie.add (name_data, getlen (name_data), &pipes[1]);
|
||||
TEST_ASSERT_FALSE (res);
|
||||
}
|
||||
|
||||
void test_rm_with_callback_duplicate ()
|
||||
{
|
||||
int pipes[2];
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_duplicate_entry (mtrie, pipes);
|
||||
|
||||
int count = 1;
|
||||
mtrie.rm (&pipes[0], check_count, &count, false);
|
||||
count = 1;
|
||||
mtrie.rm (&pipes[1], check_count, &count, false);
|
||||
}
|
||||
|
||||
void test_rm_with_callback_duplicate_uniq_only ()
|
||||
{
|
||||
int pipes[2];
|
||||
zmq::generic_mtrie_t<int> mtrie;
|
||||
add_duplicate_entry (mtrie, pipes);
|
||||
|
||||
int count = 0;
|
||||
mtrie.rm (&pipes[0], check_count, &count, true);
|
||||
count = 1;
|
||||
mtrie.rm (&pipes[1], check_count, &count, true);
|
||||
}
|
||||
|
||||
int main (void)
|
||||
{
|
||||
setup_test_environment ();
|
||||
|
||||
UNITY_BEGIN ();
|
||||
RUN_TEST (test_create);
|
||||
RUN_TEST (test_check_empty_match_nonempty_data);
|
||||
RUN_TEST (test_check_empty_match_empty_data);
|
||||
RUN_TEST (test_add_single_entry_match_exact);
|
||||
RUN_TEST (test_add_single_entry_twice_match_exact);
|
||||
RUN_TEST (test_add_rm_single_entry_match_exact);
|
||||
RUN_TEST (test_add_two_entries_match_prefix_and_exact);
|
||||
RUN_TEST (test_add_two_entries_with_same_name_match_exact);
|
||||
|
||||
RUN_TEST (test_rm_nonexistent_0_size_empty);
|
||||
RUN_TEST (test_rm_nonexistent_empty);
|
||||
RUN_TEST (test_rm_nonexistent_nonempty_samename);
|
||||
RUN_TEST (test_rm_nonexistent_nonempty_differentname);
|
||||
RUN_TEST (test_rm_nonexistent_nonempty_prefix);
|
||||
RUN_TEST (test_rm_nonexistent_nonempty_prefixed);
|
||||
RUN_TEST (test_rm_nonexistent_between);
|
||||
|
||||
RUN_TEST (test_add_multiple);
|
||||
RUN_TEST (test_add_multiple_reverse);
|
||||
RUN_TEST (test_rm_multiple_in_order);
|
||||
RUN_TEST (test_rm_multiple_reverse_order);
|
||||
|
||||
RUN_TEST (test_rm_with_callback_multiple_in_order);
|
||||
RUN_TEST (test_rm_with_callback_multiple_reverse_order);
|
||||
RUN_TEST (test_rm_with_callback_duplicate);
|
||||
RUN_TEST (test_rm_with_callback_duplicate_uniq_only);
|
||||
|
||||
return UNITY_END ();
|
||||
}
|
Reference in New Issue
Block a user