{"id":16114119,"url":"https://github.com/richelbilderbeek/the_richel_setup","last_synced_at":"2025-03-18T10:30:22.743Z","repository":{"id":70484882,"uuid":"81001375","full_name":"richelbilderbeek/the_richel_setup","owner":"richelbilderbeek","description":"Richel Bilderbeek's favorite C++ setup","archived":false,"fork":false,"pushed_at":"2021-06-20T08:21:22.000Z","size":1259,"stargazers_count":7,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-28T08:45:07.596Z","etag":null,"topics":["article","best-practices","cpp","cpp14","good-practices","setup","travis-ci"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/richelbilderbeek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-05T15:23:06.000Z","updated_at":"2024-03-27T15:28:02.000Z","dependencies_parsed_at":"2023-04-02T13:33:00.679Z","dependency_job_id":null,"html_url":"https://github.com/richelbilderbeek/the_richel_setup","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richelbilderbeek%2Fthe_richel_setup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richelbilderbeek%2Fthe_richel_setup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richelbilderbeek%2Fthe_richel_setup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richelbilderbeek%2Fthe_richel_setup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richelbilderbeek","download_url":"https://codeload.github.com/richelbilderbeek/the_richel_setup/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243918588,"owners_count":20368745,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["article","best-practices","cpp","cpp14","good-practices","setup","travis-ci"],"created_at":"2024-10-09T20:14:06.393Z","updated_at":"2025-03-18T10:30:22.736Z","avatar_url":"https://github.com/richelbilderbeek.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# the_richel_setup\n\nBranch|[![Travis CI logo](TravisCI.png)](https://travis-ci.org)|[![Codecov logo](Codecov.png)](https://www.codecov.io)\n---|---|---\nmaster|[![Build Status](https://travis-ci.org/richelbilderbeek/the_richel_setup.svg?branch=master)](https://travis-ci.org/richelbilderbeek/the_richel_setup)|[![codecov.io](https://codecov.io/github/richelbilderbeek/the_richel_setup/coverage.svg?branch=master)](https://codecov.io/github/richelbilderbeek/the_richel_setup/branch/master)\ndevelop|[![Build Status](https://travis-ci.org/richelbilderbeek/the_richel_setup.svg?branch=develop)](https://travis-ci.org/richelbilderbeek/the_richel_setup)|[![codecov.io](https://codecov.io/github/richelbilderbeek/the_richel_setup/coverage.svg?branch=develop)](https://codecov.io/github/richelbilderbeek/the_richel_setup/branch/develop)\n\n`the_richel_setup` shows Richel Bilderbeek's favorite C++ setup.\n\n## Table of Content\n\n * Goals\n * Name of the setup\n * Prerequisites\n * Overview\n   * The normal run\n     * `main.cpp`\n     * `the_richel_setup.pro`\n   * The testing run\n     * `main_test.cpp`\n     * `the_richel_setup_test.pro`\n     * `my_functions_test.cpp`\n   * The `my_functions` unit\n     * `the_richel_setup.pri`\n     * `my_functions.h`\n     * `my_functions.cpp`\n * Diagnostics\n   * Boost.Test\n   * Profile\n   * `cppcheck`\n   * `helgrind`\n   * `valgrind`\n   * OCLint\n   * Codecov\n   * proselint\n * Use it for your own work\n * Where do I put additions?\n   * I want to add a new function\n   * I want to add a new unit\n * Extensions\n * Conclusion\n * Notes\n\n## Goals\n\n`the_richel_setup` has:\n\n * clean seperation of test and normal execution\n * test build has a high amount of runtime testing\n * the normal build can be used for profiling\n * code coverage is measured\n * style is checked by OCLint\n\n## Name of the setup\n\nI think 'the Richel setup' is a bad name. I prefer not to name things after myself.\nOn the other hand, it *is* my personal setup. The major reason for chosing\nthis name, is that I lacked a better one. \n\n*Update*: At the moment I consider calling it 'The Good Boy' setup, as it tries\nto follow all Good Practices.\n\nNames I considered are:\n\n * The dual-XOR setup, as `main`s must be exclusive to each project\n * The FOE setup ('Figure Of Eigth'), after the figure of eight in the picture\n * The infinity setup, after the figure of eight in the picture\n\nIf you know a better one, please let me know, by posting an Issue.\n\n## Prerequisites\n\nTo get this setup to work, you'll need to have:\n\n * A Travis CI account at www.travis.org and sign in. This can be done with your GitHub\n * A Codecov account at www.codecov.io and sign in. This can be done with your GitHub\n\n## Overview\n\nThe files are distributed over two projects as such:\n\n![The Richel setup](the_richel_setup.png)\n\nThis picture shows which files are part of which project.\nThe files used by the normal run are in red, at the left.\nThe files used by the testing run are in blue at the right.\nThe files used by both setups are in the overlapping section.\nThe most important file used by both setups is called `my_functions.pri`,\nwhich includes the `my_functions` unit (that is: `my_functions.h` and `my_functions.cpp`)\nto the project.\n\nThe goal of this setup is to both test and profile the functions in `the_richel_project.pri` project\ninclude file.\n\nThe normal setup has one project file, `the_richel_setup.pro`. The `main` function of this project is in the file `main.cpp` (I prefer being unoriginal with naming).\nThe functions to be *used* are in the `the_richel_setup.pri` project include file. \nEven though the tests may fail, one can still use those functions. \nAnd it is this `main` function that is being profiled by `gprof`.\n\nThe testing setup has one project file, `the_richel_setup_test.pro`. The `main` function of this project is in \nthe file `main_test.cpp`. The functions to be *tested* are in the `the_richel_setup.pri` project include file. \nThe tests for the functions are in `my_functions_test.cpp`. \nBoost.Test is used as a testing harness. If a test fails, other tests are still performed, \neven if `std::abort` is called (by, for example, `assert`).\n\n### The normal run\n\nThe normal run *uses* the functions in the `the_richel_setup.pri` project include file.\nBecause the tests are absent in this project, it can still be run when tests fail.\nThe normal run is suited for profiling. Profiling only gives sensible results in release mode,\nwhich is tested for in the `main` function.\n\n##### `main.cpp`\n\nThe `main` function just demonstrates some use of the functions in the `my_function` unit:\n\n```\n#include \"my_functions.h\"\n#include \u003ciostream\u003e\n\nint main()\n{\n  #ifndef NDEBUG\n  #error(one should not profile in debug mode)\n  #endif\n\n  std::cout \u003c\u003c is_odd(42) \u003c\u003c '\\n';\n  std::cout \u003c\u003c calc_mean( { 41.0, 42.0, 43.0 } ) \u003c\u003c '\\n';\n}\n```\n\n`main.cpp` is part of `the_richel_setup.pro`, which encompasses the normal run. This file:\n\n * `#include`s some header files\n * tests that `NDEBUG` is `#defined`. `NDEBUG` will be `#defined` in release mode only. Being\n   in release mode is a prerequisite for profiling.  \n * demonstrates the functions `is_odd` and `calc_mean`. These are the functions we are interested in\n\nThe demonstration of the functions of interested is short. This is because of clarity only. In the\nend, this is the spot where to start your numerical calculations.\n\n#### `the_richel_setup.pro`\n\nThe `the_richel_setup.pro` files defines which files belong to\nthe normal run project and how this should be compiled.\n\n```\n# Files\ninclude(the_richel_setup.pri)\nSOURCES += main.cpp\n\n# C++14\nCONFIG += c++14\nQMAKE_CXXFLAGS += -std=c++14\n\n# High warnings levels\nQMAKE_CXXFLAGS += -Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic -Weffc++ -Werror\n\n# Allow debug and release mode\nCONFIG += debug_and_release\n\n# In release mode, turn on profiling\nCONFIG(release, debug|release) {\n\n  DEFINES += NDEBUG\n\n  # gprof\n  QMAKE_CXXFLAGS += -pg\n  QMAKE_LFLAGS += -pg\n}\n```\n\nThis project consists out of three files: `main.cpp` and the `my_functions` unit. \nTests are absent in this build entirely. \n\nC++14 is set to be the C++ standard used.\n\nThe warning levels are set to as high as possible: all GCC compiler warnings are enabled. And a warning cannot be ignored,\nas `-Werror` escalates a warning as an error.\n\nThen debug and release modes are both enabled. This may be unexpected: the `main` function will not compile in debug mode.\nAnd that is exactly where is relied upon [NOTE 1]. \n\n\nIn release mode, the `NDEBUG` flag is defined. This will compile the project in release mode. Among others,\nall `assert`s are removed from your code by the preprocessor. \n\nAdditionally, in release mode, the flags for profiling are enabled.\n\n## The test run\n\nThe test run *tests* the functions in the `my_functions` unit.\nIn testing, all diagnostics can be enabled\n\n### `main_test.cpp`\n\nUsing Boost.Test has many advantages. One\nis that you do not need to write a `main` \nfunction by hand: \n\n```\n#define BOOST_TEST_DYN_LINK\n#define BOOST_TEST_MODULE my_functions_test_module\n#include \u003cboost/test/unit_test.hpp\u003e\n\n#ifdef NDEBUG\n#error(one should not test in release mode)\n#endif\n```\n\nThat is great: one does not need to worry to include all tests. Boost.Test will detect\nall test cases itself and run these.\n\nNext to this, there is a `#ifdef` that checks if the tests\nare run in debug mode. There is no reason to run tests\nin release mode.\n\n### `the_richel_setup_test.pro`\n\nThe `the_richel_setup_test.pro` files defines which files belong to\nthe test run project and how this should be compiled.\n\n```\n# Files\ninclude(the_richel_setup.pri)\nSOURCES += main_test.cpp my_functions_test.cpp\n\n# C++14\nCONFIG += c++14\nQMAKE_CXXFLAGS += -std=c++14\n\n# High warnings levels\nQMAKE_CXXFLAGS += -Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic -Weffc++ -Werror\n\n# Allow debug and release mode\nCONFIG += debug_and_release\n\n# In debug mode, turn on gcov and UBSAN\nCONFIG(debug, debug|release) {\n\n  # gcov\n  QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage\n  LIBS += -lgcov\n\n  # UBSAN\n  QMAKE_CXXFLAGS += -fsanitize=undefined\n  QMAKE_LFLAGS += -fsanitize=undefined\n  LIBS += -lubsan\n}\n\n# Boost.Test\nLIBS += -lboost_unit_test_framework\n```\n\nNext to the `my_functions` unit and the testing main, \nthere is a `my_functions_test.cpp` file.\nThis file contains all -how unexpected- tests.\n\nC++14 is used at the highest warnings level. Warnings are escalated to errors.\n\nAlso this file allows for a debug and release mode, but\nthe `main_test.cpp` file will check that the project\nwill be compiled in debug mode.\n\nThe debug mode enables `gcov` and `UBSAN`. `gcov` allows for measuring code coverage,\nwhere `UBSAN` ('Undefined Behavior SANitizer') checks for undefined behavior. Testing\nwill be slower due to this, be more thorough.\n\nLastly, there is a linking to the Boost.Test library.\n\n### `my_functions_test.cpp`\n\nThis is the file in which the functions are tested:\n\n```\n#include \u003cboost/test/unit_test.hpp\u003e\n\n#include \"my_functions.h\"\n\nBOOST_AUTO_TEST_CASE(test_is_odd)\n{\n  BOOST_CHECK(!is_odd(0));\n  BOOST_CHECK( is_odd(1));\n}\n\nBOOST_AUTO_TEST_CASE(test_calc_mean)\n{\n  const double measured{\n    calc_mean( {1.0, 2.0, 3.0} )\n  };\n  const double expected{2.0};\n  BOOST_CHECK_EQUAL(measured, expected);\n}\n\nBOOST_AUTO_TEST_CASE(test_calc_mean_needs_nonempty_vector)\n{\n  std::vector\u003cdouble\u003e empty;\n  BOOST_CHECK_THROW(\n    calc_mean(empty), \n    std::invalid_argument\n  );\n}\n```\n\nThere are three test cases, that can be recognized by `BOOST_AUTO_TEST_CASE` and their label. These labels\nare checked to be unique and should inform about the test.\n\nThe first test case checks if `is_odd` returns false for a zero, and true for a one.\n\nThe second test case checks if the mean of one, two and three is indeed two, as should be\nthe result of the `calc_mean` function.\n\nThe third test case checks if `calc_mean` throws an exception if it is given an empty vector.\nOne cannot calculate the mean of zero values.\n\n### The `my_functions` unit\n\nThe `my_functions` unit consists out of a header file\ncalled `my_functions.h` and an implementation file\ncalled `my_functions.cpp`.\n\nThe functions therein are tested by the testing project and\nused by the normal run.\n\n#### `the_richel_setup.pri`\n\nThe `the_richel_setup.pri` project include file \ncontains the filenames of the `my_functions`\nunit.\n\n```\nSOURCES += my_functions.cpp\nHEADERS += my_functions.h\n```\n\nThis file is where it is determined which files\nare in both the normal and testing project.\n\nWould one like to add another, say, `my_helper`, these\ncan be added here. This would then look like:\n\n```\nSOURCES += my_functions.cpp my_helper.cpp\nHEADERS += my_functions.h   my_helper.h\n```\n\n#### `my_functions.h`\n\n```\n#ifndef MY_FUNCTIONS_H\n#define MY_FUNCTIONS_H\n\n#include \u003cvector\u003e\n\n///Calculate the mean.\n///Will throw if the input is empty\ndouble calc_mean(const std::vector\u003cdouble\u003e\u0026 v);\n\n///Determine if a number is odd\nbool is_odd(const int i) noexcept;\n\n#endif // MY_FUNCTIONS_H\n```\n\nThis header file starts, as is common, with an #include guard.\n\nThe header file `vector` in #included and two functions are declared.\n\nThe function `is_odd` is `noexcept`, which is a C++11 (and beyond) \nsyntax to designate that the function cannot, or will not, throw an exception.\n\nBoth functions are documented. Three slashes are used, so that a\ndocument generation like Doxygen will interpret these comments\nas documentation.\n\n#### `my_functions.cpp`\n\n\n```\n#include \"my_functions.h\"\n\n#include \u003cnumeric\u003e\n#include \u003cstdexcept\u003e\n\nbool is_odd(const int i) noexcept\n{\n  return i % 2 != 0;\n}\n\ndouble calc_mean(const std::vector\u003cdouble\u003e\u0026 v)\n{\n  if (v.empty())\n  {\n    throw std::invalid_argument(\n      \"cannot calculate the mean\"\n      \"of an empty vector\"\n    );\n  }\n  const double sum{\n    std::accumulate(\n      std::begin(v),\n      std::end(v),\n      000'000.0 //seperators are new to C++14\n    )\n  };\n  return sum / static_cast\u003cdouble\u003e(v.size());\n}\n```\n\nThis implementation file defines the two functions.\n\nThe only noteworthy is that `calc_mean` uses\nseperators in the zero being used `000'000.0`.\nThis is allowed since C++14, ensuring that this\nproject uses C++14, else it does not compile\n\n## Diagnostics\n\nThis setup gives a lot of automatically generated goodies.\n\n### Boost.Tests\n\n![Travis CI has run all Boost.Test tests](travis_boost_tests.png)\n\nOn Travis CI, the testing project is executed and the results\nare shown. \n\nIn this case, all tests pass. Would at least one test\nfail, Travis CI will display a failed build.\n\n### `cppcheck`\n\n`cppcheck` is a static code analyser.\n\n### `helgrind`\n\n`helgrind` can be used to detect thread errors (for example, race conditions).\n\n![helgrind's output](helgrind.png)\n\n### `memcheck`\n\n`memcheck` can be used to detect memory leaks.\n\n![memcheck's output](memcheck.png)\n\n### Profile\n\n![Travis CI has profiled the code](travis_gprof.png)\n\nOn Travis CI, the normal project is profiled (using gprof) \nand the results are shown. \n\nIn this case, the profile is uninformative: all 0.0 seconds\ntook place in the `main` function. When the `main` function\nstarts taking a longer time to run, an informative profile\nwill be shown. \n\nBelow I show a profiling log that is informative:\n\n![Travis CI gives a more informative gprof log](travis_gprof_pbdmms.png)\n\nI show the profile for the top four functions. The top\nfunction, `ribi::count_species` has taken 27% of the complete\nruntime. It is followed closely by `ribi::get_genetic_distance` that\ntook 26%. The two others took 9% and 6% of the\ncomplete runtime. Would there be a need to speed up the code, these\nare the functions that would be efficient to speed up.\n\n### OCLint\n\n![Travis CI shows the output of the do_oclint script](travis_oclint.png)\n\nOn Travis CI, the code of boths projects is tested by OCLint, via\nthe script `do_oclint`.\n\nIt can be seen that there is an ignore OCLint compiler warning\nand zero OCLint suggestions. \n\nBelow, I show an OCLint report that does have remarks:\n\n![Travis shows an OCLint report that does have remarks](travis_oclint_pbdmms.png)\n\nIn this case, OCLint states that a variable name is disallowed to be\nmore than twenty characters. The locations in the code are shown. \nThe Travis build will fail due to this.\n\n### Codecov\n\n![Codecov shows the code coverage per file](code_coverage_files.png)\n\nTravis CI uses a tool called `gcov` and sends Codecov the code coverage.\n\nIn this example project, both files have a 100% code coverage. This\nmeans that all lines in the functions are tested.\n\nBelow, I will show an example with incomplete code coverage:\n\nIn this project, Codecov shows the code coverage of three files.\nThe first file, `daic_helper.cpp` has a code coverage of 70%.\nThe second file, `daic_input.cpp` does worse, with a code coverage\nof 50%. The third file `daic_input.h` does perfect again.\n\n![Codecov shows the code coverage per file for a project with incomplete code coverage](code_coverage_files_pbdmms.png)\n\nClicking on the file that has least code coverage, `daic_input.cpp`, we\ncan see that there is a file that is untested, called `daic::save`. \n\n![Codecov has spotted an untested function](code_coverage_daic_helper.png)\n\nThanks to Codecov, we can see this void in our testing suite.\n\n## proselint\n\nThis `README.md` file is checked by `proselint`. `proselint` checks prose for style.\n\nWhen adding `proselint` to this project, these errors were given:\n\n![proselint](proselint.png)\n\nI put these errors here as a picture, else `proselint` will complain.\n\n## Use it for your own work\n\nJust clone/download this GitHub, put the\ncode in a GitHub of your own and push.\nGet that green Travis build status first.\n\nThen start modifying the filename and\nreplacing the example functions by\nfunctions of your own.\n\nIt is easier to start from something that works, than\nto recreate it from scratch.\n\nEnjoy!\n\n\n## Where do I put additions?\n\n### I want to add a new function\n\n * Add the function declaration to `my_functions.h`\n * Add the function definition to `my_functions.cpp`\n * Add the function's tests to `my_functions_test.cpp`\n\nTo give an example, suppose a function that determines if a number is\neven needs to be added. The function is called `is_even`.\n\nAdd to `my_functions.h`:\n\n```\nbool is_even(const int x) noexcept;\n```\n\nAdd to `my_functions.cpp`:\n\n```\nbool is_even(const int x) noexcept\n{\n  return !is_odd(x);\n}\n```\n\nAdd to `my_functions_test.cpp`:\n\n```\nBOOST_AUTO_TEST_CASE(test_is_even)\n{\n  BOOST_CHECK( is_even(0));\n  BOOST_CHECK(!is_even(1));\n}\n```\n\nNote that the implementation and tests are also just examples.\n\n## I want to add a new unit\n\n * Add the unit's header and implementation filename to `the_richel_setup.pri`\n * Add the unit's test implementation filename to `the_richel_setup_test.pro`\n\nTo give an example, suppose a class needs to be added to store odd numbers.\n\nThe class:\n\n * is called `odd_number`\n * its declaration will be stored in `odd_number.h`\n * its implementation will be put in `odd_number.cpp`\n * will be tested in the file `odd_number_test.cpp`\n\nTo add the `odd_number` unit to the setup, add to `the_richel_setup.pri`:\n\n```\nSOURCES += odd_number.cpp\nHEADERS += odd_number.h\n```\n\nThe testing must be added to `the_richel_setup_test.pro`:\n\n```\nSOURCES += odd_number_test.cpp\n```\n\nWith the files added to the correct project and project include files, the\nclass `odd_number` can be developed similar as described in 'How do\nI add a new function?'.\n\n## Extensions\n\nThis setup has always been under construction, as I always look for\ndiagnostic tools to add. `the_richel_setup` started\nas [part of the Travis C++ tutorial](https://github.com/richelbilderbeek/travis_qmake_gcc_cpp14_boost_test_gcov_oclint).\n\nThe addition of UBSAN is its newest addition. But I\nlook forward to add `valgrind` to detect memory leaks,\nand to enforce a coding standard using `clang-format`.\n\nFeel invited to suggest these additions to me by \nusing an Issue or Pull Request.\n\n## Conclusion\n\nThis is my personal favorite setup. Me and my friends\nuse it as a starting point. And then, I've seen them\ngrow: all that feedback from tools, that you get for\nfree, forces you learning seasoned/correct C++.\n\nA lighter version is used in [my Correct C++ course](https://github.com/richelbilderbeek/correct_cpp).\n\n## Notes\n\n### Note 1\n\nAn idea would be to add:\n\n```\n# In debug mode, do not compile\nCONFIG(debug, debug|release) {\n  error(Must not compile a profiling run in debug mode)\n}\n```\n\nAlas, that will not work. Instead, the `main` function is relied upon.\n\n## I want to contribute\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md)\n\n## Code of Conduct\n\nSee [code_of_conduct.md](code_of_conduct.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichelbilderbeek%2Fthe_richel_setup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichelbilderbeek%2Fthe_richel_setup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichelbilderbeek%2Fthe_richel_setup/lists"}