diff --git a/.github/install-dependency-packages.sh b/.github/install-dependency-packages.sh
index f65e7dac..39a776c4 100755
--- a/.github/install-dependency-packages.sh
+++ b/.github/install-dependency-packages.sh
@@ -8,12 +8,16 @@ set -e
GENERAL_PACKAGE_LIST_LINUX=(
python
gcc
+ clang
make
cmake
tree
pkgconf
ninja
meson
+ gcovr # for coverage
+ python-pygments # for coverage report syntax colors
+ llvm # for `llvm-symbolizer`, for human-readable sanitizer results
)
IGUANA_PACKAGE_LIST_LINUX=(
fmt
diff --git a/.github/pages-index.html b/.github/pages-index.html
new file mode 100644
index 00000000..51986a6f
--- /dev/null
+++ b/.github/pages-index.html
@@ -0,0 +1,13 @@
+
+
+
+ Iguana
+
+
+ Iguana
+
+
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index faeca294..ec1a2a58 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,23 @@ on:
required: false
type: string
default: 'latest'
+ test_matrix:
+ description: 'Test matrix for `test_iguana` job (JSON)'
+ required: false
+ type: string
+ # test coverage and sanitizers
+ # FIXME: `b_sanitize=memory` is not used because `libc++` needs to be re-compiled with `-fsanitize=memory`, otherwise
+ # we are bothered by false positives (e.g., from `std::map` insertion)
+ default: >-
+ {
+ "include": [
+ { "mode": "coverage", "CC": "gcc", "CXX": "g++", "buildtype": "release" },
+ { "mode": "address sanitizer", "CC": "clang", "CXX": "clang++", "buildtype": "debug" },
+ { "mode": "thread sanitizer", "CC": "clang", "CXX": "clang++", "buildtype": "debug" },
+ { "mode": "undefined behavior sanitizer", "CC": "clang", "CXX": "clang++", "buildtype": "debug" },
+ { "mode": "leak sanitizer", "CC": "clang", "CXX": "clang++", "buildtype": "debug" }
+ ]
+ }
defaults:
run:
@@ -28,6 +45,25 @@ env:
jobs:
+ # download test data
+ #########################################################
+
+ download_validation_files:
+ name: Download validation files
+ runs-on: ${{ inputs.runner }}
+ steps:
+ - name: download
+ run: wget --no-check-certificate http://clasweb.jlab.org/clas12offline/distribution/clas12-timeline/validation_files.tar.gz
+ - name: untar
+ run: tar xzvf validation_files.tar.gz
+ - name: select one file
+ run: mv -v $(find validation_files -type f -name "*.hipo" | head -n1) test_data.hipo
+ - uses: actions/upload-artifact@v4
+ with:
+ name: validation_files
+ retention-days: 1
+ path: test_data.hipo
+
# dependencies
#########################################################
@@ -48,13 +84,13 @@ jobs:
ref: ${{ env.hipo_version }}
- name: build
run: |
- cmake -S . -B build -G Ninja --install-prefix $(pwd)/hipo
+ cmake -S . -B build -G Ninja --install-prefix $(pwd)/hipo -DCMAKE_POSITION_INDEPENDENT_CODE=ON # using PIE build, for sanitizer readibility
cmake --build build
cmake --install build
tar czvf hipo{.tar.gz,}
- uses: actions/upload-artifact@v4
with:
- name: build_deps_hipo
+ name: install_deps_hipo
retention-days: 1
path: hipo.tar.gz
@@ -69,12 +105,19 @@ jobs:
container:
image: ${{ inputs.container }}
strategy:
- fail-fast: false
+ fail-fast: true
matrix:
- binding: [ cpp, python ]
include:
- - { binding: cpp, binding_opts: '' }
- - { binding: python, binding_opts: '-Dbind_python=True' }
+ # build C++-only version with various compilers and buildtypes
+ - { id: cpp-gcc-release, CC: gcc, CXX: g++, buildtype: release, binding_opts: '' }
+ - { id: cpp-gcc-debug, CC: gcc, CXX: g++, buildtype: debug, binding_opts: '' }
+ - { id: cpp-clang-release, CC: clang, CXX: clang++, buildtype: release, binding_opts: '' }
+ - { id: cpp-clang-debug, CC: clang, CXX: clang++, buildtype: debug, binding_opts: '' }
+ # build with bindings
+ - { id: python, CC: gcc, CXX: g++, buildtype: release, binding_opts: '-Dbind_python=True' }
+ env:
+ CC: ${{ matrix.CC }}
+ CXX: ${{ matrix.CXX }}
steps:
### setup
- uses: actions/checkout@v4
@@ -88,31 +131,30 @@ jobs:
- name: get local dependencies
uses: actions/download-artifact@v4
with:
- pattern: build_deps_*
+ pattern: install_deps_*
merge-multiple: true
- name: untar local dependencies
run: ls *.tar.gz | xargs -I{} tar xzvf {}
- name: tree local dependencies
run: tree hipo
- name: summarize dependencies
+ if: ${{ matrix.id == 'cpp-gcc-release' }}
run: |
echo '### Dependencies' >> $GITHUB_STEP_SUMMARY
echo '| Dependency | Version |' >> $GITHUB_STEP_SUMMARY
echo '| --- | --- |' >> $GITHUB_STEP_SUMMARY
echo "| \`hipo\` | ${{ env.hipo_version }} |" >> $GITHUB_STEP_SUMMARY
cat pkg_summary.md >> $GITHUB_STEP_SUMMARY
- ### build iguana
- - name: resolve local dependencies
- run: meson/resolve-dependencies.py --ini native.ini --hipo ./hipo
- name: meson setup
- run: meson setup --native-file=native.ini build-iguana
+ run: meson setup build-iguana $(meson/resolve-dependencies.py --cli --hipo ./hipo)
- name: meson configure
run: |
- meson configure \
- --prefix=$(pwd)/iguana \
- -Dexamples=True \
- -Ddocumentation=False \
- ${{ matrix.binding_opts }} \
+ meson configure \
+ --prefix=$(pwd)/iguana \
+ -Dbuildtype=${{ matrix.buildtype }} \
+ -Dexamples=True \
+ -Ddocumentation=False \
+ ${{ matrix.binding_opts }} \
build-iguana
- name: dump build options
run: meson configure build-iguana | cat
@@ -123,7 +165,7 @@ jobs:
if: always()
run: cat build-iguana/meson-logs/meson-log.txt
- name: readelf/otool iguana examples
- if: ${{ matrix.binding == 'cpp' }}
+ if: ${{ matrix.id == 'cpp-gcc-release' || matrix.id == 'cpp-gcc-debug' || matrix.id == 'cpp-clang-release' || matrix.id == 'cpp-clang-debug' }}
run: |
binaries=$(find iguana/bin -type f -name "iguana-example-*")
libraries=$(find iguana -type f -name "*.so")
@@ -145,37 +187,127 @@ jobs:
- run: tree iguana
### upload artifacts
- name: tar
- run: tar czvf iguana{.tar.gz,}
+ run: |
+ tar czvf iguana{.tar.gz,}
+ tar czvf build-iguana{.tar.gz,}
- uses: actions/upload-artifact@v4
with:
- name: build_iguana_${{ matrix.binding }}
+ name: install_iguana_${{ matrix.id }}
retention-days: 1
path: iguana.tar.gz
+ - uses: actions/upload-artifact@v4
+ if: ${{ matrix.id == 'cpp-gcc-release' || matrix.id == 'cpp-gcc-debug' || matrix.id == 'cpp-clang-release' || matrix.id == 'cpp-clang-debug' }}
+ with:
+ name: build_iguana_${{ matrix.id }}
+ retention-days: 1
+ path: build-iguana.tar.gz
- # download test data
+ # run tests
#########################################################
- download_validation_files:
- name: Download validation files
+ test_iguana:
+ name: Test Iguana
+ needs:
+ - build_hipo
+ - download_validation_files
+ - build_iguana
runs-on: ${{ inputs.runner }}
+ container:
+ image: ${{ inputs.container }}
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(inputs.test_matrix) }}
+ env:
+ CC: ${{ matrix.CC }}
+ CXX: ${{ matrix.CXX }}
+ build_id: ${{ matrix.CC }}-${{ matrix.buildtype }}
steps:
- - name: download
- run: wget --no-check-certificate http://clasweb.jlab.org/clas12offline/distribution/clas12-timeline/validation_files.tar.gz
- - name: untar
- run: tar xzvf validation_files.tar.gz
- - name: select one file
- run: mv -v $(find validation_files -type f -name "*.hipo" | head -n1) test_data.hipo
- - uses: actions/upload-artifact@v4
+ ### setup
+ - uses: actions/checkout@v4
+ with: # settings needed for version number detection
+ clean: false
+ fetch-tags: true
+ fetch-depth: 0
+ - name: get test data
+ uses: actions/download-artifact@v4
with:
name: validation_files
+ - name: get iguana build dir
+ uses: actions/download-artifact@v4
+ with:
+ name: build_iguana_cpp-${{ env.build_id }}
+ ### dependencies
+ - name: install dependency packages
+ run: .github/install-dependency-packages.sh ${{ inputs.runner }} ${{ inputs.verset }}
+ - name: get local dependencies
+ uses: actions/download-artifact@v4
+ with:
+ pattern: install_deps_*
+ merge-multiple: true
+ - name: untar local dependencies
+ run: ls *.tar.gz | xargs -I{} tar xzvf {}
+ ### re-build iguana
+ - name: meson configure
+ run: |
+ meson configure \
+ -Dtest_data_file=$(pwd)/test_data.hipo \
+ -Dtest_num_events=1000 \
+ build-iguana
+ case "${{ matrix.mode }}" in
+ coverage)
+ meson configure \
+ -Db_coverage=true \
+ build-iguana
+ ;;
+ *sanitizer)
+ san=$(echo ${{ matrix.mode }} | sed 's; .*;;g')
+ meson configure \
+ -Db_sanitize=$san \
+ -Db_lundef=false \
+ -Db_pie=true \
+ build-iguana
+ ;;
+ esac
+ - name: dump build options
+ run: meson configure build-iguana | cat
+ - name: meson install
+ run: meson install -C build-iguana
+ ### run tests
+ - name: TEST algorithms
+ run: |
+ echo -e "\e[1;35m[NOTE]: log files are found in artifact 'logs_build_iguana_${{ matrix.mode }}' \e[0m"
+ meson test --print-errorlogs -C build-iguana
+ - name: TEST coverage
+ if: ${{ matrix.mode == 'coverage' }}
+ run: |
+ ninja -C build-iguana coverage
+ mv build-iguana/meson-logs/coveragereport coverage-report
+ echo '### Coverage Report' >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ cat build-iguana/meson-logs/coverage.txt >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo '' >> $GITHUB_STEP_SUMMARY
+ echo '- for details, see the `coverage-report` artifact' >> $GITHUB_STEP_SUMMARY
+ echo '- to compare to the report from the `main` branch, see ' >> $GITHUB_STEP_SUMMARY
+ ### upload artifacts
+ - uses: actions/upload-artifact@v4
+ if: ${{ matrix.mode != 'coverage' }}
+ with:
+ name: logs_build_iguana_${{ matrix.mode }}
retention-days: 1
- path: test_data.hipo
+ path: build-iguana/meson-logs
+ - uses: actions/upload-artifact@v4
+ if: ${{ matrix.mode == 'coverage' }}
+ with:
+ name: coverage-report
+ retention-days: 7
+ path: coverage-report
- # run tests
+ # run examples
#########################################################
- test_iguana:
- name: Test Iguana
+ test_examples:
+ name: Test Examples
needs:
- download_validation_files
- build_iguana
@@ -185,10 +317,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- binding: [ cpp, python ]
include:
- - { binding: cpp, extension: '' }
- - { binding: python, extension: '.py' }
+ - { id: cpp-gcc-release, extension: '' }
+ - { id: python, extension: '.py' }
steps:
### dependencies and test data
- uses: actions/checkout@v4
@@ -200,12 +331,12 @@ jobs:
- name: get dependency build artifacts
uses: actions/download-artifact@v4
with:
- pattern: build_deps_*
+ pattern: install_deps_*
merge-multiple: true
- name: get iguana build artifacts
uses: actions/download-artifact@v4
with:
- name: build_iguana_${{ matrix.binding }}
+ name: install_iguana_${{ matrix.id }}
- name: get test data
uses: actions/download-artifact@v4
with:
@@ -218,7 +349,7 @@ jobs:
run: tree
### setup python virtaul environment (for python binding tests)
- name: install python binding runtime dependencies
- if: ${{ matrix.binding == 'python' }}
+ if: ${{ matrix.id == 'python' }}
run: |
python -m venv .venv
source .venv/bin/activate
@@ -226,13 +357,13 @@ jobs:
python -m pip install -r iguana_src/bind/python/requirements.txt
### set env vars - depends on runner and binding
- name: source environment for Linux and python
- if: ${{ inputs.runner == 'ubuntu-latest' && matrix.binding == 'python' }}
+ if: ${{ inputs.runner == 'ubuntu-latest' && matrix.id == 'python' }}
run: |
source iguana/bin/this_iguana.sh verbose
echo PKG_CONFIG_PATH=$PKG_CONFIG_PATH >> $GITHUB_ENV
echo PYTHONPATH=$PYTHONPATH >> $GITHUB_ENV
- name: source environment for macOS and python
- if: ${{ inputs.runner == 'macos-latest' && matrix.binding == 'python' }}
+ if: ${{ inputs.runner == 'macos-latest' && matrix.id == 'python' }}
run: |
source iguana/bin/this_iguana.sh verbose ld
echo PKG_CONFIG_PATH=$PKG_CONFIG_PATH >> $GITHUB_ENV
@@ -245,10 +376,13 @@ jobs:
run: iguana/bin/iguana-example-01-bank-rows${{ matrix.extension }} test_data.hipo ${{ env.num_events }}
- name: test 02
run: iguana/bin/iguana-example-02-YAMLReader${{ matrix.extension }}
- if: ${{ matrix.binding == 'cpp' }} #FIXME
+ if: ${{ matrix.id == 'cpp-gcc-release' }} #FIXME
- name: test 03
run: iguana/bin/iguana-example-03-zvertex-filter${{ matrix.extension }} test_data.hipo ${{ env.num_events }}
- if: ${{ matrix.binding == 'cpp' }} #FIXME
+ if: ${{ matrix.id == 'cpp-gcc-release' }} #FIXME
+
+ # test consumers
+ #########################################################
test_consumer_builds:
name: Test consumer builds
@@ -262,6 +396,8 @@ jobs:
fail-fast: false
matrix:
tool: [ cmake, make, meson ]
+ env:
+ build_id: cpp-gcc-release
steps:
### dependencies and test data
- uses: actions/checkout@v4
@@ -270,12 +406,12 @@ jobs:
- name: get dependency build artifacts
uses: actions/download-artifact@v4
with:
- pattern: build_deps_*
+ pattern: install_deps_*
merge-multiple: true
- name: get iguana build artifacts
uses: actions/download-artifact@v4
with:
- name: build_iguana_cpp
+ name: install_iguana_${{ env.build_id }}
- name: get test data
uses: actions/download-artifact@v4
with:
@@ -309,7 +445,7 @@ jobs:
#########################################################
doc_generate:
- if: ${{ inputs.runner == 'ubuntu-latest' }}
+ if: ${{ inputs.runner == 'ubuntu-latest' && inputs.verset == 'latest' }}
name: Generate documentation
runs-on: ${{ inputs.runner }}
steps:
@@ -318,15 +454,53 @@ jobs:
uses: mattnotmitt/doxygen-action@v1
with:
doxyfile-path: doc/Doxyfile
- - uses: actions/upload-pages-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
+ name: doxygen
retention-days: 1
path: doc/api/
- doc_deploy:
- if: ${{ (github.head_ref == 'main' || github.ref_name == 'main') && inputs.runner == 'ubuntu-latest' }}
- name: Deploy documentation
- needs: doc_generate
+ # deployment
+ #########################################################
+
+ collect_webpages:
+ if: ${{ (github.head_ref == 'main' || github.ref_name == 'main') && inputs.runner == 'ubuntu-latest' && inputs.verset == 'latest' }}
+ name: Collect webpages
+ needs:
+ - doc_generate
+ - test_iguana
+ runs-on: ${{ inputs.runner }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ path: iguana_src
+ - name: download doxygen documentation
+ uses: actions/download-artifact@v4
+ with:
+ name: doxygen
+ path: doxygen
+ - name: download coverage report
+ uses: actions/download-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage-report
+ - run: tree
+ - name: collect
+ run: |
+ mkdir pages
+ cp iguana_src/.github/pages-index.html pages/index.html
+ mv doxygen pages/
+ mv coverage-report pages/
+ - run: tree
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ retention-days: 1
+ path: pages/
+
+ deploy_webpages:
+ if: ${{ (github.head_ref == 'main' || github.ref_name == 'main') && inputs.runner == 'ubuntu-latest' && inputs.verset == 'latest' }}
+ name: Deploy webpages
+ needs: collect_webpages
permissions:
pages: write
id-token: write
@@ -346,6 +520,7 @@ jobs:
name: Final
needs:
- test_iguana
+ - test_examples
- test_consumer_builds
runs-on: ${{ inputs.runner }}
steps:
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 7ceedd0e..8d35745d 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -16,3 +16,9 @@ jobs:
uses: ./.github/workflows/ci.yml
with:
runner: macos-latest
+ test_matrix: >-
+ {
+ "include": [
+ { "mode": "test", "CC": "gcc", "CXX": "g++", "buildtype": "release" }
+ ]
+ }
diff --git a/.github/workflows/minver.yml b/.github/workflows/minver.yml
index 312f43a5..2beabf50 100644
--- a/.github/workflows/minver.yml
+++ b/.github/workflows/minver.yml
@@ -18,3 +18,9 @@ jobs:
runner: ubuntu-latest
container: archlinux/archlinux:latest
verset: minver
+ test_matrix: >-
+ {
+ "include": [
+ { "mode": "test", "CC": "gcc", "CXX": "g++", "buildtype": "release" }
+ ]
+ }
diff --git a/README.md b/README.md
index 7527d5b7..2e919099 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,12 @@
1. [Setup Guide](doc/setup.md)
1. [Troubleshooting](doc/troubleshooting.md)
1. [Design Notes](doc/design.md)
-1. [API documentation](https://jeffersonlab.github.io/iguana/)
+1. [API documentation](https://jeffersonlab.github.io/iguana/doxygen)
1. [Developing a new Algorithm](src/iguana/algorithms/example/README.md)
1. [Repository Maintenance](doc/maintenance.md)
+## Status
+1. [Coverage Report](https://jeffersonlab.github.io/iguana/coverage-report)
+
> [!CAUTION]
> This is still a _prototype_ design
diff --git a/examples/meson.build b/examples/meson.build
index 6fe166d2..6a976fe7 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -5,21 +5,6 @@ example_sources = [
'iguana-example-03-zvertex-filter.cc',
]
-# add dependencies' libraries to rpath
-example_rpaths = [
- hipo_dep.get_variable(pkgconfig: 'libdir'),
-]
-if host_machine.system() != 'darwin'
- # FIXME(darwin): not sure how to set multiple rpaths on darwin executables, aside
- # from running `install_name_tool -add_rpath` post-installation; luckily,
- # darwin-built examples only need `hipo_dep` libraries in the rpath and they
- # don't need the `iguana` library path explictly included in the rpath, so
- # this `if` block just keeps `example_rpaths` to only one element. If we need
- # more rpath elements someday, either we need to use `install_name_tool` or,
- # preferably, resolution of https://github.com/mesonbuild/meson/issues/5760
- example_rpaths += '$ORIGIN' / '..' / get_option('libdir')
-endif
-
# build executables
foreach src : example_sources
executable(
@@ -29,7 +14,7 @@ foreach src : example_sources
dependencies: project_deps,
link_with: project_libs,
install: true,
- install_rpath: ':'.join(example_rpaths),
+ install_rpath: ':'.join(project_exe_rpath),
)
endforeach
diff --git a/meson.build b/meson.build
index eeed83f5..2647caf7 100644
--- a/meson.build
+++ b/meson.build
@@ -17,6 +17,10 @@ project(
)
project_description = 'Implementation Guardian of Analysis Algorithms'
+# meson modules
+pkg = import('pkgconfig')
+fs = import('fs')
+
# resolve dependencies
# NOTE: those that are typically installed by package managers should use `meson/minimum-version.sh`
fmt_dep = dependency(
@@ -62,6 +66,21 @@ project_pkg_vars = [
'dep_libdirs=' + ':'.join(dep_lib_paths),
]
+# executables' rpath
+project_exe_rpath = [
+ hipo_dep.get_variable(pkgconfig: 'libdir'),
+]
+if host_machine.system() != 'darwin'
+ # FIXME(darwin): not sure how to set multiple rpaths on darwin executables, aside
+ # from running `install_name_tool -add_rpath` post-installation; luckily,
+ # darwin-built examples only need `hipo_dep` libraries in the rpath and they
+ # don't need the `iguana` library path explictly included in the rpath, so
+ # this `if` block just keeps `project_exe_rpath` to only one element. If we need
+ # more rpath elements someday, either we need to use `install_name_tool` or,
+ # preferably, resolution of https://github.com/mesonbuild/meson/issues/5760
+ project_exe_rpath += '$ORIGIN' / '..' / get_option('libdir')
+endif
+
# set preprocessor macros
add_project_arguments(
'-DIGUANA_ETC="' + get_option('prefix') / project_etc + '"',
@@ -71,6 +90,7 @@ add_project_arguments(
# build and install shared libraries
subdir('src/iguana/services')
subdir('src/iguana/algorithms')
+subdir('src/iguana/tests')
# build bindings
if get_option('bind_python')
@@ -78,7 +98,6 @@ if get_option('bind_python')
endif
# generate pkg-config file
-pkg = import('pkgconfig')
pkg.generate(
name: meson.project_name(),
description: project_description,
diff --git a/meson.options b/meson.options
index 93858080..c128e1a8 100644
--- a/meson.options
+++ b/meson.options
@@ -1,3 +1,5 @@
-option('examples', type: 'boolean', value: false, description: 'Build Iguana C++ examples')
-option('documentation', type: 'boolean', value: false, description: 'Generate API documentation (requires Doxygen)')
-option('bind_python', type: 'boolean', value: false, description: 'Generate Python bindings and their examples')
+option('examples', type: 'boolean', value: false, description: 'Build Iguana C++ examples')
+option('documentation', type: 'boolean', value: false, description: 'Generate API documentation (requires Doxygen)')
+option('test_data_file', type: 'string', value: '', description: 'Sample HIPO file for testing. Must be an absolute path or relative to the build directory. If unspecified, tests are not built')
+option('test_num_events', type: 'string', value: '10', description: 'Number of events from `test_data_file` to test')
+option('bind_python', type: 'boolean', value: false, description: 'Generate Python bindings and their examples')
diff --git a/src/iguana/algorithms/meson.build b/src/iguana/algorithms/meson.build
index 2180c5cf..0ca46f61 100644
--- a/src/iguana/algorithms/meson.build
+++ b/src/iguana/algorithms/meson.build
@@ -1,3 +1,4 @@
+# algorithm source files
algo_sources = [
'Algorithm.cc',
'AlgorithmFactory.cc',
@@ -8,6 +9,7 @@ algo_sources = [
'clas12/LorentzTransformer.cc',
]
+# algorithm headers
algo_public_headers = [
'Algorithm.h',
'AlgorithmBoilerplate.h',
@@ -19,10 +21,19 @@ algo_public_headers = [
'clas12/LorentzTransformer.h',
]
+# algorithm configurations
algo_config_dirs = [
'clas12/config',
]
+# algorithm unique names and required banks, for those we want to test automatically
+algos_and_banks_for_unit_testing = {
+ 'example::ExampleAlgorithm': ['REC::Particle'],
+ 'clas12::EventBuilderFilter': ['REC::Particle'],
+ 'clas12::ZVertexFilter': ['REC::Particle'],
+ 'clas12::LorentzTransformer': ['REC::Particle'],
+}
+
algo_lib = shared_library(
'IguanaAlgorithms',
algo_sources,
diff --git a/src/iguana/tests/iguana-test.cc b/src/iguana/tests/iguana-test.cc
new file mode 100644
index 00000000..c75da515
--- /dev/null
+++ b/src/iguana/tests/iguana-test.cc
@@ -0,0 +1,126 @@
+#include
+#include
+#include
+
+int main(int argc, char **argv) {
+
+ // parse arguments
+ int opt;
+ std::string command = "";
+ std::string data_file = "";
+ int num_events = 10;
+ std::string algo_name = "";
+ std::vector bank_names;
+ bool verbose = false;
+ auto Usage = [&]() {
+ fmt::print(stderr, "\nUSAGE: {} [OPTIONS]...\n", argv[0]);
+ fmt::print("\n");
+ fmt::print(" OPTIONS:\n");
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n\n", "-c COMMAND", "which test command to use:");
+ fmt::print(" {:<20} {:<15} {}\n", "", "algorithm", "call `Run` on an algorithm");
+ fmt::print(" {:<20} {:<15} {}\n", "", "unit", "call `Test` on an algorithm, for unit tests");
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n", "-f FILE", "input data file, for when COMMAND==algorithm");
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n", "-n NUM_EVENTS", "number of events from the data file");
+ fmt::print(" {:<20} default: {}\n", "", num_events);
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n", "-a ALGORITHM", "the name of the algorithm");
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n", "-b BANKS", "add a bank to process");
+ fmt::print("\n");
+ fmt::print(" {:<20} {}\n", "-v", "increase verbosity");
+ fmt::print("\n");
+ return 2;
+ };
+ if(argc==1)
+ return Usage();
+ while((opt=getopt(argc, argv, "c:f:n:a:b:v|")) != -1) {
+ switch(opt) {
+ case 'c':
+ command = std::string(optarg);
+ break;
+ case 'f':
+ data_file = std::string(optarg);
+ break;
+ case 'n':
+ num_events = std::stoi(optarg);
+ break;
+ case 'a':
+ algo_name = std::string(optarg);
+ break;
+ case 'b':
+ bank_names.push_back(std::string(optarg));
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ return Usage();
+ }
+ }
+ if(command == "" || algo_name == "" || bank_names.empty()) {
+ fmt::print(stderr, "ERROR: need at least a command, algorithm name, and banks\n");
+ return Usage();
+ }
+ if(command == "algorithm" && data_file == "") {
+ fmt::print(stderr, "ERROR: need a data file for command 'algorithm'\n");
+ return Usage();
+ }
+ if(verbose) {
+ fmt::print("TEST IGUANA:\n");
+ fmt::print(" {:>20} = {}\n", "command", command);
+ fmt::print(" {:>20} = {}\n", "data_file", data_file);
+ fmt::print(" {:>20} = {}\n", "num_events", num_events);
+ fmt::print(" {:>20} = {}\n", "algo_name", algo_name);
+ fmt::print(" {:>20} = {}\n", "banks", fmt::join(bank_names,", "));
+ fmt::print("\n");
+ }
+
+ // open the HIPO file; we use 2 readers, one for 'before' (i.e., not passed through iguana), and one for 'after'
+ // (passed through iguana), so we may compare them
+ hipo::reader reader_before(data_file.c_str()); // NOTE: not copy-constructable, so make two separate readers
+ hipo::reader reader_after(data_file.c_str());
+ auto banks_before = reader_before.getBanks(bank_names);
+ auto banks_after = reader_after.getBanks(bank_names);
+
+ // define the algorithm
+ iguana::AlgorithmSequence seq;
+ seq.Add(algo_name);
+ seq.SetOption(algo_name, "log", verbose ? "debug" : "info");
+
+ // start the algorithm
+ seq.Start(banks_after);
+
+ // event loop
+ int it_ev = 0;
+ while(reader_after.next(banks_after) && (num_events==0 || it_ev++ < num_events)) {
+ // iterate the 'before' reader too
+ reader_before.next(banks_before);
+ // run the algorithm
+ if(command=="algorithm")
+ seq.Run(banks_after);
+ else if(command=="unit") {
+ fmt::print(stderr, "ERROR: unit tests are not yet implemented (TODO)\n");
+ return 1;
+ }
+ else {
+ fmt::print(stderr, "ERROR: unknown command '{}'\n", command);
+ return 1;
+ }
+ // print the banks, before and after
+ if(verbose) {
+ for(decltype(bank_names)::size_type it_bank=0; it_bank < bank_names.size(); it_bank++) {
+ fmt::print("{:=^70}\n", fmt::format(" BEFORE: {} ", bank_names.at(it_bank)));
+ banks_before.at(it_bank).show();
+ fmt::print("{:=^70}\n", fmt::format(" AFTER: {} ", bank_names.at(it_bank)));
+ banks_after.at(it_bank).show();
+ }
+ }
+ }
+
+ // stop the algorithm
+ seq.Stop();
+ return 0;
+}
diff --git a/src/iguana/tests/meson.build b/src/iguana/tests/meson.build
new file mode 100644
index 00000000..b001325e
--- /dev/null
+++ b/src/iguana/tests/meson.build
@@ -0,0 +1,34 @@
+test_exe_name = 'iguana-test'
+
+test_exe = executable(
+ test_exe_name,
+ test_exe_name + '.cc',
+ include_directories: project_inc,
+ dependencies: project_deps,
+ link_with: project_libs,
+ install: true,
+ install_rpath: ':'.join(project_exe_rpath),
+)
+
+if fs.is_file(get_option('test_data_file'))
+ message('Test sample file provided; you may run tests with `meson test`')
+ foreach algo, banks : algos_and_banks_for_unit_testing
+ bank_args = []
+ foreach bank : banks
+ bank_args += ['-b', bank]
+ endforeach
+ test(
+ test_exe_name + ' ' + algo.replace('::', '__'),
+ test_exe,
+ args: [
+ '-c', 'algorithm',
+ '-f', get_option('test_data_file'),
+ '-n', get_option('test_num_events'),
+ '-a', algo
+ ] + bank_args,
+ )
+ endforeach
+else
+ stat_file = get_option('test_data_file')=='' ? 'provided' : 'found'
+ message('Test sample file NOT ' + stat_file + '; you may run tests manually with `' + test_exe_name + '`')
+endif