Skip to content

Project

Contributing

FairMQ is developed on GitHub. This page tells you how to get the code, how to build and test it, and which rules your changes must follow. It also tells you how to report a problem and how to work on this documentation.

The repository is FairRootGroup/FairMQ on GitHub.

Branch or tag Content
dev The development branch. Send your pull requests to dev.
master The latest release. It is the default branch on GitHub.
v1.11.0, v1.10.1, … The releases. The release tags are on commits of dev.

The release notes list the changes of each release and the changes on dev that are not released yet.

Terminal window
git clone -b dev https://github.com/FairRootGroup/FairMQ fairmq_source

Do not make a shallow clone. The build reads the version of FairMQ from the Git tags (see Version).

The bundled packages GoogleTest, PicoSHA2 and FairCMakeModules are Git submodules in extern/. The configure step gets the submodules that it needs with git submodule update --init.

  1. Install the dependencies. See Install. To install them with Spack, use the file spack.yaml of the repository:

    Terminal window
    spack --env fairmq_source install
    spack env activate fairmq_source
  2. Configure a debug build with the tests:

    Terminal window
    cmake -S fairmq_source -B fairmq_build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
  3. Build:

    Terminal window
    cmake --build fairmq_build
  4. Run all tests:

    Terminal window
    ctest --test-dir fairmq_build --output-on-failure --parallel "$(nproc)"

The debug build compiles with -Wshadow -Wall -Wextra -Wpedantic. Your change must compile without warnings, because the CI job static-analysis fails on each warning.

The test suites are GoogleTest programs in test/: Channel, Device, EventManager, MemoryResources, Message, Parts, Plugins, PluginServices, PluginsPrelinked, Poller, Properties, Protocols, Region, StateMachine, Tools and Transport. CTest gives each test the name <suite>.<test>, for example Channel.ConnectedPeers.Pair/zeromq. Many tests run once for each transport and have the transport in the name. The suite Tools tests the helpers in fair::mq::tools. To test your own devices in your project, see Test your devices.

The examples add tests with names like Example.1-1.zeromq and Example.builtin-devices.multipart.shmem.

The tests have no CTest labels. Select tests with a regular expression on the name:

Terminal window
ctest --test-dir fairmq_build -R '^Channel\.'
ctest --test-dir fairmq_build -R 'shmem'
ctest --test-dir fairmq_build -R '^Example\.'

The first command runs the suite Channel. The second command runs all tests with shmem in the name. The third command runs the tests of the examples.

Each suite is a program test/testsuite_<suite> in the build directory. CTest runs it in the build directory. To run some tests of a suite without CTest, give a GoogleTest filter:

Terminal window
cd fairmq_build
test/testsuite_Channel --gtest_filter='ConnectedPeers.*'

CTest stops a test after its timeout: 20 s for most suites, 5 s for Tools, and 30 s for the tests of the examples.

The CI also builds FairMQ with sanitizers and with static analysis. Use the same settings to find problems before you send a change:

  • Address, leak and undefined behavior sanitizers, as the CI job asan+lsan+ubsan:

    Terminal window
    cmake -S fairmq_source -B build_asan -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON \
    -DENABLE_SANITIZER_ADDRESS=ON -DENABLE_SANITIZER_LEAK=ON \
    -DENABLE_SANITIZER_UNDEFINED_BEHAVIOR=ON -DFAIRMQ_DEBUG_MODE=ON \
    -DCMAKE_CXX_FLAGS="-O1 -fno-omit-frame-pointer"
  • Static analysis, as the CI job static-analysis. The build runs clang-tidy and cpplint on each file that it compiles, if CMake finds these programs.

    Terminal window
    cmake -S fairmq_source -B build_tidy -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON \
    -DRUN_STATIC_ANALYSIS=ON

CMake options and variables describes all sanitizer options and the static analysis options.

The tests are GoogleTest programs. test/CMakeLists.txt makes one program for each suite with the function add_testsuite() of cmake/GTestHelper.cmake.

  1. Write the tests in a source file in the folder of the suite, for example test/poller/_poller.cxx. Start the file with the license header.

  2. Add the file to the SOURCES of the suite in test/CMakeLists.txt. For a new suite, make a new folder and add a call of add_testsuite().

  3. Configure with -DBUILD_TESTING=ON, and build. After the build, CMake runs the suite program and registers each of its tests in CTest (gtest_discover_tests()).

  4. Run the new tests with ctest -R. See Run the new test.

This is the suite Poller in test/CMakeLists.txt:

test/CMakeLists.txt
add_testsuite(Poller
SOURCES
${CMAKE_CURRENT_BINARY_DIR}/runner.cxx
poller/_poller.cxx
LINKS FairMQ
INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 20
${definitions}
${environment}
)
test/CMakeLists.txt:307–318

${definitions} and ${environment} add the compile definitions and the test environment that test/CMakeLists.txt sets at the top of the file.

Argument What it does
<name> The name of the suite. The program is test/testsuite_<name> in the build directory. CTest puts <name>. in front of each test name.
SOURCES <file>… The source files. Add ${CMAKE_CURRENT_BINARY_DIR}/runner.cxx. It contains the main() of the suite.
LINKS <lib>… The libraries. The function adds GTest::GTest. The suites link FairMQ, the library target of the build.
DEPENDS <target>… Targets that CMake must build before the suite, for example testhelper_runTestDevice.
INCLUDES <dir>… The include directories. The suites need ${CMAKE_CURRENT_SOURCE_DIR} for runner.h and the headers in test/helper/, and ${CMAKE_CURRENT_BINARY_DIR} for the generated TestEnvironment.h.
DEFINITIONS <def>… Compile definitions.
ENVIRONMENT <var>=<op>:<value>… Changes to the environment of each test, in the format of the CTest property ENVIRONMENT_MODIFICATION. CMake older than 3.22 ignores this argument.
TIMEOUT <seconds> The CTest timeout of each test. The default is 10 s. Most suites set 20 s.
RUN_SERIAL <ON/OFF> ON: CTest runs no other test at the same time as a test of this suite. The default is OFF. No suite sets it at the moment.

add_testhelper(<name> …) makes the program testhelper_<name>, which is not a test. It takes SOURCES, LINKS, DEPENDS, INCLUDES and DEFINITIONS. add_testlib(<name> …) makes a shared library. It takes the same arguments, and also HIDDEN (hidden symbol visibility) and VERSION. The suite Plugins uses two of these libraries as plugins.

The header test/helper/TransportParam.h makes a GoogleTest test run once for each transport. Write the test with TEST_P() for the fixture TransportTest. Then give the transports to INSTANTIATE_TEST_SUITE_P():

test/poller/_poller.cxx
auto RunPoller(TransportParam const& param, int pollType) -> void
{
auto const& transport = param.transport;
IpcAddress const data1("poller_data1");
IpcAddress const data2("poller_data2");
auto const results = RunDevices({
{"pollout_" + transport, "name=data1,type=push,method=bind,address=" + data1.str()
+ " name=data2,type=push,method=bind,address=" + data2.str()},
{"pollin_" + transport, "name=data1,type=pull,method=connect,address=" + data1.str()
+ " name=data2,type=pull,method=connect,address=" + data2.str(),
"--poll-type " + to_string(pollType)},
}, RunOptions(transport, param.DeviceArgs()));
for (auto const& result : results) {
EXPECT_TRUE(Succeeded(result, transport));
}
}
using Poller = TransportTest;
TEST_P(Poller, Subchannel)
{
RunPoller(GetParam(), 0);
}
TEST_P(Poller, Channel)
{
RunPoller(GetParam(), 1);
}
INSTANTIATE_TEST_SUITE_P(, Poller, AllTransports(), TransportParamName());
test/poller/_poller.cxx:23–54
Values Transports Test name suffix
AllTransports() zeromq, shmem, and shmem with --shm-metadata-msg-size 2048 /zeromq, /shmem, /shmem_expanded_metadata
BasicTransports() zeromq and shmem /zeromq, /shmem
ShmTransports() shmem, and shmem with --shm-metadata-msg-size 2048 /shmem, /shmem_expanded_metadata
testing::Values(TransportParam("zeromq")) zeromq only /zeromq

TransportParamName() gives the suffix. GetParam().transport is the name of the transport. GetParam().DeviceArgs() gives the extra command-line options of a device process for this parameter. For example, the suite Poller has the tests Poller.Poller.Subchannel/zeromq and Poller.Poller.Channel/shmem_expanded_metadata.

RunDevices() of test/helper/Devices.h starts one process of testhelper_runTestDevice for each DeviceSpec. All processes get the same new session and the transport of RunOptions. The default control mode is static. The function waits for all processes. It kills a process that runs longer than RunOptions::timeout, by default 15 s.

The prefix of the device ID selects the device class, in getDevice() of test/helper/runTestDevice.cxx. For example, push_zeromq starts the class Push. A test device derives from TestDevice (test/helper/devices/TestDevice.h). At the end of its test, it calls Succeed(), which logs test device succeeded, transport: and the transport name. Succeeded() checks the exit code 0 and this log line.

To add a test device:

  1. Write a header test/helper/devices/Test<Name>.h with a class that derives from TestDevice.

  2. Include the header in test/helper/runTestDevice.cxx. In getDevice(), return your class for a new ID prefix.

  3. Add the header to the SOURCES of add_testhelper(runTestDevice …) in test/CMakeLists.txt.

  4. Give your suite the argument DEPENDS testhelper_runTestDevice.

Build, and run the suite with CTest:

Terminal window
cmake --build fairmq_build
ctest --test-dir fairmq_build -R '^Poller\.' --output-on-failure

To see the test names without running the tests, add -N. To run the tests of one transport, select the suffix, for example -R '^Poller\..*/shmem$'. To run the program directly, see Select tests.

Each example is a folder in examples/. Most examples use the function add_example() of examples/CMakeLists.txt. It builds the devices, writes the scripts, registers the tests, and installs the programs and the start scripts. The examples custom-controller, dds, n-m and qc have their own CMake code.

Example 1-1 has these files:

  • Directoryexamples/
    • CMakeLists.txt calls add_subdirectory(1-1)
    • Directory1-1/
      • CMakeLists.txt
      • sampler.cxx
      • sink.cxx
      • fairmq-start-ex-1-1.sh.in the start script
      • test-ex-1-1.sh.in the test script
      • README.md
  1. Make a folder examples/<name>/.

  2. Write a file <device>.cxx for each device, with fairmq/runDevice.h. See examples/1-1/sampler.cxx. The test script needs devices that stop by themselves. The devices of example 1-1 have the option --max-iterations for this.

  3. Write examples/<name>/CMakeLists.txt with a call of add_example(). Example 1-1 has this call:

    examples/1-1/CMakeLists.txt
    add_example(NAME 1-1 DEVICE sampler sink)
    examples/1-1/CMakeLists.txt:9–9
  4. Write the start script fairmq-start-ex-<name>.sh.in and the test script test-ex-<name>.sh.in. See The scripts.

  5. Add add_subdirectory(<name>) to examples/CMakeLists.txt.

  6. Configure with -DBUILD_TESTING=ON. BUILD_EXAMPLES is ON by default. Build, and run the tests of the example:

    Terminal window
    ctest --test-dir fairmq_build -R '^Example\.<name>\.' --output-on-failure
Argument What it does
NAME <name> Required. The name of the example. Without it, CMake stops with NAME arg is required.
DEVICE <device>… For each device, the program fairmq-ex-<name>-<device> from the file <device>.cxx, linked to FairMQ.
SCRIPT <script>… The start scripts fairmq-start-ex-<script>.sh.in. The default is one script with the name of the example.
CONFIG Copies ex-<name>.json to the build directory of the example, and installs it into share/fairmq/. The scripts get @EX_CONF_DIR@.
TRANSPORT <transport>… The transports of the tests. The default is zeromq and shmem.
VARIANT <variant>… One test for each variant and transport, Example.<name>.<variant>.<transport>. The test script gets the variant as its second argument.
NO_TRANSPORT One test, without the transport argument. Known defect: the test name is Example.<name>., with a dot at the end.
NO_TEST No test script and no test.

Other arguments stop CMake with Unexpected unparsed arguments. Without VARIANT and NO_TRANSPORT, the tests are Example.<name>.<transport>, for example Example.1-1.zeromq and Example.1-1.shmem.

CMake writes two copies of each start script. The copy in the build directory of the example has the name fairmq-start-ex-<script>.sh. The copy fairmq-start-ex-<script>.sh_install has the paths of the installation, and the install step copies it to bin/ as fairmq-start-ex-<script>.sh. CMake replaces these variables:

Variable Build directory Installation
@EX_BIN_DIR@ The build directory of the example <prefix>/bin
@FAIRMQ_BIN_DIR@ <build>/fairmq <prefix>/bin/fairmq
@EX_CONF_DIR@ (with CONFIG) The build directory of the example <prefix>/share/fairmq

CTest runs test-ex-<name>.sh <transport>, or test-ex-<name>.sh <transport> <variant>, with a timeout of 30 s. The test passes if the script exits with 0. The install step does not install the test script. This is the test script of example 1-1:

examples/1-1/test-ex-1-1.sh.in
#!/bin/bash
set -e
export FAIRMQ_PATH=@FAIRMQ_BIN_DIR@
transport="zeromq"
if [[ $1 =~ ^[a-z]+$ ]]; then
transport=$1
fi
session="$(@CMAKE_BINARY_DIR@/fairmq/fairmq-uuid-gen -h)"
chan="data"
chanAddr="/tmp/fmq_$session""_""$chan""_""$transport"
# setup a trap to kill everything if the test fails/timeouts
trap 'set +e; kill -TERM $SAMPLER_PID; kill -TERM $SINK_PID; wait $SAMPLER_PID; wait $SINK_PID; rm $chanAddr; exit 0' TERM
SAMPLER="fairmq-ex-1-1-sampler"
SAMPLER+=" --id sampler1"
SAMPLER+=" --rate 1"
SAMPLER+=" --transport $transport"
SAMPLER+=" --verbosity veryhigh"
SAMPLER+=" --session $session"
SAMPLER+=" --shm-segment-size 100000000"
SAMPLER+=" --shm-monitor true"
SAMPLER+=" --control static --color false"
SAMPLER+=" --max-iterations 1"
SAMPLER+=" --channel-config name=$chan,type=push,method=bind,address=ipc://$chanAddr,rateLogging=0"
@CMAKE_CURRENT_BINARY_DIR@/$SAMPLER &
SAMPLER_PID=$!
SINK="fairmq-ex-1-1-sink"
SINK+=" --id sink1"
SINK+=" --transport $transport"
SINK+=" --verbosity veryhigh"
SINK+=" --session $session"
SINK+=" --shm-segment-size 100000000"
SINK+=" --shm-monitor true"
SINK+=" --control static --color false"
SINK+=" --max-iterations 1"
SINK+=" --channel-config name=$chan,type=pull,method=connect,address=ipc://$chanAddr,rateLogging=0"
@CMAKE_CURRENT_BINARY_DIR@/$SINK &
SINK_PID=$!
# wait for sampler and sink to finish
wait $SAMPLER_PID
wait $SINK_PID
set +e
rm $chanAddr
exit 0
examples/1-1/test-ex-1-1.sh.in:1–53

The script starts both devices with --control static, --max-iterations 1 and a new session from fairmq-uuid-gen. The ipc address contains the session and the transport, so that tests that run at the same time do not use the same address. With set -e, a device that fails also fails the test.

  • Format: .clang-format in the repository root sets the format. It is based on the Mozilla style, with an indent of 4 spaces, no tabs and a column limit of 100. The pointer alignment follows the most frequent style in the file, with left alignment (int* p) as the fallback. The existing code does not follow this file everywhere. Format only the lines that you change, for example with git clang-format.
  • Checks: .clang-tidy enables a selected set of misc, modernize, performance and readability checks. With RUN_STATIC_ANALYSIS=ON, the build runs them if CMake finds clang-tidy. The CI job static-analysis uses this option.
  • Language: C++17. Put new code in the namespace fair::mq. Do not use the deprecated FairMQ* names in new code. See Move from the FairMQ* names.
  • License header: each source file starts with the license header of FairMQ. Copy the header from a file in the same directory. See License and citation.

The repository has no .editorconfig file.

The commit titles use the Conventional Commits format type(scope): summary. The file .gitlint configures the tool gitlint with these rules:

  • The type is one of fix, feat, build, ci, docs, style, refactor, perf and test.
  • The title has a maximum of 75 characters.
  • A body is optional.

The scope is optional. It names the part of FairMQ, for example shmem, zeromq, tools or test. Titles from the history:

fix(shmem): lock the region mutexes in one order
feat(tools): add an execute() overload with a deadline
test(channel): add tcp tests with and without auto-bind
ci: run the tests in parallel

Write the summary in the imperative mood. Use the body to tell what changed and why. The history marks a breaking change with ! after the type or the scope, for example feat!: or feat(ofi)!:. Most of these commits also have a paragraph BREAKING CHANGE in the body. The pull request template also links the seven rules of a great commit message.

To check the message of your last commit, install gitlint and run gitlint in the repository.

  1. Fork the repository on GitHub and make a branch from dev.

  2. Make your change, with tests for new behavior.

  3. Add your name to CONTRIBUTORS. See Authors and contributors.

  4. Rebase your branch on the latest dev.

  5. Open a pull request to dev. Describe your change, and name the issues that it is related to. The template asks you to confirm three items: the rebase on dev, your name in CONTRIBUTORS or AUTHORS, and the rules for commit messages.

The workflows are in .github/workflows/.

Workflow When it runs What it does
CI Each pull request, each push to master and dev The job build builds FairMQ with GCC 12, 13, 14 and 15 and the Spack environment test/ci/spack-latest.yaml, and with GCC 15 and test/ci/spack-boost187.yaml (Boost 1.87). It runs the tests and the install step. The job sanitizers runs the tests with the address, leak and undefined behavior sanitizers (GCC 14), and with the thread sanitizer (GCC 15). The job static-analysis builds with RUN_STATIC_ANALYSIS=ON and fails on each warning.
Docs Each pull request, each push to dev, and a manual start Builds this site and checks its links. A push to dev also publishes the site on GitHub Pages.
Check AUTHORS and CONTRIBUTORS in metadata Changes to AUTHORS, CONTRIBUTORS, codemeta.json or .zenodo.json Runs meta_update.py and fails if the metadata files change.
validate codemeta Changes to codemeta.json Validates codemeta.json.
fair-software Each push Measures the compliance with the recommendations of fair-software.eu.
Spack Buildcache Each week, a manual start, and a push to dev or master that changes the Spack files of the CI Builds the dependencies of the CI and stores them in a Spack build cache.

The CI gets its dependencies from the Spack environments in test/ci/. Dependabot updates the GitHub Actions, the Git submodules and the npm packages of the documentation each month, with pull requests to dev.

Report bugs and ask for features in the GitHub issues. The issue templates are Bug report, Feature request and Question / Support.

For a bug report, give the information that the template asks for:

  • The steps to reproduce the problem: your environment, the CMake options and the commands.
  • The expected behavior.
  • The logs.
  • The operating system, the compiler and the environment, for example the FairSoft version.

The file AUTHORS lists the authors of FairMQ. The file CONTRIBUTORS lists the other people who contributed. Each line has this format, with an optional e-mail address and an optional ORCID:

Family name, Given name <e-mail> [https://orcid.org/0000-0000-0000-0000]

The files codemeta.json and .zenodo.json contain the same names. After you change AUTHORS or CONTRIBUTORS, update them with the script in the repository root:

Terminal window
python3 meta_update.py

Commit the changed metadata files together with your change. The CI workflow Check AUTHORS and CONTRIBUTORS in metadata fails if they do not agree. License and citation tells how the project uses these names.

This site is in the directory docs/ of the repository. It uses Astro and Starlight.

You need these programs:

  • Node.js 22.23.4 or later. engines in docs/package.json sets this version. The CI uses Node.js 24.
  • Doxygen, for the API pages and the source pages. The CI installs the Doxygen package of Ubuntu 24.04.

Run these commands in docs/:

Command What it does
npm ci Installs the packages of package-lock.json.
npm run dev Starts a live preview at http://localhost:4321/FairMQ/.
npm run build Builds the site in dist/.
npm run preview Shows the built site from dist/.
npm run check Checks all internal links and anchors in the built site. Run it after npm run build.
node scripts/check-pages.mjs Checks the pages without a build: the frontmatter, the MDX syntax, the imports, the links and anchors, and the code snippets. Give page files to check only these pages.
node scripts/print-api.mjs <name> --members Lists the API pages with <name> in the name, and the anchors of their members. It reads the Doxygen XML.

npm run dev and npm run build run Doxygen first if necessary. This occurs if the XML in docs/build/doxygen/xml/ is missing or older than a .h or .cxx file in fairmq/. If Doxygen is not installed, they stop with an error.

The pages are MDX files in docs/src/content/docs/<section>/<page>.mdx. The site makes some content from the code:

  • The API pages (/api/) and the source pages (/source/) come from the headers and sources in fairmq/, through the XML output of Doxygen.
  • The names, the types and the defaults in the tables of command-line options come from the option definitions in the code. The descriptions are in src/data/option-texts.mjs.
  • Code samples come from files of the repository, through the component Snippet. The build fails if the selected lines do not exist. Use the examples and the tests for samples, because the CI compiles them.

The pages use Simplified Technical English (ASD-STE100): short sentences, the active voice, and one word for one concept. Links to other pages are root-relative, without the base /FairMQ, and end with /, for example /concepts/states/. Each page has the link Edit page at the bottom. It opens the file of the page on GitHub.