Merge pull request #2593 from ConorPKeegan/devel_app_config_switch

Add app config command line switch for test and make
pull/2661/merge
Sam Grove 2016-09-10 14:30:38 -05:00 committed by GitHub
commit c3737b4f7a
9 changed files with 513 additions and 30 deletions

View File

@ -276,7 +276,8 @@ def get_mbed_official_release(version):
def prepare_toolchain(src_paths, target, toolchain_name,
macros=None, options=None, clean=False, jobs=1,
notify=None, silent=False, verbose=False,
extra_verbose=False, config=None):
extra_verbose=False, config=None,
app_config=None):
""" Prepares resource related objects - toolchain, target, config
Positional arguments:
@ -294,6 +295,7 @@ def prepare_toolchain(src_paths, target, toolchain_name,
verbose - Write the actual tools command lines used if True
extra_verbose - even more output!
config - a Config object to use instead of creating one
app_config - location of a chosen mbed_app.json file
"""
# We need to remove all paths which are repeated to avoid
@ -301,7 +303,7 @@ def prepare_toolchain(src_paths, target, toolchain_name,
src_paths = [src_paths[0]] + list(set(src_paths[1:]))
# If the configuration object was not yet created, create it now
config = config or Config(target, src_paths)
config = config or Config(target, src_paths, app_config=app_config)
# If the 'target' argument is a string, convert it to a target instance
if isinstance(target, basestring):
@ -369,7 +371,8 @@ def build_project(src_paths, build_path, target, toolchain_name,
clean=False, notify=None, verbose=False, name=None,
macros=None, inc_dirs=None, jobs=1, silent=False,
report=None, properties=None, project_id=None,
project_description=None, extra_verbose=False, config=None):
project_description=None, extra_verbose=False, config=None,
app_config=None):
""" Build a project. A project may be a test or a user program.
Positional arguments:
@ -397,6 +400,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
project_description - the human-readable version of what this thing does
extra_verbose - even more output!
config - a Config object to use instead of creating one
app_config - location of a chosen mbed_app.json file
"""
# Convert src_path to a list if needed
@ -415,7 +419,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
toolchain = prepare_toolchain(
src_paths, target, toolchain_name, macros=macros, options=options,
clean=clean, jobs=jobs, notify=notify, silent=silent, verbose=verbose,
extra_verbose=extra_verbose, config=config)
extra_verbose=extra_verbose, config=config, app_config=app_config)
# The first path will give the name to the library
if name is None:
@ -489,7 +493,7 @@ def build_library(src_paths, build_path, target, toolchain_name,
archive=True, notify=None, verbose=False, macros=None,
inc_dirs=None, jobs=1, silent=False, report=None,
properties=None, extra_verbose=False, project_id=None,
remove_config_header_file=False):
remove_config_header_file=False, app_config=None):
""" Build a library
Positional arguments:
@ -516,6 +520,7 @@ def build_library(src_paths, build_path, target, toolchain_name,
extra_verbose - even more output!
project_id - the name that goes in the report
remove_config_header_file - delete config header file when done building
app_config - location of a chosen mbed_app.json file
"""
# Convert src_path to a list if needed
@ -539,7 +544,7 @@ def build_library(src_paths, build_path, target, toolchain_name,
toolchain = prepare_toolchain(
src_paths, target, toolchain_name, macros=macros, options=options,
clean=clean, jobs=jobs, notify=notify, silent=silent, verbose=verbose,
extra_verbose=extra_verbose)
extra_verbose=extra_verbose, app_config=app_config)
# The first path will give the name to the library
if name is None:

View File

@ -350,7 +350,7 @@ class Config(object):
"UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE"
]
def __init__(self, target, top_level_dirs=None):
def __init__(self, target, top_level_dirs=None, app_config=None):
"""Construct a mbed configuration
Positional arguments:
@ -359,7 +359,8 @@ class Config(object):
Keyword argumets:
top_level_dirs - a list of top level source directories (where
mbed_abb_config.json could be found)
mbed_app_config.json could be found)
app_config - location of a chosen mbed_app.json file
NOTE: Construction of a Config object will look for the application
configuration file in top_level_dirs. If found once, it'll parse it and
@ -368,22 +369,24 @@ class Config(object):
exception is raised. top_level_dirs may be None (in this case,
the constructor will not search for a configuration file)
"""
app_config_location = None
for directory in top_level_dirs or []:
full_path = os.path.join(directory, self.__mbed_app_config_name)
if os.path.isfile(full_path):
if app_config_location is not None:
raise ConfigException("Duplicate '%s' file in '%s' and '%s'"
% (self.__mbed_app_config_name,
app_config_location, full_path))
else:
app_config_location = full_path
app_config_location = app_config
if app_config_location is None:
for directory in top_level_dirs or []:
full_path = os.path.join(directory, self.__mbed_app_config_name)
if os.path.isfile(full_path):
if app_config_location is not None:
raise ConfigException("Duplicate '%s' file in '%s' and '%s'"
% (self.__mbed_app_config_name,
app_config_location, full_path))
else:
app_config_location = full_path
try:
self.app_config_data = json_file_to_dict(app_config_location) \
if app_config_location else {}
except ValueError as exc:
sys.stderr.write(str(exc) + "\n")
self.app_config_data = {}
# Check the keys in the application configuration data
unknown_keys = set(self.app_config_data.keys()) - \
self.__allowed_keys["application"]

View File

@ -52,7 +52,7 @@ from tools.settings import CLI_COLOR_MAP
if __name__ == '__main__':
# Parse Options
parser = get_default_options_parser()
parser = get_default_options_parser(add_app_config=True)
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument("-p",
type=argparse_many(test_known),
@ -274,7 +274,8 @@ if __name__ == '__main__':
silent=options.silent,
macros=options.macros,
jobs=options.jobs,
name=options.artifact_name)
name=options.artifact_name,
app_config=options.app_config)
print 'Image: %s'% bin_file
if options.disk:

View File

@ -18,9 +18,11 @@ from argparse import ArgumentParser
from tools.toolchains import TOOLCHAINS
from tools.targets import TARGET_NAMES
from tools.utils import argparse_force_uppercase_type, \
argparse_lowercase_hyphen_type, argparse_many
argparse_lowercase_hyphen_type, argparse_many, \
argparse_filestring_type
def get_default_options_parser(add_clean=True, add_options=True):
def get_default_options_parser(add_clean=True, add_options=True,
add_app_config=False):
"""Create a new options parser with the default compiler options added
Keyword arguments:
@ -80,4 +82,9 @@ def get_default_options_parser(add_clean=True, add_options=True):
'std-lib'],
"build option"))
if add_app_config:
parser.add_argument("--app-config", default=None, dest="app_config",
type=argparse_filestring_type,
help="Path of an app configuration file (Default is to look for 'mbed_app.json')")
return parser

View File

@ -41,7 +41,7 @@ from tools.settings import CLI_COLOR_MAP
if __name__ == '__main__':
try:
# Parse Options
parser = get_default_options_parser()
parser = get_default_options_parser(add_app_config=True)
parser.add_argument("-D",
action="append",
@ -117,7 +117,8 @@ if __name__ == '__main__':
# Find all tests in the relevant paths
for path in all_paths:
all_tests.update(find_tests(path, mcu, toolchain, options.options))
all_tests.update(find_tests(path, mcu, toolchain, options.options,
app_config=options.app_config))
# Filter tests by name if specified
if options.names:
@ -177,7 +178,8 @@ if __name__ == '__main__':
verbose=options.verbose,
notify=notify,
archive=False,
remove_config_header_file=True)
remove_config_header_file=True,
app_config=options.app_config)
library_build_success = True
except ToolException, e:
@ -203,7 +205,8 @@ if __name__ == '__main__':
verbose=options.verbose,
notify=notify,
jobs=options.jobs,
continue_on_build_fail=options.continue_on_build_fail)
continue_on_build_fail=options.continue_on_build_fail,
app_config=options.app_config)
# If a path to a test spec is provided, write it to a file
if options.test_spec:

View File

@ -0,0 +1,188 @@
"""
mbed SDK
Copyright (c) 2016 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
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.
"""
import unittest
from mock import patch
from tools.build_api import prepare_toolchain, build_project, build_library
"""
Tests for build_api.py
"""
class BuildApiTests(unittest.TestCase):
"""
Test cases for Build Api
"""
def setUp(self):
"""
Called before each test case
:return:
"""
self.target = "K64F"
self.src_paths = ['.']
self.toolchain_name = "ARM"
self.build_path = "build_path"
def tearDown(self):
"""
Called after each test case
:return:
"""
pass
@patch('tools.config.Config.__init__')
def test_prepare_toolchain_app_config(self, mock_config_init):
"""
Test that prepare_toolchain uses app_config correctly
:param mock_config_init: mock of Config __init__
:return:
"""
app_config = "app_config"
mock_config_init.return_value = None
prepare_toolchain(self.src_paths, self.target, self.toolchain_name,
app_config=app_config)
mock_config_init.assert_called_with(self.target, self.src_paths,
app_config=app_config)
@patch('tools.config.Config.__init__')
def test_prepare_toolchain_no_app_config(self, mock_config_init):
"""
Test that prepare_toolchain correctly deals with no app_config
:param mock_config_init: mock of Config __init__
:return:
"""
mock_config_init.return_value = None
prepare_toolchain(self.src_paths, self.target, self.toolchain_name)
mock_config_init.assert_called_with(self.target, self.src_paths,
app_config=None)
@patch('tools.build_api.scan_resources')
@patch('tools.build_api.mkdir')
@patch('os.path.exists')
@patch('tools.build_api.prepare_toolchain')
def test_build_project_app_config(self, mock_prepare_toolchain, mock_exists, _, __):
"""
Test that build_project uses app_config correctly
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_exists: mock of function os.path.exists
:param _: mock of function mkdir (not tested)
:param __: mock of function scan_resources (not tested)
:return:
"""
app_config = "app_config"
mock_exists.return_value = False
mock_prepare_toolchain().link_program.return_value = 1, 2
build_project(self.src_paths, self.build_path, self.target,
self.toolchain_name, app_config=app_config)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], app_config,
"prepare_toolchain was called with an incorrect app_config")
@patch('tools.build_api.scan_resources')
@patch('tools.build_api.mkdir')
@patch('os.path.exists')
@patch('tools.build_api.prepare_toolchain')
def test_build_project_no_app_config(self, mock_prepare_toolchain, mock_exists, _, __):
"""
Test that build_project correctly deals with no app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_exists: mock of function os.path.exists
:param _: mock of function mkdir (not tested)
:param __: mock of function scan_resources (not tested)
:return:
"""
mock_exists.return_value = False
# Needed for the unpacking of the returned value
mock_prepare_toolchain().link_program.return_value = 1, 2
build_project(self.src_paths, self.build_path, self.target,
self.toolchain_name)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], None,
"prepare_toolchain was called with an incorrect app_config")
@patch('tools.build_api.scan_resources')
@patch('tools.build_api.mkdir')
@patch('os.path.exists')
@patch('tools.build_api.prepare_toolchain')
def test_build_library_app_config(self, mock_prepare_toolchain, mock_exists, _, __):
"""
Test that build_library uses app_config correctly
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_exists: mock of function os.path.exists
:param _: mock of function mkdir (not tested)
:param __: mock of function scan_resources (not tested)
:return:
"""
app_config = "app_config"
mock_exists.return_value = False
build_library(self.src_paths, self.build_path, self.target,
self.toolchain_name, app_config=app_config)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], app_config,
"prepare_toolchain was called with an incorrect app_config")
@patch('tools.build_api.scan_resources')
@patch('tools.build_api.mkdir')
@patch('os.path.exists')
@patch('tools.build_api.prepare_toolchain')
def test_build_library_no_app_config(self, mock_prepare_toolchain, mock_exists, _, __):
"""
Test that build_library correctly deals with no app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_exists: mock of function os.path.exists
:param _: mock of function mkdir (not tested)
:param __: mock of function scan_resources (not tested)
:return:
"""
mock_exists.return_value = False
build_library(self.src_paths, self.build_path, self.target,
self.toolchain_name)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], None,
"prepare_toolchain was called with an incorrect app_config")
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,132 @@
"""
mbed SDK
Copyright (c) 2016 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
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.
"""
import os.path
import unittest
from mock import patch
from tools.config import Config
"""
Tests for config.py
"""
class ConfigTests(unittest.TestCase):
"""
Test cases for Config class
"""
def setUp(self):
"""
Called before each test case
:return:
"""
self.target = "K64F"
def tearDown(self):
"""
Called after each test case
:return:
"""
pass
@patch.object(Config, '_process_config_and_overrides')
@patch('tools.config.json_file_to_dict')
def test_init_app_config(self, mock_json_file_to_dict, _):
"""
Test that the initialisation correctly uses app_config
:param mock_json_file_to_dict: mock of function json_file_to_dict
:param _: mock of function _process_config_and_overrides (not tested)
:return:
"""
app_config = "app_config"
mock_return = {'config': 'test'}
mock_json_file_to_dict.return_value = mock_return
config = Config(self.target, app_config=app_config)
mock_json_file_to_dict.assert_called_with(app_config)
self.assertEqual(config.app_config_data, mock_return,
"app_config_data should be set to the returned value")
@patch.object(Config, '_process_config_and_overrides')
@patch('tools.config.json_file_to_dict')
def test_init_no_app_config(self, mock_json_file_to_dict, _):
"""
Test that the initialisation works without app config
:param mock_json_file_to_dict: mock of function json_file_to_dict
:param _: patch of function _process_config_and_overrides (not tested)
:return:
"""
config = Config(self.target)
mock_json_file_to_dict.assert_not_called()
self.assertEqual(config.app_config_data, {},
"app_config_data should be set an empty dictionary")
@patch.object(Config, '_process_config_and_overrides')
@patch('os.path.isfile')
@patch('tools.config.json_file_to_dict')
def test_init_no_app_config_with_dir(self, mock_json_file_to_dict, mock_isfile, _):
"""
Test that the initialisation works without app config and with a
specified top level directory
:param mock_json_file_to_dict: mock of function json_file_to_dict
:param _: patch of function _process_config_and_overrides (not tested)
:return:
"""
directory = '.'
path = os.path.join('.', 'mbed_app.json')
mock_return = {'config': 'test'}
mock_json_file_to_dict.return_value = mock_return
mock_isfile.return_value = True
config = Config(self.target, [directory])
mock_isfile.assert_called_with(path)
mock_json_file_to_dict.assert_called_once_with(path)
self.assertEqual(config.app_config_data, mock_return,
"app_config_data should be set to the returned value")
@patch.object(Config, '_process_config_and_overrides')
@patch('tools.config.json_file_to_dict')
def test_init_override_app_config(self, mock_json_file_to_dict, _):
"""
Test that the initialisation uses app_config instead of top_level_dir
when both are specified
:param mock_json_file_to_dict: mock of function json_file_to_dict
:param _: patch of function _process_config_and_overrides (not tested)
:return:
"""
app_config = "app_config"
directory = '.'
mock_return = {'config': 'test'}
mock_json_file_to_dict.return_value = mock_return
config = Config(self.target, [directory], app_config=app_config)
mock_json_file_to_dict.assert_called_once_with(app_config)
self.assertEqual(config.app_config_data, mock_return,
"app_config_data should be set to the returned value")
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,141 @@
"""
mbed SDK
Copyright (c) 2016 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
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.
"""
import unittest
from mock import patch
from tools.test_api import find_tests, build_tests
"""
Tests for test_api.py
"""
class TestApiTests(unittest.TestCase):
"""
Test cases for Test Api
"""
def setUp(self):
"""
Called before each test case
:return:
"""
self.base_dir = 'base_dir'
self.target = "K64F"
self.toolchain_name = "ARM"
def tearDown(self):
"""
Called after each test case
:return:
"""
pass
@patch('tools.test_api.scan_resources')
@patch('tools.test_api.prepare_toolchain')
def test_find_tests_app_config(self, mock_prepare_toolchain, mock_scan_resources):
"""
Test find_tests for correct use of app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_scan_resources: mock of function scan_resources
:return:
"""
app_config = "app_config"
mock_scan_resources().inc_dirs.return_value = []
find_tests(self.base_dir, self.target, self.toolchain_name, app_config=app_config)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], app_config,
"prepare_toolchain was called with an incorrect app_config")
@patch('tools.test_api.scan_resources')
@patch('tools.test_api.prepare_toolchain')
def test_find_tests_no_app_config(self, mock_prepare_toolchain, mock_scan_resources):
"""
Test find_tests correctly deals with no app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_scan_resources: mock of function scan_resources
:return:
"""
mock_scan_resources().inc_dirs.return_value = []
find_tests(self.base_dir, self.target, self.toolchain_name)
args = mock_prepare_toolchain.call_args
self.assertTrue('app_config' in args[1],
"prepare_toolchain was not called with app_config")
self.assertEqual(args[1]['app_config'], None,
"prepare_toolchain was called with an incorrect app_config")
@patch('tools.test_api.scan_resources')
@patch('tools.test_api.build_project')
def test_build_tests_app_config(self, mock_build_project, mock_scan_resources):
"""
Test build_tests for correct use of app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_scan_resources: mock of function scan_resources
:return:
"""
tests = {'test1': 'test1_path','test2': 'test2_path'}
src_paths = ['.']
build_path = "build_path"
app_config = "app_config"
mock_build_project.return_value = "build_project"
build_tests(tests, src_paths, build_path, self.target, self.toolchain_name,
app_config=app_config)
arg_list = mock_build_project.call_args_list
for args in arg_list:
self.assertTrue('app_config' in args[1],
"build_tests was not called with app_config")
self.assertEqual(args[1]['app_config'], app_config,
"build_tests was called with an incorrect app_config")
@patch('tools.test_api.scan_resources')
@patch('tools.test_api.build_project')
def test_build_tests_no_app_config(self, mock_build_project, mock_scan_resources):
"""
Test build_tests correctly deals with no app_config
:param mock_prepare_toolchain: mock of function prepare_toolchain
:param mock_scan_resources: mock of function scan_resources
:return:
"""
tests = {'test1': 'test1_path', 'test2': 'test2_path'}
src_paths = ['.']
build_path = "build_path"
mock_build_project.return_value = "build_project"
build_tests(tests, src_paths, build_path, self.target, self.toolchain_name)
arg_list = mock_build_project.call_args_list
for args in arg_list:
self.assertTrue('app_config' in args[1],
"build_tests was not called with app_config")
self.assertEqual(args[1]['app_config'], None,
"build_tests was called with an incorrect app_config")
if __name__ == '__main__':
unittest.main()

View File

@ -1990,18 +1990,20 @@ def test_path_to_name(path, base):
return "-".join(name_parts).lower()
def find_tests(base_dir, target_name, toolchain_name, options=None):
def find_tests(base_dir, target_name, toolchain_name, options=None, app_config=None):
""" Finds all tests in a directory recursively
base_dir: path to the directory to scan for tests (ex. 'path/to/project')
target_name: name of the target to use for scanning (ex. 'K64F')
toolchain_name: name of the toolchain to use for scanning (ex. 'GCC_ARM')
options: Compile options to pass to the toolchain (ex. ['debug-info'])
app_config - location of a chosen mbed_app.json file
"""
tests = {}
# Prepare the toolchain
toolchain = prepare_toolchain([base_dir], target_name, toolchain_name, options=options, silent=True)
toolchain = prepare_toolchain([base_dir], target_name, toolchain_name, options=options,
silent=True, app_config=app_config)
# Scan the directory for paths to probe for 'TESTS' folders
base_resources = scan_resources([base_dir], toolchain)
@ -2060,7 +2062,7 @@ def norm_relative_path(path, start):
def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
options=None, clean=False, notify=None, verbose=False, jobs=1,
macros=None, silent=False, report=None, properties=None,
continue_on_build_fail=False):
continue_on_build_fail=False, app_config=None):
"""Given the data structure from 'find_tests' and the typical build parameters,
build all the tests
@ -2101,7 +2103,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
project_id=test_name,
report=report,
properties=properties,
verbose=verbose)
verbose=verbose,
app_config=app_config)
except Exception, e:
if not isinstance(e, NotSupportedException):