Added support for cpputest library. UT libray now compiles as separate lib, just like other libs. For example ETH or RTX. Added support for cpputest sources in root of mbed project (on the same level with mbed sdk repo). changes include adding new paths to the project. Two example UT (UT_1, UT_2 are added). New option -O is introduced to force 'test build only'

pull/385/head
Przemek Wirkus 2014-07-01 17:45:12 +01:00
parent 4f86d39719
commit c97f1b8139
11 changed files with 230 additions and 31 deletions

View File

@ -1,10 +1,14 @@
#include "test_env.h"
#include "semihost_api.h"
#define MAC_VENDOR_ARM_0 0x00
#define MAC_VENDOR_ARM_1 0x02
#define MAC_VENDOR_ARM_2 0xF7
int main() {
printf("Semihost connected: %s\n", (semihost_connected()) ? ("Yes") : ("No"));
char uid[DEVICE_ID_LENGTH + 1] = {0};
bool result = true;

View File

@ -0,0 +1,10 @@
#include "CppUTest/TestHarness.h"
TEST_GROUP(FirstTestGroup)
{
};
TEST(FirstTestGroup, FirstTest)
{
CHECK(true);
}

View File

@ -0,0 +1,63 @@
#include "CppUTest/TestHarness.h"
#include "mbed.h"
#include "semihost_api.h"
#include <stdio.h>
#define FILENAME "/local/out.txt"
#define TEST_STRING "Hello World!"
TEST_GROUP(FirstTestGroup)
{
FILE *test_open(const char *mode) {
FILE *f = fopen(FILENAME, mode);
return f;
}
bool test_write(FILE *f, char *str, int str_len) {
int n = fprintf(f, str);
return (n == str_len) ? true : false;
}
bool test_read(FILE *f, char *str, int str_len) {
int n = fread(str, sizeof(unsigned char), str_len, f);
return (n == str_len) ? true : false;
}
bool test_close(FILE *f) {
int rc = fclose(f);
return rc ? true : false;
}
};
TEST(FirstTestGroup, FirstTest)
{
CHECK_TEXT(semihost_connected(), "Semihost not connected")
LocalFileSystem local("local");
char *str = TEST_STRING;
char *buffer = (char *)malloc(sizeof(unsigned char) * strlen(TEST_STRING));
int str_len = strlen(TEST_STRING);
CHECK_TEXT(buffer != NULL, "Buffer allocation failed");
CHECK_TEXT(str_len > 0, "Test string is empty (len <= 0)");
{
// Perform write / read tests
FILE *f = NULL;
// Write
f = test_open("w");
CHECK_TEXT(f != NULL, "Error opening file for writing")
CHECK_TEXT(test_write(f, str, str_len), "Error writing file");
CHECK_TEXT(test_close(f) != EOF, "Error closing file after write");
// Read
f = test_open("r");
CHECK_TEXT(f != NULL, "Error opening file for reading")
CHECK_TEXT(test_read(f, buffer, str_len), "Error reading file");
CHECK_TEXT(test_close(f) != EOF, "Error closing file after read");
}
CHECK(strncmp(buffer, str, str_len) == 0);
}

View File

@ -0,0 +1,22 @@
#include "CppUTest\CommandLineTestRunner.h"
#include <stdio.h>
#include "mbed.h"
#include "testrunner.h"
#include "test_env.h"
/**
Object 'console' is used to show prints on console.
It is declared in \cpputest\src\Platforms\armcc\UtestPlatform.cpp
*/
Serial console(STDIO_UART_TX, STDIO_UART_RX);
int main(int ac, char** av)
{
unsigned failureCount = 0;
{
failureCount = CommandLineTestRunner::RunAllTests(ac, av);
}
notify_completion(failureCount == 0);
return failureCount;
}

View File

@ -0,0 +1,4 @@
#ifndef TEST_RUNNER_H_
#define TEST_RUNNER_H_
#endif

View File

@ -4,6 +4,7 @@ Copyright (c) 2011-2013 ARM Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
@ -16,7 +17,6 @@ limitations under the License.
"""
import tempfile
import os
from os.path import join, exists, basename
from shutil import rmtree
from types import ListType
@ -30,13 +30,18 @@ from workspace_tools.targets import TARGET_NAMES, TARGET_MAP
def build_project(src_path, build_path, target, toolchain_name,
libraries_paths=None, options=None, linker_script=None,
clean=False, notify=None, verbose=False, name=None, macros=None):
clean=False, notify=None, verbose=False, name=None, macros=None, inc_dirs=None):
""" This function builds project. Project can be for example one test / UT """
# Toolchain instance
toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, notify, macros)
toolchain.VERBOSE = verbose
toolchain.build_all = clean
src_paths = [src_path] if type(src_path) != ListType else src_path
# We need to remove all paths which are repeated to avoid
# multiple compilations and linking with the same objects
src_paths = [src_paths[0]] + list(set(src_paths[1:]))
if name is None:
name = basename(src_paths[0])
toolchain.info("\n>>> BUILD PROJECT: %s (%s, %s)" % (name.upper(), target.name, toolchain_name))
@ -59,6 +64,13 @@ def build_project(src_path, build_path, target, toolchain_name,
rmtree(build_path)
mkdir(build_path)
# We need to add if necessary additional include directories
if inc_dirs:
if type(inc_dirs) == ListType:
resources.inc_dirs.extend(inc_dirs)
else:
resources.inc_dirs.append(inc_dirs)
# Compile Sources
for path in src_paths:
src = toolchain.scan_resources(path)
@ -69,20 +81,20 @@ def build_project(src_path, build_path, target, toolchain_name,
return toolchain.link_program(resources, build_path, name)
"""
src_path: the path of the source directory
build_path: the path of the build directory
target: ['LPC1768', 'LPC11U24', 'LPC2368']
toolchain: ['ARM', 'uARM', 'GCC_ARM', 'GCC_CS', 'GCC_CR']
library_paths: List of paths to additional libraries
clean: Rebuild everything if True
notify: Notify function for logs
verbose: Write the actual tools command lines if True
"""
def build_library(src_paths, build_path, target, toolchain_name,
dependencies_paths=None, options=None, name=None, clean=False,
notify=None, verbose=False, macros=None):
if type(src_paths) != ListType: src_paths = [src_paths]
notify=None, verbose=False, macros=None, inc_dirs=None):
""" src_path: the path of the source directory
build_path: the path of the build directory
target: ['LPC1768', 'LPC11U24', 'LPC2368']
toolchain: ['ARM', 'uARM', 'GCC_ARM', 'GCC_CS', 'GCC_CR']
library_paths: List of paths to additional libraries
clean: Rebuild everything if True
notify: Notify function for logs
verbose: Write the actual tools command lines if True
inc_dirs: additional include directories which should be included in build"""
if type(src_paths) != ListType:
src_paths = [src_paths]
for src_path in src_paths:
if not exists(src_path):
@ -109,6 +121,9 @@ def build_library(src_paths, build_path, target, toolchain_name,
lib_resources = toolchain.scan_resources(path)
dependencies_include_dir.extend(lib_resources.inc_dirs)
if inc_dirs:
dependencies_include_dir.extend(inc_dirs)
# Create the desired build directory structure
bin_path = join(build_path, toolchain.obj_path)
mkdir(bin_path)
@ -131,9 +146,14 @@ def build_library(src_paths, build_path, target, toolchain_name,
def build_lib(lib_id, target, toolchain, options=None, verbose=False, clean=False, macros=None, notify=None):
lib = Library(lib_id)
if lib.is_supported(target, toolchain):
# We need to combine macros from parameter list with macros from library definition
MACROS = lib.macros if lib.macros else []
if macros:
MACROS.extend(macros)
build_library(lib.source_dir, lib.build_dir, target, toolchain,
lib.dependencies, options,
verbose=verbose, clean=clean, macros=macros, notify=notify)
verbose=verbose, clean=clean, macros=MACROS, notify=notify, inc_dirs=lib.inc_dirs)
else:
print '\n\nLibrary "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain)
@ -230,7 +250,7 @@ def mcu_toolchain_matrix(verbose_html=False):
pt.align[col] = "c"
pt.align["Platform"] = "l"
perm_counter = 0;
perm_counter = 0
for target in sorted(TARGET_NAMES):
row = [target] # First column is platform name
for unique_toolchain in unique_supported_toolchains:
@ -238,7 +258,7 @@ def mcu_toolchain_matrix(verbose_html=False):
if unique_toolchain in TARGET_MAP[target].supported_toolchains:
text = "Supported"
perm_counter += 1
row.append(text);
row.append(text)
pt.add_row(row)
result = pt.get_html_string() if verbose_html else pt.get_string()
@ -358,26 +378,28 @@ def static_analysis_scan(target, toolchain_name, CPPCHECK_CMD, CPPCHECK_MSG_FORM
print _stderr
def static_analysis_scan_lib(lib_id, target, toolchain, CPPCHECK_CMD, CPPCHECK_MSG_FORMAT, options=None, verbose=False, clean=False, macros=None, notify=None):
def static_analysis_scan_lib(lib_id, target, toolchain, cppcheck_cmd, cppcheck_msg_format,
options=None, verbose=False, clean=False, macros=None, notify=None):
lib = Library(lib_id)
if lib.is_supported(target, toolchain):
static_analysis_scan_library(lib.source_dir, lib.build_dir, target, toolchain, CPPCHECK_CMD, CPPCHECK_MSG_FORMAT,
static_analysis_scan_library(lib.source_dir, lib.build_dir, target, toolchain, cppcheck_cmd, cppcheck_msg_format,
lib.dependencies, options,
verbose=verbose, clean=clean, macros=macros, notify=notify)
else:
print 'Library "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain)
def static_analysis_scan_library(src_paths, build_path, target, toolchain_name, CPPCHECK_CMD, CPPCHECK_MSG_FORMAT,
def static_analysis_scan_library(src_paths, build_path, target, toolchain_name, cppcheck_cmd, cppcheck_msg_format,
dependencies_paths=None, options=None, name=None, clean=False,
notify=None, verbose=False, macros=None):
if type(src_paths) != ListType: src_paths = [src_paths]
""" Function scans library (or just some set of sources/headers) for staticly detectable defects """
if type(src_paths) != ListType:
src_paths = [src_paths]
for src_path in src_paths:
if not exists(src_path):
raise Exception("The library source folder does not exist: %s", src_path)
# Toolchain instance
toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, macros=macros, notify=notify)
toolchain.VERBOSE = verbose
@ -422,18 +444,21 @@ def static_analysis_scan_library(src_paths, build_path, target, toolchain_name,
includes = map(str.strip, includes)
macros = map(str.strip, macros)
check_cmd = CPPCHECK_CMD
check_cmd += CPPCHECK_MSG_FORMAT
check_cmd = cppcheck_cmd
check_cmd += cppcheck_msg_format
check_cmd += includes
check_cmd += macros
# We need to pass some parames via file to avoid "command line too long in some OSs"
# We need to pass some parameters via file to avoid "command line too long in some OSs"
# Temporary file is created to store e.g. cppcheck list of files for command line
tmp_file = tempfile.NamedTemporaryFile(delete=False)
tmp_file.writelines(line + '\n' for line in c_sources.split())
tmp_file.writelines(line + '\n' for line in cpp_sources.split())
tmp_file.close()
check_cmd += ["--file-list=%s"% tmp_file.name]
# This will allow us to grab result from both stdio and stderr outputs (so we can show them)
# We assume static code analysis tool is outputting defects on STDERR
_stdout, _stderr, _rc = run_cmd_ext(check_cmd)
if verbose:
print _stdout
@ -441,6 +466,7 @@ def static_analysis_scan_library(src_paths, build_path, target, toolchain_name,
def print_build_results(result_list, build_name):
""" Generate result string for build results """
result = ""
if result_list:
result += build_name + "\n"

View File

@ -16,6 +16,7 @@ limitations under the License.
"""
from workspace_tools.paths import *
from workspace_tools.data.support import *
from workspace_tools.tests import TEST_MBED_LIB
LIBRARIES = [
@ -78,6 +79,15 @@ LIBRARIES = [
"dependencies": [MBED_LIBRARIES, RTOS_LIBRARIES, USB_HOST_LIBRARIES],
},
{
"id": "cpputest",
"source_dir": [CPPUTEST_SRC, CPPUTEST_PLATFORM_SRC, CPPUTEST_TESTRUNNER_SCR],
"build_dir": CPPUTEST_LIBRARY,
"dependencies": [MBED_LIBRARIES],
'inc_dirs': [CPPUTEST_INC, CPPUTEST_PLATFORM_INC, CPPUTEST_TESTRUNNER_INC, TEST_MBED_LIB],
'inc_dirs_ext': [CPPUTEST_INC],
'macros': ["CPPUTEST_USE_MEM_LEAK_DETECTION=0", "CPPUTEST_USE_STD_CPP_LIB=0", "CPPUTEST=1"],
},
]
@ -88,6 +98,9 @@ class Library:
DEFAULTS = {
"supported": DEFAULT_SUPPORT,
'dependencies': None,
'inc_dirs': None, # Include dirs required by library build
'inc_dirs_ext': None, # Include dirs required by others to use with this library
'macros': None, # Additional macros you want to define when building library
}
def __init__(self, lib_id):
self.__dict__.update(Library.DEFAULTS)

View File

@ -92,3 +92,15 @@ EXPORT_DIR = join(BUILD_DIR, "export")
EXPORT_WORKSPACE = join(EXPORT_DIR, "workspace")
EXPORT_TMP = join(EXPORT_DIR, ".temp")
# CppUtest library
CPPUTEST_DIR = join(ROOT, "..")
CPPUTEST_SRC = join(CPPUTEST_DIR, "cpputest", "src", "CppUTest") #, "CppUTest"
CPPUTEST_INC = join(CPPUTEST_DIR, "cpputest", "include") #, "CppUTest"
# Platform dependant code is here (for armcc compiler)
CPPUTEST_PLATFORM_SRC = join(CPPUTEST_DIR, "cpputest", "src", "Platforms", "armcc")
CPPUTEST_PLATFORM_INC = join(CPPUTEST_DIR, "cpputest", "include", "Platforms", "armcc")
# Function 'main' used to run all compiled UTs
CPPUTEST_TESTRUNNER_SCR = join(TEST_DIR, "utest", "testrunner")
CPPUTEST_TESTRUNNER_INC = join(TEST_DIR, "utest", "testrunner")
CPPUTEST_LIBRARY = join(BUILD_DIR, "cpputest")

View File

@ -41,13 +41,13 @@ elif armcc == "standalone":
ARM_PATH = "C:/Program Files/ARM/armcc_4.1_791"
ARM_BIN = join(ARM_PATH, "bin")
ARM_INC = join(ARM_PATH, "include")
ARM_LIB = join(ARM_PATH, "lib")
ARM_LIB = join(ARM_PATH, "lib")
elif armcc == "ds-5":
ARM_PATH = "C:/Program Files (x86)/DS-5"
ARM_BIN = join(ARM_PATH, "bin")
ARM_INC = join(ARM_PATH, "include")
ARM_LIB = join(ARM_PATH, "lib")
ARM_LIB = join(ARM_PATH, "lib")
ARM_CPPLIB = join(ARM_LIB, "cpplib")
MY_ARM_CLIB = join(ARM_PATH, "lib", "microlib")

View File

@ -75,6 +75,7 @@ import json
import optparse
import pprint
import re
from types import ListType
from prettytable import PrettyTable
from serial import Serial
@ -98,7 +99,7 @@ from workspace_tools.paths import HOST_TESTS
from workspace_tools.targets import TARGET_MAP
from workspace_tools.tests import TEST_MAP
from workspace_tools.tests import TESTS
from workspace_tools.libraries import LIBRARIES
from workspace_tools.libraries import LIBRARIES, LIBRARY_MAP
# Be sure that the tools directory is in the search path
ROOT = abspath(join(dirname(__file__), ".."))
@ -628,6 +629,18 @@ if __name__ == '__main__':
default=False,
help="Displays supported matrix of MCUs and toolchains")
parser.add_option("-O", "--only-build",
action="store_true",
dest="only_build_tests",
default=False,
help="Only build tests, skips actual test procedures (flashing etc.)")
parser.add_option("", "--cpputest",
action="store_true",
dest="use_cpputest_library",
default=False,
help="Use cpputest library to run UT on target devices")
parser.add_option('-v', '--verbose',
dest='verbose',
default=False,
@ -726,12 +739,30 @@ if __name__ == '__main__':
build_lib(lib_id, T, toolchain, options=build_project_options,
verbose=opts.verbose, clean=clean)
# TODO: move this 2 below loops to separate function
INC_DIRS = []
for lib_id in libraries:
if LIBRARY_MAP[lib_id]['inc_dirs_ext']:
INC_DIRS.extend(LIBRARY_MAP[lib_id]['inc_dirs_ext'])
MACROS = []
for lib_id in libraries:
if LIBRARY_MAP[lib_id]['macros']:
MACROS.extend(LIBRARY_MAP[lib_id]['macros'])
path = build_project(test.source_dir, join(build_dir, test_id),
T, toolchain, test.dependencies, options=build_project_options,
clean=clean, verbose=opts.verbose)
clean=clean, verbose=opts.verbose,
macros=MACROS,
inc_dirs=INC_DIRS)
test_result_cache = join(dirname(path), "test_result.json")
if opts.only_build_tests:
# We are skipping testing phase, and suppress summary
opts.suppress_summary = True
continue
# For an automated test the duration act as a timeout after
# which the test gets interrupted
test_spec = shape_test_request(target, path, test_id, test.duration)
@ -743,7 +774,6 @@ if __name__ == '__main__':
# Human readable summary
if not opts.suppress_summary:
# prints well-formed summary with results (SQL table like)
print generate_test_summary(test_summary)

View File

@ -809,6 +809,21 @@ TESTS = [
"source_dir": join(TEST_DIR, "mbed", "fs"),
"dependencies": [MBED_LIBRARIES, RTOS_LIBRARIES, TEST_MBED_LIB, SD_FS, FAT_FS],
},
# Unit testing with cpputest library
{
"id": "UT_1", "description": "Basic",
"source_dir": join(TEST_DIR, "utest", "basic"),
"dependencies": [MBED_LIBRARIES, TEST_MBED_LIB, CPPUTEST_LIBRARY],
"automated": True,
},
{
"id": "UT_2", "description": "Semihost file system",
"source_dir": join(TEST_DIR, "utest", "file"),
"dependencies": [MBED_LIBRARIES, TEST_MBED_LIB, CPPUTEST_LIBRARY],
"automated": True,
"mcu": ["LPC1768", "LPC2368", "LPC11U24"]
},
]
# Group tests with the same goals into categories