Running tests in container

Recently, I needed to create a simple network configuration observer in Linux environment. The decision was made to use libnl. However, due to the fact that the project is conducted in C++, a simple facade to this library was cretated. Now, the questions arises: how to test this facade? Since it does not only provide access for retreiving network configuration information, but also a mechanism for change notification and RAII style subscriptions, obviously testing it makes sense.
In order to have a common enviroment with same network configuration, which can be used on different machines, with different network gear, I decided to exploit the possibilities which are given by Linux containers.

You can find cmake project with example layout in https://bitbucket.org/ltekieli/isolated

What's going on here is that a custom target is added, which runs the tests in a container:

add_executable(${PROJECT_NAME}_UT EXCLUDE_FROM_ALL  
    catch.hpp
    main.cpp
)

add_custom_target(ut  
    COMMAND ${PROJECT_SOURCE_DIR}/test/tools/run_lxc.sh
        ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_UT
    DEPENDS ${PROJECT_NAME}_UT
    COMMENT "Running unit tests in lxc"
)

The interesting part is in the run_lxc.sh script.

Due to the fact that lxc requires root privileges first we need to ask for them:

if [ $EUID != 0 ]; then  
    sudo "$0" "$@"
    exit $?
fi  

Then, a check is made to ensure that the argument provided to the script is an executable:

if [ ! -x "$1" ]; then  
    (>&2 echo "$1 is not an executable")
    exit 1
fi  

After that the container needs to be created:

lxc-create \  
    -n "${CONTAINER_NAME}" \
    -t "${CONTAINER_TEMPLATE}" \
    -- --packages="${CONTAINER_PACKAGES}"

The CONTAINER_PACKAGES variable contains all the additional software we want to have inside the container. In this case we would like to have iproute2 and vlan packages available.

Next we need to copy container config file and the executable to conatiner's rootfs:

cp "${EXECUTABLE_PATH}" "${CONTAINER_PATH}/rootfs/home/ubuntu/"  
cp "${SCRIPT_DIR}/config" "${CONTAINER_PATH}/"  

The original config file is modified to use custom network interfaces:

# Network configuration
lxc.network.type = phys  
lxc.network.link = dummy0  
lxc.network.flags = up  
lxc.network.hwaddr = 00:16:3e:b0:23:1b  

To have the dummy interfaces, the dummy kernel module needs to be loaded on the host:

$ modprobe dummy numdummies=2

$ ifconfig -a
dummy0    Link encap:Ethernet  HWaddr 2e:7f:2c:62:58:80  
          BROADCAST NOARP  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

dummy1    Link encap:Ethernet  HWaddr c6:24:cf:29:7a:33  
          BROADCAST NOARP  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

After that the test executable can be run:

lxc-start -n "${CONTAINER_NAME}"  
lxc-attach -n "${CONTAINER_NAME}" \  
    -- sh -c "/home/ubuntu/${EXECUTABLE_NAME}"
lxc-stop -n "${CONTAINER_NAME}"  

And some cleanup:

rm "${CONTAINER_PATH}/rootfs/home/ubuntu/${EXECUTABLE_NAME}"  
rmmod dummy  

The output:

$ ./run_lxc.sh ~/workspace/isolated-build/test/isolated_UT 
Container already exists  
===============================================================================
All tests passed (1 assertion in 1 test case)

Using a container with fixed network configuration enables you to easily test code which operates on libnl, without the need of providing complicated stubs.