Skip to content

Commit

Permalink
Updated unit tests (rake) to be called from make.
Browse files Browse the repository at this point in the history
The reason is that make manages the build configuration (defines and so on) and we want to reuse them in the unit tests. When rake is called from make, all defines (actually compiler flags) are passed on to rake to be used when building the unit tests.
Also added a light weight “annotation” that allows unit test files to be excluded based on defines.
  • Loading branch information
krichardsson committed Nov 17, 2016
1 parent 157fb23 commit c3eee25
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 52 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,6 @@ include tools/make/targets.mk

#include dependencies
-include $(DEPS)

unit:
rake unit "DEFINES=$(CFLAGS)" "FILES=$(FILES)"
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,37 @@ openocd : Launch OpenOCD

# Unit testing

## Running all unit tests

With the environment set up locally

make unit
with the docker builder image and the toolbelt

tb make unit
## Running one unit test
When working with one specific file it is often convinient to run only one unit test

make unit FILES=test/utils/src/TestNum.c

or with the toolbelt

tb make unit FILES=test/utils/src/TestNum.c
## Running unit tests with specific build settings
Defines are managed by make and are passed on to the unit test code. Use the
normal ways of configuring make when running tests. For instance to run test
for Crazyflie 1

make unit PLATFORM=CF1

## Dependencies

Frameworks for unit testing are pulled in as git submodules.
Frameworks for unit testing and mocking are pulled in as git submodules.

The testing framework uses ruby and rake to generate and run code.

Expand All @@ -159,13 +187,3 @@ image (bitcraze/builder) that contains all tools needed. All scripts in the
tools/build directory are intended to be run in the image. The
[toolbelt](https://wiki.bitcraze.io/projects:dockerbuilderimage:index) makes it
easy to run the tool scripts.

### Running unit tests

With the environment set up locally

rake

with the docker builder image and the toolbelt

tb test
3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ DEFAULT_CONFIG_FILE = './tools/test/gcc.yml'
configure_toolchain(DEFAULT_CONFIG_FILE)

task :unit do
# This prevents all argumets after 'unit' to be interpreted as targets by rake
ARGV.each { |a| task a.to_sym do ; end }

if ARGV.length == 0
parse_and_run_tests([])
else
Expand Down
2 changes: 2 additions & 0 deletions test/deck/drivers/src/TestLpsTdoaTag.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @IGNORE_IF_NOT PLATFORM_CF2

// File under test lpsTwrTag.h
#include "lpsTdoaTag.h"

Expand Down
2 changes: 2 additions & 0 deletions test/deck/drivers/src/TestLpsTwrTag.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @IGNORE_IF_NOT PLATFORM_CF2

// File under test lpsTwrTag.h
#include "lpsTwrTag.h"

Expand Down
2 changes: 1 addition & 1 deletion tools/build/test
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ set -e

scriptDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

rake -f ${scriptDir}/../../Rakefile unit "${@}"
make unit "${@}"
83 changes: 43 additions & 40 deletions tools/test/rakefile_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,14 @@ def report_summary
end

def parse_and_run_tests(args)
defines = find_defines_in_args(args)
test_files = find_test_files_in_args(args)

# No file names found in the args, find all files that are unit test files
if test_files.length == 0
test_files = get_unit_test_files
test_files = exclude_test_files(get_unit_test_files(), defines)
end

defines = find_defines_in_args(args)

run_tests(test_files, defines)
end

Expand Down Expand Up @@ -288,56 +287,60 @@ def build_application(main)
link_it(main_base, obj_list)
end

# Any argument without '=' in it will be considered to be a file name
def find_test_files_in_args(args)
args.select do |arg|
not arg.include? '='
key = 'FILES='
args.each do |arg|
if arg.start_with?(key)
return arg[(key.length)..-1].split(' ')
end
end
end

# Parse the arguments and find all defines that are passed in on the command line
# We support two formats
# 1. SOME_DEF=SOME_VALUE ==> generate define based on context
# 2. "EXTRA_CFLAGS=-DFIRST_DEF -DSECOND_DEF" ==> just set the define(s) in the list
# Defines are part of compiler flags and start with -D, for instance -DMY_DEFINE
# All compiler flags are passed in as one string
def find_defines_in_args(args)
non_files = args.select do |arg|
arg.include? '='
end

defines = []
non_files.each do |arg|
parts = arg.split "="

if parts[0] == "EXTRA_CFLAGS"
defines.concat parse_cflags(parts[1])
else
defines.concat handle_make_arg(parts[0], parts[1])
key = 'DEFINES='
args.each do |arg|
if arg.start_with?(key)
return extract_defines(arg[(key.length)..-1])
end
end
return defines
end

def parse_cflags(flags)
flags.split(' ').map do |flag|
# Remove '-D' and return the rest of the string
flag[2..-1]
def extract_defines(arg)
arg.split(' ').select {|part| part.start_with?('-D')}.map {|flag| flag[2..-1]}
end

def exclude_test_files(files, defines)
files.select do |file|
annotation_keep_file?(file, defines)
end
end

def handle_make_arg(name, value)
case name
when 'PLATFORM'
return ['PLATFORM_' + value]
when 'ESTIMATOR'
return ['ESTIMATOR_TYPE_' + value]
when 'CONTROLLER'
return ['CONTROLLER_TYPE_' + value]
when 'POWER_DISTRIBUTION'
return ['POWER_DISTRIBUTION_TYPE_' + value]
when 'DEBUG'
return ['DEBUG=' + value]
else
[]

# WARNING This implementation is fairly brittle. Basically only intended for cases such as
# // @IGNORE_IF_NOT PLATFORM_CF2
# Ignores values of defines, so this would fail and keep the file
# param to gradle: -DMYDEFINE=0
# // @IGNORE_IF_NOT MYDEFINE
def annotation_keep_file?(file, defines)
ignore_str = '@IGNORE_IF_NOT'

File.foreach( file ) do |line|
if line.include? ignore_str
tokens = line.split(' ')
index = tokens.index ignore_str

if tokens.length >= (index + 2)
condition = tokens[index + 1]
return defines.detect {|define| define == condition}
else
return false
end
end
end

return true
end
end

0 comments on commit c3eee25

Please sign in to comment.