blob: e3767e4e9afe2d80f89ca48c70ea37818a0fc992 [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
kellyhung218551d2019-04-08 18:20:33 +0800215 def _get_test_info_filter(self, path, methods, module_name, **kwargs):
216 """Get test info filter.
217
218 Args:
219 path: A string of the test's path.
220 methods: A set of method name strings.
221 module_name: A string of the module name.
222 rel_module_dir: Optional. A string of the module dir relative to
223 root.
224 class_name: Optional. A string of the class name.
225 is_native_test: Optional. A boolean variable of whether to search
226 for a native test or not.
227
228 Returns:
229 A set of test info filter.
230 """
231 _, file_name = test_finder_utils.get_dir_path_and_filename(path)
232 ti_filter = frozenset()
233 if kwargs.get('is_native_test', None):
234 ti_filter = frozenset([test_info.TestFilter(
235 test_finder_utils.get_cc_filter(
236 kwargs.get('class_name', '*'), methods), frozenset())])
237 # Path to java file.
238 elif file_name and _JAVA_EXT_RE.match(file_name):
239 full_class_name = test_finder_utils.get_fully_qualified_class_name(
240 path)
241 ti_filter = frozenset(
242 [test_info.TestFilter(full_class_name, methods)])
243 # Path to cc file.
244 elif file_name and _CC_EXT_RE.match(file_name):
245 if not test_finder_utils.has_cc_class(path):
246 raise atest_error.MissingCCTestCaseError(
247 "Can't find CC class in %s" % path)
248 if methods:
249 ti_filter = frozenset(
250 [test_info.TestFilter(test_finder_utils.get_cc_filter(
251 kwargs.get('class_name', '*'), methods), frozenset())])
252 # Path to non-module dir, treat as package.
253 elif (not file_name
254 and not self.module_info.is_auto_gen_test_config(module_name)
255 and kwargs.get('rel_module_dir', None) !=
256 os.path.relpath(path, self.root_dir)):
257 dir_items = [os.path.join(path, f) for f in os.listdir(path)]
258 for dir_item in dir_items:
259 if _JAVA_EXT_RE.match(dir_item):
260 package_name = test_finder_utils.get_package_name(dir_item)
261 if package_name:
262 # methods should be empty frozenset for package.
263 if methods:
264 raise atest_error.MethodWithoutClassError(
265 '%s: Method filtering requires class'
266 % str(methods))
267 ti_filter = frozenset(
268 [test_info.TestFilter(package_name, methods)])
269 break
270 return ti_filter
271
kellyhungb1cc2642019-04-08 21:32:43 +0800272 def _get_rel_config(self, test_path):
273 """Get config file's relative path.
274
275 Args:
276 test_path: A string of the test absolute path.
277
278 Returns:
279 A string of config's relative path, else None.
280 """
281 test_dir = os.path.dirname(test_path)
282 rel_module_dir = test_finder_utils.find_parent_module_dir(
283 self.root_dir, test_dir, self.module_info)
284 if rel_module_dir:
285 return os.path.join(rel_module_dir, constants.MODULE_CONFIG)
286 return None
287
288 def _get_test_info(self, test_path, rel_config, module_name, test_filter):
289 """Get test_info for test_path.
290
291 Args:
292 test_path: A string of the test path.
293 rel_config: A string of rel path of config.
294 module_name: A string of the module name to use.
295 test_filter: A test info filter.
296
297 Returns:
298 TestInfo namedtuple if found, else None.
299 """
300 if not rel_config:
301 rel_config = self._get_rel_config(test_path)
302 if not rel_config:
303 return None
304 if not module_name:
305 module_name = self._determine_testable_module(
306 os.path.dirname(rel_config))
307 # The real test config might be recorded in module-info.
308 rel_config = self._get_module_test_config(module_name,
309 rel_config=rel_config)
310 return self._process_test_info(test_info.TestInfo(
311 test_name=module_name,
312 test_runner=self._TEST_RUNNER,
313 build_targets=set(),
314 data={constants.TI_FILTER: test_filter,
315 constants.TI_REL_CONFIG: rel_config}))
316
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800317 def find_test_by_module_name(self, module_name):
318 """Find test for the given module name.
319
320 Args:
321 module_name: A string of the test's module name.
322
323 Returns:
324 A populated TestInfo namedtuple if found, else None.
325 """
Kevin Cheng5be930e2018-02-20 09:39:22 -0800326 mod_info = self.module_info.get_module_info(module_name)
easoncyleef0fb2b12019-01-22 15:49:09 +0800327 if self.module_info.is_testable_module(mod_info):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800328 # path is a list with only 1 element.
Kevin Cheng5be930e2018-02-20 09:39:22 -0800329 rel_config = os.path.join(mod_info['path'][0],
330 constants.MODULE_CONFIG)
nelsonli3cac1342018-08-21 15:33:45 +0800331 rel_config = self._get_module_test_config(module_name, rel_config=rel_config)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800332 return self._process_test_info(test_info.TestInfo(
333 test_name=module_name,
334 test_runner=self._TEST_RUNNER,
Kevin Cheng5be930e2018-02-20 09:39:22 -0800335 build_targets=set(),
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800336 data={constants.TI_REL_CONFIG: rel_config,
337 constants.TI_FILTER: frozenset()}))
338 return None
339
340 def find_test_by_class_name(self, class_name, module_name=None,
kellyhungf4b59472018-05-10 17:00:10 +0800341 rel_config=None, is_native_test=False):
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800342 """Find test files given a class name.
343
344 If module_name and rel_config not given it will calculate it determine
345 it by looking up the tree from the class file.
346
347 Args:
348 class_name: A string of the test's class name.
349 module_name: Optional. A string of the module name to use.
350 rel_config: Optional. A string of module dir relative to repo root.
kellyhungf4b59472018-05-10 17:00:10 +0800351 is_native_test: A boolean variable of whether to search for a
352 native test or not.
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800353
354 Returns:
355 A populated TestInfo namedtuple if test found, else None.
356 """
357 class_name, methods = test_finder_utils.split_methods(class_name)
358 if rel_config:
359 search_dir = os.path.join(self.root_dir,
360 os.path.dirname(rel_config))
361 else:
362 search_dir = self.root_dir
kellyhungf4b59472018-05-10 17:00:10 +0800363 test_path = test_finder_utils.find_class_file(search_dir, class_name,
364 is_native_test)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800365 if not test_path and rel_config:
366 logging.info('Did not find class (%s) under module path (%s), '
367 'researching from repo root.', class_name, rel_config)
mikehoran6740a412018-02-20 15:04:16 -0800368 test_path = test_finder_utils.find_class_file(self.root_dir,
kellyhungf4b59472018-05-10 17:00:10 +0800369 class_name,
370 is_native_test)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800371 if not test_path:
372 return None
kellyhung218551d2019-04-08 18:20:33 +0800373 test_filter = self._get_test_info_filter(
374 test_path, methods, module_name, class_name=class_name,
375 is_native_test=is_native_test)
kellyhungb1cc2642019-04-08 21:32:43 +0800376 tinfo = self._get_test_info(test_path, rel_config, module_name,
377 test_filter)
378 return tinfo
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800379
380 def find_test_by_module_and_class(self, module_class):
381 """Find the test info given a MODULE:CLASS string.
382
383 Args:
384 module_class: A string of form MODULE:CLASS or MODULE:CLASS#METHOD.
385
386 Returns:
387 A populated TestInfo namedtuple if found, else None.
388 """
389 if ':' not in module_class:
390 return None
391 module_name, class_name = module_class.split(':')
392 module_info = self.find_test_by_module_name(module_name)
393 if not module_info:
394 return None
yangbill57739f82019-03-28 18:25:43 +0800395 # If the target module is NATIVE_TEST, search CC classes only.
396 find_result = None
397 if not self.module_info.is_native_test(module_name):
398 # Find by java class.
399 find_result = self.find_test_by_class_name(
400 class_name, module_info.test_name,
401 module_info.data.get(constants.TI_REL_CONFIG))
kellyhungf4b59472018-05-10 17:00:10 +0800402 # Find by cc class.
403 if not find_result:
404 find_result = self.find_test_by_cc_class_name(
405 class_name, module_info.test_name,
406 module_info.data.get(constants.TI_REL_CONFIG))
407 return find_result
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800408
mikehoran6740a412018-02-20 15:04:16 -0800409 def find_test_by_package_name(self, package, module_name=None,
410 rel_config=None):
411 """Find the test info given a PACKAGE string.
412
413 Args:
414 package: A string of the package name.
415 module_name: Optional. A string of the module name.
416 ref_config: Optional. A string of rel path of config.
417
418 Returns:
419 A populated TestInfo namedtuple if found, else None.
420 """
421 _, methods = test_finder_utils.split_methods(package)
422 if methods:
nelsonlic4a71452018-09-13 14:10:30 +0800423 raise atest_error.MethodWithoutClassError('%s: Method filtering '
424 'requires class' % (
425 methods))
mikehoran6740a412018-02-20 15:04:16 -0800426 # Confirm that packages exists and get user input for multiples.
427 if rel_config:
428 search_dir = os.path.join(self.root_dir,
429 os.path.dirname(rel_config))
430 else:
431 search_dir = self.root_dir
432 package_path = test_finder_utils.run_find_cmd(
433 test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir,
434 package.replace('.', '/'))
kellyhungf4b59472018-05-10 17:00:10 +0800435 # Package path will be the full path to the dir represented by package.
mikehoran6740a412018-02-20 15:04:16 -0800436 if not package_path:
437 return None
438 test_filter = frozenset([test_info.TestFilter(package, frozenset())])
kellyhungb1cc2642019-04-08 21:32:43 +0800439 tinfo = self._get_test_info(package_path, rel_config, module_name,
440 test_filter)
441 return tinfo
mikehoran6740a412018-02-20 15:04:16 -0800442
443 def find_test_by_module_and_package(self, module_package):
444 """Find the test info given a MODULE:PACKAGE string.
445
446 Args:
447 module_package: A string of form MODULE:PACKAGE
448
449 Returns:
450 A populated TestInfo namedtuple if found, else None.
451 """
452 module_name, package = module_package.split(':')
453 module_info = self.find_test_by_module_name(module_name)
454 if not module_info:
455 return None
456 return self.find_test_by_package_name(
457 package, module_info.test_name,
458 module_info.data.get(constants.TI_REL_CONFIG))
459
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800460 def find_test_by_path(self, path):
461 """Find the first test info matching the given path.
462
463 Strategy:
464 path_to_java_file --> Resolve to CLASS
kellyhungf4b59472018-05-10 17:00:10 +0800465 path_to_cc_file --> Resolve to CC CLASS
mikehoran8bf6d082018-02-26 16:22:06 -0800466 path_to_module_file -> Resolve to MODULE
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800467 path_to_module_dir -> Resolve to MODULE
mikehoran8bf6d082018-02-26 16:22:06 -0800468 path_to_dir_with_class_files--> Resolve to PACKAGE
469 path_to_any_other_dir --> Resolve as MODULE
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800470
471 Args:
472 path: A string of the test's path.
473
474 Returns:
475 A populated TestInfo namedtuple if test found, else None
476 """
mikehoran8bf6d082018-02-26 16:22:06 -0800477 logging.debug('Finding test by path: %s', path)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800478 path, methods = test_finder_utils.split_methods(path)
479 # TODO: See if this can be generalized and shared with methods above
480 # create absolute path from cwd and remove symbolic links
481 path = os.path.realpath(path)
482 if not os.path.exists(path):
483 return None
kellyhung218551d2019-04-08 18:20:33 +0800484 dir_path, _ = test_finder_utils.get_dir_path_and_filename(path)
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800485 # Module/Class
Simran Basi0da58b12018-02-28 17:47:30 -0800486 rel_module_dir = test_finder_utils.find_parent_module_dir(
487 self.root_dir, dir_path, self.module_info)
488 if not rel_module_dir:
489 return None
Kevin Cheng8b2c94c2017-12-18 14:43:26 -0800490 rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
kellyhungb1cc2642019-04-08 21:32:43 +0800491 test_filter = self._get_test_info_filter(path, methods, None,
492 rel_module_dir=rel_module_dir)
493 return self._get_test_info(path, rel_config, None, test_filter)
kellyhungf4b59472018-05-10 17:00:10 +0800494
495 def find_test_by_cc_class_name(self, class_name, module_name=None,
496 rel_config=None):
497 """Find test files given a cc class name.
498
499 If module_name and rel_config not given, test will be determined
500 by looking up the tree for files which has input class.
501
502 Args:
503 class_name: A string of the test's class name.
504 module_name: Optional. A string of the module name to use.
505 rel_config: Optional. A string of module dir relative to repo root.
506
507 Returns:
508 A populated TestInfo namedtuple if test found, else None.
509 """
Dan Shic095efc2019-04-18 16:05:52 -0700510 # Check if class_name is prepended with file name. If so, trim the
511 # prefix and keep only the class_name.
512 if '.' in class_name:
513 # Assume the class name has a format of file_name.class_name
514 class_name = class_name[class_name.rindex('.')+1:]
515 logging.info('Search with updated class name: %s', class_name)
516 return self.find_test_by_class_name(
517 class_name, module_name, rel_config, is_native_test=True)
Jim Tangd740fa52019-03-14 16:07:08 +0800518
519 def get_testable_modules_with_ld(self, user_input, ld_range=0):
520 """Calculate the edit distances of the input and testable modules.
521
522 The user input will be calculated across all testable modules and
523 results in integers generated by Levenshtein Distance algorithm.
524 To increase the speed of the calculation, a bound can be applied to
525 this method to prevent from calculating every testable modules.
526
527 Guessing from typos, e.g. atest atest_unitests, implies a tangible range
528 of length that Atest only needs to search within it, and the default of
529 the bound is 2.
530
531 Guessing from keywords however, e.g. atest --search Camera, means that
532 the uncertainty of the module name is way higher, and Atest should walk
533 through all testable modules and return the highest possibilities.
534
535 Args:
536 user_input: A string of the user input.
537 ld_range: An integer that range the searching scope. If the length of
538 user_input is 10, then Atest will calculate modules of which
539 length is between 8 and 12. 0 is equivalent to unlimited.
540
541 Returns:
542 A List of LDs and possible module names. If the user_input is "fax",
543 the output will be like:
544 [[2, "fog"], [2, "Fix"], [4, "duck"], [7, "Duckies"]]
545
546 Which means the most lilely names of "fax" are fog and Fix(LD=2),
547 while Dickies is the most unlikely one(LD=7).
548 """
549 atest_utils.colorful_print('\nSearching for similar module names using '
550 'fuzzy search...', constants.CYAN)
551 testable_modules = sorted(self.module_info.get_testable_modules(), key=len)
552 lower_bound = len(user_input) - ld_range
553 upper_bound = len(user_input) + ld_range
554 testable_modules_with_ld = []
555 for module_name in testable_modules:
556 # Dispose those too short or too lengthy.
557 if ld_range != 0:
558 if len(module_name) < lower_bound:
559 continue
560 elif len(module_name) > upper_bound:
561 break
562 testable_modules_with_ld.append(
563 [test_finder_utils.get_levenshtein_distance(
564 user_input, module_name), module_name])
565 return testable_modules_with_ld
566
567 def get_fuzzy_searching_results(self, user_input):
568 """Give results which have no more than allowance of edit distances.
569
570 Args:
571 user_input: the target module name for fuzzy searching.
572
573 Return:
574 A list of guessed modules.
575 """
576 modules_with_ld = self.get_testable_modules_with_ld(user_input,
577 ld_range=constants.LD_RANGE)
578 guessed_modules = []
579 for _distance, _module in modules_with_ld:
580 if _distance <= abs(constants.LD_RANGE):
581 guessed_modules.append(_module)
582 return guessed_modules