blob: 910caf3bab56a511c5d8fba0e17faf7007065c57 [file] [log] [blame]
Kevin Cheng8b2c94c2017-12-18 14:43:26 -08001# Copyright 2018, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""
16Module Finder class.
17"""
18
19import logging
20import os
21import re
22
23# pylint: disable=import-error
24import atest_error
Jim Tangd740fa52019-03-14 16:07:08 +080025import atest_utils
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080026import constants
yelinhsieh6dc7f142018-08-31 16:09:39 +080027from test_finders import test_info
28from test_finders import test_finder_base
29from test_finders import test_finder_utils
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080030from test_runners import atest_tf_test_runner
Kevin Cheng5be930e2018-02-20 09:39:22 -080031from test_runners import robolectric_test_runner
Kevin Chenge084eb02018-02-12 12:48:35 -080032from test_runners import vts_tf_test_runner
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080033
kellyhungf4b59472018-05-10 17:00:10 +080034_CC_EXT_RE = re.compile(r'.*(\.cc|\.cpp)$', re.I)
yelinhsiehe4580a52018-08-16 15:51:54 +080035_JAVA_EXT_RE = re.compile(r'.*(\.java|\.kt)$', re.I)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080036
37_MODULES_IN = 'MODULES-IN-%s'
38_ANDROID_MK = 'Android.mk'
39
40# These are suites in LOCAL_COMPATIBILITY_SUITE that aren't really suites so
41# we can ignore them.
42_SUITES_TO_IGNORE = frozenset({'general-tests', 'device-tests', 'tests'})
43
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080044class ModuleFinder(test_finder_base.TestFinderBase):
45 """Module finder class."""
46 NAME = 'MODULE'
47 _TEST_RUNNER = atest_tf_test_runner.AtestTradefedTestRunner.NAME
Kevin Cheng5be930e2018-02-20 09:39:22 -080048 _ROBOLECTRIC_RUNNER = robolectric_test_runner.RobolectricTestRunner.NAME
Kevin Chenge084eb02018-02-12 12:48:35 -080049 _VTS_TEST_RUNNER = vts_tf_test_runner.VtsTradefedTestRunner.NAME
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080050
51 def __init__(self, module_info=None):
52 super(ModuleFinder, self).__init__()
53 self.root_dir = os.environ.get(constants.ANDROID_BUILD_TOP)
54 self.module_info = module_info
55
Simran Basi1f3c9c82018-08-20 17:08:00 -070056 def _determine_testable_module(self, path):
57 """Determine which module the user is trying to test.
58
59 Returns the module to test. If there are multiple possibilities, will
60 ask the user. Otherwise will return the only module found.
Kevin Cheng5be930e2018-02-20 09:39:22 -080061
62 Args:
63 path: String path of module to look for.
64
65 Returns:
Simran Basi1f3c9c82018-08-20 17:08:00 -070066 String of the module name.
Kevin Cheng5be930e2018-02-20 09:39:22 -080067 """
Simran Basi1f3c9c82018-08-20 17:08:00 -070068 testable_modules = []
Kevin Cheng5be930e2018-02-20 09:39:22 -080069 for mod in self.module_info.get_module_names(path):
70 mod_info = self.module_info.get_module_info(mod)
Simran Basi1f3c9c82018-08-20 17:08:00 -070071 # Robolectric tests always exist in pairs of 2, one module to build
72 # the test and another to run it. For now, we are assuming they are
73 # isolated in their own folders and will return if we find one.
easoncyleef0fb2b12019-01-22 15:49:09 +080074 if self.module_info.is_robolectric_test(mod):
Simran Basi1f3c9c82018-08-20 17:08:00 -070075 return mod
easoncyleef0fb2b12019-01-22 15:49:09 +080076 if self.module_info.is_testable_module(mod_info):
Simran Basi1f3c9c82018-08-20 17:08:00 -070077 testable_modules.append(mod_info.get(constants.MODULE_NAME))
78 return test_finder_utils.extract_test_from_tests(testable_modules)
Kevin Cheng5be930e2018-02-20 09:39:22 -080079
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080080 def _is_vts_module(self, module_name):
81 """Returns True if the module is a vts module, else False."""
82 mod_info = self.module_info.get_module_info(module_name)
Kevin Cheng5be930e2018-02-20 09:39:22 -080083 suites = []
84 if mod_info:
85 suites = mod_info.get('compatibility_suites', [])
Kevin Cheng8b2c94c2017-12-18 14:43:26 -080086 # Pull out all *ts (cts, tvts, etc) suites.
87 suites = [suite for suite in suites if suite not in _SUITES_TO_IGNORE]
88 return len(suites) == 1 and 'vts' in suites
89
Kevin Chenge084eb02018-02-12 12:48:35 -080090 def _update_to_vts_test_info(self, test):
91 """Fill in the fields with vts specific info.
92
93 We need to update the runner to use the vts runner and also find the
94 test specific depedencies
95
96 Args:
97 test: TestInfo to update with vts specific details.
98
99 Return:
100 TestInfo that is ready for the vts test runner.
101 """
102 test.test_runner = self._VTS_TEST_RUNNER
103 config_file = os.path.join(self.root_dir,
104 test.data[constants.TI_REL_CONFIG])
105 # Need to get out dir (special logic is to account for custom out dirs).
106 # The out dir is used to construct the build targets for the test deps.
107 out_dir = os.environ.get(constants.ANDROID_HOST_OUT)
108 custom_out_dir = os.environ.get(constants.ANDROID_OUT_DIR)
109 # If we're not an absolute custom out dir, get relative out dir path.
110 if custom_out_dir is None or not os.path.isabs(custom_out_dir):
111 out_dir = os.path.relpath(out_dir, self.root_dir)
112 vts_out_dir = os.path.join(out_dir, 'vts', 'android-vts', 'testcases')
kellyhungd69de382018-08-07 18:32:58 +0800113 # Parse dependency of default staging plans.
yangbill22ed51a2018-12-04 16:48:24 +0800114
115 xml_path = test_finder_utils.search_integration_dirs(
116 constants.VTS_STAGING_PLAN,
117 self.module_info.get_paths(constants.VTS_TF_MODULE))
kellyhungd69de382018-08-07 18:32:58 +0800118 vts_xmls = test_finder_utils.get_plans_from_vts_xml(xml_path)
119 vts_xmls.add(config_file)
120 for config_file in vts_xmls:
121 # Add in vts test build targets.
122 test.build_targets |= test_finder_utils.get_targets_from_vts_xml(
123 config_file, vts_out_dir, self.module_info)
Kevin Chenge084eb02018-02-12 12:48:35 -0800124 test.build_targets.add('vts-test-core')
Kevin Cheng284aafc2018-03-29 09:09:31 -0700125 test.build_targets.add(test.test_name)
Kevin Chenge084eb02018-02-12 12:48:35 -0800126 return test
127
Kevin Cheng5be930e2018-02-20 09:39:22 -0800128 def _update_to_robolectric_test_info(self, test):
129 """Update the fields for a robolectric test.
130
131 Args:
132 test: TestInfo to be updated with robolectric fields.
133
134 Returns:
135 TestInfo with robolectric fields.
136 """
137 test.test_runner = self._ROBOLECTRIC_RUNNER
easoncyleef0fb2b12019-01-22 15:49:09 +0800138 test.test_name = self.module_info.get_robolectric_test_name(test.test_name)
Kevin Cheng5be930e2018-02-20 09:39:22 -0800139 return test
140
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800141 def _process_test_info(self, test):
142 """Process the test info and return some fields updated/changed.
143
144 We need to check if the test found is a special module (like vts) and
145 update the test_info fields (like test_runner) appropriately.
146
147 Args:
mikehoran6740a412018-02-20 15:04:16 -0800148 test: TestInfo that has been filled out by a find method.
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800149
150 Return:
Jim Tangd740fa52019-03-14 16:07:08 +0800151 TestInfo that has been modified as needed and return None if
nelsonlie8f76d92018-08-31 16:21:47 +0800152 this module can't be found in the module_info.
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800153 """
kellyhung0625d172018-06-21 16:40:27 +0800154 module_name = test.test_name
155 mod_info = self.module_info.get_module_info(module_name)
nelsonlie8f76d92018-08-31 16:21:47 +0800156 if not mod_info:
157 return None
kellyhung0625d172018-06-21 16:40:27 +0800158 test.module_class = mod_info['class']
159 test.install_locations = test_finder_utils.get_install_locations(
160 mod_info['installed'])
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800161 # Check if this is only a vts module.
162 if self._is_vts_module(test.test_name):
Kevin Chenge084eb02018-02-12 12:48:35 -0800163 return self._update_to_vts_test_info(test)
easoncyleef0fb2b12019-01-22 15:49:09 +0800164 elif self.module_info.is_robolectric_test(test.test_name):
Kevin Cheng5be930e2018-02-20 09:39:22 -0800165 return self._update_to_robolectric_test_info(test)
Kevin Cheng5be930e2018-02-20 09:39:22 -0800166 rel_config = test.data[constants.TI_REL_CONFIG]
167 test.build_targets = self._get_build_targets(module_name, rel_config)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800168 return test
169
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800170 def _get_build_targets(self, module_name, rel_config):
171 """Get the test deps.
172
173 Args:
174 module_name: name of the test.
175 rel_config: XML for the given test.
176
177 Returns:
178 Set of build targets.
179 """
180 targets = set()
easoncyleef0fb2b12019-01-22 15:49:09 +0800181 if not self.module_info.is_auto_gen_test_config(module_name):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800182 config_file = os.path.join(self.root_dir, rel_config)
183 targets = test_finder_utils.get_targets_from_xml(config_file,
184 self.module_info)
yangbill73109122018-10-25 20:09:33 +0800185 for module_path in self.module_info.get_paths(module_name):
186 mod_dir = module_path.replace('/', '-')
187 targets.add(_MODULES_IN % mod_dir)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800188 return targets
189
nelsonli3cac1342018-08-21 15:33:45 +0800190 def _get_module_test_config(self, module_name, rel_config=None):
191 """Get the value of test_config in module_info.
192
193 Get the value of 'test_config' in module_info if its
194 auto_test_config is not true.
195 In this case, the test_config is specified by user.
196 If not, return rel_config.
197
198 Args:
199 module_name: A string of the test's module name.
200 rel_config: XML for the given test.
201
202 Returns:
203 A string of test_config path if found, else return rel_config.
204 """
205 mod_info = self.module_info.get_module_info(module_name)
nelsonlie8f76d92018-08-31 16:21:47 +0800206 if mod_info:
207 test_config = ''
208 test_config_list = mod_info.get(constants.MODULE_TEST_CONFIG, [])
209 if test_config_list:
210 test_config = test_config_list[0]
easoncyleef0fb2b12019-01-22 15:49:09 +0800211 if not self.module_info.is_auto_gen_test_config(module_name) and test_config != '':
nelsonlie8f76d92018-08-31 16:21:47 +0800212 return test_config
nelsonli3cac1342018-08-21 15:33:45 +0800213 return rel_config
214
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800215 def find_test_by_module_name(self, module_name):
216 """Find test for the given module name.
217
218 Args:
219 module_name: A string of the test's module name.
220
221 Returns:
222 A populated TestInfo namedtuple if found, else None.
223 """
Kevin Cheng5be930e2018-02-20 09:39:22 -0800224 mod_info = self.module_info.get_module_info(module_name)
easoncyleef0fb2b12019-01-22 15:49:09 +0800225 if self.module_info.is_testable_module(mod_info):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800226 # path is a list with only 1 element.
Kevin Cheng5be930e2018-02-20 09:39:22 -0800227 rel_config = os.path.join(mod_info['path'][0],
228 constants.MODULE_CONFIG)
nelsonli3cac1342018-08-21 15:33:45 +0800229 rel_config = self._get_module_test_config(module_name, rel_config=rel_config)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800230 return self._process_test_info(test_info.TestInfo(
231 test_name=module_name,
232 test_runner=self._TEST_RUNNER,
Kevin Cheng5be930e2018-02-20 09:39:22 -0800233 build_targets=set(),
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800234 data={constants.TI_REL_CONFIG: rel_config,
235 constants.TI_FILTER: frozenset()}))
236 return None
237
238 def find_test_by_class_name(self, class_name, module_name=None,
kellyhungf4b59472018-05-10 17:00:10 +0800239 rel_config=None, is_native_test=False):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800240 """Find test files given a class name.
241
242 If module_name and rel_config not given it will calculate it determine
243 it by looking up the tree from the class file.
244
245 Args:
246 class_name: A string of the test's class name.
247 module_name: Optional. A string of the module name to use.
248 rel_config: Optional. A string of module dir relative to repo root.
kellyhungf4b59472018-05-10 17:00:10 +0800249 is_native_test: A boolean variable of whether to search for a
250 native test or not.
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800251
252 Returns:
253 A populated TestInfo namedtuple if test found, else None.
254 """
255 class_name, methods = test_finder_utils.split_methods(class_name)
256 if rel_config:
257 search_dir = os.path.join(self.root_dir,
258 os.path.dirname(rel_config))
259 else:
260 search_dir = self.root_dir
kellyhungf4b59472018-05-10 17:00:10 +0800261 test_path = test_finder_utils.find_class_file(search_dir, class_name,
262 is_native_test)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800263 if not test_path and rel_config:
264 logging.info('Did not find class (%s) under module path (%s), '
265 'researching from repo root.', class_name, rel_config)
mikehoran6740a412018-02-20 15:04:16 -0800266 test_path = test_finder_utils.find_class_file(self.root_dir,
kellyhungf4b59472018-05-10 17:00:10 +0800267 class_name,
268 is_native_test)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800269 if not test_path:
270 return None
kellyhungf4b59472018-05-10 17:00:10 +0800271 if is_native_test:
272 test_filter = frozenset([test_info.TestFilter(
273 test_finder_utils.get_cc_filter(class_name, methods), frozenset())])
274 else:
275 full_class_name = test_finder_utils.get_fully_qualified_class_name(
276 test_path)
277 test_filter = frozenset([test_info.TestFilter(full_class_name,
278 methods)])
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800279 if not rel_config:
280 test_dir = os.path.dirname(test_path)
281 rel_module_dir = test_finder_utils.find_parent_module_dir(
Simran Basi0da58b12018-02-28 17:47:30 -0800282 self.root_dir, test_dir, self.module_info)
kellyhung2cef0e92018-07-31 11:15:40 +0800283 if not rel_module_dir:
284 return None
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800285 rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
286 if not module_name:
Simran Basi1f3c9c82018-08-20 17:08:00 -0700287 module_name = self._determine_testable_module(os.path.dirname(
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800288 rel_config))
nelsonli3cac1342018-08-21 15:33:45 +0800289 # The real test config might be record in module-info.
290 rel_config = self._get_module_test_config(module_name, rel_config=rel_config)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800291 return self._process_test_info(test_info.TestInfo(
292 test_name=module_name,
293 test_runner=self._TEST_RUNNER,
Kevin Cheng5be930e2018-02-20 09:39:22 -0800294 build_targets=set(),
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800295 data={constants.TI_FILTER: test_filter,
296 constants.TI_REL_CONFIG: rel_config}))
297
298 def find_test_by_module_and_class(self, module_class):
299 """Find the test info given a MODULE:CLASS string.
300
301 Args:
302 module_class: A string of form MODULE:CLASS or MODULE:CLASS#METHOD.
303
304 Returns:
305 A populated TestInfo namedtuple if found, else None.
306 """
307 if ':' not in module_class:
308 return None
309 module_name, class_name = module_class.split(':')
310 module_info = self.find_test_by_module_name(module_name)
311 if not module_info:
312 return None
kellyhungf4b59472018-05-10 17:00:10 +0800313 # Find by java class.
314 find_result = self.find_test_by_class_name(
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800315 class_name, module_info.test_name,
316 module_info.data.get(constants.TI_REL_CONFIG))
kellyhungf4b59472018-05-10 17:00:10 +0800317 # Find by cc class.
318 if not find_result:
319 find_result = self.find_test_by_cc_class_name(
320 class_name, module_info.test_name,
321 module_info.data.get(constants.TI_REL_CONFIG))
322 return find_result
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800323
mikehoran6740a412018-02-20 15:04:16 -0800324 def find_test_by_package_name(self, package, module_name=None,
325 rel_config=None):
326 """Find the test info given a PACKAGE string.
327
328 Args:
329 package: A string of the package name.
330 module_name: Optional. A string of the module name.
331 ref_config: Optional. A string of rel path of config.
332
333 Returns:
334 A populated TestInfo namedtuple if found, else None.
335 """
336 _, methods = test_finder_utils.split_methods(package)
337 if methods:
nelsonlic4a71452018-09-13 14:10:30 +0800338 raise atest_error.MethodWithoutClassError('%s: Method filtering '
339 'requires class' % (
340 methods))
mikehoran6740a412018-02-20 15:04:16 -0800341 # Confirm that packages exists and get user input for multiples.
342 if rel_config:
343 search_dir = os.path.join(self.root_dir,
344 os.path.dirname(rel_config))
345 else:
346 search_dir = self.root_dir
347 package_path = test_finder_utils.run_find_cmd(
348 test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir,
349 package.replace('.', '/'))
kellyhungf4b59472018-05-10 17:00:10 +0800350 # Package path will be the full path to the dir represented by package.
mikehoran6740a412018-02-20 15:04:16 -0800351 if not package_path:
352 return None
353 test_filter = frozenset([test_info.TestFilter(package, frozenset())])
354 if not rel_config:
355 rel_module_dir = test_finder_utils.find_parent_module_dir(
Simran Basi0da58b12018-02-28 17:47:30 -0800356 self.root_dir, package_path, self.module_info)
kellyhung2cef0e92018-07-31 11:15:40 +0800357 if not rel_module_dir:
358 return None
mikehoran6740a412018-02-20 15:04:16 -0800359 rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
360 if not module_name:
Simran Basi1f3c9c82018-08-20 17:08:00 -0700361 module_name = self._determine_testable_module(
mikehoran6740a412018-02-20 15:04:16 -0800362 os.path.dirname(rel_config))
nelsonli3cac1342018-08-21 15:33:45 +0800363 # The real test config might be record in module-info.
364 rel_config = self._get_module_test_config(module_name, rel_config=rel_config)
mikehoran6740a412018-02-20 15:04:16 -0800365 return self._process_test_info(test_info.TestInfo(
366 test_name=module_name,
367 test_runner=self._TEST_RUNNER,
Kevin Cheng5be930e2018-02-20 09:39:22 -0800368 build_targets=set(),
mikehoran6740a412018-02-20 15:04:16 -0800369 data={constants.TI_FILTER: test_filter,
370 constants.TI_REL_CONFIG: rel_config}))
371
372 def find_test_by_module_and_package(self, module_package):
373 """Find the test info given a MODULE:PACKAGE string.
374
375 Args:
376 module_package: A string of form MODULE:PACKAGE
377
378 Returns:
379 A populated TestInfo namedtuple if found, else None.
380 """
381 module_name, package = module_package.split(':')
382 module_info = self.find_test_by_module_name(module_name)
383 if not module_info:
384 return None
385 return self.find_test_by_package_name(
386 package, module_info.test_name,
387 module_info.data.get(constants.TI_REL_CONFIG))
388
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800389 def find_test_by_path(self, path):
390 """Find the first test info matching the given path.
391
392 Strategy:
393 path_to_java_file --> Resolve to CLASS
kellyhungf4b59472018-05-10 17:00:10 +0800394 path_to_cc_file --> Resolve to CC CLASS
mikehoran8bf6d082018-02-26 16:22:06 -0800395 path_to_module_file -> Resolve to MODULE
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800396 path_to_module_dir -> Resolve to MODULE
mikehoran8bf6d082018-02-26 16:22:06 -0800397 path_to_dir_with_class_files--> Resolve to PACKAGE
398 path_to_any_other_dir --> Resolve as MODULE
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800399
400 Args:
401 path: A string of the test's path.
402
403 Returns:
404 A populated TestInfo namedtuple if test found, else None
405 """
mikehoran8bf6d082018-02-26 16:22:06 -0800406 logging.debug('Finding test by path: %s', path)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800407 path, methods = test_finder_utils.split_methods(path)
408 # TODO: See if this can be generalized and shared with methods above
409 # create absolute path from cwd and remove symbolic links
410 path = os.path.realpath(path)
411 if not os.path.exists(path):
412 return None
413 dir_path, file_name = test_finder_utils.get_dir_path_and_filename(path)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800414 # Module/Class
Simran Basi0da58b12018-02-28 17:47:30 -0800415 rel_module_dir = test_finder_utils.find_parent_module_dir(
416 self.root_dir, dir_path, self.module_info)
417 if not rel_module_dir:
418 return None
Simran Basi1f3c9c82018-08-20 17:08:00 -0700419 module_name = self._determine_testable_module(rel_module_dir)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800420 rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
nelsonli3cac1342018-08-21 15:33:45 +0800421 # The real test config might be record in module-info.
422 rel_config = self._get_module_test_config(module_name, rel_config=rel_config)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800423 data = {constants.TI_REL_CONFIG: rel_config,
424 constants.TI_FILTER: frozenset()}
kellyhungf4b59472018-05-10 17:00:10 +0800425 # Path is to java file.
yelinhsiehe4580a52018-08-16 15:51:54 +0800426 if file_name and _JAVA_EXT_RE.match(file_name):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800427 full_class_name = test_finder_utils.get_fully_qualified_class_name(
428 path)
429 data[constants.TI_FILTER] = frozenset(
430 [test_info.TestFilter(full_class_name, methods)])
kellyhungf4b59472018-05-10 17:00:10 +0800431 # Path is to cc file.
432 elif file_name and _CC_EXT_RE.match(file_name):
433 if not test_finder_utils.has_cc_class(path):
nelsonlic4a71452018-09-13 14:10:30 +0800434 raise atest_error.MissingCCTestCaseError(
435 "Can't find CC class in %s" % path)
kellyhungf4b59472018-05-10 17:00:10 +0800436 if methods:
437 data[constants.TI_FILTER] = frozenset(
438 [test_info.TestFilter(test_finder_utils.get_cc_filter(
439 '*', methods), frozenset())])
440 # Path to non-module dir, treat as package.
easoncyleef0fb2b12019-01-22 15:49:09 +0800441 elif (not file_name and not self.module_info.is_auto_gen_test_config(module_name)
mikehoran8bf6d082018-02-26 16:22:06 -0800442 and rel_module_dir != os.path.relpath(path, self.root_dir)):
443 dir_items = [os.path.join(path, f) for f in os.listdir(path)]
444 for dir_item in dir_items:
yelinhsiehe4580a52018-08-16 15:51:54 +0800445 if _JAVA_EXT_RE.match(dir_item):
mikehoran8bf6d082018-02-26 16:22:06 -0800446 package_name = test_finder_utils.get_package_name(dir_item)
447 if package_name:
448 # methods should be empty frozenset for package.
449 if methods:
nelsonlic4a71452018-09-13 14:10:30 +0800450 raise atest_error.MethodWithoutClassError(
451 '%s: Method filtering requires class'
452 % str(methods))
mikehoran8bf6d082018-02-26 16:22:06 -0800453 data[constants.TI_FILTER] = frozenset(
454 [test_info.TestFilter(package_name, methods)])
455 break
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800456 return self._process_test_info(test_info.TestInfo(
457 test_name=module_name,
458 test_runner=self._TEST_RUNNER,
Kevin Cheng5be930e2018-02-20 09:39:22 -0800459 build_targets=set(),
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800460 data=data))
kellyhungf4b59472018-05-10 17:00:10 +0800461
462 def find_test_by_cc_class_name(self, class_name, module_name=None,
463 rel_config=None):
464 """Find test files given a cc class name.
465
466 If module_name and rel_config not given, test will be determined
467 by looking up the tree for files which has input class.
468
469 Args:
470 class_name: A string of the test's class name.
471 module_name: Optional. A string of the module name to use.
472 rel_config: Optional. A string of module dir relative to repo root.
473
474 Returns:
475 A populated TestInfo namedtuple if test found, else None.
476 """
477 return self.find_test_by_class_name(class_name, module_name,
478 rel_config, is_native_test=True)
Jim Tangd740fa52019-03-14 16:07:08 +0800479
480 def get_testable_modules_with_ld(self, user_input, ld_range=0):
481 """Calculate the edit distances of the input and testable modules.
482
483 The user input will be calculated across all testable modules and
484 results in integers generated by Levenshtein Distance algorithm.
485 To increase the speed of the calculation, a bound can be applied to
486 this method to prevent from calculating every testable modules.
487
488 Guessing from typos, e.g. atest atest_unitests, implies a tangible range
489 of length that Atest only needs to search within it, and the default of
490 the bound is 2.
491
492 Guessing from keywords however, e.g. atest --search Camera, means that
493 the uncertainty of the module name is way higher, and Atest should walk
494 through all testable modules and return the highest possibilities.
495
496 Args:
497 user_input: A string of the user input.
498 ld_range: An integer that range the searching scope. If the length of
499 user_input is 10, then Atest will calculate modules of which
500 length is between 8 and 12. 0 is equivalent to unlimited.
501
502 Returns:
503 A List of LDs and possible module names. If the user_input is "fax",
504 the output will be like:
505 [[2, "fog"], [2, "Fix"], [4, "duck"], [7, "Duckies"]]
506
507 Which means the most lilely names of "fax" are fog and Fix(LD=2),
508 while Dickies is the most unlikely one(LD=7).
509 """
510 atest_utils.colorful_print('\nSearching for similar module names using '
511 'fuzzy search...', constants.CYAN)
512 testable_modules = sorted(self.module_info.get_testable_modules(), key=len)
513 lower_bound = len(user_input) - ld_range
514 upper_bound = len(user_input) + ld_range
515 testable_modules_with_ld = []
516 for module_name in testable_modules:
517 # Dispose those too short or too lengthy.
518 if ld_range != 0:
519 if len(module_name) < lower_bound:
520 continue
521 elif len(module_name) > upper_bound:
522 break
523 testable_modules_with_ld.append(
524 [test_finder_utils.get_levenshtein_distance(
525 user_input, module_name), module_name])
526 return testable_modules_with_ld
527
528 def get_fuzzy_searching_results(self, user_input):
529 """Give results which have no more than allowance of edit distances.
530
531 Args:
532 user_input: the target module name for fuzzy searching.
533
534 Return:
535 A list of guessed modules.
536 """
537 modules_with_ld = self.get_testable_modules_with_ld(user_input,
538 ld_range=constants.LD_RANGE)
539 guessed_modules = []
540 for _distance, _module in modules_with_ld:
541 if _distance <= abs(constants.LD_RANGE):
542 guessed_modules.append(_module)
543 return guessed_modules