From d4e5f2eaa6ed14ac8e22dbbfcf0ed126071cee1b Mon Sep 17 00:00:00 2001 From: Brian Daniels Date: Tue, 5 Jul 2016 12:02:08 -0500 Subject: [PATCH 1/2] Adding test discovery logic to toolchains. This allows toolchains to find test case directories when they are traversing the source tree. It must be done in the toolchians because they are aware of the target, toolchain, enabled features, config, and .mbedignore files. --- tools/test.py | 4 ++-- tools/test_api.py | 34 ++++++++++------------------------ tools/toolchains/__init__.py | 22 ++++++++++++++-------- tools/utils.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/tools/test.py b/tools/test.py index c993794cb50..7dea97d3e7b 100644 --- a/tools/test.py +++ b/tools/test.py @@ -106,8 +106,8 @@ # Find all tests in the relevant paths for path in all_paths: - all_tests.update(find_tests(path)) - + all_tests.update(find_tests(path, options.mcu, options.tool, options.options)) + # Filter tests by name if specified if options.names: all_names = options.names diff --git a/tools/test_api.py b/tools/test_api.py index 6797cae9240..5ae787b7fb8 100644 --- a/tools/test_api.py +++ b/tools/test_api.py @@ -58,8 +58,7 @@ from tools.build_api import add_result_to_report from tools.build_api import scan_for_source_paths from tools.libraries import LIBRARIES, LIBRARY_MAP -from tools.toolchains import TOOLCHAIN_BIN_PATH -from tools.toolchains import TOOLCHAINS +from tools.toolchains import TOOLCHAINS, TOOLCHAIN_BIN_PATH, TOOLCHAIN_CLASSES from tools.test_exporters import ReportExporter, ResultExporterType from tools.utils import argparse_filestring_type from tools.utils import argparse_uppercase_type @@ -1975,6 +1974,7 @@ def get_default_test_options_parser(): help='Prints script version and exits') return parser + def test_path_to_name(path): """Change all slashes in a path into hyphens This creates a unique cross-platform test name based on the path @@ -1987,33 +1987,19 @@ def test_path_to_name(path): return "-".join(name_parts).lower() -def find_tests(base_dir): +def find_tests(base_dir, target, toolchain, options): """Given any directory, walk through the subdirectories and find all tests""" - def find_test_in_directory(directory, tests_path): - """Given a 'TESTS' directory, return a dictionary of test names and test paths. - The formate of the dictionary is {"test-name": "./path/to/test"}""" - test = None - if tests_path in directory: - head, test_case_directory = os.path.split(directory) - if test_case_directory != tests_path and test_case_directory != "host_tests": - head, test_group_directory = os.path.split(head) - if test_group_directory != tests_path and test_case_directory != "host_tests": - test = { - "name": test_path_to_name(directory), - "path": directory - } - - return test + # If the 'target' argument is a string, convert it to a target instance + target = TARGET_MAP[target] + toolchain = TOOLCHAIN_CLASSES[toolchain](target) + resources = toolchain.scan_resources(base_dir) - tests_path = 'TESTS' tests = {} - dirs = scan_for_source_paths(base_dir) - for directory in dirs: - test = find_test_in_directory(directory, tests_path) - if test: - tests[test['name']] = test['path'] + for directory in resources.test_directories: + test_name = test_path_to_name(directory) + tests[test_name] = directory return tests diff --git a/tools/toolchains/__init__.py b/tools/toolchains/__init__.py index 20262619062..6ad370e4b5b 100644 --- a/tools/toolchains/__init__.py +++ b/tools/toolchains/__init__.py @@ -28,7 +28,7 @@ from tools.config import Config from multiprocessing import Pool, cpu_count -from tools.utils import run_cmd, mkdir, rel_path, ToolException, NotSupportedException, split_path +from tools.utils import run_cmd, mkdir, rel_path, ToolException, NotSupportedException, split_path, directory_in_path, is_test_directory from tools.settings import BUILD_OPTIONS, MBED_ORG_USER import tools.hooks as hooks from tools.memap import MemapParser @@ -94,6 +94,10 @@ def __init__(self, base_path=None): # Features self.features = {} + + # Tests + self.test_directories = [] + def __add__(self, resources): if resources is None: @@ -496,7 +500,7 @@ def _add_dir(self, path, resources, base_path, exclude_paths=None): for d in copy(dirs): dir_path = join(root, d) # Add internal repo folders/files. This is needed for exporters - if d == '.hg': + if d == '.hg' and not directory_in_path('TESTS', relpath(root, path)): resources.repo_dirs.append(dir_path) resources.repo_files.extend(self.scan_repository(dir_path)) @@ -506,10 +510,11 @@ def _add_dir(self, path, resources, base_path, exclude_paths=None): # Ignore toolchain that do not match the current TOOLCHAIN (d.startswith('TOOLCHAIN_') and d[10:] not in labels['TOOLCHAIN']) or # Ignore .mbedignore files - self.is_ignored(join(dir_path,"")) or - # Ignore TESTS dir - (d == 'TESTS')): + self.is_ignored(join(dir_path,""))): dirs.remove(d) + elif is_test_directory(relpath(dir_path, path)): + resources.test_directories.append(dir_path) + dirs.remove(d) elif d.startswith('FEATURE_'): # Recursively scan features but ignore them in the current scan. # These are dynamically added by the config system if the conditions are matched @@ -525,9 +530,10 @@ def _add_dir(self, path, resources, base_path, exclude_paths=None): # Add root to include paths resources.inc_dirs.append(root) - for file in files: - file_path = join(root, file) - self._add_file(file_path, resources, base_path) + if not directory_in_path('TESTS', relpath(root, path)): + for file in files: + file_path = join(root, file) + self._add_file(file_path, resources, base_path) # A helper function for both scan_resources and _add_dir. _add_file adds one file # (*file_path*) to the resources object based on the file type. diff --git a/tools/utils.py b/tools/utils.py index 8c33e36d4e0..28100e199ad 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -83,6 +83,22 @@ def find_cmd_abspath(cmd): return abspath +def directory_in_path(directory, path): + """ Returns True if `directory` is found within `path`. + Ex. directory_in_path("dir", "/is/dir/in/here") returns True + Ex. directory_in_path("dir", "/is/my_dir/in/here") returns False + """ + head, tail = split(path) + + while tail: + if tail == directory: + return True + else: + head, tail = split(head) + + return False + + def mkdir(path): if not exists(path): makedirs(path) @@ -140,6 +156,23 @@ def split_path(path): return base, name, ext +def is_test_directory(path): + """Given a path, return true if it is a test case directory. + Directory must follow this pattern: TESTS/testgroup/testcase. This + would return True.""" + tests_path = 'TESTS' + if directory_in_path(tests_path, path): + head, test_case_directory = split(path) + if test_case_directory != tests_path and test_case_directory != "host_tests": + head, test_group_directory = split(head) + if test_group_directory != tests_path and test_case_directory != "host_tests": + head, candidate_tests_path = split(head) + if candidate_tests_path == tests_path: + return True + + return False + + def args_error(parser, message): print "\n\n%s\n\n" % message parser.print_help() From 2497c8f7e217e1fe8c4f429c42d1668bde5284e5 Mon Sep 17 00:00:00 2001 From: Brian Daniels Date: Tue, 5 Jul 2016 16:43:06 -0500 Subject: [PATCH 2/2] test.py now requires -m and -t options --- tools/test.py | 23 +++++++++++++++-------- tools/test_api.py | 1 - 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/test.py b/tools/test.py index 7dea97d3e7b..39107c5b487 100644 --- a/tools/test.py +++ b/tools/test.py @@ -31,7 +31,7 @@ from tools.build_api import build_project, build_library from tools.build_api import print_build_memory_usage_results from tools.targets import TARGET_MAP -from tools.utils import mkdir, ToolException, NotSupportedException +from tools.utils import mkdir, ToolException, NotSupportedException, args_error from tools.test_exporters import ReportExporter, ResultExporterType from utils import argparse_filestring_type, argparse_lowercase_type, argparse_many from tools.toolchains import mbedToolchain @@ -95,6 +95,16 @@ options = parser.parse_args() + if not options.mcu: + args_error(parser, "[ERROR] You should specify an MCU") + + mcu = options.mcu[0] + + if not options.tool: + args_error(parser, "[ERROR] You should specify a TOOLCHAIN") + + toolchain = options.tool[0] + # Filter tests by path if specified if options.paths: all_paths = options.paths @@ -106,8 +116,8 @@ # Find all tests in the relevant paths for path in all_paths: - all_tests.update(find_tests(path, options.mcu, options.tool, options.options)) - + all_tests.update(find_tests(path, mcu, toolchain, options.options)) + # Filter tests by name if specified if options.names: all_names = options.names @@ -150,16 +160,13 @@ if not base_source_paths: base_source_paths = ['.'] - - target = options.mcu[0] - build_report = {} build_properties = {} library_build_success = False try: # Build sources - build_library(base_source_paths, options.build_dir, target, options.tool[0], + build_library(base_source_paths, options.build_dir, mcu, toolchain, options=options.options, jobs=options.jobs, clean=options.clean, @@ -186,7 +193,7 @@ print "Failed to build library" else: # Build all the tests - test_build_success, test_build = build_tests(tests, [options.build_dir], options.build_dir, target, options.tool[0], + test_build_success, test_build = build_tests(tests, [options.build_dir], options.build_dir, mcu, toolchain, options=options.options, clean=options.clean, report=build_report, diff --git a/tools/test_api.py b/tools/test_api.py index 5ae787b7fb8..8aaa9bd2258 100644 --- a/tools/test_api.py +++ b/tools/test_api.py @@ -1974,7 +1974,6 @@ def get_default_test_options_parser(): help='Prints script version and exits') return parser - def test_path_to_name(path): """Change all slashes in a path into hyphens This creates a unique cross-platform test name based on the path