Skip to content

Robust Development Methodologies - SW Quality

Introduction

Thus far, you have created, run and debugged your own application developed with Zephyr RTOS. However, you have little to no knowledge whether this application could be used - from a qualitative standpoint - in a safety environment. This codelab aims at adding this dimension to your project so as to have sound foundations for future work.

What you’ll build

In this codelab, you will understand the required tools and guidelines for building qualitatively good SW - attitude that should be used while creating any type of SW. You will also understand how to interpret the results delivered by the tools used in order to improve your work.

What you’ll learn

  • How to set up the tools for ensuring sufficiently good SW quality.
  • Continue ensuring quality throughout the development lifecycle.
  • Understand the different elements composing quality and to interpret the results provided by the different analysis tools.

What you’ll need

Install the SW Quality checkers - cloc, lizard, clang-formatand cpplint

You need python3 installed

You should have it already as it is demanded in []getting-started-zephyr.md){target=”blank”}.

During this course, we will use cloc, lizard, clang-format, cpplint (and pre-commit later) for ensuring our code conforms to expected quality.

In order to install the checker tools:

  • head to clang-format - LLVM Download Page, download and install the appropriate package ( here a direct link for Windows users ). Important: make sure to ignore the warning during installation and install the tool in the PATH for all users. This tool will be used for formatting the C/C++ code.
  • enter pip3 install cpplint to install a linter for our code. This will be used for ensuring compliancy to Google styleguide . Remember, it is important to have 1 styleguide. Whether this is the best is up to debate (as we should tailor it anyhow).
  • head to cloc - releases to download and install the appropriate package (here a direct link for Windows users, see below for alternatives ). Save it on your harddisk and add it to the $PATH.
  • issue pip3 install lizard to install lizard.

Note

In case you had a different operating system, you can also issue:

  • clang-format:
    • MacOS: brew install clang-format
    • Linux: sudo apt install clang-format
  • cloc
    • MacOS: brew install cloc
    • Linux: pip3 install cloc

  • cpplint
    • MacOS: brew install cpplint
    • Linux: pip3 install cpplint

Obviously, these tools may be available as plugin in various IDEs ( VsCode, CLion, …) you may use regularly.

Test your tools from the command line before continuing

Ensure that your tools are working by issuing the following commands:

  • clang-format --version
  • cpplint --version
  • cloc --version

Not finding the tool from the command line?

In case Windows did not find the tool, check that it is indeed present in your $PATH.

Configure the tools

Now that you have installed the tools, it is time to make use of them. You first start by checking they do work and then move on to test what your code looks like.

Run a first analysis

To test lizard functionality, go to where your dependencies are and issue:

lizard deps/zpp_lib/zpp_rtos/thread.cpp

You should see something like

(.venv)   zephyr_based_course lizard deps/zpp_lib/zpp_rtos/thread.cpp   
================================================
  NLOC    CCN   token  PARAM  length  location  
------------------------------------------------
       3      1     21      2       3 zpp_lib::Thread::Thread@50-52@deps/zpp_lib/zpp_rtos/thread.cpp
       8      3     40      0       8 zpp_lib::Thread::~Thread@54-61@deps/zpp_lib/zpp_rtos/thread.cpp
       4      2     26      2       4 zpp_lib::Thread::constructor@63-66@deps/zpp_lib/zpp_rtos/thread.cpp
      43      6    242      1      63 zpp_lib::Thread::start@68-130@deps/zpp_lib/zpp_rtos/thread.cpp
       1      1     15      0       1 zpp_lib::Thread::waitStarted@132-132@deps/zpp_lib/zpp_rtos/thread.cpp
      20      3    153      0      29 zpp_lib::Thread::join@134-162@deps/zpp_lib/zpp_rtos/thread.cpp
      13      1     84      3      14 zpp_lib::Thread::_thunk@164-177@deps/zpp_lib/zpp_rtos/thread.cpp
1 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
    103      13.1     2.4       83.0         7     deps/zpp_lib/zpp_rtos/thread.cpp

===============================================================================================================
No thresholds exceeded (cyclomatic_complexity > 15 or length > 1000 or nloc > 1000000 or parameter_count > 100)
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
       103      13.1     2.4       83.0        7            0      0.00    0.00

If you now take the non-encapsulated example of thread.c, directly from the kernel, you should get a different result - something like

(.venv)   zephyr_based_course lizard deps/zephyr/kernel/thread.c 
================================================
  NLOC    CCN   token  PARAM  length  location  
------------------------------------------------
       7      3     34      0      15 init_thread_obj_core_list@52-66@deps/zephyr/kernel/thread.c
       4      1     11      0       4 k_is_in_isr@76-79@deps/zephyr/kernel/thread.c
       4      1     14      1       4 z_impl_k_thread_custom_data_set@83-86@deps/zephyr/kernel/thread.c
       4      1     13      1       4 z_vrfy_k_thread_custom_data_set@89-92@deps/zephyr/kernel/thread.c
       4      1     11      0       4 z_impl_k_thread_custom_data_get@96-99@deps/zephyr/kernel/thread.c
       4      1     11      0       4 z_vrfy_k_thread_custom_data_get@102-105@deps/zephyr/kernel/thread.c
       4      2     17      0       4 z_impl_k_is_preempt_thread@111-114@deps/zephyr/kernel/thread.c
       4      1     11      0       4 z_vrfy_k_is_preempt_thread@117-120@deps/zephyr/kernel/thread.c
       4      1     14      1       4 z_impl_k_thread_priority_get@124-127@deps/zephyr/kernel/thread.c
       5      1     23      1       5 z_vrfy_k_thread_priority_get@130-134@deps/zephyr/kernel/thread.c
      15      4     95      2      26 z_impl_k_thread_name_set@138-163@deps/zephyr/kernel/thread.c
      14      5     77      2      24 z_vrfy_k_thread_name_set@166-189@deps/zephyr/kernel/thread.c
       6      2     25      1       9 k_thread_name_get@193-201@deps/zephyr/kernel/thread.c
       9      2     47      3      12 z_impl_k_thread_name_copy@203-214@deps/zephyr/kernel/thread.c
       7      1     43      4       9 copy_bytes@216-224@deps/zephyr/kernel/thread.c
      39      6    227      3      56 k_thread_state_str@226-281@deps/zephyr/kernel/thread.c
      22      7    140      3      30 z_vrfy_k_thread_name_copy@284-313@deps/zephyr/kernel/thread.c
      12      3     59      0      15 z_check_stack_sentinel@333-347@deps/zephyr/kernel/thread.c
      14      3     77      1      24 random_offset@353-376@deps/zephyr/kernel/thread.c
      52     14    328      3     120 setup_thread_stack@383-502@deps/zephyr/kernel/thread.c
      70     23    482     10     141 z_setup_new_thread@509-649@deps/zephyr/kernel/thread.c
      14      2     95     10      17 z_impl_k_thread_create@652-668@deps/zephyr/kernel/thread.c
       4      1     16      1       4 z_stack_is_user_capable@671-674@deps/zephyr/kernel/thread.c
      36      3    243     10      64 z_vrfy_k_thread_create@676-739@deps/zephyr/kernel/thread.c
      13      3     78      4      25 z_init_thread_base@743-767@deps/zephyr/kernel/thread.c
      20      5    143      4      31 k_thread_user_mode_enter@769-799@deps/zephyr/kernel/thread.c
      23      7    132      3      50 z_stack_space_get@806-855@deps/zephyr/kernel/thread.c
       9      3     55      2      12 z_impl_k_thread_stack_space_get@857-868@deps/zephyr/kernel/thread.c
      19      1     90      2      23 z_vrfy_k_thread_stack_space_get@871-893@deps/zephyr/kernel/thread.c
       6      1     26      1       6 z_vrfy_k_thread_timeout_remaining_ticks@899-904@deps/zephyr/kernel/thread.c
       6      1     26      1       6 z_vrfy_k_thread_timeout_expires_ticks@907-912@deps/zephyr/kernel/thread.c
       5      3     18      0      10 z_thread_mark_switched_in@917-926@deps/zephyr/kernel/thread.c
       9      6     38      0      17 z_thread_mark_switched_out@928-944@deps/zephyr/kernel/thread.c
      10      4     50      2      15 k_thread_runtime_stats_get@947-961@deps/zephyr/kernel/thread.c
      19      6    115      1      33 k_thread_runtime_stats_all_get@963-995@deps/zephyr/kernel/thread.c
      12      4     63      2      20 k_thread_runtime_stats_cpu_get@997-1016@deps/zephyr/kernel/thread.c
       8      2     53      1      31 defer_thread_cleanup@1030-1060@deps/zephyr/kernel/thread.c
       9      3     35      1      17 do_thread_cleanup@1062-1078@deps/zephyr/kernel/thread.c
      14      3     54      1      31 k_thread_abort_cleanup@1080-1110@deps/zephyr/kernel/thread.c
       9      2     33      1      13 k_thread_abort_cleanup_check_reuse@1112-1124@deps/zephyr/kernel/thread.c
      13      6     83      1      26 z_dummy_thread_init@1128-1153@deps/zephyr/kernel/thread.c
1 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
    611      13.7     3.6       78.2        41     deps/zephyr/kernel/thread.c

===========================================================================================================
!!!! Warnings (cyclomatic_complexity > 15 or length > 1000 or nloc > 1000000 or parameter_count > 100) !!!!
================================================
  NLOC    CCN   token  PARAM  length  location  
------------------------------------------------
      70     23    482     10     141 z_setup_new_thread@509-649@deps/zephyr/kernel/thread.c
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
       611      13.7     3.6       78.2       41            1      0.02    0.12

You should be able to see that z_setup_new_thread has a cyclomatic complexity of 23. Open the file and see it yourself what that 23 means as far understanding a foreign piece of SW.

In fact, one may even state that if there were no #ifdef, the code is understandable. It is true that Zephyr RTOS is well written from a cyclomatic complexity standpoint.

Note

If you want a not so good example, check out stm32l4xx_hal_spi.c which corresponds to a cyclomatic complexity bigger than 60.

In order to analyze your own project, head to the root of your simple car project, run the lizard tool and inspect the results. You can run the tool by entering:

// use lizard (note: "blinky" is the folder in which you put your own application)
lizard blinky deps/zpp_lib/zpp_rtos/thread.cpp

Question 1

  1. With lizard, what does the option -C mean?
  2. What C value would you set according to the theory?
Solution
  1. Threshold for cyclomatic complexity number warning
  2. Ideally 10 (12 topmost value)

Subsequently, run the cloc tool and inspect the results. You can run the tool by entering:

// use cloc (note: "blinky" is the folder in which you put your own application)
cloc blinky deps/zpp_lib/zpp_rtos/thread.cpp

Afterwards, check out what clang-format does. For doing so, first create a .clang-format at the root of your project with the following content:

---
  BasedOnStyle: Google
  IndentWidth: 4
---
  Language: Cpp
  ColumnLimit: 120
  AlignConsecutiveAssignments: true
  DerivePointerAlignment: false
  PointerAlignment: Left
  BinPackArguments: false
  BinPackParameters: false
Subsequently, run the command clang-format -i blinky/src/main.cpp.

Question 2

Respond to the following questions:

  1. has the file been modified?
  2. how can you check that? (write down the command)
  3. what is the meaning of option -i?
  4. what does ColumnLimit in .clang-format mean?
Solution
  1. Yes, it has
  2. git diff blinky/src/main.cpp
  3. Inplace edit files, if specified
  4. the maximum length of a line


Lastly, have a look at what cpplint.

Warning

Be sure you have created a CPPLINT.cfg file at the root of your project containing, at least, the line length aligned with .clang-format. Moreover, prevent cpplint from expecting include files in the same directory.

# Stop searching for additional config files.
set noparent
# Limit line length.
linelength=120
# Header files are not always placed in the same directory
# disables the build/include warnings
filter=-build/include
You will find other filters that can be activated in cpplint manual (aka cpplint.py 😉).

Issue:

  cpplint blinky/* 
and check how many violations there are. You will fix those in the next section.

Question 3

  1. What does -build/include mean? Hint: check cpplint “manual” and search for CheckIncludeLine function. There you will understand what checks are run.
  2. What do - and + mean in -build/include or +build/include_alpha?
  3. What are the directives in Google C++ Styleguide concerning namespaces? How can I disable that?
Solution
  1. Directly from the code
    # "include" should use the new style "foo/bar.h" instead of just "bar.h"
    # Only do this check if the included header follows google naming
    # conventions.  If not, assume that it's a 3rd party API that
    # requires special include conventions.
    #
    # We also make an exception for Lua headers, which follow google
    # naming convention but not the include convention.
    .
    .
    .
    # we shouldn't include a file more than once. actually, there are a
    # handful of instances where doing so is okay, but in general it's
    # not.
    .
    .
    .
    # We DO want to include a 3rd party looking header if it matches the
    # filename. Otherwise we get an erroneous error "...should include its
    # header" error later.
    
  2. The - disables a filter (e.g. build/include_order) and + enables one (e.g. build/include_alpha).
  3. The guideline is https://google.github.io/styleguide/cppguide.html#Namespaces and addresses
    "build/namespaces_headers",
    "build/namespaces/header/block/literals",
    "build/namespaces/header/block/nonliterals",
    "build/namespaces/header/namespace/literals",
    "build/namespaces/header/namespace/nonliterals",
    "build/namespaces/source/block/literals",
    "build/namespaces/source/block/nonliterals",
    "build/namespaces/source/namespace/literals",
    "build/namespaces/source/namespace/nonliterals",
    
    These correspond to
Warning File type Scope of using namespace Namespace type Example trigger
build/namespaces_headers Header namespace { } (unnamed namespace) in a .h file
build/namespaces/header/block/literals Header Inside a function/block {} Literals (std::literals, etc.) void f() { using namespace std::literals; } in .h
build/namespaces/header/block/nonliterals Header Inside a function/block {} Any other namespace void f() { using namespace foo; } in .h
build/namespaces/header/namespace/literals Header At namespace/file scope Literals using namespace std::literals; at top of .h
build/namespaces/header/namespace/nonliterals Header At namespace/file scope Any other namespace using namespace foo; at top of .h
build/namespaces/source/block/literals Source Inside a function/block {} Literals void f() { using namespace std::literals; } in .cc
build/namespaces/source/block/nonliterals Source Inside a function/block {} Any other namespace void f() { using namespace foo; } in .cc
build/namespaces/source/namespace/literals Source At namespace/file scope Literals using namespace std::literals; at top of .cc
build/namespaces/source/namespace/nonliterals Source At namespace/file scope Any other namespace using namespace foo; at top of .cc

One can disable these by simply adding -build/namespaces.

Info

In C++, literals are user-defined literal namespaces — namespaces that exist solely to bring literal suffix operators into scope, like s, ms, etc. Examples are:

using namespace std::string_view_literals;  // "foo"sv
using namespace std::chrono_literals;       // 100ms, 2s, 1h
using namespace std::complex_literals;      // 2i
More information under https://en.cppreference.com/index.html.

Fixing the issues

You have tested most tools and inspected results. However, the goal is to ensure qualitatively good code. As a result:

  • create a dedicated git branch for fixing the eventual issues
  • use lizard -C 10 blinky bme280 semaphores .
    Fix all the functions - if any - crossing the 10 maximal value for the cyclomatic complexity.
  • commit your changes (git add * and git commit -m "fix: cyclomatic complecity issues")
  • apply clang-format -i blinky/src/* bme28/src/* semaphores/src/*
  • commit your changes (git add * and git commit -m "fix: formatting issues with clang-format")
  • run cpplint blinky/src/* bme28/src/* semaphores/src/*

    Note

    There is a case in which you are explicitly asked to reduce the scope. Namely, you likely had something like

    using namespace std::literals;
    
    and you need to reduce it scope (as the above imports everything) to solely what you use

    using std::literals::chrono_literals::operator""ms;  // Just the ms suffix
    
  • commit your changes (git add * and git commit -m "Fixed Google Styleguides issues")

REMEMBER: make small commits!

As we learned, small commits are instrumental to a proper way of developing. Make sure to respect it when you are fixing the issues. That is, you will commit only one step at a time - not all changes at once.

Use spaces, not tabs

Make sure your editor makes use of spaces instead of tabs so as to act according to the chosen styleguide).

Note

There may be issues flagged by cpplint that may not seem trivial. However:

  • check Google’s Styleguide concerning the rationale of the rule
  • control the Styleguide Script as it may help the understanding
  • be aware of compiler’s behaviours. For instance const static aVariable is accepted by the compiler but it is not standard compliant ( static const aVariable is)
  • although one could disable a rule with -filter, it is strongly suggested not to - and if so, only with a written justification

Copyrighting your work

No matter what SW one writes, a copyright shall always be specified - no matter whether open source or proprietary in its nature. With cpplint, in case one forgot, the following error will be shown ” No copyright message found. You should have a line: Copyright [year] <Copyright Owner> “. An example of such copyright can be found below:

/**
******************************************************************************
* @file        : monnalisa.h
* @brief       : pensive woman module
* @author      : Leonardo Da Vinci <leo@davinci.net>
* @date        : 19. March 1503
******************************************************************************
* @copyright   : Copyright (c) 1503 
*                Haute école de peinture de Florence
* @attention   : SPDX-License-Identifier: MIT OR Apache-2.0
******************************************************************************
* @details
* Pensive woman module for impressing mankind
******************************************************************************
*/

Putting all together: pre-commit

Now that you are capable of checking the code, a question arises: how to ensure to always remember to execute this? One way would be to check it centrally on a CI/CD pipeline, but that would be late (and consume energy for nothing). Do not worry!

We got you covered: pre-commit does exactly what its name implies. It will run a serie of checks prior to committing. In order to install it, head to https://pre-commit.com/ and follow the instructions.

So, once installed, we put all the tools seen so far by following the following instructions:

  • create a .pre-commit-config.yaml at the root of your git project with the following content:
files: ^blinky/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v6.0.0
  hooks:
  -   id: check-yaml
      args: [--allow-multiple-documents]
      files: \.(yaml|yml)$
  -   id: end-of-file-fixer
      files: \.(yaml|yml|c|cpp|cc|cxx|h|hpp)$
  -   id: trailing-whitespace
      files: \.(yaml|yml|c|cpp|cc|cxx|h|hpp)$
- repo: https://github.com/pre-commit/mirrors-clang-format
  rev: 'v21.1.8'
  hooks:
  -   id: clang-format
      files: \.(c|cpp|cc|cxx|h|hpp)$
- repo: https://github.com/cpplint/cpplint
  rev: '2.0.2'
  hooks:
  -   id: cpplint
      files: \.(c|cpp|cc|cxx|h|hpp)$
- repo: https://github.com/johnor/pre-commit-lizard
  rev: 'v1.17.10' 
  hooks:
    - id: lizard-cpp
      name: lizard
      args: [-C 12]
      files: \.(c|cpp|cc|cxx|h|hpp)$
  • add .pre-commit-config.yaml to your git repository
  • install the hooks with pre-commit install
  • run the command manually by issuing pre-commit run --all-files
  • if you did a good job in Fixing the issues, all will be green
  • That’s it!. From now on, all code changes will undergo the specified checks

Question 4

Respond to the following questions:

  1. what does ^(blinky) under files mean? (hint: check https://regex101.com/)
  2. how can one ensure that all other applications (bme280, semaphores, …) are considered?
  3. what does [-C 12] mean in this instance?
  4. what would imply using repo: local in a pre-commit setup?
Solution
  1. It is a regex specifying on what the hooks will be applied
  2. Apply the appropriate regex files: ^blinky/|^bme280/ as an example
  3. It specifies the maximum cyclomatic complexity accepted
  4. It means that the procedure uses a locally installed tool. So, one needs to a) ensure the presence of the tool on the machine, b) handle the updates manually and c) manage the accessibility of the tool (e.g. tool present in the $PATH)

Obviously, it would be best not to rely on language: system ;-)

Run the analysis on your code

On a dedicated branch (named misra_fixes), run the analysis on your code to find eventual issues in your application (including MISRA). Go about fixing them one at a time and commit the fixes on the dedicated branch .

pre-commit will be part of the git repository and will throw no errors.

Hint

Important: only run the analysis on the files you created as specified above (or whatever folder(s) you put your own implementation), not those coming from 3rd party.

Metrics - Going beyond

Although we will use the above tools throughout the course, there is a multitude of other metrics that will help you assess the code quality. Scitool Understand, for which you have now received a license, will offer you a plethora of options under the “Metrics->Metrics Browser”. You have access to the metrics definitions by clicking on the following icon: