Skip to content

Commit 29dfd5b

Browse files
authored
Merge pull request #734 from lorjala/unittests
Support for unit testing
2 parents f65f141 + 6a5bef4 commit 29dfd5b

File tree

2 files changed

+199
-98
lines changed

2 files changed

+199
-98
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This document covers the installation and usage of Mbed CLI.
2525
2. [Compiling and running tests](#compiling-and-running-tests)
2626
3. [Limiting the test scope](#limiting-the-test-scope)
2727
4. [Test directory structure](#test-directory-structure)
28+
1. [Unit testing](#unit-testing)
2829
1. [Publishing your changes](#publishing-your-changes)
2930
1. [Checking status](#checking-status)
3031
2. [Pushing upstream](#pushing-upstream)
@@ -727,6 +728,65 @@ As shown above, tests exist inside `TESTS\testgroup\testcase\` directories. Plea
727728

728729
<span class="notes">**Note:** `mbed test` does not work in applications that contain a `main` function that is outside of a `TESTS` directory.</span>
729730

731+
## Unit testing
732+
733+
Use the `mbed test --unittests` command to build and run unit tests, or to generate files for new unit tests.
734+
735+
Build and run unit tests with `mbed test --unittests`. The arguments are:
736+
737+
* `--compile` to only compile unit tests.
738+
* `--run` to only run unit tests.
739+
* `-c` or `--clean` to clean build directory.
740+
* `--profile debug` to prepare debug build.
741+
* `--coverage <TYPE>` to generate code coverage report where TYPE can be "html", "xml" or "both".
742+
* `-m <NAME>` or `--make-program <NAME>` to select which make build tool to use where NAME can be "make", "gmake", "mingw32-make" or "ninja".
743+
* `-g <NAME>` or `--generator <NAME>` to select which CMake generator to use where NAME can be "Unix Makefiles", "MinGW Makefiles" or "Ninja".
744+
* `-r <EXPRESSION>` or `--regex <EXPRESSION>` to run tests matching the regular expression.
745+
* `--build <PATH>` to specify build directory.
746+
* `-v` or `--verbose` for verbose diagnostic output.
747+
748+
Generate files for a new unit test with `mbed test --unittests --new <FILE>`.
749+
750+
### Building and running unit tests
751+
752+
You can specify to only **build** the unit tests by using the `--compile option:
753+
754+
```
755+
$ mbed test --unittests --compile
756+
```
757+
758+
You can specify to only **run** the unit tests by using the `--run` option:
759+
760+
```
761+
$ mbed test --unittests --run
762+
```
763+
764+
If you do not specify any of these, `mbed test --unittests` will build all available unit tests and run them.
765+
766+
### Running a subset of tests
767+
768+
You can run a **limited set** of unit tests by using the `-r` or `--regex` option. This takes a regular expression, which it compares against the test names. For example to run all cellular unit tests you can specify:
769+
770+
```
771+
$ mbed test --unittests -r cellular
772+
```
773+
774+
### Getting code coverage
775+
776+
You can generate a code coverage report by using the `--coverage` option. For example to create an html report you can specify:
777+
778+
```
779+
$ mbed test --unittests --coverage html
780+
```
781+
782+
### Creating new unit tests
783+
784+
All unit tests are under `mbed-os/UNITTESTS` directory. You can **generate** the necessary files for a unit test by using the `--new` option. For example to create the files for `rtos/Semaphore.cpp` you can specify:
785+
786+
```
787+
$ mbed test --unittests --new rtos/Semaphore.cpp
788+
```
789+
730790
## Publishing your changes
731791

732792
### Checking status

mbed/mbed.py

Lines changed: 139 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,20 +2619,29 @@ def compile_(toolchain=None, target=None, profile=False, compile_library=False,
26192619
dict(name='--test-spec', dest="test_spec", help="Path used for the test spec file used when building and running tests (the default path is the build directory)"),
26202620
dict(name='--app-config', dest="app_config", help="Path of an application configuration file. Default is to look for \"mbed_app.json\""),
26212621
dict(name='--test-config', dest="test_config", help="Path or mbed OS keyword of a test configuration file. Example: ethernet, odin_wifi, or path/to/config.json"),
2622+
dict(name='--coverage', choices=['html', 'xml', 'both'], help='Generate code coverage report for unit tetsts'),
2623+
dict(name=['--make-program'], choices=['gmake', 'make', 'mingw32-make', 'ninja'], help='Which make program to use for unit tests'),
2624+
dict(name=['--generator'], choices=['Unix Makefiles', 'MinGW Makefiles', 'Ninja'], help='Which CMake generator to use for unit tests'),
2625+
dict(name='--new', help='generate files for a new unit test', metavar="FILEPATH"),
2626+
dict(name=['-r', '--regex'], help='Run unit tests matching regular expression'),
2627+
dict(name=['--unittests'], action="store_true", help='Run only unit tests'),
26222628
dict(name='--build-data', dest="build_data", default=None, help="Dump build_data to this file"),
26232629
dict(name=['--greentea'], dest="greentea", action='store_true', default=False, help="Run Greentea tests"),
26242630
dict(name=['--icetea'], dest="icetea", action='store_true', default=False,
26252631
help="Run Icetea tests. If used without --greentea flag then run only icetea tests."),
26262632
help='Find, build and run tests',
26272633
description="Find, build, and run tests in a program and libraries")
2628-
def test_(toolchain=None, target=None, compile_list=False, run_list=False, compile_only=False, run_only=False,
2629-
tests_by_name=None, source=False, profile=False, build=False, clean=False, test_spec=None, build_data=None,
2630-
app_config=None, test_config=None, greentea=None, icetea=None):
2631-
2634+
def test_(toolchain=None, target=None, compile_list=False, run_list=False,
2635+
compile_only=False, run_only=False, tests_by_name=None, source=False,
2636+
profile=False, build=False, clean=False, test_spec=None,
2637+
app_config=None, test_config=None, coverage=None, make_program=None,
2638+
new=None, generator=None, regex=None, unittests=None,
2639+
build_data=None, greentea=None, icetea=None):
26322640
# Default behaviour is to run only greentea tests
2633-
if not (greentea or icetea):
2641+
if not (greentea or icetea or unittests):
26342642
greentea = True
26352643
icetea = False
2644+
unittests = False
26362645

26372646
# Gather remaining arguments
26382647
args = remainder
@@ -2664,111 +2673,142 @@ def test_(toolchain=None, target=None, compile_list=False, run_list=False, compi
26642673
env = program.get_env()
26652674

26662675
with cd(program.path):
2667-
# Setup the source path if not specified
2668-
if not source or len(source) == 0:
2669-
source = [os.path.relpath(program.path, orig_path)]
2670-
2671-
# Setup the build path if not specified
2672-
build_path = build
2673-
if not build_path:
2674-
build_path = os.path.join(os.path.relpath(program.path, orig_path), program.build_dir, 'tests', target.upper(), tchain.upper())
2675-
build_path = _safe_append_profile_to_build_path(build_path, profile)
2676-
2677-
if test_spec:
2678-
# Preserve path to given test spec
2679-
test_spec = os.path.relpath(os.path.join(orig_path, test_spec), program.path)
2676+
if unittests:
2677+
mbed_os_dir = program.get_os_dir()
2678+
if mbed_os_dir is None:
2679+
error("No Mbed OS directory found.")
2680+
unittests_dir = os.path.join(mbed_os_dir, "UNITTESTS")
2681+
2682+
tool = os.path.join(unittests_dir, "mbed_unittest.py")
2683+
if os.path.exists(tool):
2684+
# Setup the build path if not specified
2685+
build_path = build
2686+
if not build_path:
2687+
build_path = os.path.join(os.path.relpath(program.path, orig_path), program.build_dir, 'unittests')
2688+
2689+
# Run unit testing tools
2690+
popen([python_cmd, tool]
2691+
+ (["--compile"] if compile_only else [])
2692+
+ (["--run"] if run_only else [])
2693+
+ (["--clean"] if clean else [])
2694+
+ (["--debug"] if profile and "debug" in profile else [])
2695+
+ (["--coverage", coverage] if coverage else [])
2696+
+ (["--make-program", make_program] if make_program else [])
2697+
+ (["--generator", generator] if generator else [])
2698+
+ (["--regex", regex] if regex else [])
2699+
+ ["--build", build_path]
2700+
+ (["--new", new] if new else [])
2701+
+ (["--verbose"] if verbose else [])
2702+
+ remainder,
2703+
env=env)
2704+
else:
2705+
warning("Unit testing is not supported with this Mbed OS version.")
26802706
else:
2681-
# Create the path to the test spec file
2682-
test_spec = os.path.join(build_path, 'test_spec.json')
2683-
2684-
if build_data:
2685-
# Preserve path to given build data
2686-
build_data = os.path.relpath(os.path.join(orig_path, build_data), program.path)
2687-
elif icetea_supported:
2688-
# Build data needed only if icetea is supported
2689-
# Create the path to the test build data file
2690-
build_data = os.path.join(build_path, 'build_data.json')
2691-
2692-
if compile_list and greentea:
2693-
popen([python_cmd, '-u', os.path.join(tools_dir, 'test.py'), '--list']
2694-
+ list(chain.from_iterable(list(zip(repeat('--profile'), profile or []))))
2695-
+ ['-t', tchain, '-m', target]
2696-
+ list(chain.from_iterable(zip(repeat('--source'), source)))
2697-
+ (['-n', tests_by_name] if tests_by_name else [])
2698-
+ (['-v'] if verbose else [])
2699-
+ (['--app-config', app_config] if app_config else [])
2700-
+ (['--test-config', test_config] if test_config else [])
2701-
+ (['--greentea'] if icetea_supported and greentea else [])
2702-
+ args,
2703-
env=env)
2704-
2705-
if compile_list and icetea:
2706-
popen(icetea_command_base + ['--compile-list'])
2707+
# Setup the source path if not specified
2708+
if not source or len(source) == 0:
2709+
source = [os.path.relpath(program.path, orig_path)]
27072710

2708-
if compile_only or build_and_run_tests:
2709-
2710-
# Add icetea binaries in compile list
2711-
tests_by_name_temp = tests_by_name if tests_by_name else ''
2712-
if icetea:
2713-
proc = popen(icetea_command_base + ['--application-list'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2714-
stderr=subprocess.STDOUT)
2715-
applications_to_add = proc.stdout.read()
2716-
# Filter right row in case that debugger print something there
2717-
if applications_to_add and 'TEST_APPS-' in applications_to_add:
2718-
applications_to_add = list(filter(lambda x: 'TEST_APPS-' in x, applications_to_add.split('\n')))[0]
2719-
if tests_by_name_temp:
2720-
tests_by_name_temp += ','
2721-
tests_by_name_temp += applications_to_add
2722-
2723-
# If the user hasn't supplied a build directory, ignore the default build directory
2724-
if not build:
2725-
program.ignore_build_dir()
2726-
2727-
popen([python_cmd, '-u', os.path.join(tools_dir, 'test.py')]
2728-
+ list(chain.from_iterable(zip(repeat('-D'), macros)))
2729-
+ list(chain.from_iterable(zip(repeat('--profile'), profile or [])))
2730-
+ ['-t', tchain, '-m', target]
2731-
+ (['-c'] if clean else [])
2732-
+ list(chain.from_iterable(zip(repeat('--source'), source)))
2733-
+ ['--build', build_path]
2734-
+ ['--test-spec', test_spec]
2735-
+ (['--build-data', build_data] if build_data else [])
2736-
+ (['-n', tests_by_name_temp] if tests_by_name_temp else [])
2737-
+ (['-v'] if verbose else [])
2738-
+ (['--app-config', app_config] if app_config else [])
2739-
+ (['--test-config', test_config] if test_config else [])
2740-
+ (['--icetea'] if icetea_supported and icetea else [])
2741-
+ (['--greentea'] if icetea_supported and greentea else [])
2742-
+ args,
2743-
env=env)
2711+
# Setup the build path if not specified
2712+
build_path = build
2713+
if not build_path:
2714+
build_path = os.path.join(os.path.relpath(program.path, orig_path), program.build_dir, 'tests', target.upper(), tchain.upper())
2715+
build_path = _safe_append_profile_to_build_path(build_path, profile)
27442716

2745-
# Greentea tests
2746-
if greentea:
2747-
if run_list:
2748-
popen(['mbedgt', '--test-spec', test_spec, '--list']
2717+
if test_spec:
2718+
# Preserve path to given test spec
2719+
test_spec = os.path.relpath(os.path.join(orig_path, test_spec), program.path)
2720+
else:
2721+
# Create the path to the test spec file
2722+
test_spec = os.path.join(build_path, 'test_spec.json')
2723+
2724+
if build_data:
2725+
# Preserve path to given build data
2726+
build_data = os.path.relpath(os.path.join(orig_path, build_data), program.path)
2727+
elif icetea_supported:
2728+
# Build data needed only if icetea is supported
2729+
# Create the path to the test build data file
2730+
build_data = os.path.join(build_path, 'build_data.json')
2731+
2732+
if compile_list and greentea:
2733+
popen([python_cmd, '-u', os.path.join(tools_dir, 'test.py'), '--list']
2734+
+ list(chain.from_iterable(list(zip(repeat('--profile'), profile or []))))
2735+
+ ['-t', tchain, '-m', target]
2736+
+ list(chain.from_iterable(zip(repeat('--source'), source)))
27492737
+ (['-n', tests_by_name] if tests_by_name else [])
2750-
+ (['-V'] if verbose else [])
2738+
+ (['-v'] if verbose else [])
2739+
+ (['--app-config', app_config] if app_config else [])
2740+
+ (['--test-config', test_config] if test_config else [])
2741+
+ (['--greentea'] if icetea_supported and greentea else [])
27512742
+ args,
27522743
env=env)
27532744

2754-
if run_only or build_and_run_tests:
2755-
popen(['mbedgt', '--test-spec', test_spec]
2756-
+ (['-n', tests_by_name] if tests_by_name else [])
2757-
+ (['-V'] if verbose else [])
2745+
if compile_list and icetea:
2746+
popen(icetea_command_base + ['--compile-list'])
2747+
2748+
if compile_only or build_and_run_tests:
2749+
2750+
# Add icetea binaries in compile list
2751+
tests_by_name_temp = tests_by_name if tests_by_name else ''
2752+
if icetea:
2753+
proc = popen(icetea_command_base + ['--application-list'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2754+
stderr=subprocess.STDOUT)
2755+
applications_to_add = proc.stdout.read()
2756+
# Filter right row in case that debugger print something there
2757+
if applications_to_add and 'TEST_APPS-' in applications_to_add:
2758+
applications_to_add = list(filter(lambda x: 'TEST_APPS-' in x, applications_to_add.split('\n')))[0]
2759+
if tests_by_name_temp:
2760+
tests_by_name_temp += ','
2761+
tests_by_name_temp += applications_to_add
2762+
2763+
# If the user hasn't supplied a build directory, ignore the default build directory
2764+
if not build:
2765+
program.ignore_build_dir()
2766+
2767+
popen([python_cmd, '-u', os.path.join(tools_dir, 'test.py')]
2768+
+ list(chain.from_iterable(zip(repeat('-D'), macros)))
2769+
+ list(chain.from_iterable(zip(repeat('--profile'), profile or [])))
2770+
+ ['-t', tchain, '-m', target]
2771+
+ (['-c'] if clean else [])
2772+
+ list(chain.from_iterable(zip(repeat('--source'), source)))
2773+
+ ['--build', build_path]
2774+
+ ['--test-spec', test_spec]
2775+
+ (['--build-data', build_data] if build_data else [])
2776+
+ (['-n', tests_by_name_temp] if tests_by_name_temp else [])
2777+
+ (['-v'] if verbose else [])
2778+
+ (['--app-config', app_config] if app_config else [])
2779+
+ (['--test-config', test_config] if test_config else [])
2780+
+ (['--icetea'] if icetea_supported and icetea else [])
2781+
+ (['--greentea'] if icetea_supported and greentea else [])
27582782
+ args,
27592783
env=env)
27602784

2761-
# Icetea tests
2762-
if icetea:
2763-
icetea_command = icetea_command_base \
2764-
+ ['--build-data', build_data] \
2765-
+ ['--test-suite', os.path.join(build_path, 'test_suite.json')]
2785+
# Greentea tests
2786+
if greentea:
2787+
if run_list:
2788+
popen(['mbedgt', '--test-spec', test_spec, '--list']
2789+
+ (['-n', tests_by_name] if tests_by_name else [])
2790+
+ (['-V'] if verbose else [])
2791+
+ args,
2792+
env=env)
2793+
2794+
if run_only or build_and_run_tests:
2795+
popen(['mbedgt', '--test-spec', test_spec]
2796+
+ (['-n', tests_by_name] if tests_by_name else [])
2797+
+ (['-V'] if verbose else [])
2798+
+ args,
2799+
env=env)
2800+
2801+
# Icetea tests
2802+
if icetea:
2803+
icetea_command = icetea_command_base \
2804+
+ ['--build-data', build_data] \
2805+
+ ['--test-suite', os.path.join(build_path, 'test_suite.json')]
27662806

2767-
if run_list:
2768-
popen(icetea_command + ['--run-list'])
2807+
if run_list:
2808+
popen(icetea_command + ['--run-list'])
27692809

2770-
if run_only or build_and_run_tests:
2771-
popen(icetea_command)
2810+
if run_only or build_and_run_tests:
2811+
popen(icetea_command)
27722812

27732813
program.set_defaults(target=target, toolchain=tchain)
27742814

@@ -3144,6 +3184,7 @@ def help_():
31443184
return parser.print_help()
31453185

31463186

3187+
31473188
def main():
31483189
global verbose, very_verbose, remainder, cwd_root
31493190

0 commit comments

Comments
 (0)