You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2017/04/10 20:39:31 UTC

celix git commit: CELIX-408: Initial commit for adding add_runtime command. Also add some usage for running/testing pubsub

Repository: celix
Updated Branches:
  refs/heads/feature/CELIX-408_runtime [created] 97df926c2


CELIX-408: Initial commit for adding add_runtime command. Also add some usage for running/testing pubsub


Project: http://git-wip-us.apache.org/repos/asf/celix/repo
Commit: http://git-wip-us.apache.org/repos/asf/celix/commit/97df926c
Tree: http://git-wip-us.apache.org/repos/asf/celix/tree/97df926c
Diff: http://git-wip-us.apache.org/repos/asf/celix/diff/97df926c

Branch: refs/heads/feature/CELIX-408_runtime
Commit: 97df926c2289323379506d47a6095b0182d752b7
Parents: 0616690
Author: Pepijn Noltes <pe...@gmail.com>
Authored: Sat Apr 8 21:26:39 2017 +0200
Committer: Pepijn Noltes <pe...@gmail.com>
Committed: Mon Apr 10 22:37:29 2017 +0200

----------------------------------------------------------------------
 cmake/CMakeCelix.cmake                          |   1 +
 cmake/cmake_celix/DeployPackaging.cmake         |  22 +-
 cmake/cmake_celix/Runtimes.cmake                | 161 +++++++++++++++
 cmake/cmake_celix/runtime_common.sh.in          | 152 ++++++++++++++
 cmake/cmake_celix/runtime_start.sh.in           |  26 +++
 cmake/cmake_celix/runtime_stop.sh.in            |  19 ++
 documents/cmake_commands/readme.md              |   2 +
 framework/private/src/bundle_context.c          |   2 +-
 launcher/CMakeLists.txt                         |  17 ++
 launcher/private/src/celix_test_runner.cpp      |  73 +++++++
 pubsub/CMakeLists.txt                           |   3 +
 pubsub/deploy/CMakeLists.txt                    |  58 ++++++
 pubsub/mock/CMakeLists.txt                      |   8 +-
 .../private/src/topic_publication.c             |   8 +-
 pubsub/test/CMakeLists.txt                      |  87 ++++++++
 pubsub/test/msg_descriptors/msg.descriptor      |   8 +
 pubsub/test/test/msg.h                          |  27 +++
 pubsub/test/test/sut_activator.c                | 112 +++++++++++
 pubsub/test/test/tst_activator.cpp              | 199 +++++++++++++++++++
 19 files changed, 971 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/CMakeCelix.cmake
----------------------------------------------------------------------
diff --git a/cmake/CMakeCelix.cmake b/cmake/CMakeCelix.cmake
index 9ca4de7..8c14577 100644
--- a/cmake/CMakeCelix.cmake
+++ b/cmake/CMakeCelix.cmake
@@ -26,6 +26,7 @@ include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/Dependencies.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/BundlePackaging.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/DeployPackaging.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/DockerPackaging.cmake)
+include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/Runtimes.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/ApacheRat.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/CodeCoverage.cmake)
 include(${CELIX_CMAKE_DIRECTORY}/cmake_celix/BuildOptions.cmake)

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/cmake_celix/DeployPackaging.cmake
----------------------------------------------------------------------
diff --git a/cmake/cmake_celix/DeployPackaging.cmake b/cmake/cmake_celix/DeployPackaging.cmake
index 22e2885..626b7b8 100644
--- a/cmake/cmake_celix/DeployPackaging.cmake
+++ b/cmake/cmake_celix/DeployPackaging.cmake
@@ -28,24 +28,27 @@ function(add_deploy)
     list(REMOVE_AT ARGN 0)
 
     set(OPTIONS COPY)
-    set(ONE_VAL_ARGS GROUP NAME LAUNCHER)
+    set(ONE_VAL_ARGS GROUP NAME LAUNCHER DIR)
     set(MULTI_VAL_ARGS BUNDLES PROPERTIES)
     cmake_parse_arguments(DEPLOY "${OPTIONS}" "${ONE_VAL_ARGS}" "${MULTI_VAL_ARGS}" ${ARGN})
 
     ##### Check arguments #####
-    if(NOT DEPLOY_NAME) 
+    if (NOT DEPLOY_NAME)
         set(DEPLOY_NAME "${DEPLOY_TARGET}")
-    endif()
+    endif ()
+    if (NOT DEPLOY_DIR)
+        set(DEPLOY_DIR "${CMAKE_BINARY_DIR}/deploy")
+    endif ()
     ######
 
     ##### Setting defaults #####
-    if(DEPLOY_GROUP) 
-        set(DEPLOY_LOCATION "${CMAKE_BINARY_DIR}/deploy/${DEPLOY_GROUP}/${DEPLOY_NAME}")
+    if (DEPLOY_GROUP)
+        set(DEPLOY_LOCATION "${DEPLOY_DIR}/${DEPLOY_GROUP}/${DEPLOY_NAME}")
         set(DEPLOY_PRINT_NAME "${DEPLOY_GROUP}/${DEPLOY_NAME}")
-    else()
-        set(DEPLOY_LOCATION "${CMAKE_BINARY_DIR}/deploy/${DEPLOY_NAME}")
+    else ()
+        set(DEPLOY_LOCATION "${DEPLOY_DIR}/${DEPLOY_NAME}")
         set(DEPLOY_PRINT_NAME "${DEPLOY_NAME}")
-    endif()
+    endif ()
     ######
 
 
@@ -77,6 +80,9 @@ function(add_deploy)
     #setup dependencies based on timestamp
     add_custom_command(OUTPUT "${TIMESTAMP_FILE}"
         COMMAND ${CMAKE_COMMAND} -E touch ${TIMESTAMP_FILE}
+        COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_PROPERTY:${DEPLOY_TARGET},DEPLOY_LOCATION>
+        COMMAND chmod +x $<TARGET_PROPERTY:${DEPLOY_TARGET},DEPLOY_LOCATION>/run.sh
+        COMMAND chmod +x $<TARGET_PROPERTY:${DEPLOY_TARGET},DEPLOY_LOCATION>/release.sh
         DEPENDS  "$<TARGET_PROPERTY:${DEPLOY_TARGET},DEPLOY_TARGET_DEPS>" ${DEPLOY_FILE_TARGETS} 
         WORKING_DIRECTORY "${DEPLOY_LOCATION}"
         COMMENT "Deploying ${DEPLOY_PRINT_NAME}" VERBATIM

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/cmake_celix/Runtimes.cmake
----------------------------------------------------------------------
diff --git a/cmake/cmake_celix/Runtimes.cmake b/cmake/cmake_celix/Runtimes.cmake
new file mode 100644
index 0000000..14f88eb
--- /dev/null
+++ b/cmake/cmake_celix/Runtimes.cmake
@@ -0,0 +1,161 @@
+add_custom_target(runtimes ALL
+    DEPENDS "$<TARGET_PROPERTY:runtimes,DEPS>"
+)
+set_target_properties(runtimes PROPERTIES "DEPS" "")
+set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_BINARY_DIR}/runtimes")
+
+function(add_runtime)
+    list(GET ARGN 0 RUNTIME_TARGET_NAME)
+    list(REMOVE_AT ARGN 0)
+
+    set(OPTIONS USE_TERM LOG_TO_FILES)
+    set(ONE_VAL_ARGS WAIT_FOR NAME GROUP)
+    set(MULTI_VAL_ARGS DEPLOYMENTS COMMANDS)
+    cmake_parse_arguments(RUNTIME "${OPTIONS}" "${ONE_VAL_ARGS}" "${MULTI_VAL_ARGS}" ${ARGN})
+
+    if (NOT RUNTIME_NAME)
+        set(RUNTIME_NAME ${RUNTIME_TARGET_NAME})
+    endif ()
+    if (NOT RUNTIME_GROUP)
+        set(RUNTIME_LOCATION "${PROJECT_BINARY_DIR}/runtimes/${RUNTIME_NAME}")
+    else ()
+        set(RUNTIME_LOCATION "${PROJECT_BINARY_DIR}/runtimes/${RUNTIME_GROUP}/${RUNTIME_NAME}")
+    endif ()
+    set(TIMESTAMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/${RUNTIME_TARGET_NAME}-runtime-timestamp")
+
+    set(START_SCRIPT "$<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>/start.sh")
+    set(STOP_SCRIPT "$<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>/stop.sh")
+    set(COMMON_SCRIPT "$<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>/common.sh")
+
+    add_custom_command(OUTPUT "${TIMESTAMP_FILE}"
+        COMMAND ${CMAKE_COMMAND} -E touch ${TIMESTAMP_FILE}
+        COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>
+        COMMAND chmod +x $<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>/start.sh
+        COMMAND chmod +x $<TARGET_PROPERTY:${RUNTIME_TARGET_NAME},RUNTIME_LOCATION>/stop.sh
+        #TODO DEPENDS  "$<TARGET_PROPERTY:${DEPLOY_TARGET},DEPLOY_TARGET_DEPS>" ${DEPLOY_FILE_TARGETS}
+        DEPENDS  ${START_SCRIPT} ${STOP_SCRIPT} ${SETUP_SCRIPT}
+        WORKING_DIRECTORY "${RUNTIME_LOCATION}"
+        COMMENT "Creating runtime ${RUNTIME_TARGET_NAME}" VERBATIM
+    )
+    add_custom_target(${RUNTIME_TARGET_NAME}
+        DEPENDS "${TIMESTAMP_FILE}"
+    )
+
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_DEPLOYMENTS" "") #deployments that should be runned
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_COMMANDS" "") #command that should be executed
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_NAME" "${RUNTIME_NAME}") #The runtime workdir
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_GROUP" "${RUNTIME_GROUP}") #The runtime workdir
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_LOCATION" "${RUNTIME_LOCATION}") #The runtime workdir
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_USE_TERM" "${RUNTIME_USE_TERM}") #Wether or not the use terminal
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_LOG_TO_FILES" "${RUNTIME_LOG_TO_FILES}") #log to files or std out/err
+
+    #wait for deployment, e.g. the one that control the lifecycle of the runtime.
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_WAIT_FOR_DEPLOYMENT" "")
+
+    #wait for command, e.g. the one that control the lifecycle of the runtime.
+    set_target_properties(${RUNTIME_TARGET_NAME} PROPERTIES "RUNTIME_WAIT_FOR_COMMAND" "")
+
+
+    #replaces @RUNTIME_TARGET_NAME@
+    #TODO move to another location
+    configure_file("${CELIX_CMAKE_DIRECTORY}/cmake_celix/runtime_start.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/start.sh.${RUNTIME_TARGET_NAME}.in.1" @ONLY)
+    configure_file("${CELIX_CMAKE_DIRECTORY}/cmake_celix/runtime_stop.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/stop.sh.${RUNTIME_TARGET_NAME}.in.1" @ONLY)
+    configure_file("${CELIX_CMAKE_DIRECTORY}/cmake_celix/runtime_common.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/common.sh.${RUNTIME_TARGET_NAME}.in.1" @ONLY)
+
+
+    #replaces $<TARGET_PROPERTY:<RUNTIME_NAME>,RUNTIME_DEPLOYMENTS>
+    file(GENERATE
+            OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/common.sh.${RUNTIME_TARGET_NAME}.in.2"
+            INPUT "${CMAKE_CURRENT_BINARY_DIR}/common.sh.${RUNTIME_TARGET_NAME}.in.1"
+    )
+    file(GENERATE
+        OUTPUT "${START_SCRIPT}"
+        INPUT "${CMAKE_CURRENT_BINARY_DIR}/start.sh.${RUNTIME_TARGET_NAME}.in.1"
+    )
+    file(GENERATE
+        OUTPUT "${STOP_SCRIPT}"
+        INPUT "${CMAKE_CURRENT_BINARY_DIR}/stop.sh.${RUNTIME_TARGET_NAME}.in.1"
+    )
+
+
+    #replaces list of $<TARGET_PROPERTY:<DEPLOY_NAME>,DEPLOY_LOCATION>, only needed for common
+    file(GENERATE
+        OUTPUT "${COMMON_SCRIPT}"
+        INPUT "${CMAKE_CURRENT_BINARY_DIR}/common.sh.${RUNTIME_TARGET_NAME}.in.2"
+    )
+
+    get_target_property(DEPS runtimes "DEPS")
+    list(APPEND DEPS "${RUNTIME_TARGET_NAME}")
+    set_target_properties(runtimes PROPERTIES "DEPS" "${DEPS}")
+
+    runtime_deployments(${RUNTIME_TARGET_NAME} ${RUNTIME_DEPLOYMENTS})
+    runtime_commands(${RUNTIME_TARGET_NAME} ${RUNTIME_COMMANDS})
+
+    if (RUNTIME_WAIT_FOR)
+        runtime_deployment_wait_for(${RUNTIME_TARGET_NAME} ${RUNTIME_WAIT_FOR})
+    endif ()
+
+endfunction()
+
+function(runtime_use_term)
+    #0 is runtime TARGET
+    #1 is BOOL (use xterm)
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(GET ARGN 1 USE_TERM)
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_USE_TERM" "${USE_TERM}")
+endfunction()
+
+function(runtime_log_to_files)
+    #0 is runtime TARGET
+    #1 is BOOL (log to files)
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(GET ARGN 1 LOG_TO_FILES)
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_LOG_TO_FILES" "${LOG_TO_FILES}")
+endfunction()
+
+function(runtime_deployments)
+    #0 is runtime TARGET
+    #1..n is deployments
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(REMOVE_AT ARGN 0)
+
+    get_target_property(DEPLOYMENTS ${RUNTIME_NAME} "RUNTIME_DEPLOYMENTS")
+    foreach(DEPLOYMENT IN ITEMS ${ARGN})
+	    list(APPEND DEPLOYMENTS "$<TARGET_PROPERTY:${DEPLOYMENT},DEPLOY_LOCATION>")
+   endforeach()
+
+   set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_DEPLOYMENTS" "${DEPLOYMENTS}")
+endfunction()
+
+function(runtime_deployment_wait_for)
+    #0 is runtime TARGET
+    #1 is deployment TARGET
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(GET ARGN 1 DEPLOYMENT)
+
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_WAIT_FOR_DEPLOYMENT" "$<TARGET_PROPERTY:${DEPLOYMENT},DEPLOY_LOCATION>")
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_WAIT_FOR_COMMAND" "")
+endfunction()
+
+function(runtime_commands)
+    #0 is runtime TARGET
+    #1..n is commands
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(REMOVE_AT ARGN 0)
+
+    get_target_property(COMMANDS ${RUNTIME_NAME} "RUNTIME_COMMANDS")
+    foreach(CMD IN ITEMS ${ARGN})
+        list(APPEND COMMANDS ${CMD})
+    endforeach()
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_COMMANDS" "${COMMANDS}")
+endfunction()
+
+function(runtime_command_wait_for)
+    #0 is deploy TARGET
+    #1 is COMMAND STR
+    list(GET ARGN 0 RUNTIME_NAME)
+    list(GET ARGN 1 COMMAND)
+
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_WAIT_FOR_COMMAND" "${COMMAND}")
+    set_target_properties(${RUNTIME_NAME} PROPERTIES "RUNTIME_WAIT_FOR_DEPLOYMENT" "")
+endfunction()
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/cmake_celix/runtime_common.sh.in
----------------------------------------------------------------------
diff --git a/cmake/cmake_celix/runtime_common.sh.in b/cmake/cmake_celix/runtime_common.sh.in
new file mode 100644
index 0000000..e59c6b0
--- /dev/null
+++ b/cmake/cmake_celix/runtime_common.sh.in
@@ -0,0 +1,152 @@
+#!/bin/sh
+
+#Locations
+BUILD_DIR="${BUILD_DIR:-@PROJECT_BINARY_DIR@}"
+RUNTIME_DIR="${RUNTIME_DIR:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_LOCATION>}"
+DEPLOY_DIR="${DEPLOY_DIR:-${BUILD_DIR}/deploy}"
+
+#Name & Group
+RUNTIME_NAME="${RUNTIME_NAME:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_NAME>}"
+RUNTIME_GROUP="${RUNTIME_NAME:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_GROUP>}"
+
+#deployments & commands
+DEPLOYMENTS=${DEPLOYMENTS:-"$<JOIN:$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_DEPLOYMENTS>, >"}
+COMMANDS=${COMMANDS:-"$<JOIN:$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_COMMANDS>, >"}
+
+#Options
+TERM_CMD="${TERM_CMD:-xterm}"
+TERM_OPTS="${TERM_OPTS:-}"
+USE_TERM="${USE_TERM:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_USE_TERM>}"
+RELEASE_SH="${RELEASE_SH:-}"
+WAIT_FOR_DEPLOYMENT="${WAIT_FOR_DEPLOYMENT:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_WAIT_FOR_DEPLOYMENT>}"
+WAIT_FOR_CMD="${WAIT_FOR_CMD:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_WAIT_FOR_COMMAND>}"
+LOG_TO_FILES="${LOG_TO_FILES:-$<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_LOG_TO_FILES>}"
+KILL_OPTS="${KILL_OPTS:-}"
+
+PIDS=""
+WAIT_FOR_PID=""
+LOG_SUFFIX=$(date +"%s")
+trap stop_all INT
+
+#clear dirs
+function rt_init() {
+    rm -fr ${RUNTIME_DIR}/run #contains pids, etc
+    mkdir ${RUNTIME_DIR}/run
+    if [ ! -e ${RUNTIME_DIR}/logs ] ; then
+        mkdir ${RUNTIME_DIR}/logs
+    fi
+}
+
+##functions
+function rt_stop_all() {
+    for PID in ${PIDS}; do
+        echo "Sending signal to ${PID}"
+        kill ${KILL_OPTS} ${PID}
+    done
+}
+
+function rt_stop() {
+    PIDS=$@
+    echo "Stopping pids ${PIDS}"
+    kill ${KILL_OPTS} ${PIDS}
+}
+
+function rt_run_deployment() {
+    DEPLOYMENT_DIR=$1
+    DEPLOYMENT=$(basename ${DEPLOYMENT_DIR})
+    LOG_FILE="${RUNTIME_DIR}/logs/${DEPLOYMENT}-${LOG_SUFFIX}.log"
+    echo "Running ${DEPLOYMENT}"
+    cd ${DEPLOYMENT_DIR} #assuming absolute dir
+    if [ -d .cache ] ; then
+        echo "Clearing cache"
+        rm -fr .cache #clear cache
+    fi
+    . ./release.sh #run deployment release
+    if [ "${USE_TERM}" = "TRUE" ] ; then
+        if [ "${LOG_TO_FILES}" = "TRUE" ] ; then
+            ${TERM_CMD} ${TERM_OPTS} -e "./${DEPLOYMENT} &> ${LOG_FILE}" &
+        else
+            ${TERM_CMD} ${TERM_OPTS} -e "./${DEPLOYMENT}" &
+        fi
+    else #run in this shell
+        if [ "${LOG_TO_FILES}" = "TRUE" ] ; then
+            ./${DEPLOYMENT} &> ${LOG_FILE} &
+        else
+            ./${DEPLOYMENT} &
+        fi
+    fi
+    PID=$!
+    echo "PID of DEPLOYMENT '${DEPLOYMENT}' is ${PID}"
+    if [ ! -z "${WAIT_FOR_DEPLOYMENT}" -a "${DEPLOYMENT_DIR}" = "${WAIT_FOR_DEPLOYMENT}" ] ; then
+        WAIT_FOR_PID=${PID}
+        echo "${PID}" > ${RUNTIME_DIR}/run/wait_for_pid
+    else
+        PIDS="${PID} ${PIDS}"
+        echo "${PIDS}" > ${RUNTIME_DIR}/run/pids
+    fi
+    cd -
+}
+
+function rt_run_cmd() {
+    CMD="$1"
+    LOG_FILE="${RUNTIME_DIR}/logs/${CMD}-${LOG_SUFFIX}.log" #TODO only use first word in case of complex command
+    cd ${RUNTIME_DIR}
+    if [ "${USE_TERM}" = "TRUE" ] ; then
+        if [ "${LOG_TO_FILES}" = "TRUE" ] ; then
+            ${TERM_CMD} ${TERM_OPTS} -e "${CMD} &> ${LOG_FILE}" &
+        else
+            ${TERM_CMD} ${TERM_OPTS} -e "${CMD}" &
+        fi
+    else
+        if [ "${LOG_TO_FILES}" = "TRUE" ] ; then
+            ${CMD} &> \"${LOG_FILE}\" &
+        else
+            ${CMD} &
+        fi
+    fi
+    PID=$!
+    echo "PID of COMMAND '${CMD}' is ${PID}"
+    if [ ! -z "${WAIT_FOR_CMD}" -a "${CMD}" = "${MWAIT_FOR_CMD}" ] ; then
+        WAIT_FOR_PID=${PID}
+        echo "${PID}" > ${RUNTIME_DIR}/run/wait_for_pid
+    else
+        PIDS="${PID} ${PIDS}"
+        echo "${PIDS}" > ${RUNTIME_DIR}/run/pids
+    fi
+    cd -
+}
+
+function rt_wait_for() {
+    RESULT=0
+    echo "Waiting for pid ${WAIT_FOR_PID}"
+    if wait ${WAIT_FOR_PID}; then
+        echo "${WAIT_FOR_PID} exited normal"
+    else
+        echo "${WAIT_FOR_PID} exited with error"
+        RESULT=1
+    fi
+
+    echo "Signalling pids:${PIDS}"
+    kill ${PIDS}
+
+    for PID in ${PIDS}; do
+        if wait ${PID}; then
+            echo "${PID} exited normal"
+        else
+            echo "${PID} exited with error"
+        fi
+    done
+
+    echo ${RESULT}
+}
+
+
+if [ -z "${RELEASE_SH}" ] ; then
+    true #pass
+elif [ -e "${RELEASE_SH}" ]; then #absolute release file
+    source ${RELEASE_SH}
+elif [ -e "${RUNTIME_DIR}/${RELEASE_SH}" ] ; then #release file in runtime dir
+    source ${RUNTIME_DIR}/${RELEASE_SH}
+elif [ -e "${BUILD_DIR}/${RELEASE_SH}" ] ; then #release file in build dir
+    source ${BUILD_DIR}/${RELEASE_SH}
+fi
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/cmake_celix/runtime_start.sh.in
----------------------------------------------------------------------
diff --git a/cmake/cmake_celix/runtime_start.sh.in b/cmake/cmake_celix/runtime_start.sh.in
new file mode 100644
index 0000000..e7e94f6
--- /dev/null
+++ b/cmake/cmake_celix/runtime_start.sh.in
@@ -0,0 +1,26 @@
+#!/bin/sh
+source $<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_LOCATION>/common.sh
+
+echo "Starting runtime ${RUNTIME_NAME}"
+
+rt_init
+
+for DEPLOYMENT_DIR in ${DEPLOYMENTS}; do
+    rt_run_deployment ${DEPLOYMENT_DIR}
+done
+
+for CMD in ${COMMANDS}; do
+    rt_run_cmd "${CMD}"
+done
+
+echo "PIDS are ${PIDS}."
+
+if [ ! -z "${WAIT_FOR_PID}" ] ; then
+    rt_wait_for #sets RESULT
+    echo "RESULT is ${RESULT}"
+    exit ${RESULT}
+fi
+
+
+
+

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/cmake/cmake_celix/runtime_stop.sh.in
----------------------------------------------------------------------
diff --git a/cmake/cmake_celix/runtime_stop.sh.in b/cmake/cmake_celix/runtime_stop.sh.in
new file mode 100644
index 0000000..fd7338e
--- /dev/null
+++ b/cmake/cmake_celix/runtime_stop.sh.in
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+source $<TARGET_PROPERTY:@RUNTIME_TARGET_NAME@,RUNTIME_LOCATION>/common.sh
+
+#Options
+USE_SIGNAL=${USE_SIGNAL:-}
+
+#TODO parse option to easily select kill -9
+
+if [ -e ${RUNTIME_DIR}/run/main_pid ] ; then
+    MAIN_PID=$(cat ${RUNTIME_DIR}/run/wait_for_pid)
+    rt_stop ${MAIN_PID}
+fi
+
+if [ -e ${RUNTIME_DIR}/run/pids ] ; then
+    PIDS=$(cat ${RUNTIME_DIR}/run/pids)
+    rt_stop ${PIDS}
+fi
+

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/documents/cmake_commands/readme.md
----------------------------------------------------------------------
diff --git a/documents/cmake_commands/readme.md b/documents/cmake_commands/readme.md
index f68eb37..63f8732 100644
--- a/documents/cmake_commands/readme.md
+++ b/documents/cmake_commands/readme.md
@@ -161,6 +161,7 @@ add_deploy(<deploy_target_name>
     [GROUP group_name]
     [NAME deploy_name]
     [LAUNCHER launcher]
+    [DIR dir]
     [BUNDLES <bundle1> <bundle2> ...]
     [PROPERTIES "prop1=val1" "prop2=val2" ...]
 )
@@ -179,6 +180,7 @@ If the bundle target is never added CMake will give an error:
 - If GROUP is provided the deployment will be grouped in the provided group name. 
 - If NAME is provided that name will be used for the deployment dir. Default the deploy target name will be used.
 - If LAUNCHER is provided that path or target will be used as launcher executable for the deployment. If no LAUNCHER is not provided the celix executable will be used.
+- If DIR is provided, the specified dir is used instead of `<cmake_build_dir>/deploy` as deploy dir 
 - If BUNDLES is provided the list of bundles will be added the the generated config.properties for startup. Combined with COPY the bundles will also be copied to a bundles dir.
 - If PROPERTIES is provided the list of properties will be appended to the generated config.properties
 

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/framework/private/src/bundle_context.c
----------------------------------------------------------------------
diff --git a/framework/private/src/bundle_context.c b/framework/private/src/bundle_context.c
index 7951f87..face85d 100644
--- a/framework/private/src/bundle_context.c
+++ b/framework/private/src/bundle_context.c
@@ -126,7 +126,7 @@ celix_status_t bundleContext_registerService(bundle_context_pt context, const ch
 	service_registration_pt registration = NULL;
 	celix_status_t status = CELIX_SUCCESS;
 
-	if (context != NULL && *service_registration == NULL) {
+	if (context != NULL) {
 	    fw_registerService(context->framework, &registration, context->bundle, serviceName, svcObj, properties);
 	    *service_registration = registration;
 	} else {

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/launcher/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 920d4c0..8bac20e 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -36,4 +36,21 @@ if (LAUNCHER)
     include_directories("${CURL_INCLUDE_DIRS}")
     
     install(TARGETS celix RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT framework)
+
+    find_package(CppUTest QUIET)
+    if (CPPUTEST_FOUND)
+        #Test running which start celix and run CppUTest RUN_ALL_TESTS.
+        #Using this test running it is possible to create bundles containing CppUTests.
+        add_executable(celix_test_runner
+                private/src/celix_test_runner.cpp
+        )
+        set_target_properties(celix_test_runner PROPERTIES "INSTALL_RPATH" "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
+        target_link_libraries(celix_test_runner
+            celix_framework
+            ${CURL_LIBRARIES}
+            ${CPPUTEST_LIBRARIES}
+            ${CPPUTEST_EXT_LIBRARIES}
+        )
+        install(TARGETS celix_test_runner RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT framework)
+    endif ()
 endif (LAUNCHER) 

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/launcher/private/src/celix_test_runner.cpp
----------------------------------------------------------------------
diff --git a/launcher/private/src/celix_test_runner.cpp b/launcher/private/src/celix_test_runner.cpp
new file mode 100644
index 0000000..d1ea2e6
--- /dev/null
+++ b/launcher/private/src/celix_test_runner.cpp
@@ -0,0 +1,73 @@
+/**
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+
+#include <signal.h>
+
+#include "celix_launcher.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+
+static void shutdown_framework(int signal);
+static void ignore(int signal);
+
+#define DEFAULT_CONFIG_FILE "config.properties"
+
+static framework_pt framework = NULL;
+
+int main(int argc, char *argv[]) {
+    // Perform some minimal command-line option parsing...
+    char *cfg = DEFAULT_CONFIG_FILE;
+
+    // Set signal handler
+    (void) signal(SIGINT, shutdown_framework);
+    (void) signal(SIGUSR1, ignore);
+    (void) signal(SIGUSR2, ignore);
+
+    int rc = celixLauncher_launch(cfg, &framework);
+    if (rc != 0) {
+        printf("Error starting Celix\n");
+    }
+
+    if (rc == 0 && framework != NULL) {
+        rc = RUN_ALL_TESTS(argc, argv);
+
+        celixLauncher_stop(framework);
+        celixLauncher_waitForShutdown(framework);
+        celixLauncher_destroy(framework);
+    }
+
+    if (rc != 0) {
+        printf("*** FAILURE ***\n");
+    } else {
+        printf("*** SUCCESS ***\n");
+    }
+
+    return rc;
+}
+
+static void shutdown_framework(int signal) {
+    if (framework != NULL) {
+        celixLauncher_stop(framework); //NOTE main thread will destroy
+    }
+}
+
+static void ignore(int signal) {
+    //ignoring for signal SIGUSR1, SIGUSR2. Can be used to interrupt sleep, etc
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/pubsub/CMakeLists.txt b/pubsub/CMakeLists.txt
index 44f3bd1..7ad7677 100644
--- a/pubsub/CMakeLists.txt
+++ b/pubsub/CMakeLists.txt
@@ -36,6 +36,9 @@ if (PUBSUB)
 	add_subdirectory(deploy)
 	add_subdirectory(keygen)
 	add_subdirectory(mock)
+	if (ENABLE_TESTING)
+		add_subdirectory(test)
+	endif()
 
 	#install api
 	install(FILES api/pubsub/publisher.h api/pubsub/subscriber.h DESTINATION include/celix/pubsub COMPONENT framework)

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/deploy/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/pubsub/deploy/CMakeLists.txt b/pubsub/deploy/CMakeLists.txt
index b35ae14..52fecc9 100644
--- a/pubsub/deploy/CMakeLists.txt
+++ b/pubsub/deploy/CMakeLists.txt
@@ -15,6 +15,8 @@
 # specific language governing permissions and limitations
 # under the License.
 
+find_program(ETCD_CMD NAMES etcd)
+find_program(XTERM_CMD NAMES xterm)
 
 # UDP Multicast
 add_deploy("pubsub_publisher_udp_mc" 
@@ -54,6 +56,23 @@ add_deploy("pubsub_subscriber2_udp_mc"
        org.apache.celix.pubsub_serializer.PubSubSerializerJson
 )
 
+if (ETCD_CMD AND XTERM_CMD)
+	#Runtime starting a publish and subscriber for udp mc
+	add_runtime(pubsub_rt_upd_mc
+		NAME udp_mc
+		GROUP pubsub
+		DEPLOYMENTS
+			pubsub_publisher_udp_mc
+			pubsub_subscriber_udp_mc
+			pubsub_subscriber2_udp_mc
+		COMMANDS
+			etcd
+		USE_TERM
+	)
+else ()
+	message(WARNING "Cannot create runtime for pubsub because etcd and/or xterm is not installed")
+endif ()
+
 if (BUILD_PUBSUB_PSA_ZMQ)
 
 	# Dynamic ZMQ / UDP admin
@@ -168,4 +187,43 @@ if (BUILD_PUBSUB_PSA_ZMQ)
 	       org.apache.celix.pubsub_serializer.PubSubSerializerJson
 	)
 
+	if (ETCD_CMD AND XTERM_CMD)
+		#Runtime starting two bundles using both zmq and upd mc pubsub
+		add_runtime(pubsub_rt_zmq_udpmc_combi
+			NAME combi
+			GROUP pubsub
+			DEPLOYMENTS
+				pubsub_publisher_zmq
+				pubsub_subscriber_zmq
+				pubsub_subscriber_zmq
+			COMMANDS
+				etcd
+			USE_TERM
+		)
+
+		#Runtime starting a publish and 2 subscribers for zmq
+		add_runtime(pubsub_rt_zmq
+			NAME zmq
+			GROUP pubsub
+			DEPLOYMENTS
+				pubsub_publisher
+				pubsub_subscriber2_zmq
+			COMMANDS
+				etcd
+			USE_TERM
+		)
+
+		#Runtime starting a multipart (multiple message in one send) publish and subscriber for zmq
+		add_runtime(pubsub_rt_multipart_zmq
+			NAME zmq_multipart
+			GROUP pubsub
+			DEPLOYMENTS
+				pubsub_mp_subscriber_zmq
+				pubsub_mp_publisher_zmq
+			COMMANDS
+				etcd
+			USE_TERM
+		)
+	endif ()
+
 endif()

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/mock/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/pubsub/mock/CMakeLists.txt b/pubsub/mock/CMakeLists.txt
index 09d7b2a..189ddc2 100644
--- a/pubsub/mock/CMakeLists.txt
+++ b/pubsub/mock/CMakeLists.txt
@@ -23,15 +23,15 @@ if (CPPUTEST_FOUND)
         api
         ${CPPUTEST_INCLUDE_DIR}
     )
-    
+
     add_library(celix_pubsubmock STATIC
         src/publisher_mock.cc
-    )
+            ../test/test/msg.h)
     target_link_libraries(celix_pubsubmock ${CPPUTEST_LIBRARY})
-	
+
     install(TARGETS celix_pubsubmock DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT framework)
     install(FILES api/pubsub/publisher_mock.h DESTINATION include/celix/pubsub COMPONENT framework)
-    
+
     if (ENABLE_TESTING)
         add_executable(pubsubmock_test
             tst/pubsubmock_test.cc

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/pubsub_admin_udp_mc/private/src/topic_publication.c
----------------------------------------------------------------------
diff --git a/pubsub/pubsub_admin_udp_mc/private/src/topic_publication.c b/pubsub/pubsub_admin_udp_mc/private/src/topic_publication.c
index cff5005..aa3faf0 100644
--- a/pubsub/pubsub_admin_udp_mc/private/src/topic_publication.c
+++ b/pubsub/pubsub_admin_udp_mc/private/src/topic_publication.c
@@ -364,6 +364,8 @@ static int pubsub_topicPublicationSend(void* handle, unsigned int msgTypeId, con
     celixThreadMutex_lock(&(bound->parent->tp_lock));
     celixThreadMutex_lock(&(bound->mp_lock));
 
+    //TODO //FIXME -> should use pointer to int as identifier, can be many pointers to int ....
+    printf("TODO FIX usage of msg id's in the serializer hashmap. This seems wrongly based on pointers to uints!!!!\n");
     pubsub_message_type *msgType = hashMap_get(bound->msgTypes, &msgTypeId);
 
     int major=0, minor=0;
@@ -401,7 +403,11 @@ static int pubsub_topicPublicationSend(void* handle, unsigned int msgTypeId, con
 		free(serializedOutput);
 
     } else {
-        printf("TP: Message %u not supported.",msgTypeId);
+		if (bound->parent->serializerSvc == NULL) {
+			printf("TP: Serializer is not set!\n");
+		} else {
+			printf("TP: Message %u not supported.\n",msgTypeId);
+		}
         status=-1;
     }
 

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/test/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/pubsub/test/CMakeLists.txt b/pubsub/test/CMakeLists.txt
new file mode 100644
index 0000000..7cd0003
--- /dev/null
+++ b/pubsub/test/CMakeLists.txt
@@ -0,0 +1,87 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+find_program(ETCD_CMD NAMES etcd)
+
+find_package(CppUTest REQUIRED)
+include_directories(${CPPUTEST_INCLUDE_DIR})
+
+include_directories(
+        ${CMAKE_SOURCE_DIR}/pubsub/api
+        test
+)
+
+add_bundle(pubsub_sut
+    #"Vanilla" bundle which is under test
+    SOURCES
+        test/sut_activator.c
+    VERSION 1.0.0
+)
+bundle_files(pubsub_sut
+    msg_descriptors/msg.descriptor
+    DESTINATION "META-INF/descriptors/messages"
+)
+add_deploy(pubsub_udpmc_sut
+    NAME deploy_sut
+    BUNDLES
+        org.apache.celix.pubsub_discovery.etcd.PubsubDiscovery
+        org.apache.celix.pubsub_topology_manager.PubSubTopologyManager
+        org.apache.celix.pubsub_admin.PubSubAdminUdpMc
+        #org.apache.celix.pubsub_admin.PubSubAdminZmq
+        org.apache.celix.pubsub_serializer.PubSubSerializerJson
+        pubsub_sut
+    DIR ${PROJECT_BINARY_DIR}/runtimes/test/pubsub/udpmc
+)
+
+add_bundle(pubsub_tst
+    #Test bundle containing cpputests and uses celix_test_runner launcher instead of the celix launcher
+    SOURCES
+        test/tst_activator.cpp
+    VERSION 1.0.0
+)
+bundle_files(pubsub_tst
+    msg_descriptors/msg.descriptor
+    DESTINATION "META-INF/descriptors/messages"
+)
+add_deploy(pubsub_udpmc_tst
+    NAME deploy_tst
+    BUNDLES
+        org.apache.celix.pubsub_discovery.etcd.PubsubDiscovery
+        org.apache.celix.pubsub_topology_manager.PubSubTopologyManager
+        org.apache.celix.pubsub_admin.PubSubAdminUdpMc
+        #org.apache.celix.pubsub_admin.PubSubAdminZmq
+        org.apache.celix.pubsub_serializer.PubSubSerializerJson
+        pubsub_tst
+    DIR ${PROJECT_BINARY_DIR}/runtimes/test/pubsub/udpmc
+    LAUNCHER celix_test_runner
+)
+
+if (ETCD_CMD)
+    add_runtime(pubsub_rt_test_udpmc
+        NAME udpmc
+        GROUP test/pubsub
+        DEPLOYMENTS
+            pubsub_udpmc_sut
+            pubsub_udpmc_tst
+        COMMANDS
+            etcd
+        WAIT_FOR
+            pubsub_udpmc_tst
+        USE_TERM
+        #LOG_TO_FILES
+    )
+endif ()

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/test/msg_descriptors/msg.descriptor
----------------------------------------------------------------------
diff --git a/pubsub/test/msg_descriptors/msg.descriptor b/pubsub/test/msg_descriptors/msg.descriptor
new file mode 100644
index 0000000..808644c
--- /dev/null
+++ b/pubsub/test/msg_descriptors/msg.descriptor
@@ -0,0 +1,8 @@
+:header
+type=message
+name=msg
+version=1.0.0
+:annotations
+:types
+:message
+{n seqnR}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/test/test/msg.h
----------------------------------------------------------------------
diff --git a/pubsub/test/test/msg.h b/pubsub/test/test/msg.h
new file mode 100644
index 0000000..babfd1f
--- /dev/null
+++ b/pubsub/test/test/msg.h
@@ -0,0 +1,27 @@
+/**
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+#ifndef MSG_H
+#define MSG_H
+
+typedef struct msg {
+    int seqNr;
+} msg_t;
+
+#endif //MSG_H

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/test/test/sut_activator.c
----------------------------------------------------------------------
diff --git a/pubsub/test/test/sut_activator.c b/pubsub/test/test/sut_activator.c
new file mode 100644
index 0000000..3e3b33b
--- /dev/null
+++ b/pubsub/test/test/sut_activator.c
@@ -0,0 +1,112 @@
+/**
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "bundle_activator.h"
+#include "service_tracker.h"
+
+#include "pubsub/subscriber.h"
+#include "pubsub/publisher.h"
+
+static int sut_receive(void *handle, const char *msgType, unsigned int msgTypeId, void *msg, pubsub_multipart_callbacks_t *callbacks, bool *release);
+static int sut_pubAdded(void *handle, service_reference_pt reference, void *service);
+static int sut_pubRemoved(void *handle, service_reference_pt reference, void *service);
+
+
+struct activator {
+	pubsub_subscriber_t subSvc;
+	service_registration_pt reg;
+
+	service_tracker_pt tracker;
+
+	pthread_mutex_t mutex;
+	pubsub_publisher_t* pubSvc;
+};
+
+celix_status_t bundleActivator_create(bundle_context_pt context, void **userData) {
+	struct activator* act = malloc(sizeof(*act));
+	*userData = act;
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) {
+	struct activator* act = (struct activator*) userData;
+
+	properties_pt props = properties_create();
+	properties_set(props, "pubsub.topic", "ping");
+	act->subSvc.handle = act;
+	act->subSvc.receive = sut_receive;
+	act->reg = NULL;
+	bundleContext_registerService(context, PUBSUB_SUBSCRIBER_SERVICE_NAME, &act->subSvc, props, &act->reg);
+
+	const char* filter = "(&(objectClass=pubsub.publisher)(pubsub.topic=pong))";
+	service_tracker_customizer_pt customizer = NULL;
+	serviceTrackerCustomizer_create(act, NULL, sut_pubAdded, NULL, sut_pubRemoved, &customizer);
+	serviceTracker_createWithFilter(context, filter, customizer, &act->tracker);
+	serviceTracker_open(act->tracker);
+
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_stop(void * userData, bundle_context_pt __attribute__((unused)) context) {
+	struct activator* act = userData;
+	serviceTracker_close(act->tracker);
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_destroy(void * userData, bundle_context_pt  __attribute__((unused)) context) {
+	struct activator* act = userData;
+	serviceTracker_destroy(act->tracker);
+	return CELIX_SUCCESS;
+}
+
+static int sut_receive(void *handle, const char *msgType, unsigned int msgTypeId, void *msg, pubsub_multipart_callbacks_t *callbacks, bool *release) {
+	struct activator* act = handle;
+	printf("Received msg %s, sending back\n", msgType);
+	pthread_mutex_lock(&act->mutex);
+	if (act->pubSvc != NULL) {
+		unsigned int sendId = 0;
+		act->pubSvc->localMsgTypeIdForMsgType(act->pubSvc->handle, msgType, &sendId);
+		act->pubSvc->send(act->pubSvc->handle, sendId, msg);
+	}
+	pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+}
+
+static int sut_pubAdded(void *handle, service_reference_pt reference, void *service) {
+	struct activator* act = handle;
+	pthread_mutex_lock(&act->mutex);
+	act->pubSvc = service;
+	pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+
+}
+
+static int sut_pubRemoved(void *handle, service_reference_pt reference, void *service) {
+	struct activator* act = handle;
+	pthread_mutex_lock(&act->mutex);
+	if (act->pubSvc == service) {
+		act->pubSvc = NULL;
+	}
+	pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+}
+

http://git-wip-us.apache.org/repos/asf/celix/blob/97df926c/pubsub/test/test/tst_activator.cpp
----------------------------------------------------------------------
diff --git a/pubsub/test/test/tst_activator.cpp b/pubsub/test/test/tst_activator.cpp
new file mode 100644
index 0000000..6d957a0
--- /dev/null
+++ b/pubsub/test/test/tst_activator.cpp
@@ -0,0 +1,199 @@
+/**
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bundle_activator.h"
+#include "service_tracker.h"
+
+#include "pubsub/subscriber.h"
+#include "pubsub/publisher.h"
+
+#include "msg.h"
+
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+
+
+static int tst_receive(void *handle, const char *msgType, unsigned int msgTypeId, void *msg, pubsub_multipart_callbacks_t *callbacks, bool *release);
+static int tst_pubAdded(void *handle, service_reference_pt reference, void *service);
+static int tst_pubRemoved(void *handle, service_reference_pt reference, void *service);
+
+#define MSG_NAME "msg"
+
+struct activator {
+	pubsub_subscriber_t subSvc;
+	service_registration_pt reg = nullptr;
+
+	service_tracker_pt tracker = nullptr;
+
+	pthread_mutex_t mutex; //protects below
+	pubsub_publisher_t* pubSvc = nullptr;
+    unsigned int msgId = 0;
+    unsigned int count = 0;
+
+    bool started = false;
+
+};
+
+static struct activator g_act; //global
+
+celix_status_t bundleActivator_create(__attribute__((unused)) bundle_context_pt context, __attribute__((unused)) void **userData) {
+    //memset(&g_act, 0, sizeof(g_act));
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_start(__attribute__((unused)) void * userData, bundle_context_pt context) {
+	properties_pt props = properties_create();
+	properties_set(props, "pubsub.topic", "pong");
+	g_act.subSvc.handle = &g_act;
+	g_act.subSvc.receive = tst_receive;
+	bundleContext_registerService(context, PUBSUB_SUBSCRIBER_SERVICE_NAME, &g_act.subSvc, props, &g_act.reg);
+
+    const char* filter = "(&(objectClass=pubsub.publisher)(pubsub.topic=ping))";
+	service_tracker_customizer_pt customizer = NULL;
+	serviceTrackerCustomizer_create(&g_act, NULL, tst_pubAdded, NULL, tst_pubRemoved, &customizer);
+	serviceTracker_createWithFilter(context, filter, customizer, &g_act.tracker);
+	serviceTracker_open(g_act.tracker);
+
+    g_act.started = true;
+
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_stop(__attribute__((unused)) void * userData, bundle_context_pt __attribute__((unused)) context) {
+	serviceTracker_close(g_act.tracker);
+	return CELIX_SUCCESS;
+}
+
+celix_status_t bundleActivator_destroy(__attribute__((unused)) void * userData, bundle_context_pt  __attribute__((unused)) context) {
+	serviceTracker_destroy(g_act.tracker);
+	return CELIX_SUCCESS;
+}
+
+static int tst_receive(void *handle, const char *msgType, unsigned int msgTypeId, void *msg, pubsub_multipart_callbacks_t *callbacks, bool *release) {
+	struct activator* act = static_cast<struct activator*>(handle);
+    pthread_mutex_lock(&act->mutex);
+    act->count += 1;
+    pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+}
+
+static int tst_pubAdded(void *handle, service_reference_pt reference, void *service) {
+    struct activator* act = static_cast<struct activator*>(handle);
+    pthread_mutex_lock(&act->mutex);
+	act->pubSvc = static_cast<pubsub_publisher_t*>(service);
+    act->pubSvc->localMsgTypeIdForMsgType(act->pubSvc->handle, MSG_NAME, &g_act.msgId);
+	pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+
+}
+
+static int tst_pubRemoved(void *handle, service_reference_pt reference, void *service) {
+    struct activator* act = static_cast<struct activator*>(handle);
+    pthread_mutex_lock(&act->mutex);
+	if (act->pubSvc == service) {
+		act->pubSvc = NULL;
+	}
+	pthread_mutex_unlock(&act->mutex);
+	return CELIX_SUCCESS;
+}
+
+
+TEST_GROUP(PUBSUB_INT_GROUP)
+{
+	void setup() {
+        constexpr int TRIES = 25;
+        constexpr int TIMEOUT = 1000000;
+		CHECK_EQUAL(true, g_act.started);
+
+        //check if publisher is available
+        unsigned int msgId = 0;
+        for (int i = 0; i < TRIES; ++i) {
+            pthread_mutex_lock(&g_act.mutex);
+            msgId = g_act.msgId;
+            pthread_mutex_unlock(&g_act.mutex);
+            if (msgId == 0) {
+                printf("publisher still nullptr / msg Id is still 0, waiting for a while\n");
+                usleep(TIMEOUT);
+            } else {
+                break;
+            }
+        }
+        CHECK(msgId != 0);
+
+        //check if message are returned
+        msg_t initMsg;
+        initMsg.seqNr = 0;
+        for (int i = 0; i < TRIES; ++i) {
+            pthread_mutex_lock(&g_act.mutex);
+            g_act.pubSvc->send(g_act.pubSvc->handle, g_act.msgId, &initMsg);
+            pthread_mutex_unlock(&g_act.mutex);
+            usleep(TIMEOUT);
+            pthread_mutex_lock(&g_act.mutex);
+            int count = g_act.count;
+            pthread_mutex_unlock(&g_act.mutex);
+            if (count > 0) {
+                break;
+            } else {
+                printf("No return message received, waiting for a while\n");
+            }
+        }
+	}
+
+	void teardown() {
+		//nop
+	}
+};
+
+TEST(PUBSUB_INT_GROUP, sendRecvTest) {
+    g_act.count = 0;
+    constexpr int COUNT = 50;
+    msg_t msg;
+    for (int i = 0; i < COUNT; ++i) {
+        msg.seqNr = i;
+        pthread_mutex_lock(&g_act.mutex);
+        g_act.pubSvc->send(g_act.pubSvc->handle, g_act.msgId, &msg);
+        pthread_mutex_unlock(&g_act.mutex);
+        usleep(100000);
+    }
+
+    constexpr int TRIES = 25;
+    constexpr int TIMEOUT = 250000;
+    for (int i = 0; i < TRIES; ++i) {
+        pthread_mutex_lock(&g_act.mutex);
+        int count = g_act.count;
+        pthread_mutex_unlock(&g_act.mutex);
+        if (count == COUNT) {
+            break;
+        } else {
+            printf("Current count is %i, should be %i. Waiting a little\n", count, COUNT);
+            usleep(TIMEOUT);
+        }
+    }
+
+    pthread_mutex_lock(&g_act.mutex);
+    int count = g_act.count;
+    pthread_mutex_unlock(&g_act.mutex);
+    CHECK_EQUAL(COUNT, count);
+}
\ No newline at end of file