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

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


The following commit(s) were added to refs/heads/master by this push:
     new 716017467 Feature/add fuzzing (#799)
716017467 is described below

commit 7160174679a54807ec761b565629755d049e12ec
Author: Pepijn Noltes <[email protected]>
AuthorDate: Thu Dec 18 20:21:40 2025 +0100

    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 <[email protected]>
---
 .github/workflows/fuzzing.yml                     | 59 ++++++++++++++++++
 .github/workflows/macos.yml                       |  4 +-
 .github/workflows/ubuntu.yml                      |  4 ++
 CMakeLists.txt                                    |  2 +
 cmake/celix_project/CelixProject.cmake            | 24 ++++----
 cmake/cmake_celix/BundlePackaging.cmake           | 28 ++++-----
 conanfile.py                                      | 13 ++++
 documents/README.md                               |  1 -
 documents/building/README.md                      |  7 +++
 documents/building/benchmarks.md                  | 74 +++++++++++++++++++++++
 documents/building/fuzz_testing.md                | 74 +++++++++++++++++++++++
 documents/building/testing.md                     | 65 ++++++++++++++++++++
 libs/framework/benchmark/CMakeLists.txt           |  9 +--
 libs/utils/CMakeLists.txt                         | 18 ++++++
 libs/utils/benchmark/CMakeLists.txt               |  9 +--
 libs/utils/fuzzing/CMakeLists.txt                 | 52 ++++++++++++++++
 libs/utils/fuzzing/filter_corpus/complex.txt      |  1 +
 libs/utils/fuzzing/filter_corpus/simple.txt       |  1 +
 libs/utils/fuzzing/properties_corpus/complex.json |  2 +
 libs/utils/fuzzing/properties_corpus/simple.json  |  1 +
 libs/utils/fuzzing/src/FilterFuzz.cc              | 23 +++++++
 libs/utils/fuzzing/src/PropertiesFuzz.cc          | 32 ++++++++++
 libs/utils/fuzzing/src/VersionFuzz.cc             | 23 +++++++
 libs/utils/fuzzing/version_corpus/qualifier.txt   |  1 +
 libs/utils/fuzzing/version_corpus/simple.txt      |  1 +
 25 files changed, 484 insertions(+), 44 deletions(-)

diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml
new file mode 100644
index 000000000..242b47567
--- /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 61bfd9eba..eceb860b5 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -56,6 +56,7 @@ jobs:
         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 @@ jobs:
         uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0
       - name: Install dependencies
         run: |
-          brew install lcov jansson rapidjson libzip ccache ninja [email protected]
+          brew install lcov jansson rapidjson libzip ccache ninja [email protected] 
google-benchmark
       - name: Prepare ccache timestamp
         id: ccache_cache_timestamp
         run: |
@@ -95,6 +96,7 @@ jobs:
         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 a472b72e8..dc0964f4a 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -77,7 +77,9 @@ jobs:
           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 @@ jobs:
           libzip-dev \
           libjansson-dev \
           libcurl4-openssl-dev \
+          libbenchmark-dev \
           default-jdk \
           cmake \
           libffi-dev \
@@ -145,6 +148,7 @@ jobs:
         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 d72ce4d76..09e065ae8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -168,6 +168,8 @@ endif ()
 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 0f3132744..8f231a2dd 100644
--- a/cmake/celix_project/CelixProject.cmake
+++ b/cmake/celix_project/CelixProject.cmake
@@ -25,13 +25,17 @@ mark_as_advanced(CLEAR ENABLE_UNDEFINED_SANITIZER)
 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 @@ if (ENABLE_ADDRESS_SANITIZER)
         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 4200f390a..bb4869022 100644
--- a/cmake/cmake_celix/BundlePackaging.cmake
+++ b/cmake/cmake_celix/BundlePackaging.cmake
@@ -287,23 +287,23 @@ function(add_celix_bundle)
     #########################################################
 
     ###### Packaging the bundle using using jar or zip and a content dir. 
Configuring dependencies ######
-    if (JAR_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 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} .
+                COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} 
${BUNDLE_FILE} *
                 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}
+                WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR}
         )
-    elseif (ZIP_COMMAND)
-        file(MAKE_DIRECTORY ${BUNDLE_CONTENT_DIR}) #Note needed because 
working_directory is 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 ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} 
${BUNDLE_FILE} *
+                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 ${BUNDLE_CONTENT_DIR}
+                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
         )
     else ()
         message(FATAL_ERROR "A jar or zip command is needed to jar/zip 
bundles")
@@ -934,21 +934,21 @@ function(install_celix_bundle)
     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)
+    if (ZIP_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}
+                COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} 
${BUNDLE_FILE_INSTALL} . -i *
+                WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR}
             )"
                 COMPONENT ${BUNDLE}
         )
-    elseif (ZIP_COMMAND)
+    elseif (JAR_COMMAND)
         install(CODE
                 "execute_process(
                 COMMAND ${CMAKE_COMMAND} -E copy_if_different 
${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json
-                COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} 
${BUNDLE_FILE_INSTALL} . -i *
-                WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR}
+                COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} 
${BUNDLE_FILE_INSTALL} -C ${BUNDLE_CONTENT_DIR} .
+                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
             )"
                 COMPONENT ${BUNDLE}
         )
diff --git a/conanfile.py b/conanfile.py
index 09ca5a332..bb1c63907 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -47,6 +47,8 @@ class CelixConan(ConanFile):
         "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 @@ class CelixConan(ConanFile):
         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 @@ class CelixConan(ConanFile):
         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 @@ class CelixConan(ConanFile):
             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 fefec7971..c633e7d11 100644
--- a/documents/README.md
+++ b/documents/README.md
@@ -81,7 +81,6 @@ bundles contains binaries depending on the stdlibc++ library.
 
 * 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 c64351003..7d01b8bb7 100644
--- a/documents/building/README.md
+++ b/documents/building/README.md
@@ -237,3 +237,10 @@ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../libs/pushstreams
 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 000000000..d1061b3b8
--- /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 000000000..50f0abb21
--- /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 000000000..557258ce0
--- /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 ba39fcbb4..875d2f7f3 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 4b8c08a35..e28317b49 100644
--- a/libs/utils/CMakeLists.txt
+++ b/libs/utils/CMakeLists.txt
@@ -126,5 +126,23 @@ if (UTILS)
         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 2f891e6cb..4c22fc2df 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 000000000..a7e248e35
--- /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 000000000..1ffe40511
--- /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 000000000..bfa80ac8d
--- /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 000000000..070856559
--- /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 000000000..29f05f6bf
--- /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 000000000..ddc5021c0
--- /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 000000000..627c2dd56
--- /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 000000000..b1fc7e67f
--- /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 000000000..da3ad8b4e
--- /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 000000000..0495c4a88
--- /dev/null
+++ b/libs/utils/fuzzing/version_corpus/simple.txt
@@ -0,0 +1 @@
+1.2.3


Reply via email to