The purpose of the Kodo unit tests is to assert that the various Kodo
features work across a number of different platforms and
compilers. Having an extensive unit test suite makes it
easier for developers to ensure that their changes work as expected
and do not cause regressions in unexpected areas of the library.

The goal is that all features in Kodo should have a corresponding test
case. Often a test case is added while when implementing a new
feature. This makes it possible to assert that the new functionality
works as expected as early as possible.

The unit tests are implemented using the Google C++ Testing Framework
(gtest) which defines a bunch of helpers useful when writing
tests. You can find more information on gtest on their homepage.

The test binary is built using the waf build scripts shipped as part
of the Kodo source code. You can read more about how you can get the
source code and how to build it in the Getting Started section.

Once the code is built, the test binary will be located in a subfolder of the
build folder that depends on your platform:

Running the unit tests may take a long time on mobile and embedded
platforms, since we test with an extensive set of parameters. You
can lower the complexity and speed up the tests if you invoke the
test binary with the embedded profile:

The purpose of this is to make it easy to find the unit test for a
specific class. In some cases it makes sense to have multiple classes
tested in the same file. In those cases we still make a placeholder
cpp file referring to the actual cpp file where the test can be
found. An example of this can be seen for some of the codecs e.g. the
class full_rlnc_encoder located in
src/kodo/rlnc/full_rlnc_encoder.hpp is tested in
full_rlnc_codes.cpp but the place-holder still exists.

The placeholder file in this cases
(test/src/test_full_rlnc_encoder.cpp) looks like the following:

In some cases we have headers containing only type-aliases such as
using statements. We currently do not require that
these are explicitly unit tested.

Regardless of whether a unit test is implemented or not we still leave
a .cpp file in the test folder.

An example is src/kodo/partial_mutable_shallow_storage_layers.hpp
which only contains a single using declaration. It does not have
any explicit unit tests but a place holder file is still created
(test/src/test_mutable_partial_shallow_symbol_storage.cpp):

// Copyright Steinwurf ApS 2015.// Distributed under the "STEINWURF EVALUATION LICENSE 1.0".// See accompanying file LICENSE.rst or// http://www.steinwurf.com/licensing/// @file test_mutable_partial_shallow_symbol_storage.cpp Unit tests for the/// mutable_partial_shallow_symbol_storage class#include<kodo_core/mutable_partial_shallow_symbol_storage.hpp>#include<cstdint>#include<storage/storage.hpp>#include<storage/mutable_storage.hpp>#include<storage/is_same.hpp>#include<gtest/gtest.h>#include<stub/function.hpp>namespacekodo_core{// Put dummy layers and tests classes in an anonymous namespace// to avoid violations of ODF (one-definition-rule) in other// translation unitsnamespace{/// Helper layerclassdummy_layer{public:template<classFactory>voidinitialize(Factory&the_factory){m_initialize();symbol_size.set_return(the_factory.symbol_size());}stub::function<void()>m_initialize;stub::function<uint32_t()>block_size;stub::function<uint32_t()>symbol_size;stub::function<uint32_t()>symbols;stub::function<void(uint32_t,conststorage::mutable_storage&)>set_mutable_symbol;stub::function<void(conststorage::mutable_storage&)>set_mutable_symbols;};// Helper stackclassdummy_stack:publicmutable_partial_shallow_symbol_storage<dummy_layer>{};// Helper factoryclassdummy_factory{public:stub::function<uint32_t()>symbol_size;};}}// Test that the stack functions properly when the partial symbol is// not neededTEST(test_mutable_partial_shallow_symbol_storage,no_partial){kodo_core::dummy_factoryfactory;uint32_tsymbol_size=5U;factory.symbol_size.set_return(symbol_size);kodo_core::dummy_stackstack;kodo_core::dummy_layer&layer=stack;stack.initialize(factory);// This is initialize in the dummy layer using the factoryEXPECT_EQ(stack.symbol_size(),5U);EXPECT_TRUE(layer.m_initialize.expect_calls().with().to_bool());EXPECT_FALSE(stack.has_partial_symbol());// Set the remaining needed state in the stackuint32_tsymbols=5U;layer.symbols.set_return(symbols);uint32_tblock_size=25U;layer.block_size.set_return(block_size);// Make a buffer that perfectly fits 5 symbols of size 5 bytesstd::vector<uint8_t>data(block_size);stack.set_mutable_symbols(storage::storage(data));EXPECT_FALSE(stack.has_partial_symbol());EXPECT_EQ(0U,layer.set_mutable_symbol.calls());EXPECT_TRUE(layer.set_mutable_symbols.expect_calls().with(storage::storage(data)).to_bool());}// Test that the stack functions properly when the partial symbol is// neededTEST(test_mutable_partial_shallow_symbol_storage,partial){kodo_core::dummy_factoryfactory;uint32_tsymbol_size=5U;factory.symbol_size.set_return(symbol_size);kodo_core::dummy_stackstack;kodo_core::dummy_layer&layer=stack;stack.initialize(factory);// This is initialize in the dummy layer using the factoryEXPECT_EQ(stack.symbol_size(),5U);EXPECT_TRUE(layer.m_initialize.expect_calls().with().to_bool());EXPECT_FALSE(stack.has_partial_symbol());// Set the remaining needed state in the stackuint32_tsymbols=5U;layer.symbols.set_return(symbols);uint32_tblock_size=25U;layer.block_size.set_return(block_size);// Make a buffer that is 23 bytes. That means we will have 4// symbols of 5 bytes and one symbol which only be 3 bytes.std::vector<uint8_t>data(block_size-2U,'a');stack.set_mutable_symbols(storage::storage(data));EXPECT_TRUE(stack.has_partial_symbol());// To check that the calls made to the set_mutable_symbols functions// are what we expect we need a custom predicate function to// compare the arguments. The reason is that we need to use the// is_same function to compare the storage::mutable_storage// objects. is_same compares that the pointers point to the same// memory as opposed to just checking whether the content is equalusingparameter=std::tuple<uint32_t,storage::mutable_storage>;autocompare=[](constparameter&a,constparameter&b)->bool{if(std::get<0>(a)!=std::get<0>(b))returnfalse;if(storage::is_same(std::get<1>(a),std::get<1>(b)))returntrue;returnfalse;};EXPECT_EQ(layer.set_mutable_symbol.calls(),5U);// The actual calls that were madeautozero=std::make_tuple(0,storage::mutable_storage(&data[0],5U));autoone=std::make_tuple(1,storage::mutable_storage(&data[5],5U));autotwo=std::make_tuple(2,storage::mutable_storage(&data[10],5U));autothree=std::make_tuple(3,storage::mutable_storage(&data[15],5U));autofour=std::make_tuple(4,storage::mutable_storage(&data[20],5U));EXPECT_TRUE(compare(layer.set_mutable_symbol.call_arguments(0),zero));EXPECT_TRUE(compare(layer.set_mutable_symbol.call_arguments(1),one));EXPECT_TRUE(compare(layer.set_mutable_symbol.call_arguments(2),two));EXPECT_TRUE(compare(layer.set_mutable_symbol.call_arguments(3),three));EXPECT_FALSE(compare(layer.set_mutable_symbol.call_arguments(4),four));}

The Kodo library is build using the
parameterized-inheritance/ mixin-layers C++ design
technique. When unit testing a layer we try to isolate it as much as
possible. To do this we typically introduce dummy layers with the sole
purpose of satisfying the layer’s dependencies. To see this in action
let’s look at one of the existing unit tests.

The storage_bytes_used layer is used when we want add
functionality allowing us to keep track of how many useful bytes an
encoder or decoder contains.

Note

In general the amount of data which can be encoded or
decoded will be determined by the number of symbols we are
coding and the size of every symbol in bytes (we call this
the block size). However, in practical applications we
sometimes do not have enough data to fill an entire
block. In those cases we can add the storage_bytes_used
layer to embed in every encoder and decoder the ability to
store the number of actual data bytes.

// Copyright Steinwurf ApS 2011.// Distributed under the "STEINWURF EVALUATION LICENSE 1.0".// See accompanying file LICENSE.rst or// http://www.steinwurf.com/licensing#pragma once#include<cassert>#include<cstdint>namespacekodo_core{/// @ingroup storage_info_layers////// @brief Provides access to the number of useful bytes used out/// of the total size of the encoders or decoders storage.template<classSuperCoder>classstorage_bytes_used:publicSuperCoder{public:/// Constructorstorage_bytes_used():m_bytes_used(0){}/// @copydoc layer::initialize()template<classFactory>voidinitialize(Factory&the_factory){SuperCoder::initialize(the_factory);m_bytes_used=0;}/// @copydoc layer::set_bytes_used()voidset_bytes_used(uint32_tbytes_used){assert(bytes_used>0);assert(bytes_used<=SuperCoder::block_size());m_bytes_used=bytes_used;}/// @copydoc layer::bytes_used()uint32_tbytes_used()const{returnm_bytes_used;}protected:/// The number of bytes useduint32_tm_bytes_used;};}

As seen, the layer depends on two functions being provided by the
SuperCoder:

SuperCoder::initialize(the_factory)

SuperCoder::block_size()

Using our Doxygen documentation, it is possible to look up the
purpose of the two undefined functions.

In this case we want to check that the state is correctly updated when
calling set_bytes_used and that the state is correctly reset when
calling initialize. The following unit test code was implemented
in test/src/test_storage_bytes_used.cpp to test this:

When we define a test using gtest we use the TEST(test_case_name,test_name) macro to define and name a test function. In Kodo we use
the following naming guideline:

The test_case_name should be name according to its placement in
the test/src directory. If the file is place in the root of the
test/src folder e.g. test/src/test_my_fancy_code.cpp we
name the test_case_name as test_my_fancy_code. Similarly if
the file is placed in a subdirectory
e.g. object/test_new_code.cpp we will specify the
test_case_name as object_test_new_code. This should make
it easy to find the source code of a failing unit test. Remember to
place the test file as described in
Namespaces and directories

The test_name is up to the developer but should be as
descriptive of the purpose of the unit test as possible.