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
- You need to have completed the getting started with Zephyr RTOS codelab.
- You need to have finished the Digging into ZephyrOS codelab.
- You need to have finished Scheduling of periodic tasks
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 cpplintto 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 lizardto installlizard.
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
- MacOS:
cloc- MacOS:
brew install cloc - Linux:
pip3 install cloc
- MacOS:
cpplint- MacOS:
brew install cpplint - Linux:
pip3 install cpplint
- MacOS:
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 --versioncpplint --versioncloc --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
- With
lizard, what does the option-Cmean? - What
Cvalue would you set according to the theory?
Solution
- Threshold for cyclomatic complexity number warning
- 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
clang-format -i blinky/src/main.cpp.
Question 2
Respond to the following questions:
- has the file been modified?
- how can you check that? (write down the command)
- what is the meaning of option
-i? - what does
ColumnLimitin.clang-formatmean?
Solution
- Yes, it has
git diff blinky/src/main.cpp- Inplace edit
files, if specified - 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
cpplint manual (aka
cpplint.py 😉).
Issue:
cpplint blinky/*
Question 3
- What does
-build/includemean? Hint: checkcpplint“manual” and search forCheckIncludeLinefunction. There you will understand what checks are run. - What do
-and+mean in-build/includeor+build/include_alpha? - What are the directives in Google C++ Styleguide concerning
namespaces? How can I disable that?
Solution
- 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. - The
-disables a filter (e.g.build/include_order) and+enables one (e.g.build/include_alpha). - The guideline is
https://google.github.io/styleguide/cppguide.html#Namespaces and addresses
These correspond to
"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",
| 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
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 branchfor 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 *andgit commit -m "fix: cyclomatic complecity issues") - apply
clang-format -i blinky/src/* bme28/src/* semaphores/src/* - commit your changes (
git add *andgit 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
and you need to reduce it scope (as the above imports everything) to solely what you useusing namespace std::literals;using std::literals::chrono_literals::operator""ms; // Just the ms suffix -
commit your changes (
git add *andgit 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 aVariableis accepted by the compiler but it is not standard compliant (static const aVariableis) - 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.yamlat the root of yourgitproject 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.yamlto yourgitrepository - 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:
- what does
^(blinky)underfilesmean? (hint: check https://regex101.com/) - how can one ensure that all other applications (
bme280,semaphores, …) are considered? - what does
[-C 12]mean in this instance? - what would imply using
repo: localin apre-commitsetup?
Solution
- It is a regex specifying on what the hooks will be applied
- Apply the appropriate regex
files: ^blinky/|^bme280/as an example - It specifies the maximum cyclomatic complexity accepted
- 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 ;-)
Note
For those who would like to know more about git hooks and pre-commit,
here a few interesting links:
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:
