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 2022/05/29 17:16:48 UTC

[celix] branch feature/update_component_and_pattern_documentation updated: Updates dm component document and examples.

This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/update_component_and_pattern_documentation
in repository https://gitbox.apache.org/repos/asf/celix.git


The following commit(s) were added to refs/heads/feature/update_component_and_pattern_documentation by this push:
     new b74930c8 Updates dm component document and examples.
b74930c8 is described below

commit b74930c86d44cd9b5d1138fe83ddb3586d253192
Author: Pepijn Noltes <pe...@gmail.com>
AuthorDate: Sun May 29 19:16:35 2022 +0200

    Updates dm component document and examples.
    
    Also:
    - Adds small chapter for `celix::lb` and `celix::query` shell commands
    - Update find_package(CppUTest) to optional so that Celix can be build
      with tests, but without CppUTest.
---
 documents/bundles.md                               |  12 +-
 documents/components.md                            | 373 ++++++++++++---------
 documents/services.md                              |  22 +-
 .../component_with_service_dependency_activator.c  |  30 +-
 .../src/ComponentWithProvidedServiceActivator.cc   |   7 +-
 libs/utils/CMakeLists.txt                          | 112 ++++---
 6 files changed, 321 insertions(+), 235 deletions(-)

diff --git a/documents/bundles.md b/documents/bundles.md
index bd18f7b1..908442eb 100644
--- a/documents/bundles.md
+++ b/documents/bundles.md
@@ -286,4 +286,14 @@ add_celix_container(test_container BUNDLES
 )
 ```
 
-See [Apache Celix CMake Commands](cmake_commands) for more detailed information.
\ No newline at end of file
+See [Apache Celix CMake Commands](cmake_commands) for more detailed information.
+
+# The `celix::lb` shell command
+To interactively see the installed bundles the `celix::lb` shell command (list bundles) can be used.
+
+Examples of supported `lb` command lines are:
+ - `celix::lb` - Show an overview of the installed bundles with their bundle id, bundle state, bundle name and 
+   bundle group.
+ - `lb` - Same as `celix::lb` (as long as there is no colliding other `lb` commands). 
+ - `lb -s` - Same as `celix::lb` but instead of showing the bundle name the bundle symbolic name is printed.
+ - `lb -u` - Same as `celix::lb` but instead of showing the bundle name the bundle update location is printed.
diff --git a/documents/components.md b/documents/components.md
index 86e2986a..2f20555b 100644
--- a/documents/components.md
+++ b/documents/components.md
@@ -25,49 +25,51 @@ Components can provide services and depend on services. Components are configure
 
 Service dependencies will influence the component's lifecycle as a component will only be active when all required
 dependencies are available.   
-The DM is responsible for managing the component's service dependencies, the component's lifecycle and when 
+The DM is responsible for managing the co7mponent's service dependencies, the component's lifecycle and when
 to register/unregister the component's provided services.
 
 Note that the Apache Celix Dependency Manager is inspired by the [Apache Felix Dependency Manager](http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html), adapted to Apache Celix and the C/C++ usage.
 
 # Component Lifecycle
-Each DM Component has its own lifecycle. 
+Each component has its own lifecycle.
 A component's lifecycle state model is depicted in the state diagram below.
 
 ![Component Life Cycle](diagrams/component_lifecycle.png)
 
-The DM can be used to configure a component's lifecycle callbacks, the following component's lifecycle callbacks can 
+The DM can be used to configure a component's lifecycle callbacks, the following component's lifecycle callbacks can
 be configured:
 
- - `init`
- - `start`
- - `stop`
- - `deinit`
-   
+- `init`
+- `start`
+- `stop`
+- `deinit`
+
 These callbacks are used in the intermediate component's lifecycle states `Initializing`, `Starting`, `Suspending`, `Resuming`, `Stopping` and `Deinitializing` and the lifecycle callbacks are always called from the Celix event thread.
 
-A DM Component has the following lifecycle states:
-- `Inactive`: _The component is inactive and the DM is not managing the component yet._
-- `Waiting For Required`: _The component is waiting for required service dependencies._
-- `Initializing`: _The component has found its required dependencies and is initializing: 
-  Calling the `init` callback._
-- `Initialized And Waiting For Required`: _The component has been initialized, but is waiting for required
-  dependencies._
-  _Note: that this can mean: that during `init` callback new service dependencies was added or that the component
-  was active, but a required service dependency is removed and as result the component is not active anymore._
-- `Starting`: _The component has found its required dependencies and is starting: Calling the `start` callback and
-  registering it's provided services.
-- `Tracking Optional`: _The component has found its required dependencies and is started. It is still tracking for
-  additional optional and required services._
-- `Suspending`: _The component has found its required dependencies, but is suspending to prepare for a service change:
-  Unregistering its provided service and calling the `stop` callback._
-- `Suspended`: _The component has found its required dependencies and is suspended so that a service change can be
-  processed._
-- `Resuming`: _The component has found its required dependencies, a service change has been processed, and it is
-  resuming: Calling the `start` callback and registering its provided services.
-- `Stopping`: _The component has lost one or more of its required dependencies and is stopping: Unregistering its
-  provided service and calling the `stop` callback._
-- `Deinitializing`: _The component is being removed and is deinitializing: Calling the `deinit` callback._
+A component has the following lifecycle states:
+- `Inactive`: The component is inactive and the DM is not managing the component yet.
+- `Waiting For Required`: The component is waiting for required service dependencies.
+- `Initializing`: The component has found its required dependencies and is initializing by
+  calling the `init` callback.
+- `Initialized And Waiting For Required`: The component has been initialized, but is waiting for required
+  dependencies.
+  _Note: that this can mean that:
+    - During the `init` callback, 1 or more unavailable required service dependencies where added.
+    - The component was active, but 1 or more required service dependency where removed and as result the
+      component is not active anymore.
+- `Starting`: The component has found its required dependencies and is starting by calling the `start` callback and
+  registering the components provided services.
+- `Tracking Optional`: The component has found its required dependencies and is started. It is still tracking for
+  additional optional and required services.
+- `Suspending`: The component has found its required dependencies, but is suspending to prepare for a service change by
+  unregistering the components provided service and calling the `stop` callback.
+- `Suspended`: The component has found its required dependencies and is suspended so that a service change can be
+  processed.
+- `Resuming`: The component has found its required dependencies, a service change has been processed, and it is
+  resuming by calling the `start` callback and registering the components provided services.
+- `Stopping`: The component has lost one or more of its required dependencies and is stopping by unregistering the
+  components provided service and calling the `stop` callback.
+- `Deinitializing`: The component is being removed and is deinitializing by calling the `deinit` callback.
 
 ## Component API
 
@@ -77,33 +79,34 @@ The DM Component C api can be found in the `celix_dm_component.h` header and the
 ## Example: Create and configure component's lifecycle callbacks in C
 The following example shows how a simple component can be created and managed with the DM in C.
 Because the component's lifecycle is managed by the DM, this also means that if configured correctly no additional
-code is needed to remove and destroy the DM component and its implementation. 
+code is needed to remove and destroy the DM component and its implementation.
 
 Remarks for the C example:
- 1. Although this is a C component. The simple component functions have been design for a component approach,
-    using the component pointer as first argument. 
- 2. The component implementation can be any POCO, as long as its lifecycle and destroy function follow a
-    component approach (one argument with the component implementation pointer as type)
- 3. Creates the DM component, but note that this component is not yet known to the DM. This makes it possible to
-    first configure the DM component over multiple function calls, before adding - and as result 
-    activating - the DM component to the DM.
- 4. Configures the component implementation in the DM component, so that the implementation pointer can be used 
-    in the configured component callbacks.
- 5. Configures the component lifecycle callbacks to the DM Component. These callbacks should accept the component 
-    implementation as its only argument. The `CELIX_DM_COMPONENT_SET_CALLBACKS` marco is used instead of the 
-    `celix_dmComponent_setCallbacks` function so that the component implementation type can directly be used 
-    in the lifecycle callbacks (instead of `void*`). 
- 6. Configures the component destroy implementation callback to the Dm Component. This callback will be called when 
-    the DM component is removed from the DM and has become inactive. The callback will be called from the Celix event
-    thread. The advantages of configuring this callback is that the DM manages when the callback needs to be called; 
-    this removes some complexity for the users. The `CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION` marco 
-    is used instead of the `celix_dmComponent_setImplementationDestroyFunction` function so that the component
-    implementation type can be directly used in the callback (instead of `void*`).
- 7. Adds the DM Component the DM and as result the DM will activate manage the component.
- 8. No additional code is needed to clean up components and as such no activator stop callback function needs to be 
-    configured. The generated bundle activator will ensure that  all component are removed from the DM when the 
-    bundle is stopped and the DM will ensure that the components are deactivated and destroyed correctly.
-    
+1. Although this is a C component. The simple component functions have been design for a component approach,
+   using the component pointer as first argument.
+2. The component implementation can be any POCO, as long as its lifecycle and destroy function signature follow a
+   component approach: A single argument, with as type the component implementation pointer and an int return
+   for the component lifecycle functions and a void return for the component destroy function.
+3. Creates the DM component, but note that the DM component is not yet known to the DM. This makes it possible to
+   first configure the DM component over multiple function calls, before adding it to the DM.
+4. Configures the component implementation in the DM component, so that the implementation pointer can be used
+   in the configured component callbacks.
+5. Configures the component lifecycle callbacks to the DM Component. These callbacks should accept the component
+   implementation as its only argument. The `CELIX_DM_COMPONENT_SET_CALLBACKS` marco is used instead of the
+   `celix_dmComponent_setCallbacks` function so that the component implementation type can directly be used
+   in the lifecycle callbacks (instead of `void*`).
+6. Configures the component destroy implementation callback to the Dm Component. This callback will be called when
+   the DM component is removed from the DM and has become inactive. The callback will be called from the Celix event
+   thread. The advantages of configuring this callback is that the DM manages when the callback needs to be called;
+   this removes some complexity for the users. The `CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION` marco
+   is used instead of the `celix_dmComponent_setImplementationDestroyFunction` function so that the component
+   implementation type can be directly used in the callback (instead of `void*`).
+7. Adds the DM Component the DM and as result the DM will that point on manage the components' lifecycle, service
+   dependencies and provided services.
+8. No additional code is needed to clean up components and as such no activator stop callback function needs to be
+   configured. The generated bundle activator will ensure that all components are removed from the DM when the
+   bundle is stopped and the DM will ensure that the components are deactivated and destroyed correctly.
+
 ```C
 //src/simple_component_activator.c
 #include <stdio.h>
@@ -181,26 +184,26 @@ CELIX_GEN_BUNDLE_ACTIVATOR(simple_component_activator_t, simpleComponentActivato
 ```
 
 ## Example: Create and configure component's lifecycle callbacks in C++
-The following example shows how a simple component can be created and managed with the DM in C++. 
-For C++ the DM will manage the components and also ensures that component implementations are kept in scope for as
-long as the components are managed by the DM. 
+The following example shows how a simple component can be created and managed with the DM in C++.
+For C++ the DM will manage the component and also ensures that component implementation is kept in scope for as
+long as the component is managed by the DM.
 
 Remarks for the C++ example:
 1. For C++ the DM can directly work on classes and as result lifecycle callback can be class methods.
-2. Creates a component implementation using a unique_ptr. 
+2. Creates a component implementation using a unique_ptr.
 3. Create a C++ DM Component and directly add it to the DM. For C++ DM Component needs to be "build" first, before the
-   DM will activate them. This way C++ components can be build using a fluent api and marked ready with a `build()`
-   method call. 
-   As component implementation the DM accepts a unique_pt, shared_ptr, value type of no implementation. If no 
-   implementation is provided the DM will create a component implementation using the template argument and 
-   assuming a default constructor (e.g. `ctx->getDependencyManager()->createComponent<CmpWithDefaultCTOR>()`). 
+   DM will manage them. This way C++ components can be build using a fluent api and marked complete with a `build()`
+   method call.
+   For a component implementation the DM accepts a unique_ptr, a shared_ptr, a value type or no implementation. If no
+   implementation is provided the DM will create a component implementation using the template argument and
+   assuming a default constructor (e.g. `ctx->getDependencyManager()->createComponent<CmpWithDefaultCTOR>()`).
 4. Configures the component lifecycle callbacks as class methods. The DM will call these callbacks using the
-   component implementation as object instance.
-5. "Builds" the component. C++ component will only be managed by the DM after they are build. This makes it possible
-   to configure a component over multiple method calls before marking the component as ready (build).
-   The generated C++ bundle activator will also enable all components created during the bundle activation, to ensure  
-   that the build behaviour is backwards compatible with previous released DM implementation. It is preferred that
-   users explicitly build their components when they are completely configured.
+   component implementation raw pointer as object instance (`this`).
+5. "Builds" the component. C++ components will only be managed by the DM after they are build. This makes it possible
+   to configure a component over multiple method calls before marking the component complete (build).
+   The generated C++ bundle activator will also enable all components created during the bundle activation, this is
+   done to ensure that the build behaviour is backwards compatible with previous released DM implementation.
+   It is preferred that users explicitly build their components when they are completely configured.
 
 ```C++
 //src/SimpleComponentActivator.cc
@@ -245,23 +248,23 @@ CELIX_GEN_CXX_BUNDLE_ACTIVATOR(SimpleComponentActivator)
 ```
 
 # Component's Provided Services
-Components can be configured to provide services. These provided services will correspondent to service registration 
-when a component is in the `Tracking Optional` state. 
+Components can be configured to provide services. These provided services will result in service registrations
+when a component is `Starting` or `Resuming` (i.e. when a component goes to the `Tracking Optional` state).
 
-If a component provide services, these service will have an additional metadata property - named "component.uuid"
-that couples the services to the component named "component.uuid".
+If a component provide services, these services will have an additional automatically added service property - named "component.uuid" - next to its configured provided service properties. The "component.uuid" service property can be
+used to identify if a service is provided by a component and which component.
 
 ## Example: Component with a provided service in C
-The following example shows how a component that provide a `celix_shell_command` service. 
+The following example shows how a component that provide a `celix_shell_command` service.
 
 Remarks for the C example:
-1. C and C services do not support inheritance. So even if a C component provides a certain service it is not an
-   instance of said service. This also means the C service struct provided by a component needs to be stored 
+1. C services do not support inheritance. So even if a C component provides a certain service, it is not an
+   instance of said service. This also means the C service struct provided by a component needs to be stored
    separately. In this example this is done storing the service struct in the bundle activator data. Note
-   that the bundle activator data "outlives" the component, because all components are removed before a bundle 
+   that the bundle activator data "outlives" the component, because all components are removed before a bundle
    is completely stopped.
 2. Configures a provided service (interface) for the component. The service will not directly be registered, but
-   instead will be registered if the component enters the state `Tracking Optional`.
+   instead will be registered in the component states `Starting` and `Resuming`.
 
 ```C
  //src/component_with_provided_service_activator.c
@@ -338,28 +341,28 @@ CELIX_GEN_BUNDLE_ACTIVATOR(
 ```
 
 ## Example: Component with a provided service in C++
-The following example shows how a C++ component that provide a C++ `celix::IShellCommand` service 
-and a C `elix_shell_command` service. For a C++ component it's possible to provide C and C++ services.  
+The following example shows how a C++ component that provide a C++ `celix::IShellCommand` service
+and a C `elix_shell_command` service. For a C++ component it's possible to provide C and C++ services.
 
 Remarks for the C++ example:
 1. If a component provides a C++ services, it also expected that the component implementation inherits the service
-   interface. 
+   interface.
 2. The overridden `executeCommand` method of `celix::IShellCommand`.
-3. Methods of C service interfaces can be implemented as class methods, but the bundle activator should ensure that 
-   the underlining C service interface structs are assigned with compatible C function pointers. 
-4. Creating a component using only a template argument. The DM will construct - using a default constructor - a 
+3. Methods of C service interfaces can be implemented as class methods, but the bundle activator should ensure that
+   the underlining C service interface structs are assigned with compatible C function pointers.
+4. Creating a component using only a template argument. The DM will construct - using a default constructor - a
    component implementation instance.
 5. Configures the component to provide a C++ `celix::IShellCommand` service. Note that because the component
-   implementation is an instance of `celix::IShellCommand` no additional storage is needed. The service will not 
-   directly be registered, but instead will be registered if the component enters the state `Tracking Optional`.
-6. Set the C `executeCommand` function pointer of the `celix_shell_command_t` service interface struct to a 
-   capture-less lambda expression. The lambda expression is used to forward the call to the `executeCCommand` 
-   class method. Note the capture-less lambda expression can decay to function pointers. 
-7. Configures the component to provide a C `celix_shell_command_t` service. Note that for a C service, the 
-   `createUnassociatedProvidedService` must be used, because the component does not inherit `celix_shell_command_t`. 
-   The service will not directly be registered, but instead will be registered if the component enters the state
-   `Tracking Optional`..
-8. "Build" the component so the DM will manage and try to activate the component. 
+   implementation is an instance of `celix::IShellCommand` no additional storage is needed. The service will not
+   directly be registered, but instead will be registered in the components states `Starting` and `Resuming`.
+6. Set the C `executeCommand` function pointer of the `celix_shell_command_t` service interface struct to a
+   capture-less lambda expression. The lambda expression is used to forward the call to the `executeCCommand`
+   class method. Note the capture-less lambda expression can decay to C-style function pointers.
+7. Configures the component to provide a C `celix_shell_command_t` service. Note that for a C service, the
+   `createUnassociatedProvidedService` must be used, because the component does not inherit `celix_shell_command_t`.
+   The service will not directly be registered, but instead will be registered in the component states `Starting` and
+   `Resuming`.
+8. "Build" the component so the DM will manage the component.
 
 
 ```C++
@@ -377,7 +380,8 @@ public:
             const std::vector<std::string>& /*commandArgs*/,
             FILE* outStream,
             FILE* /*errorStream*/) override {
-        fprintf(outStream, "Hello from cmp. C++ command called %i times. commandLine is %s\n", cxxCallCount++, commandLine.c_str());
+        fprintf(outStream, "Hello from cmp. C++ command called %i times. commandLine is %s\n", 
+                cxxCallCount++, commandLine.c_str());
     } // <-----------------------------------------------------------------------------------------------------------<2>
 
     void executeCCommand(const char* commandLine, FILE* outStream) {
@@ -394,7 +398,7 @@ public:
         auto& cmp = ctx->getDependencyManager()->createComponent<ComponentWithProvidedService>(); // <---------------<4>
 
         cmp.createProvidedService<celix::IShellCommand>()
-                .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <---------------------------------<5>
+                .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <-----------------------------<5>
 
         auto shellCmd = std::make_shared<celix_shell_command_t>();
         shellCmd->handle = static_cast<void*>(&cmp.getInstance());
@@ -405,7 +409,7 @@ public:
         }; // <------------------------------------------------------------------------------------------------------<6>
 
         cmp.createUnassociatedProvidedService(std::move(shellCmd), CELIX_SHELL_COMMAND_SERVICE_NAME)
-                .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -----------------------------------------<7>
+                .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -------------------------------------<7>
 
         cmp.build(); // <--------------------------------------------------------------------------------------------<8>
     }
@@ -416,41 +420,68 @@ CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithProvidedServiceActivator)
 ```
 
 # Component's Service Dependencies
-Components can be configured to have service dependencies. These service dependencies will influence the component's 
+Components can be configured to have service dependencies. These service dependencies will influence the component's
 lifecycle. Components can have optional and required service dependencies. When service dependencies are required the
-component can only be active if all required dependencies are available; 
+component can only be active if all required dependencies are available; where available means at least 1 matching
+service dependency is found.
 
-When configuring service dependencies, callbacks can be configured when services are being added, removed or when a 
-new highest ranking service is available. It is also possible to configure callbacks which only inject/remove service 
-pointers, or service pointers with their properties (metadata) and even service pointers with their properties and
-their providing bundle. Service dependency callbacks will always be called from the Celix event thread.
+When configuring service dependencies, callbacks can be configured for handling services that are being added,
+removed or for when a new highest ranking service is available.
+
+Service dependency callbacks can be configured with 3 different types of argument signatures:
+- A single argument for the service pointer (raw pointer or shared_ptr);
+- A service pointer (raw pointer or shared_ptr) as first argument and the service properties as second argument.
+- A service pointer (raw pointer or shared_ptr) as first argument, the service properties as second argument and
+  the bundle providing the service as third argument.
+
+Service dependency callbacks will always be called from the Celix event thread.
 
 A service change (injection/removal) can be handled by the component using a Locking-strategy or a suspend-strategy.
-This strategy can be configured per service dependency and expect the following behaviour from the component 
+This strategy can be configured per service dependency and expect the following behaviour from the component
 implementation:
-- Locking-strategy: The component implementation must ensure that the service pointers (and if applicable the service
-  properties and its bundle) are protected using a locking mechanism (e.g. a mutex). This should ensure that services
-  are no longer in use after they are removed (or replaced) from a component. 
+- Locking-strategy: The component implementation must ensure that the stored service pointers (and if applicable the
+  service properties and its bundle) are protected using a locking mechanism (e.g. a mutex).
+  This should ensure that services are no longer in use after they are removed (or replaced) from a component and
+  thus can be safely deleted from memory.
 - Suspend-strategy: The DM will ensure that before service dependency callbacks are called, all provided services
-  are (temporary) unregistered and the component is suspended (using the components' stop callback). This should mean
-  that there are no active users - through the provided services or active threads - of the service dependencies 
+  are (temporary) unregistered and the component is suspended (using the components' `stop` callback). This should mean
+  that there are no active users - through the provided services or active threads - of the service dependencies
   anymore and that service changes can safely be handling without locking. The component implementation must ensure
-  that after `stop` callback no active thread, thread pools, timers, etc that use service dependencies are active 
-  anymore.
+  that after a `stop` callback there are no active threads, thread pools, timers, etc - that use service dependencies -
+  are active anymore.
 
 ## Example: Component with a service dependencies in C
 The following example shows how a C component that has two service dependency on the `celix_shell_command_t` service.
 
-One service dependency is a required dependency with a suspend-strategy and uses a `set ` callback which ensure 
+One service dependency is a required dependency with a suspend-strategy and uses a `set ` callback which ensure
 that a single service is injected and that is always the highest ranking service. Note that the highest ranking
-service can be `NULL` if there are no service. 
+service can be `NULL` if there are no other matching services.
 
-The other dependency is an optional dependency with a locking-strategy and uses a `addWithProps` and 
+The other dependency is an optional dependency with a locking-strategy and uses a `addWithProps` and
 `removeWithProps` callback. These callbacks will be called for every `celix_shell_command_t` service being added/removed
-and will be called with not only the service pointer, but also the service metadata properties. 
+and will be called with not only the service pointer, but also the service properties.
 
 Remarks for the C example:
-1. TODO
+1. Creates a mutex to protect the `cmdShells` field which is configured with a locking-strategy service dependency.
+2. Updates the `highestRankingCmdShell` field without locking. Note that because the service dependency is
+   configured with a suspend-strategy the `componentWithServiceDependency_setHighestRankingShellCommand` function
+   will only be called when the component is in the `Suspended` state or when it is not in the `Active` compound state.
+3. Locks the mutex and adds the newly added service to the `cmdShells` list. Note that because the service dependency
+   is configured with a locking-strategy the `componentWithServiceDependency_addShellCommand` and
+   `componentWithServiceDependency_removeShellCommand` functions can be called from any component lifecycle state.
+4. Creates a new DM service dependency object. Note that the DM service dependency is not yet known to the DM Component.
+5. Configures for which service name the service dependency will track services for. Optionally it is also possible
+   to fine tune the tracked service by providing a service version range and/or service filter.
+6. Configures the update strategy for the service dependency to suspend-strategy.
+7. Configures the service dependency as a required service dependency.
+8. Creates an empty service dependency callback options struct. This struct can be used to configure different
+   service dependency callbacks.
+9. Configures the `set` service dependency callback to `componentWithServiceDependency_setHighestRankingShellCommand`
+10. Configures the dependency manager to use the callbacks configures in opts.
+11. Adds the DM service dependency object to the DM component object.
+12. Configures the update strategy for the service dependency to locking-strategy.
+13. Configures the service dependency as an optional service dependency.
+14. Configures the `addWithProps` service dependency callback to `componentWithServiceDependency_addShellCommand`.
 
 ```C
 //src/component_with_service_dependency_activator.c
@@ -462,13 +493,13 @@ Remarks for the C example:
 
 typedef struct component_with_service_dependency {
     celix_shell_command_t* highestRankingCmdShell; //only updated when component is not active or suspended
-    celix_thread_mutex_t mutex;
+    celix_thread_mutex_t mutex; //protects cmdShells
     celix_array_list_t* cmdShells;
 } component_with_service_dependency_t;
 
 static component_with_service_dependency_t* componentWithServiceDependency_create() {
     component_with_service_dependency_t* cmp = calloc(1, sizeof(*cmp));
-    celixThreadMutex_create(&cmp->mutex, NULL);
+    celixThreadMutex_create(&cmp->mutex, NULL); // <-----------------------------------------------------------------<1>
     cmp->cmdShells = celix_arrayList_create();
     return cmp;
 }
@@ -483,7 +514,7 @@ static void componentWithServiceDependency_setHighestRankingShellCommand(
         component_with_service_dependency_t* cmp,
         celix_shell_command_t* shellCmd) {
     printf("New highest ranking service (can be NULL): %p\n", shellCmd);
-    cmp->highestRankingCmdShell = shellCmd;
+    cmp->highestRankingCmdShell = shellCmd; // <---------------------------------------------------------------------<2>
 }
 
 static void componentWithServiceDependency_addShellCommand(
@@ -492,7 +523,7 @@ static void componentWithServiceDependency_addShellCommand(
         const celix_properties_t* props) {
     long id = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1);
     printf("Adding shell command service with service.id %li\n", id);
-    celixThreadMutex_lock(&cmp->mutex);
+    celixThreadMutex_lock(&cmp->mutex); // <-------------------------------------------------------------------------<3>
     celix_arrayList_add(cmp->cmdShells, shellCmd);
     celixThreadMutex_unlock(&cmp->mutex);
 }
@@ -527,22 +558,22 @@ static celix_status_t componentWithServiceDependencyActivator_start(component_wi
             componentWithServiceDependency_destroy);
 
     //create mandatory service dependency with cardinality one and with a suspend-strategy
-    celix_dm_service_dependency_t* dep1 = celix_dmServiceDependency_create();
-    celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL);
-    celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND);
-    celix_dmServiceDependency_setRequired(dep1, true);
-    celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS;
-    opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand;
-    celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1);
-    celix_dmComponent_addServiceDependency(dmCmp, dep1);
+    celix_dm_service_dependency_t* dep1 = celix_dmServiceDependency_create(); // <-----------------------------------<4>
+    celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL); // <-------------------<5>
+    celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND); // <------------------------<6>
+    celix_dmServiceDependency_setRequired(dep1, true); // <----------------------------------------------------------<7>
+    celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; // <--<8>
+    opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand; // <----------------------------<9>
+    celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1); // <-------------------------------------------<10>
+    celix_dmComponent_addServiceDependency(dmCmp, dep1); // <-------------------------------------------------------<11>
 
     //create optional service dependency with cardinality many and with a locking-strategy
     celix_dm_service_dependency_t* dep2 = celix_dmServiceDependency_create();
     celix_dmServiceDependency_setService(dep2, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL);
-    celix_dmServiceDependency_setStrategy(dep2, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING);
-    celix_dmServiceDependency_setRequired(dep2, false);
+    celix_dmServiceDependency_setStrategy(dep2, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING);  // <----------------------<12>
+    celix_dmServiceDependency_setRequired(dep2, false); // <--------------------------------------------------------<13>
     celix_dm_service_dependency_callback_options_t opts2 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS;
-    opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand;
+    opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand;  // <-------------------------------<14>
     opts2.removeWithProps = (void*)componentWithServiceDependency_removeShellCommand;
     celix_dmServiceDependency_setCallbacksWithOptions(dep2, &opts2);
     celix_dmComponent_addServiceDependency(dmCmp, dep2);
@@ -560,23 +591,39 @@ CELIX_GEN_BUNDLE_ACTIVATOR(
 ```
 
 ## Example: Component with a service dependencies in C++
-The following example shows how a C++ component that has two service dependency. One 
+The following example shows how a C++ component that has two service dependency. One
 service dependency for the C++ `celix::IShellCommand` service and one for the C `celix_shell_command_t` service.
 
-The `celix::IShellCommand` service dependency is a required dependency with a suspend-strategy and uses a 
-`set ` callback which ensure that a single service is injected and that is always the highest ranking service. 
+The `celix::IShellCommand` service dependency is a required dependency with a suspend-strategy and uses a
+`set` callback which ensure that a single service is injected and that is always the highest ranking service.
 Note that the highest ranking service can be an empty shared_ptr if there are no service.
 
 The `celix_shell_command_t` service dependency is an optional dependency with a locking-strategy and uses a
-`addWithProperties` and `removeWithProperties` callback. 
+`addWithProperties` and `removeWithProperties` callback.
 These callbacks will be called for every `celix_shell_command_t` service being added/removed
-and will be called with not only the service shared_ptr, but also the service metadata properties.
+and will be called with not only the service shared_ptr, but also the service properties.
 
-Note that for C++ component service dependencies there is no real different between a C++ or a C service; Both
-are injected as shared_ptr, and as optional properties or bundle argument the C++ variant are provided.
+Note that for C++ component service dependencies, there is no real different between a C++ or a C service dependency;
+In both cases the service pointers are injected using shared_ptr and if applicable the service properties and
+bundle argument are also provided as shared_ptr using the C++ `celix::Properties` and `celix::Bundle`.
 
 Remarks for the C++ example:
-1. TODO
+1. Creates a mutex to protect the `shellCommands` field which is configured with a locking-strategy service dependency.
+2. Updates the `highestRankingShellCmd` field without locking. Note that because the service dependency is
+   configured with a suspend-strategy the `ComponentWithServiceDependency::setHighestRankingShellCommand` method
+   will only be called when the component is in the `Suspended` state or when it is not in the `Active` compound state.
+3. Locks the mutex and adds the newly added service to the `shellCommands` list. Note that because the service
+   dependency is configured with a locking-strategy the `ComponentWithServiceDependency::addCShellCmd` and
+   `ComponentWithServiceDependency::removeCShellCmd` methods can be called from any component lifecycle state.
+4. Creates a new DM service dependency object, the service dependency is considered incomplete until the
+   service dependency, component or DM is build. Note that the `celix::dm::Component::createServiceDependency` method
+   is called without provided a service name, the service name will be inferred using the `celix::typeName`.
+5. Configures the service dependency set callback.
+6. Configures the service dependency as a required service dependency.
+7. Configures the update strategy for the service dependency to suspend-strategy.
+8. Creates another new DM service dependency object and in this case also explicitly provides the service name
+   to use (`CELIX_SHELL_COMMAND_SERVICE_NAME`).
+9. Builds the component and as result also builds the components' service dependencies (i.e. marking them as complete).
 
 ```C++
 //src/ComponentWithServiceDependencyActivator.cc
@@ -588,7 +635,7 @@ class ComponentWithServiceDependency {
 public:
     void setHighestRankingShellCommand(const std::shared_ptr<celix::IShellCommand>& cmdSvc) {
         std::cout << "New highest ranking service (can be NULL): " << (intptr_t)cmdSvc.get() << std::endl;
-        highestRankingShellCmd = cmdSvc;
+        highestRankingShellCmd = cmdSvc; // <------------------------------------------------------------------------<2>
     }
 
     void addCShellCmd(
@@ -596,7 +643,7 @@ public:
             const std::shared_ptr<const celix::Properties>& props) {
         auto id = props->getAsLong(celix::SERVICE_ID, -1);
         std::cout << "Adding shell command service with service.id: " << id << std::endl;
-        std::lock_guard lck{mutex};
+        std::lock_guard lck{mutex}; // <-----------------------------------------------------------------------------<3>
         shellCommands.emplace(id, cmdSvc);
     }
 
@@ -610,7 +657,7 @@ public:
     }
 private:
     std::shared_ptr<celix::IShellCommand> highestRankingShellCmd{};
-    std::mutex mutex{}; //protect below
+    std::mutex mutex{}; //protect shellCommands // <-----------------------------------------------------------------<1>
     std::unordered_map<long, std::shared_ptr<celix_shell_command_t>> shellCommands{};
 };
 
@@ -618,19 +665,19 @@ class ComponentWithServiceDependencyActivator {
 public:
     explicit ComponentWithServiceDependencyActivator(const std::shared_ptr<celix::BundleContext>& ctx) {
         using Cmp = ComponentWithServiceDependency;
-        auto& cmp = ctx->getDependencyManager()->createComponent<Cmp>(); // <-------------<1>
+        auto& cmp = ctx->getDependencyManager()->createComponent<Cmp>(); 
 
-        cmp.createServiceDependency<celix::IShellCommand>()
-                .setCallbacks(&Cmp::setHighestRankingShellCommand)
-                .setRequired(true)
-                .setStrategy(DependencyUpdateStrategy::suspend);
+        cmp.createServiceDependency<celix::IShellCommand>() // <-----------------------------------------------------<4>
+                .setCallbacks(&Cmp::setHighestRankingShellCommand) // <----------------------------------------------<5>
+                .setRequired(true) // <------------------------------------------------------------------------------<6>
+                .setStrategy(DependencyUpdateStrategy::suspend); // <------------------------------------------------<7>
 
-        cmp.createServiceDependency<celix_shell_command_t>(CELIX_SHELL_COMMAND_SERVICE_NAME)
-                .setCallbacks(&Cmp::addCShellCmd, &Cmp::removeCShellCmd)
+        cmp.createServiceDependency<celix_shell_command_t>(CELIX_SHELL_COMMAND_SERVICE_NAME) // <--------------------<8>
+                .setCallbacks(&Cmp::addCShellCmd, &Cmp::removeCShellCmd) 
                 .setRequired(false)
                 .setStrategy(DependencyUpdateStrategy::locking);
 
-        cmp.build(); // <--------------------------------------------------------------------------------------------<8>
+        cmp.build(); // <--------------------------------------------------------------------------------------------<9>
     }
 };
 
@@ -638,10 +685,20 @@ CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithServiceDependencyActivator)
 ```
 
 # When will a component be suspended
-
-TODO explain that a component will be suspended with service dependency on suspend strategy, a matching service update 
-and if there is a svc inject callback configured. TODO check also start/stop method?
-
-# TODOs
-
-overview of function (e.g. setFilter for dependencies, etc)
\ No newline at end of file
+Components will only suspend if:
+- The component is in the state `Tracking Optional`;
+- The component has at least 1 service dependency where the update strategy is configured as suspend-strategy;
+- There is a service update event ongoing, where the update service event matches 1 of the components'
+  suspend-strategy service dependencies;
+- And least one of the component's matching suspend-strategy service dependency has a configured service injection/
+  removal callback configured.
+
+# The `celix::dm` shell command
+To interactively see the available components, their current lifecycle state, provided service and service dependencies
+the `celix::dm` shell command can be used.
+
+Examples of supported `dm` command lines are:
+- `celix::dm` - Show an overview of all components in the Celix framework. Only shows component lifecycle state.
+- `dm` - Same as `celix::dm` (as long as there is no colliding other `dm` commands).
+- `dm full` - Show a detailed overview of all components in the Celix framework. This also shows the provided
+  services and service dependencies of each component.
\ No newline at end of file
diff --git a/documents/services.md b/documents/services.md
index ae7e17bd..81e22a25 100644
--- a/documents/services.md
+++ b/documents/services.md
@@ -365,7 +365,7 @@ The Celix framework provides service usage through callbacks - instead of direct
 to ensure that services are prevented from removal while the services are still in use without forwarding 
 this responsibility to the user; i.e. by adding an api to "lock" and "unlock" services for usage.
 
-###Example: Using a service in C
+### Example: Using a service in C
 ```C
 #include <stdio.h>
 #include <celix_api.h>
@@ -400,7 +400,7 @@ static celix_status_t use_command_service_example_stop(use_command_service_examp
 CELIX_GEN_BUNDLE_ACTIVATOR(use_command_service_example_data_t, use_command_service_example_start, use_command_service_example_stop)
 ```
 
-###Example: Using a service in C++
+### Example: Using a service in C++
 ```C++
 //src/UsingCommandServicesExample.cc
 #include <celix/IShellCommand.h>
@@ -474,7 +474,7 @@ C++ service trackers are created and opened asynchronized, but closed synchroniz
 The closing is done synchronized so that users can be sure that after a `celix::ServiceTracker::close()` call the 
 added callbacks will not be invoked anymore.  
 
-###Example: Tracking services in C
+### Example: Tracking services in C
 ```C
 //src/track_command_services_example.c
 #include <stdio.h>
@@ -535,7 +535,7 @@ static celix_status_t track_command_services_example_stop(track_command_services
 CELIX_GEN_BUNDLE_ACTIVATOR(track_command_services_example_data_t, track_command_services_example_start, track_command_services_example_stop)
 ```
 
-###Example: Tracking services in C++
+### Example: Tracking services in C++
 ```C++
 //src/TrackingCommandServicesExample.cc
 #include <unordered_map>
@@ -625,3 +625,17 @@ Service tracker callback with a synchronized service registration
 ![Register Service Async](diagrams/services_tracker_services_rem_seq.png)
 Service tracker callback with a synchronized service un-registration
 ---
+
+# The `celix::query` shell command
+To interactively see the which service and service trackers are available the `celix::query` shell command 
+can be used.
+
+Examples of supported `query` command lines are:
+- `celix::query` - Show an overview of registered services and active service trackers per bundle.
+- `query` - Same as `celix::query` (as long as there is no colliding other `query` commands).
+- `query -v` - Show a detailed overview of registered services and active service trackers per bundle.
+  For registered services the services properties are also printed and for active service trackers the number
+  of tracked services is also printed.
+- `query foo` - Show an overview of registered services and active service tracker where "foo" is
+  (case-insensitive) part of the provided/tracked service name.   
+- `query (service.id>=10)` - Shown an overview of registered services which match the provided LDAP filter. 
diff --git a/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c b/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c
index e44347e1..79883b40 100644
--- a/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c
+++ b/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c
@@ -26,13 +26,13 @@
 
 typedef struct component_with_service_dependency {
     celix_shell_command_t* highestRankingCmdShell; //only updated when component is not active or suspended
-    celix_thread_mutex_t mutex;
+    celix_thread_mutex_t mutex; //protects cmdShells
     celix_array_list_t* cmdShells;
 } component_with_service_dependency_t;
 
 static component_with_service_dependency_t* componentWithServiceDependency_create() {
     component_with_service_dependency_t* cmp = calloc(1, sizeof(*cmp));
-    celixThreadMutex_create(&cmp->mutex, NULL);
+    celixThreadMutex_create(&cmp->mutex, NULL); // <-----------------------------------------------------------------<1>
     cmp->cmdShells = celix_arrayList_create();
     return cmp;
 }
@@ -47,7 +47,7 @@ static void componentWithServiceDependency_setHighestRankingShellCommand(
         component_with_service_dependency_t* cmp,
         celix_shell_command_t* shellCmd) {
     printf("New highest ranking service (can be NULL): %p\n", shellCmd);
-    cmp->highestRankingCmdShell = shellCmd;
+    cmp->highestRankingCmdShell = shellCmd; // <---------------------------------------------------------------------<2>
 }
 
 static void componentWithServiceDependency_addShellCommand(
@@ -56,7 +56,7 @@ static void componentWithServiceDependency_addShellCommand(
         const celix_properties_t* props) {
     long id = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1);
     printf("Adding shell command service with service.id %li\n", id);
-    celixThreadMutex_lock(&cmp->mutex);
+    celixThreadMutex_lock(&cmp->mutex); // <-------------------------------------------------------------------------<3>
     celix_arrayList_add(cmp->cmdShells, shellCmd);
     celixThreadMutex_unlock(&cmp->mutex);
 }
@@ -91,22 +91,22 @@ static celix_status_t componentWithServiceDependencyActivator_start(component_wi
             componentWithServiceDependency_destroy);
 
     //create mandatory service dependency with cardinality one and with a suspend-strategy
-    celix_dm_service_dependency_t* dep1 = celix_dmServiceDependency_create();
-    celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL);
-    celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND);
-    celix_dmServiceDependency_setRequired(dep1, true);
-    celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS;
-    opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand;
-    celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1);
-    celix_dmComponent_addServiceDependency(dmCmp, dep1);
+    celix_dm_service_dependency_t* dep1 = celix_dmServiceDependency_create(); // <-----------------------------------<4>
+    celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL); // <-------------------<5>
+    celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND); // <------------------------<6>
+    celix_dmServiceDependency_setRequired(dep1, true); // <----------------------------------------------------------<7>
+    celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; // <--<8>
+    opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand; // <----------------------------<9>
+    celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1); // <-------------------------------------------<10>
+    celix_dmComponent_addServiceDependency(dmCmp, dep1); // <-------------------------------------------------------<11>
 
     //create optional service dependency with cardinality many and with a locking-strategy
     celix_dm_service_dependency_t* dep2 = celix_dmServiceDependency_create();
     celix_dmServiceDependency_setService(dep2, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL);
-    celix_dmServiceDependency_setStrategy(dep2, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING);
-    celix_dmServiceDependency_setRequired(dep2, false);
+    celix_dmServiceDependency_setStrategy(dep2, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING);  // <----------------------<12>
+    celix_dmServiceDependency_setRequired(dep2, false); // <--------------------------------------------------------<13>
     celix_dm_service_dependency_callback_options_t opts2 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS;
-    opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand;
+    opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand;  // <-------------------------------<14>
     opts2.removeWithProps = (void*)componentWithServiceDependency_removeShellCommand;
     celix_dmServiceDependency_setCallbacksWithOptions(dep2, &opts2);
     celix_dmComponent_addServiceDependency(dmCmp, dep2);
diff --git a/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
index 31b9758e..73be1ab5 100644
--- a/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
+++ b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
@@ -31,7 +31,8 @@ public:
             const std::vector<std::string>& /*commandArgs*/,
             FILE* outStream,
             FILE* /*errorStream*/) override {
-        fprintf(outStream, "Hello from cmp. C++ command called %i times. commandLine is %s\n", cxxCallCount++, commandLine.c_str());
+        fprintf(outStream, "Hello from cmp. C++ command called %i times. commandLine is %s\n",
+                cxxCallCount++, commandLine.c_str());
     } // <-----------------------------------------------------------------------------------------------------------<2>
 
     void executeCCommand(const char* commandLine, FILE* outStream) {
@@ -48,7 +49,7 @@ public:
         auto& cmp = ctx->getDependencyManager()->createComponent<ComponentWithProvidedService>(); // <---------------<4>
 
         cmp.createProvidedService<celix::IShellCommand>()
-                .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <---------------------------------<5>
+                .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <-----------------------------<5>
 
         auto shellCmd = std::make_shared<celix_shell_command_t>();
         shellCmd->handle = static_cast<void*>(&cmp.getInstance());
@@ -59,7 +60,7 @@ public:
         }; // <------------------------------------------------------------------------------------------------------<6>
 
         cmp.createUnassociatedProvidedService(std::move(shellCmd), CELIX_SHELL_COMMAND_SERVICE_NAME)
-                .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -----------------------------------------<7>
+                .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -------------------------------------<7>
 
         cmp.build(); // <--------------------------------------------------------------------------------------------<8>
     }
diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt
index b33781a8..1cdae108 100644
--- a/libs/utils/CMakeLists.txt
+++ b/libs/utils/CMakeLists.txt
@@ -84,60 +84,64 @@ add_library(Celix::utils ALIAS utils)
 if (ENABLE_TESTING)
     add_subdirectory(gtest)
 
-    find_package(CppUTest REQUIRED)
-
-    include_directories(SYSTEM PRIVATE ${CppUTest_INCLUDE_DIR})
-    include_directories(include)
-    include_directories(src)
-
-    add_executable(hash_map_test private/test/hash_map_test.cpp)
-    target_link_libraries(hash_map_test Celix::utils CppUTest::CppUTest pthread)
-
-    add_executable(array_list_test private/test/array_list_test.cpp)
-    target_link_libraries(array_list_test  Celix::utils CppUTest::CppUTest pthread)
-
-    add_executable(celix_threads_test private/test/celix_threads_test.cpp)
-    target_link_libraries(celix_threads_test Celix::utils CppUTest::CppUTest ${CppUTest_EXT_LIBRARIES} pthread)
-
-    add_executable(linked_list_test private/test/linked_list_test.cpp)
-    target_link_libraries(linked_list_test  Celix::utils CppUTest::CppUTest pthread)
-
-    add_executable(properties_test private/test/properties_test.cpp)
-    target_link_libraries(properties_test CppUTest::CppUTest CppUTest::CppUTestExt  Celix::utils pthread)
-
-    add_executable(utils_test private/test/utils_test.cpp)
-    target_link_libraries(utils_test CppUTest::CppUTest  Celix::utils pthread)
-
-    add_executable(ip_utils_test private/test/ip_utils_test.cpp)
-    target_link_libraries(ip_utils_test CppUTest::CppUTest  Celix::utils pthread)
-
-    add_executable(filter_test private/test/filter_test.cpp)
-    target_link_libraries(filter_test CppUTest::CppUTest  Celix::utils pthread)
-
-    add_executable(version_test private/test/version_test.cpp)
-    target_link_libraries(version_test CppUTest::CppUTest  Celix::utils pthread)
-
-    configure_file(private/resources-test/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
-
-    add_test(NAME run_array_list_test COMMAND array_list_test)
-    add_test(NAME run_hash_map_test COMMAND hash_map_test)
-    add_test(NAME run_celix_threads_test COMMAND celix_threads_test)
-    add_test(NAME run_linked_list_test COMMAND linked_list_test)
-    add_test(NAME run_properties_test COMMAND properties_test)
-    add_test(NAME run_utils_test COMMAND utils_test)
-    add_test(NAME run_ip_utils_test COMMAND ip_utils_test)
-    add_test(NAME filter_test COMMAND filter_test)
-    add_test(NAME version_test COMMAND version_test)
-
-    setup_target_for_coverage(array_list_test)
-    setup_target_for_coverage(hash_map_test)
-    setup_target_for_coverage(celix_threads_test)
-    setup_target_for_coverage(linked_list_test)
-    setup_target_for_coverage(properties_test)
-    setup_target_for_coverage(utils_test)
-    setup_target_for_coverage(ip_utils_test)
-    setup_target_for_coverage(filter_test)
-    setup_target_for_coverage(version_test)
+    find_package(CppUTest)
+
+    if (CppUTest_FOUND)
+        include_directories(SYSTEM PRIVATE ${CppUTest_INCLUDE_DIR})
+        include_directories(include)
+        include_directories(src)
+
+        add_executable(hash_map_test private/test/hash_map_test.cpp)
+        target_link_libraries(hash_map_test Celix::utils CppUTest::CppUTest pthread)
+
+        add_executable(array_list_test private/test/array_list_test.cpp)
+        target_link_libraries(array_list_test  Celix::utils CppUTest::CppUTest pthread)
+
+        add_executable(celix_threads_test private/test/celix_threads_test.cpp)
+        target_link_libraries(celix_threads_test Celix::utils CppUTest::CppUTest ${CppUTest_EXT_LIBRARIES} pthread)
+
+        add_executable(linked_list_test private/test/linked_list_test.cpp)
+        target_link_libraries(linked_list_test  Celix::utils CppUTest::CppUTest pthread)
+
+        add_executable(properties_test private/test/properties_test.cpp)
+        target_link_libraries(properties_test CppUTest::CppUTest CppUTest::CppUTestExt  Celix::utils pthread)
+
+        add_executable(utils_test private/test/utils_test.cpp)
+        target_link_libraries(utils_test CppUTest::CppUTest  Celix::utils pthread)
+
+        add_executable(ip_utils_test private/test/ip_utils_test.cpp)
+        target_link_libraries(ip_utils_test CppUTest::CppUTest  Celix::utils pthread)
+
+        add_executable(filter_test private/test/filter_test.cpp)
+        target_link_libraries(filter_test CppUTest::CppUTest  Celix::utils pthread)
+
+        add_executable(version_test private/test/version_test.cpp)
+        target_link_libraries(version_test CppUTest::CppUTest  Celix::utils pthread)
+
+        configure_file(private/resources-test/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
+
+        add_test(NAME run_array_list_test COMMAND array_list_test)
+        add_test(NAME run_hash_map_test COMMAND hash_map_test)
+        add_test(NAME run_celix_threads_test COMMAND celix_threads_test)
+        add_test(NAME run_linked_list_test COMMAND linked_list_test)
+        add_test(NAME run_properties_test COMMAND properties_test)
+        add_test(NAME run_utils_test COMMAND utils_test)
+        add_test(NAME run_ip_utils_test COMMAND ip_utils_test)
+        add_test(NAME filter_test COMMAND filter_test)
+        add_test(NAME version_test COMMAND version_test)
+
+        setup_target_for_coverage(array_list_test)
+        setup_target_for_coverage(hash_map_test)
+        setup_target_for_coverage(celix_threads_test)
+        setup_target_for_coverage(linked_list_test)
+        setup_target_for_coverage(properties_test)
+        setup_target_for_coverage(utils_test)
+        setup_target_for_coverage(ip_utils_test)
+        setup_target_for_coverage(filter_test)
+        setup_target_for_coverage(version_test)
+    else ()
+        message(WARNING "Cannot find CppUTest, deprecated cpputest-based unit test will not be added")
+    endif () #end CppUTest_FOUND
 
 endif ()