Newer
Older
mbed-os / UNITTESTS / unit_test / test.py
"""
Copyright (c) 2018, Arm Limited
SPDX-License-Identifier: Apache-2.0

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

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


UNIT TEST BUILD & RUN
"""

import os
import logging
import sys
import psutil
from shutil import copy

from .utils import execute_program
from .get_tools import get_make_tool, \
                       get_cmake_tool, \
                       get_cxx_tool, \
                       get_c_tool, \
                       get_valgrind_tool
from .settings import DEFAULT_CMAKE_GENERATORS

class UnitTestTool(object):
    """
    Unit test tool to:
        - prepare build directory
        - create makefiles
        - build unit tests
        - run unit tests
        - generate code coverage reports
    """

    def __init__(self,
                 make_program=None):
        """
        Constructor

        Keyword arguments:
        make_program - Make tool to use
        """

        self.make_program = make_program

        if self.make_program is None:
            self.make_program = get_make_tool()

    def create_makefiles(self,
                         path_to_src=None,
                         generator=None,
                         coverage_output_type=None,
                         debug=False,
                         valgrind=False):
        """
        Create Makefiles and prepare targets with CMake.

        Keyword arguments:
        path_to_src - Path to source directory
        generator - Type of Makefiles to generate
        coverage_output_type - Generate HTML, XML or both reports
        debug - Target debug or release build
        """

        if generator is None:
            generator = DEFAULT_CMAKE_GENERATORS.get(self.make_program,
                                                     "Unix Makefiles")

        cmake = get_cmake_tool()

        if cmake is None:
            logging.error(
                "No CMake found in Path. Install all the required tools.")
            sys.exit(1)

        args = [cmake,
                "-G",
                generator,
                "-DMBED_ENABLE_TESTING=ON"
                "-DCMAKE_MAKE_PROGRAM=%s" % self.make_program,
                "-DCMAKE_CXX_COMPILER=%s" % get_cxx_tool(),
                "-DCMAKE_C_COMPILER=%s" % get_c_tool()]

        if debug:
            args.append("-DCMAKE_BUILD_TYPE=Debug")

        if coverage_output_type:
            args.append("-DCOVERAGE:STRING=%s" % coverage_output_type)

        if valgrind:
            valgrind = get_valgrind_tool()
            if valgrind is None:
                logging.error(
                    "No Valgrind found in Path. Install all the required tools.\n")
                sys.exit(1)
            args.append("-DVALGRIND=1")
            args.append("-DMEMORYCHECK_COMMAND_OPTIONS=\"--track-origins=yes\" \"--leak-check=full\" \"--show-reachable=yes\" \"--error-exitcode=1\"")
        else:
            args.append("-DVALGRIND=0")

        if path_to_src is not None:
            args.append(path_to_src)

        execute_program(args,
                        "CMake failed to run successfully. See error message.")

    def build_tests(self):
        """
        Build unit tests and libraries to be tested.
        """

        args = [self.make_program]

        # Speed up compilation by running on more than one core
        count = psutil.cpu_count()
        args.append("-j{}".format(count+1))

        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
            args.append("VERBOSE=1")

        execute_program(args,
                        "Building unit tests failed.",
                        "Unit tests built successfully.")

    def run_tests(self, filter_regex=None, valgrind=False):
        """
        Run unit tests.

        Keyword arguments:
        filter_regex - Regular expression to select which tests to run
        """

        args = [self.make_program, "test"]
        if valgrind:
            if filter_regex:
                args.append("ARGS=-R %s -V -D ExperimentalMemCheck" % filter_regex)
            else:
                args.append("ARGS=-V -D ExperimentalMemCheck")
        else:
            if filter_regex:
                args.append("ARGS=-R %s -V -D ExperimentalTest" % filter_regex)
            else:
                args.append("ARGS=-V -D ExperimentalTest")

        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
            args.append("VERBOSE=1")

        execute_program(args, "Unit test run failed.")

    def prepare_build_directory(self,
                                path_to_src=None,
                                build_path=None,
                                clean=False):
        """
        Create build directory if not exist and
        change current working directory to it

        Keyword arguments:
        path_to_src - Path to source directory
        build_path - Path to build directory
        clean - Clean build directory
        """

        if build_path is None:
            build_path = os.getcwd()

        # Clean CMake data if exists.
        if clean:
            self._clean_build(build_path)

        # Create build directory if not exist.
        if not os.path.exists(build_path):
            os.makedirs(build_path)
            filename = ".mbedignore"
            inputfile = os.path.join(path_to_src, filename)
            outputfile = os.path.join(build_path, filename)
            copy(inputfile, outputfile)

        # Change current working directory to build directory.
        os.chdir(build_path)

    def _clean_build(self, build_path=None):
        """
        Try clean build directory

        Keyword arguments:
        build_path - Path to build directory
        """

        logging.info("Cleaning build directory...")

        if os.path.exists(os.path.join(build_path, "CMakeCache.txt")):
            args = [self.make_program,
                    "-C",
                    build_path,
                    "--no-print-directory",
                    "clean"]

            # Remove coverage files
            for root, _, files in os.walk(build_path):
                for current_file in files:
                    if current_file.endswith((".gcno", ".gcda")):
                        os.remove(os.path.join(root, current_file))

            execute_program(args,
                            "Clean step failed.",
                            "Clean done.")

        else:
            logging.warning("%s does not exist or \
            does not contain previous build data.", build_path)