LibreMesh has unit tests that help us add new features while keeping maintenance effort contained.
We encourage contributors to write tests when adding new functionality and also while fixing regressions.
LibreMesh unit testing is based in the powerful busted library which has a very good documentation.
Tests are run inside a x86_64 Docker image with some lua and openwrt libraries avaible.
We also have a development qemu virtual machine, this is a full libremesh image that can be used in development.
This will build the testing Docker image automaticaly in the first run and then execute the tests and create the coverage report. Note: you must have Docker installed and running.
Use LUA_ENABLE_LOGGING=1 ./run_tests
if you want to send the logging to stdout.
The lua code of package foo
should be in the expanded files tree structure form:
package/foo/files/usr/lib/lua/foo.lua
Test files live inside a tests
directory with its names begining with test_
:
package/foo/tests/test_foo.lua
package/foo/tests/test_utils.lua
Testing utilities, fake libraries and integration tests live inside a root tests/
directory:
tests/test_some_integration_tests.lua
tests/test_other_general_tests.lua
tests/fake/bazlib.lua
tests/tests/test_bazlib.lua
Here is a very simple test of a library foo
:
local foo = require 'foo'
describe('foo library tests', function()
it('very simple test of f_sum(a, b)', function()
assert.is.equal(4, foo.f_sum(2, 2))
assert.is.equal(2, foo.f_sum(2, 0))
assert.is.equal(0, foo.f_sum(2, -2))
end)
end)
If you need to test something that directly or indirectly uses the configuration uci
library then you must do the following in order to have a clean and temporary uci
environment for each test:
local foo = require 'foo'
local test_utils = require 'tests.utils'
local uci -- do not forget this line!
describe('foo lib tests', function()
it('test directly using uci', function()
uci:set('wireless', 'radio0', 'wifi-device')
uci:set('wireless', 'radio0', 'type', 'mac80211')
-- this updates a config file in /tmp/tmpdir.XYZ/config/wireless
uci:commit('wireless')
assert.is.equal('mac80211', foo.get_radio_type('radio0')
end)
before_each('', function()
-- this creates a temporary and fresh uci envornment for each test. There is no initial configuration, you have to create the config somehow inside the test.
uci = test_utils.setup_test_uci()
end)
after_each('', function()
-- this cleans the temporary uci envornment
test_utils.teardown_test_uci(uci)
end)
end)
You also must use lime.config.get_uci_cursor()
when you need a uci
cursor, instead of using libuci:cursor()
.
This way the functions test_utils.setup_test_uci()
and test_utils.teardown_test_uci(uci)
can do its work providing a shared and clean uci
config environment for each test. (Note: if something is not working as expected make sure all the uci cursors in all the code you use and its dependencies use the cursor provided by lime.config.get_uci_cursor()
)
- Libraries should provide a way to change *hardcoded things. For example using module variables to declare paths and then at the test code override this variable:
-- file foo.lua
foo.search_paths = {"/usr/lib/lua/lime/hwd/*.lua"}
- Execution of commands with side effects, like
os.execute('reboot')
, should be put inside a library function likefoo._reboot()
and then using stubs or mocks in the tests. Even better is to separate the logic part of the code of the executional part so in the test you don't even have tomock
this. - Put special atention on untrivial logic: regex, parsing, multiple ifs, nested conditions.
- Testing trivialities is not helpful, but if you have trivial code you should have at least one test just to run through the code so you know you don't have syntax or require errors. This helps to future developers when then want to perform refactoring.
- Use
setup() / teardown()
andbefore_each() / after_each()
to refactor repeated code in the tests. - Look to existing tests to find inspiration.
- Ask for help or advice in a pull request!
Coverage is measured using the luacov library each time the tests are run. The results statistics are merged at ./luacov.stats.out
and a human friendly report is generated at luacov.report.out
.
As one of the goals is that it must be easy for developers to write, modify and run the tests we created some simple tools to do this:
- testing image ->
Dockerfiles/Dockerfile.unittests
- testing shell environment ->
tools/dockertestshell
- running the tests ->
./run_tests
script
To provide an easy way to develop or test things within the docker image there is a tool that opens a bash shell inside the docker image that has some features that allows easy development:
/home/$USER
is mounted inside the docker image so each change you do to the code from inside is maintained when you close the docker container- the same applies to
/tmp
- you have the same user outside and inside
- network access is garanted
- and some goodies like bashrc, some useful ENV variables, PS1 modification, etc.
To enter the shell environment run:
[lime-packages]$ ./tools/dockertestshell
(docker) [lime-packages]$
You can see that the prompt is changed adding (docker)
in the left part so you can easily remember that you are inside the docker container.
This environment is also used by run_tests
script.
The idea behind this script is simple:
- creates the testing docker image if it is not available
- sets the search path of the tests for
buster
- sets the lua library paths, prepending the fake library paths and adding the paths to the libremesh packages with
packages/lime-system/files/usr/lib/lua/?.lua
. This doesn't work automaticaly for every package if the paths does not use the files/path/to/final/destination. So if you want to test some package without the files convention maybe it would be good to move the package to this convention. Also it does not work if the lua module we want to test does not finish with.lua
, in this case the path must be explicitly added (how to do this here). - runs the tests using the dockertestshell
run_tests
also passes the first argument as an argument to busted so you can do things like./run_tests --help
to see the busted help, or ./run_tests '--tags=footag --verbose'
so only the tests that have the tag #footag
in the description of the test are run
This image is not a perfect firmware image, it does not have wifi for example but ethernet network
LAN and WAN is supported. All the files inside the packages files/
can be copied into the rootfs,
overwriting a precooked image that is a full LibreMesh x86_64 image.
So don't expect that everything runs exactly as in a wireless router but most things will perform
as expected:
- initialization scripts: uci-defaults, init.d, etc
- lime-config
- ubus / rpcd
- lime-app
ICMPv4 does NOT work between qemu nodes, so ping (v4) will not work as expected. Everything else (including ICMPv6 i.e. ping6) does work as expected, however.
You will need a rootfs and ramfs LibreMesh files. To generate one you can use a libremesh buildroot and select x86_64 target and select the option to generate an initramfs.
Prebuilt development images can be downloaded from here:
- http://repo.libremesh.org/tmp/openwrt-18.06-x86-64-generic-rootfs.tar.gz
- http://repo.libremesh.org/tmp/openwrt-18.06-x86-64-ramfs.bzImage
Install the package qemu-system-x86_64
if you don't have already installed.
Up to 100 qemu nodes can be setup. Use the --node-id N
. All the node's LAN interfaces are
bridged together. You can use --enable-wan
in only one of the nodes to share your internet connection
to the network.
$ sudo ./tools/qemu_dev_start path/to/openwrt-x86-64-generic-rootfs.tar.gz path/to/openwrt-x86-64-ramfs.bzImage
$ ./tools/qemu_dev_stop
### apt install ansible, if you don't have it already
$ cd tools/ansible/
$ sudo ansible-playbook qemu_cloud_start.yml
### use clusterssh to manage the nodes
$ sudo ansible-playbook qemu_cloud_stop.yml
If you want to update the qemu image with new LibreMesh files of a local repository you can use
the option --libremesh-workdir path/to/workdir
, for example:
$ sudo ./tools/qemu_dev_start path/to/rootfs.tar.gz path/to/bzImage --libremesh-workdir .
Use the --enable-wan IFC
, this will create a NAT and share your internet connection to the virtual machine
using the specified interface IFC.
If you want to test a specific version of the Lime-App you can copy the build files into the lime-app package after each build:
[lime-app ]$ mkdir -p path/to/lime-packages/packages/lime-app/files/www/app/
[lime-app ]$ npm run build:dev_router && cp -r build/* path/to/lime-packages/packages/lime-app/files/www/app/