Skip to content

Commit 64928b0

Browse files
authored
Merge pull request #2244 from bridadan/test-discovery-refactor
Test discovery refactor
2 parents 5d73176 + 7b20b0f commit 64928b0

File tree

4 files changed

+73
-93
lines changed

4 files changed

+73
-93
lines changed

tools/build_api.py

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -970,63 +970,3 @@ def write_build_report(build_report, template_filename, filename):
970970

971971
with open(filename, 'w+') as f:
972972
f.write(template.render(failing_builds=build_report_failing, passing_builds=build_report_passing))
973-
974-
975-
def scan_for_source_paths(path, exclude_paths=None):
976-
ignorepatterns = []
977-
paths = []
978-
979-
def is_ignored(file_path):
980-
for pattern in ignorepatterns:
981-
if fnmatch.fnmatch(file_path, pattern):
982-
return True
983-
return False
984-
985-
986-
""" os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
987-
When topdown is True, the caller can modify the dirnames list in-place
988-
(perhaps using del or slice assignment), and walk() will only recurse into
989-
the subdirectories whose names remain in dirnames; this can be used to prune
990-
the search, impose a specific order of visiting, or even to inform walk()
991-
about directories the caller creates or renames before it resumes walk()
992-
again. Modifying dirnames when topdown is False is ineffective, because in
993-
bottom-up mode the directories in dirnames are generated before dirpath
994-
itself is generated.
995-
"""
996-
for root, dirs, files in walk(path, followlinks=True):
997-
# Remove ignored directories
998-
# Check if folder contains .mbedignore
999-
if ".mbedignore" in files :
1000-
with open (join(root,".mbedignore"), "r") as f:
1001-
lines=f.readlines()
1002-
lines = [l.strip() for l in lines] # Strip whitespaces
1003-
lines = [l for l in lines if l != ""] # Strip empty lines
1004-
lines = [l for l in lines if not re.match("^#",l)] # Strip comment lines
1005-
# Append root path to glob patterns
1006-
# and append patterns to ignorepatterns
1007-
ignorepatterns.extend([join(root,line.strip()) for line in lines])
1008-
1009-
for d in copy(dirs):
1010-
dir_path = join(root, d)
1011-
1012-
# Always ignore hidden directories
1013-
if d.startswith('.'):
1014-
dirs.remove(d)
1015-
1016-
# Remove dirs that already match the ignorepatterns
1017-
# to avoid travelling into them and to prevent them
1018-
# on appearing in include path.
1019-
if is_ignored(join(dir_path,"")):
1020-
dirs.remove(d)
1021-
1022-
if exclude_paths:
1023-
for exclude_path in exclude_paths:
1024-
rel_path = relpath(dir_path, exclude_path)
1025-
if not (rel_path.startswith('..')):
1026-
dirs.remove(d)
1027-
break
1028-
1029-
# Add root to include paths
1030-
paths.append(root)
1031-
1032-
return paths

tools/test.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from tools.build_api import build_project, build_library
3232
from tools.build_api import print_build_memory_usage_results
3333
from tools.targets import TARGET_MAP
34-
from tools.utils import mkdir, ToolException, NotSupportedException
34+
from tools.utils import mkdir, ToolException, NotSupportedException, args_error
3535
from tools.test_exporters import ReportExporter, ResultExporterType
3636
from utils import argparse_filestring_type, argparse_lowercase_type, argparse_many
3737
from utils import argparse_dir_not_parent
@@ -105,9 +105,19 @@
105105
all_tests = {}
106106
tests = {}
107107

108+
# Target
109+
if options.mcu is None :
110+
args_error(parser, "[ERROR] You should specify an MCU")
111+
mcu = options.mcu[0]
112+
113+
# Toolchain
114+
if options.tool is None:
115+
args_error(parser, "[ERROR] You should specify a TOOLCHAIN")
116+
toolchain = options.tool[0]
117+
108118
# Find all tests in the relevant paths
109119
for path in all_paths:
110-
all_tests.update(find_tests(path))
120+
all_tests.update(find_tests(path, mcu, toolchain, options.options))
111121

112122
# Filter tests by name if specified
113123
if options.names:
@@ -151,16 +161,13 @@
151161
if not base_source_paths:
152162
base_source_paths = ['.']
153163

154-
155-
target = options.mcu[0]
156-
157164
build_report = {}
158165
build_properties = {}
159166

160167
library_build_success = False
161168
try:
162169
# Build sources
163-
build_library(base_source_paths, options.build_dir, target, options.tool[0],
170+
build_library(base_source_paths, options.build_dir, mcu, toolchain,
164171
options=options.options,
165172
jobs=options.jobs,
166173
clean=options.clean,
@@ -187,7 +194,7 @@
187194
print "Failed to build library"
188195
else:
189196
# Build all the tests
190-
test_build_success, test_build = build_tests(tests, [options.build_dir], options.build_dir, target, options.tool[0],
197+
test_build_success, test_build = build_tests(tests, [options.build_dir], options.build_dir, mcu, toolchain,
191198
options=options.options,
192199
clean=options.clean,
193200
report=build_report,

tools/test_api.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
from types import ListType
3232
from colorama import Fore, Back, Style
3333
from prettytable import PrettyTable
34+
from copy import copy
3435

3536
from time import sleep, time
3637
from Queue import Queue, Empty
37-
from os.path import join, exists, basename
38+
from os.path import join, exists, basename, relpath
3839
from threading import Thread, Lock
3940
from subprocess import Popen, PIPE
4041

@@ -56,7 +57,8 @@
5657
from tools.build_api import prep_properties
5758
from tools.build_api import create_result
5859
from tools.build_api import add_result_to_report
59-
from tools.build_api import scan_for_source_paths
60+
from tools.build_api import prepare_toolchain
61+
from tools.build_api import scan_resources
6062
from tools.libraries import LIBRARIES, LIBRARY_MAP
6163
from tools.toolchains import TOOLCHAIN_PATHS
6264
from tools.toolchains import TOOLCHAINS
@@ -65,6 +67,7 @@
6567
from tools.utils import argparse_uppercase_type
6668
from tools.utils import argparse_lowercase_type
6769
from tools.utils import argparse_many
70+
from tools.utils import get_path_depth
6871

6972
import tools.host_tests.host_tests_plugins as host_tests_plugins
7073

@@ -1987,33 +1990,46 @@ def test_path_to_name(path):
19871990

19881991
return "-".join(name_parts).lower()
19891992

1990-
def find_tests(base_dir):
1991-
"""Given any directory, walk through the subdirectories and find all tests"""
1992-
1993-
def find_test_in_directory(directory, tests_path):
1994-
"""Given a 'TESTS' directory, return a dictionary of test names and test paths.
1995-
The formate of the dictionary is {"test-name": "./path/to/test"}"""
1996-
test = None
1997-
if tests_path in directory:
1998-
head, test_case_directory = os.path.split(directory)
1999-
if test_case_directory != tests_path and test_case_directory != "host_tests":
2000-
head, test_group_directory = os.path.split(head)
2001-
if test_group_directory != tests_path and test_case_directory != "host_tests":
2002-
test = {
2003-
"name": test_path_to_name(directory),
2004-
"path": directory
2005-
}
2006-
2007-
return test
1993+
def find_tests(base_dir, target_name, toolchain_name, options=None):
1994+
""" Finds all tests in a directory recursively
1995+
base_dir: path to the directory to scan for tests (ex. 'path/to/project')
1996+
target_name: name of the target to use for scanning (ex. 'K64F')
1997+
toolchain_name: name of the toolchain to use for scanning (ex. 'GCC_ARM')
1998+
options: Compile options to pass to the toolchain (ex. ['debug-info'])
1999+
"""
20082000

2009-
tests_path = 'TESTS'
20102001
tests = {}
2011-
dirs = scan_for_source_paths(base_dir)
20122002

2003+
# Prepare the toolchain
2004+
toolchain = prepare_toolchain(base_dir, target_name, toolchain_name, options=options, silent=True)
2005+
2006+
# Scan the directory for paths to probe for 'TESTS' folders
2007+
base_resources = scan_resources(base_dir, toolchain)
2008+
2009+
dirs = base_resources.inc_dirs
20132010
for directory in dirs:
2014-
test = find_test_in_directory(directory, tests_path)
2015-
if test:
2016-
tests[test['name']] = test['path']
2011+
subdirs = os.listdir(directory)
2012+
2013+
# If the directory contains a subdirectory called 'TESTS', scan it for test cases
2014+
if 'TESTS' in subdirs:
2015+
walk_base_dir = join(directory, 'TESTS')
2016+
test_resources = toolchain.scan_resources(walk_base_dir, base_path=base_dir)
2017+
2018+
# Loop through all subdirectories
2019+
for d in test_resources.inc_dirs:
2020+
2021+
# If the test case folder is not called 'host_tests' and it is
2022+
# located two folders down from the main 'TESTS' folder (ex. TESTS/testgroup/testcase)
2023+
# then add it to the tests
2024+
path_depth = get_path_depth(relpath(d, walk_base_dir))
2025+
if path_depth == 2:
2026+
test_group_directory_path, test_case_directory = os.path.split(d)
2027+
test_group_directory = os.path.basename(test_group_directory_path)
2028+
2029+
# Check to make sure discoverd folder is not in a host test directory
2030+
if test_case_directory != 'host_tests' and test_group_directory != 'host_tests':
2031+
test_name = test_path_to_name(d)
2032+
tests[test_name] = d
20172033

20182034
return tests
20192035

tools/utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import math
2222
from os import listdir, remove, makedirs
2323
from shutil import copyfile
24-
from os.path import isdir, join, exists, split, relpath, splitext, abspath, commonprefix
24+
from os.path import isdir, join, exists, split, relpath, splitext, abspath, commonprefix, normpath
2525
from subprocess import Popen, PIPE, STDOUT, call
2626
import json
2727
from collections import OrderedDict
@@ -173,6 +173,23 @@ def split_path(path):
173173
return base, name, ext
174174

175175

176+
def get_path_depth(path):
177+
""" Given a path, return the number of directory levels present.
178+
This roughly translates to the number of path separators (os.sep) + 1.
179+
Ex. Given "path/to/dir", this would return 3
180+
Special cases: "." and "/" return 0
181+
"""
182+
normalized_path = normpath(path)
183+
path_depth = 0
184+
head, tail = split(normalized_path)
185+
186+
while(tail and tail != '.'):
187+
path_depth += 1
188+
head, tail = split(head)
189+
190+
return path_depth
191+
192+
176193
def args_error(parser, message):
177194
print "\n\n%s\n\n" % message
178195
parser.print_help()

0 commit comments

Comments
 (0)