Feature/add fuzzing (#799)

Add fuzz testing 

* Also improves benchmark setup
* Add fuzzing workflow and enable benchmarking options in CI configurations
* Add documentation for building benchmarks, fuzz testing, and running tests
* Remove C++ flag for macos, resulted in undefined symbols
* Increase celix error buffer size for fuzz testing
* Eliminate clang warnings -Werror,-Wvla-cxx-extension
* Refactor `benchmark` dependency to `test_requires` when `enable_benchmarking` is set.
* Fix formatting issue in README under "C Patterns" section.
* Update to actions/cache 4.3.0
* Add asan and ubsan for fuzz testing builds
* Enable ubsan with asan for linux ci

---------

Co-authored-by: PengZheng <howtofly@gmail.com>
diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml
new file mode 100644
index 0000000..242b475
--- /dev/null
+++ b/.github/workflows/fuzzing.yml
@@ -0,0 +1,59 @@
+name: Celix Fuzzing
+
+on:
+  push:
+  pull_request:
+  schedule:
+    - cron: '0 3 * * *'
+
+jobs:
+  fuzz-utils:
+    runs-on: ubuntu-22.04
+    timeout-minutes: 30
+    steps:
+      - name: Checkout source code
+        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0
+      - name: Set up Python
+        uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c #v4.9.1
+        with:
+          python-version: '3.x'
+      - name: Set Compiler Environment Variables
+        run: |
+          echo "CC=clang" >> $GITHUB_ENV
+          echo "CXX=clang++" >> $GITHUB_ENV
+      - name: Install Conan
+        run: pip install conan
+      - name: Cache Conan
+        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0
+        with:
+          path: ~/.conan2/p
+          key: ${{ runner.os }}-conan-${{ hashFiles('conanfile.py', 'libs/utils/**') }}
+          restore-keys: |
+            ${{ runner.os }}-conan-
+      - name: Setup Conan Profile
+        run: |
+          conan profile detect 
+      - name: Conan install
+        run: conan install . --output-folder=build --build=missing -o "celix/*:build_utils=True" -o "celix/*:enable_fuzzing=True" -o "celix/*:enable_address_sanitizer=True" -o "celix/*:enable_undefined_sanitizer=True"
+      - name: Conan build
+        run: conan build . --output-folder=build -o "celix/*:build_utils=True" -o "celix/*:enable_fuzzing=True"  -o "celix/*:enable_address_sanitizer=True" -o "celix/*:enable_undefined_sanitizer=True" -o "celix/*:celix_err_buffer_size=5120"
+      - name: Set fuzzer run time
+        id: set-runtime
+        run: |
+          if [[ "${{ github.event_name }}" == "schedule" ]]; then
+            echo "FUZZ_TIME=600" >> ${GITHUB_ENV}
+          else
+            echo "FUZZ_TIME=30" >> ${GITHUB_ENV}
+          fi
+      - name: Run properties fuzzer
+        run: |
+          source build/conanrun.sh
+          ./build/libs/utils/fuzzing/celix_properties_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/properties_corpus
+      - name: Run version fuzzer
+        run: |
+          source build/conanrun.sh
+          ./build/libs/utils/fuzzing/celix_version_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/version_corpus
+      - name: Run filter fuzzer
+        run: |
+          source build/conanrun.sh
+          ./build/libs/utils/fuzzing/celix_filter_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/filter_corpus
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 61bfd9e..eceb860 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -56,6 +56,7 @@
         env:
           CONAN_BUILD_OPTIONS: |
             -o celix/*:enable_testing=True
+            -o celix/*:enable_benchmarking=True
             -o celix/*:enable_address_sanitizer=True
             -o celix/*:build_all=True
             -o celix/*:enable_cmake_warning_tests=True
@@ -79,7 +80,7 @@
         uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0
       - name: Install dependencies
         run: |
-          brew install lcov jansson rapidjson libzip ccache ninja openssl@1.1
+          brew install lcov jansson rapidjson libzip ccache ninja openssl@1.1 google-benchmark
       - name: Prepare ccache timestamp
         id: ccache_cache_timestamp
         run: |
@@ -95,6 +96,7 @@
         env:
           BUILD_OPTIONS: |
             -DENABLE_TESTING=ON
+            -DENABLE_BENCHMARKING=ON
             -DENABLE_ADDRESS_SANITIZER=ON
             -DENABLE_TESTING_ON_CI=ON
             -DCMAKE_BUILD_TYPE=Release
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index a472b72..dc0964f 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -77,7 +77,9 @@
           CXX: ${{ matrix.compiler[1] }}
           CONAN_BUILD_OPTIONS: |
             -o celix:enable_testing=True
+            -o celix:enable_benchmarking=True
             -o celix:enable_address_sanitizer=True
+            -o celix:enable_undefined_sanitizer=True
             -o celix:build_all=True
             -o celix:enable_cmake_warning_tests=True
             -o celix:enable_testing_on_ci=True
@@ -120,6 +122,7 @@
           libzip-dev \
           libjansson-dev \
           libcurl4-openssl-dev \
+          libbenchmark-dev \
           default-jdk \
           cmake \
           libffi-dev \
@@ -145,6 +148,7 @@
         BUILD_OPTIONS: |
           -DBUILD_EXPERIMENTAL=ON
           -DENABLE_TESTING=ON
+          -DENABLE_BENCHMARKING=ON
           -DRSA_JSON_RPC=ON
           -DRSA_REMOTE_SERVICE_ADMIN_SHM_V2=ON
           -DENABLE_TESTING_ON_CI=ON
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d72ce4d..09e065a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -168,6 +168,8 @@
 option(ENABLE_CMAKE_WARNING_TESTS "Enable cmake warning tests to test warning prints" OFF)
 option(ENABLE_TESTING_ON_CI "Whether to enable testing on CI. This influence allowed timing errors during unit tests" OFF)
 option(ENABLE_DEPRECATED_WARNINGS "Enable compiler warnings for usage of deprecated functionality" OFF)
+option(ENABLE_FUZZING "Enable fuzz testing, using LibFuzzer" OFF) #Note support for LibFuzzer is built-in for Clang
+option(ENABLE_BENCHMARKING "Enable benchmarking, using Google Benchmark" OFF)
 
 if (NOT ENABLE_DEPRECATED_WARNINGS)
     set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}")
diff --git a/cmake/celix_project/CelixProject.cmake b/cmake/celix_project/CelixProject.cmake
index 0f31327..8f231a2 100644
--- a/cmake/celix_project/CelixProject.cmake
+++ b/cmake/celix_project/CelixProject.cmake
@@ -25,13 +25,17 @@
 mark_as_advanced(CLEAR ENABLE_THREAD_SANITIZER)
 
 if (ENABLE_ADDRESS_SANITIZER)
+    set(UBSAN_SAN "")
+    if (ENABLE_UNDEFINED_SANITIZER)
+        set(UBSAN_SAN "undefined,")
+    endif ()
     if("${CMAKE_C_COMPILER_ID}" MATCHES "Clang")
         set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}")
-        set(CMAKE_C_FLAGS "-shared-libasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}")
-        set(CMAKE_CXX_FLAGS "-shared-libasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
+        set(CMAKE_C_FLAGS "-shared-libasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}")
+        set(CMAKE_CXX_FLAGS "-shared-libasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
         if (APPLE)
-            set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=address ${CMAKE_EXE_LINKER_FLAGS}")
-            set(CMAKE_SHARED_LINKER_FLAGS "-fsanitize=address ${CMAKE_SHARED_LINKER_FLAGS}")
+            set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=${UBSAN_SAN}address ${CMAKE_EXE_LINKER_FLAGS}")
+            set(CMAKE_SHARED_LINKER_FLAGS "-fsanitize=${UBSAN_SAN}address ${CMAKE_SHARED_LINKER_FLAGS}")
         else ()
             # Fix a linux clang deficiency where the ASan runtime library is not found automatically
             # Find the ASan runtime library path and set RPATH
@@ -56,19 +60,15 @@
         endif ()
     elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
         set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}")
-        set(CMAKE_C_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}")
-        set(CMAKE_CXX_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
+        set(CMAKE_C_FLAGS "-lasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}")
+        set(CMAKE_CXX_FLAGS "-lasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
     else ()
         message(WARNING "Address sanitizer is not supported for ${CMAKE_C_COMPILER_ID}")
     endif ()
-endif()
-
-if (ENABLE_UNDEFINED_SANITIZER)
+elseif (ENABLE_UNDEFINED_SANITIZER)
     set(CMAKE_C_FLAGS "-fsanitize=undefined ${CMAKE_C_FLAGS}")
     set(CMAKE_CXX_FLAGS "-fsanitize=undefined ${CMAKE_CXX_FLAGS}")
-endif()
-
-if (ENABLE_THREAD_SANITIZER)
+elseif (ENABLE_THREAD_SANITIZER)
     set(CMAKE_C_FLAGS "-fsanitize=thread ${CMAKE_C_FLAGS}")
     set(CMAKE_CXX_FLAGS "-fsanitize=thread ${CMAKE_CXX_FLAGS}")
 endif()
diff --git a/cmake/cmake_celix/BundlePackaging.cmake b/cmake/cmake_celix/BundlePackaging.cmake
index 4200f39..bb48690 100644
--- a/cmake/cmake_celix/BundlePackaging.cmake
+++ b/cmake/cmake_celix/BundlePackaging.cmake
@@ -287,16 +287,7 @@
     #########################################################
 
     ###### Packaging the bundle using using jar or zip and a content dir. Configuring dependencies ######
-    if (JAR_COMMAND)
-        add_custom_command(OUTPUT ${BUNDLE_FILE}
-                COMMAND ${CMAKE_COMMAND} -E make_directory ${BUNDLE_CONTENT_DIR}
-                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json ${BUNDLE_CONTENT_DIR}/META-INF/MANIFEST.json
-                COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE} -C ${BUNDLE_CONTENT_DIR} .
-                COMMENT "Packaging ${BUNDLE_TARGET_NAME}"
-                DEPENDS ${BUNDLE_TARGET_NAME} "$<TARGET_PROPERTY:${BUNDLE_TARGET_NAME},BUNDLE_DEPEND_TARGETS>" ${BUNDLE_GEN_DIR}/MANIFEST.json
-                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-        )
-    elseif (ZIP_COMMAND)
+    if (ZIP_COMMAND)
         file(MAKE_DIRECTORY ${BUNDLE_CONTENT_DIR}) #Note needed because working_directory is bundle content dir
         add_custom_command(OUTPUT ${BUNDLE_FILE}
                 COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json ${BUNDLE_CONTENT_DIR}/META-INF/MANIFEST.json
@@ -305,6 +296,15 @@
                 DEPENDS ${BUNDLE_TARGET_NAME} "$<TARGET_PROPERTY:${BUNDLE_TARGET_NAME},BUNDLE_DEPEND_TARGETS>" ${BUNDLE_GEN_DIR}/MANIFEST.json
                 WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR}
         )
+    elseif (JAR_COMMAND)
+        add_custom_command(OUTPUT ${BUNDLE_FILE}
+                COMMAND ${CMAKE_COMMAND} -E make_directory ${BUNDLE_CONTENT_DIR}
+                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json ${BUNDLE_CONTENT_DIR}/META-INF/MANIFEST.json
+                COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE} -C ${BUNDLE_CONTENT_DIR} .
+                COMMENT "Packaging ${BUNDLE_TARGET_NAME}"
+                DEPENDS ${BUNDLE_TARGET_NAME} "$<TARGET_PROPERTY:${BUNDLE_TARGET_NAME},BUNDLE_DEPEND_TARGETS>" ${BUNDLE_GEN_DIR}/MANIFEST.json
+                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+        )
     else ()
         message(FATAL_ERROR "A jar or zip command is needed to jar/zip bundles")
     endif ()
@@ -934,16 +934,7 @@
     set(BUNDLE_FILE_INSTALL "${BUNDLE_FILE}.install")
     get_target_property(BUNDLE_FILE_NAME ${BUNDLE} "BUNDLE_FILE_NAME")
     get_target_property(BUNDLE_GEN_DIR ${BUNDLE} "BUNDLE_GEN_DIR")
-    if (JAR_COMMAND)
-        install(CODE
-                "execute_process(
-                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json
-                COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} -C ${BUNDLE_CONTENT_DIR} .
-                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-            )"
-                COMPONENT ${BUNDLE}
-        )
-    elseif (ZIP_COMMAND)
+    if (ZIP_COMMAND)
         install(CODE
                 "execute_process(
                 COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json
@@ -952,6 +943,15 @@
             )"
                 COMPONENT ${BUNDLE}
         )
+    elseif (JAR_COMMAND)
+        install(CODE
+                "execute_process(
+                COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json
+                COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} -C ${BUNDLE_CONTENT_DIR} .
+                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+            )"
+                COMPONENT ${BUNDLE}
+        )
     else ()
         message(FATAL_ERROR "A jar or zip command is needed to jar/zip bundles")
     endif ()
diff --git a/conanfile.py b/conanfile.py
index 09ca5a3..bb1c639 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -47,6 +47,8 @@
         "enable_address_sanitizer": False,
         "enable_undefined_sanitizer": False,
         "enable_thread_sanitizer": False,
+        "enable_fuzzing": False,
+        "enable_benchmarking": False,
         "install_find_modules": False,
         "build_all": False,
         "build_http_admin": False,
@@ -130,6 +132,9 @@
         if self.options.build_rsa_discovery_zeroconf and self.settings.os != "Linux":
             raise ConanInvalidConfiguration("Celix build_rsa_discovery_zeroconf is only supported for Linux")
 
+        if self.options.enable_fuzzing and self.settings.compiler != "clang" and self.settings.compiler != "apple-clang":
+            raise ConanInvalidConfiguration("Celix enable_fuzzing=True requires the 'clang' compiler")
+
         self.validate_config_option_is_positive_number("celix_err_buffer_size")
         self.validate_config_option_is_positive_number("celix_utils_max_strlen")
         self.validate_config_option_is_positive_number("celix_properties_optimization_string_buffer_size")
@@ -145,12 +150,18 @@
         del self.info.options.enable_testing_on_ci
         del self.info.options.enable_ccache
         del self.info.options.enable_deprecated_warnings
+        del self.info.options.enable_testing
+        del self.info.options.enable_benchmarking
+        del self.info.options.enable_fuzzing
+        del self.info.options.enable_code_coverage
 
     def build_requirements(self):
         if self.options.enable_testing:
             self.test_requires("gtest/1.10.0")
         if self.options.enable_ccache:
             self.build_requires("ccache/4.7.4")
+        if self.options.enable_benchmarking:
+            self.test_requires("benchmark/[>=1.6.2]")
 
     def configure(self):
         # copy options to options, fill in defaults if not set
@@ -308,6 +319,8 @@
             self.options['openssl'].shared = True
         if self.options.enable_testing:
             self.options['gtest'].shared = True
+        if self.options.enable_benchmarking:
+            self.options['benchmark'].shared = True
         if (self.options.build_rsa_discovery_common
                 or (self.options.build_rsa_remote_service_admin_dfi and self.options.enable_testing)):
             self.options['libxml2'].shared = True
diff --git a/documents/README.md b/documents/README.md
index fefec79..c633e7d 100644
--- a/documents/README.md
+++ b/documents/README.md
@@ -81,7 +81,6 @@
 
 * Building
   * [Building and Installing Apache Celix](building/README.md)
-  * [Building and Developing Apache Celix with CLion](building/dev_celix_with_clion.md)
 * C Patterns
   * [Apache Celix C Patterns](c_patterns.md)
 * Utils
diff --git a/documents/building/README.md b/documents/building/README.md
index c643510..7d01b8b 100644
--- a/documents/building/README.md
+++ b/documents/building/README.md
@@ -237,3 +237,10 @@
 make -j
 sudo make install
 ```
+
+# Further Reading
+
+- [Building with CLion](dev_celix_with_clion.md)
+- [Building and Running Tests](testing.md)
+- [Fuzz Testing](fuzz_testing.md)
+- [Building and Running Benchmarks](benchmarks.md)
diff --git a/documents/building/benchmarks.md b/documents/building/benchmarks.md
new file mode 100644
index 0000000..d1061b3
--- /dev/null
+++ b/documents/building/benchmarks.md
@@ -0,0 +1,74 @@
+---
+title: Benchmarks in Apache Celix
+---
+
+<!--
+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.
+-->
+
+
+# Benchmarks in Apache Celix
+
+This document describes how to build and run benchmarks for Apache Celix.
+
+## Building Benchmarks
+
+Benchmarks can be built using the CMake option `ENABLE_BENCHMARKING`
+The Apache Celix benchmarks uses Google benchmark library. 
+
+To build benchmarks run:
+
+```sh
+cmake -B build -DENABLE_BENCHMARKING=ON
+cmake --build build
+```
+
+## Benchmarks
+
+The following benchmark executables are available after building the utils and framework benchmarks:
+
+**Utils Benchmarks:**
+- `build/libs/utils/benchmark/celix_filter_benchmark`
+- `build/libs/utils/benchmark/celix_long_hashmap_benchmark`
+- `build/libs/utils/benchmark/celix_string_hashmap_benchmark`
+- `build/libs/utils/benchmark/celix_utils_benchmark`
+
+**Framework Benchmarks:**
+- `build/libs/framework/benchmark/celix_framework_benchmark`
+
+Paths may vary depending on your configuration and enabled options.
+
+## Running Benchmarks
+
+Benchmark executables are located in the `build` directory, typically under the relevant bundle or library subdirectory. To run a benchmark:
+
+```sh
+./build/libs/utils/benchmarks/celix_utils_benchmark
+## Command-Line Options
+The benchmark executables accept standard Google Benchmark command-line options. 
+For example, to run only benchmarks matching a specific pattern and output results in JSON format:
+
+```bash
+./build/libs/utils/benchmark/celix_filter_benchmark --benchmark_filter=complexFilter --benchmark_format=json
+```
+
+Replace `celix_utils_benchmark` and the filter pattern as needed. To see a list of supported command-line flags, run the benchmark executable with the `--help` option:
+
+```bash
+./build/libs/utils/benchmarks/./celix_filter_benchmark --help
+```
+
+This will display all available Google Benchmark options.
diff --git a/documents/building/fuzz_testing.md b/documents/building/fuzz_testing.md
new file mode 100644
index 0000000..50f0abb
--- /dev/null
+++ b/documents/building/fuzz_testing.md
@@ -0,0 +1,74 @@
+---
+title: Fuzz testing with libFuzzer
+---
+
+<!--
+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.
+-->
+
+# Fuzz Testing with libFuzzer
+
+The utilities library contains fuzz targets that can be built with
+[LLVM libFuzzer](https://llvm.org/docs/LibFuzzer.html).  Fuzzing is
+enabled when using the Clang compiler and the `UTILS_LIBFUZZER` CMake
+option.
+
+## Building
+
+Configure CMake with Clang and enable the libFuzzer option:
+
+```bash
+cmake \
+  -G Ninja \
+  -S . -B build \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DENABLE_FUZZING=ON
+```
+
+Build the fuzzer executables:
+
+```bash
+cmake --build build --parallel --target celix_properties_fuzzer celix_version_fuzzer celix_filter_fuzzer
+```
+
+## Corpus
+
+The `corpus` directories for the fuzzers contain a few seed inputs, which help guide the initial fuzzing process. 
+More files can be added to these directories to improve coverage. The fuzzer will automatically use all files in the 
+specified corpus directory as starting points for mutation and exploration.
+
+## Running
+The resulting fuzzers accept standard libFuzzer command line options. For example, to run each fuzzer for 30 seconds 
+using the provided seed corpus and print coverage information:
+
+```bash
+./build/libs/utils/celix_filter_fuzzer -max_total_time=30 -print_coverage=1 ./build/libs/utils/filter_corpus
+```
+
+Replace `celix_filter_fuzzer` and `filter_corpus` with the appropriate fuzzer executable and corpus directory as needed.
+To see a list of supported command-line flags, run the fuzzer executable with the `-help=1` option. For example:
+
+```bash
+./build/libs/utils/celix_filter_fuzzer -help=1
+```
+
+This will display all available LibFuzzer options.
+
+## Continuous Fuzzing
+
+A GitHub Actions workflow runs the fuzzer periodically. The workflow
+configuration can be found at `.github/workflows/fuzzing.yml`.
diff --git a/documents/building/testing.md b/documents/building/testing.md
new file mode 100644
index 0000000..557258c
--- /dev/null
+++ b/documents/building/testing.md
@@ -0,0 +1,65 @@
+---
+title: Testing Apache Celix
+---
+
+<!--
+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.
+-->
+
+# Testing Apache Celix
+
+This document describes how to build and run tests for Apache Celix.
+
+## Building Tests
+
+Celix uses CMake and Google Test for its unit and integration tests. To build the tests, ensure you have all dependencies installed, then run:
+
+```sh
+cmake -B build -DENABLE_TESTING=ON -DCMAKE_BUILD_TYPE=Debug
+cmake --build build
+```
+
+```sh
+#conan
+
+To enable AddressSanitizer (ASAN) when building tests, configure CMake with the `ENABLE_ASAN` option:
+
+```sh
+cmake -B build -DENABLE_TESTING=ON -DENABLE_ADDRESS_SANITIZER=ON -DCMAKE_BUILD_TYPE=Debug
+cmake --build build
+```
+
+This will build Apache Celix and its tests with ASAN enabled, helping to detect memory errors during test execution.
+
+## Running Tests
+
+After building, you can run all tests using CTest:
+
+```sh
+ctest --output-on-failure --test-dir build
+```
+
+Or run a test for a specific subdir, e.g.: 
+
+```sh
+ctest --output-on-failure --test-dir build/bundles/shell
+```
+
+Or run a specific test binary directly from the `build` directory, e.g.:
+
+```sh
+./build/bundles/components_ready_check/tests/components_ready_check_test
+```
diff --git a/libs/framework/benchmark/CMakeLists.txt b/libs/framework/benchmark/CMakeLists.txt
index ba39fcb..875d2f7 100644
--- a/libs/framework/benchmark/CMakeLists.txt
+++ b/libs/framework/benchmark/CMakeLists.txt
@@ -15,14 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set(FRAMEWORK_BENCHMARK_DEFAULT "OFF")
-find_package(benchmark QUIET)
-if (benchmark_FOUND)
-    set(FRAMEWORK_BENCHMARK_DEFAULT "ON")
-endif ()
-
-celix_subproject(FRAMEWORK_BENCHMARK "Option to enable Celix framework benchmark" ${FRAMEWORK_BENCHMARK_DEFAULT})
-if (FRAMEWORK_BENCHMARK AND CELIX_CXX17)
+if (ENABLE_BENCHMARKING AND CELIX_CXX17)
     set(CMAKE_CXX_STANDARD 17)
     find_package(benchmark REQUIRED)
 
diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt
index 4b8c08a..e28317b 100644
--- a/libs/utils/CMakeLists.txt
+++ b/libs/utils/CMakeLists.txt
@@ -126,5 +126,23 @@
         add_subdirectory(gtest)
     endif ()
 
+
+    if (ENABLE_FUZZING)
+        add_library(utils_cuf STATIC ${UTILS_SRC})
+        target_compile_definitions(utils_cuf PRIVATE CELIX_UTILS_STATIC_DEFINE)
+        target_include_directories(utils_cuf PUBLIC
+                        ${CMAKE_CURRENT_LIST_DIR}/include
+                        ${CMAKE_CURRENT_LIST_DIR}/include_internal
+                        ${CMAKE_BINARY_DIR}/celix/gen/includes/utils
+                        ${CMAKE_BINARY_DIR}/celix/gen/src/utils
+                        include_deprecated
+                        )
+        target_link_libraries(utils_cuf PUBLIC ${UTILS_PUBLIC_DEPS} ${UTILS_PRIVATE_DEPS})
+        target_compile_options(utils_cuf PRIVATE -fsanitize=fuzzer)
+        target_link_options(utils_cuf PRIVATE -Wl,--gc-sections)
+
+        add_subdirectory(fuzzing)
+    endif ()
+
     add_subdirectory(benchmark)
 endif ()
diff --git a/libs/utils/benchmark/CMakeLists.txt b/libs/utils/benchmark/CMakeLists.txt
index 2f891e6..4c22fc2 100644
--- a/libs/utils/benchmark/CMakeLists.txt
+++ b/libs/utils/benchmark/CMakeLists.txt
@@ -15,14 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set(UTILS_BENCHMARK_DEFAULT "OFF")
-find_package(benchmark QUIET)
-if (benchmark_FOUND)
-    set(UTILS_BENCHMARK_DEFAULT "ON")
-endif ()
-
-celix_subproject(UTILS_BENCHMARK "Option to enable Celix framework benchmark" ${UTILS_BENCHMARK_DEFAULT})
-if (UTILS_BENCHMARK)
+if (ENABLE_BENCHMARKING)
     find_package(benchmark REQUIRED)
 
     add_executable(celix_string_hashmap_benchmark
diff --git a/libs/utils/fuzzing/CMakeLists.txt b/libs/utils/fuzzing/CMakeLists.txt
new file mode 100644
index 0000000..a7e248e
--- /dev/null
+++ b/libs/utils/fuzzing/CMakeLists.txt
@@ -0,0 +1,52 @@
+# 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.
+
+if (ENABLE_ADDRESS_SANITIZER OR ENABLE_UNDEFINED_SANITIZER)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libsan")
+endif ()
+
+add_executable(celix_properties_fuzzer src/PropertiesFuzz.cc)
+target_link_libraries(celix_properties_fuzzer PRIVATE utils_cuf)
+target_compile_options(celix_properties_fuzzer PRIVATE -fsanitize=fuzzer)
+target_link_options(celix_properties_fuzzer PRIVATE -fsanitize=fuzzer)
+
+add_executable(celix_version_fuzzer src/VersionFuzz.cc)
+target_link_libraries(celix_version_fuzzer PRIVATE utils_cuf)
+target_compile_options(celix_version_fuzzer PRIVATE -fsanitize=fuzzer)
+target_link_options(celix_version_fuzzer PRIVATE -fsanitize=fuzzer)
+
+add_executable(celix_filter_fuzzer src/FilterFuzz.cc)
+target_link_libraries(celix_filter_fuzzer PRIVATE utils_cuf)
+target_compile_options(celix_filter_fuzzer PRIVATE -fsanitize=fuzzer)
+target_link_options(celix_filter_fuzzer PRIVATE -fsanitize=fuzzer)
+
+add_custom_command(TARGET celix_properties_fuzzer POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory
+    ${CMAKE_CURRENT_SOURCE_DIR}/properties_corpus
+    $<TARGET_FILE_DIR:celix_properties_fuzzer>/properties_corpus
+)
+
+add_custom_command(TARGET celix_version_fuzzer POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory
+    ${CMAKE_CURRENT_SOURCE_DIR}/version_corpus
+    $<TARGET_FILE_DIR:celix_version_fuzzer>/version_corpus
+)
+
+add_custom_command(TARGET celix_filter_fuzzer POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory
+    ${CMAKE_CURRENT_SOURCE_DIR}/filter_corpus
+    $<TARGET_FILE_DIR:celix_filter_fuzzer>/filter_corpus
+)
+
diff --git a/libs/utils/fuzzing/filter_corpus/complex.txt b/libs/utils/fuzzing/filter_corpus/complex.txt
new file mode 100644
index 0000000..1ffe405
--- /dev/null
+++ b/libs/utils/fuzzing/filter_corpus/complex.txt
@@ -0,0 +1 @@
+(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3=attr3)))
diff --git a/libs/utils/fuzzing/filter_corpus/simple.txt b/libs/utils/fuzzing/filter_corpus/simple.txt
new file mode 100644
index 0000000..bfa80ac
--- /dev/null
+++ b/libs/utils/fuzzing/filter_corpus/simple.txt
@@ -0,0 +1 @@
+(key=value)
diff --git a/libs/utils/fuzzing/properties_corpus/complex.json b/libs/utils/fuzzing/properties_corpus/complex.json
new file mode 100644
index 0000000..0708565
--- /dev/null
+++ b/libs/utils/fuzzing/properties_corpus/complex.json
@@ -0,0 +1,2 @@
+{"key1":"value1","key2":"value2","object1":{"key3":"value3","key4":"value4"},"object2":{"key5":"value5"},"object3":{"object4":{"key6":"value6"}},"strArr":["value1","value2"],"intArr":[1,2],"realArr":[1.0,2.0],"boolArr":[true,false],"versionArr":["version<1.2.3.qualifier>","version<4.5.6.qualifier>"]}
+
diff --git a/libs/utils/fuzzing/properties_corpus/simple.json b/libs/utils/fuzzing/properties_corpus/simple.json
new file mode 100644
index 0000000..29f05f6
--- /dev/null
+++ b/libs/utils/fuzzing/properties_corpus/simple.json
@@ -0,0 +1 @@
+{"key":"value"}
diff --git a/libs/utils/fuzzing/src/FilterFuzz.cc b/libs/utils/fuzzing/src/FilterFuzz.cc
new file mode 100644
index 0000000..ddc5021
--- /dev/null
+++ b/libs/utils/fuzzing/src/FilterFuzz.cc
@@ -0,0 +1,23 @@
+#include <celix_filter.h>
+#include <celix_err.h>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+int filterParseFuzzOneInput(const uint8_t* data, size_t size) {
+    char* buffer = static_cast<char*>(malloc(size + 1));
+    if (buffer == nullptr) {
+        return 0;
+    }
+    memcpy(buffer, data, size);
+    buffer[size] = '\0';
+
+    celix_filter_t* filter = celix_filter_create(buffer);
+    celix_filter_destroy(filter);
+    celix_err_resetErrors();
+
+    free(buffer);
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return filterParseFuzzOneInput(data, size); }
diff --git a/libs/utils/fuzzing/src/PropertiesFuzz.cc b/libs/utils/fuzzing/src/PropertiesFuzz.cc
new file mode 100644
index 0000000..627c2dd
--- /dev/null
+++ b/libs/utils/fuzzing/src/PropertiesFuzz.cc
@@ -0,0 +1,32 @@
+#include <celix_properties.h>
+#include <celix_err.h>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+int propertiesParserFuzzOneInput(const uint8_t* data, size_t size) {
+    int flags = 0;
+    if (size > 0) {
+        flags = data[0] & 0x3F; // use 6 bits for decode flags
+        data += 1;
+        size -= 1;
+    }
+    char* buffer = static_cast<char*>(malloc(size + 1));
+    if (buffer == nullptr) {
+        return 0;
+    }
+    memcpy(buffer, data, size);
+    buffer[size] = '\0';
+
+    celix_properties_t* props = nullptr;
+    celix_properties_loadFromString(buffer, flags, &props);
+    celix_properties_destroy(props);
+    celix_err_resetErrors();
+
+    free(buffer);
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    return propertiesParserFuzzOneInput(data, size);
+}
diff --git a/libs/utils/fuzzing/src/VersionFuzz.cc b/libs/utils/fuzzing/src/VersionFuzz.cc
new file mode 100644
index 0000000..b1fc7e6
--- /dev/null
+++ b/libs/utils/fuzzing/src/VersionFuzz.cc
@@ -0,0 +1,23 @@
+#include <celix_version.h>
+#include <celix_err.h>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+int versionParseFuzzOneInput(const uint8_t* data, size_t size) {
+    char* buffer = static_cast<char*>(malloc(size + 1));
+    if (buffer == nullptr) {
+        return 0;
+    }
+    memcpy(buffer, data, size);
+    buffer[size] = '\0';
+
+    celix_version_t* version = celix_version_createVersionFromString(buffer);
+    celix_version_destroy(version);
+    celix_err_resetErrors();
+
+    free(buffer);
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return versionParseFuzzOneInput(data, size); }
diff --git a/libs/utils/fuzzing/version_corpus/qualifier.txt b/libs/utils/fuzzing/version_corpus/qualifier.txt
new file mode 100644
index 0000000..da3ad8b
--- /dev/null
+++ b/libs/utils/fuzzing/version_corpus/qualifier.txt
@@ -0,0 +1 @@
+2.0.0.qualifier
diff --git a/libs/utils/fuzzing/version_corpus/simple.txt b/libs/utils/fuzzing/version_corpus/simple.txt
new file mode 100644
index 0000000..0495c4a
--- /dev/null
+++ b/libs/utils/fuzzing/version_corpus/simple.txt
@@ -0,0 +1 @@
+1.2.3