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