1# 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
19# pylint: disable=line-too-long
20
21import logging
22import os
23
24import atest_error
25import atest_utils
26import constants
27
28from test_finders import test_info
29from test_finders import test_finder_base
30from test_finders import test_finder_utils
31from test_runners import atest_tf_test_runner
32from test_runners import robolectric_test_runner
33from test_runners import vts_tf_test_runner
34
35_MODULES_IN = 'MODULES-IN-%s'
36_ANDROID_MK = 'Android.mk'
37
38# These are suites in LOCAL_COMPATIBILITY_SUITE that aren't really suites so
39# we can ignore them.
40_SUITES_TO_IGNORE = frozenset({'general-tests', 'device-tests', 'tests'})
41
42class ModuleFinder(test_finder_base.TestFinderBase):
43    """Module finder class."""
44    NAME = 'MODULE'
45    _TEST_RUNNER = atest_tf_test_runner.AtestTradefedTestRunner.NAME
46    _ROBOLECTRIC_RUNNER = robolectric_test_runner.RobolectricTestRunner.NAME
47    _VTS_TEST_RUNNER = vts_tf_test_runner.VtsTradefedTestRunner.NAME
48
49    def __init__(self, module_info=None):
50        super(ModuleFinder, self).__init__()
51        self.root_dir = os.environ.get(constants.ANDROID_BUILD_TOP)
52        self.module_info = module_info
53
54    def _determine_testable_module(self, path):
55        """Determine which module the user is trying to test.
56
57        Returns the module to test. If there are multiple possibilities, will
58        ask the user. Otherwise will return the only module found.
59
60        Args:
61            path: String path of module to look for.
62
63        Returns:
64            A list of the module names.
65        """
66        testable_modules = []
67        for mod in self.module_info.get_module_names(path):
68            mod_info = self.module_info.get_module_info(mod)
69            # Robolectric tests always exist in pairs of 2, one module to build
70            # the test and another to run it. For now, we are assuming they are
71            # isolated in their own folders and will return if we find one.
72            if self.module_info.is_robolectric_test(mod):
73                # return a list with one module name if it is robolectric.
74                return [mod]
75            if self.module_info.is_testable_module(mod_info):
76                testable_modules.append(mod_info.get(constants.MODULE_NAME))
77        return test_finder_utils.extract_test_from_tests(testable_modules)
78
79    def _is_vts_module(self, module_name):
80        """Returns True if the module is a vts10 module, else False."""
81        mod_info = self.module_info.get_module_info(module_name)
82        suites = []
83        if mod_info:
84            suites = mod_info.get('compatibility_suites', [])
85        # Pull out all *ts (cts, tvts, etc) suites.
86        suites = [suite for suite in suites if suite not in _SUITES_TO_IGNORE]
87        return len(suites) == 1 and 'vts10' in suites
88
89    def _update_to_vts_test_info(self, test):
90        """Fill in the fields with vts10 specific info.
91
92        We need to update the runner to use the vts10 runner and also find the
93        test specific dependencies.
94
95        Args:
96            test: TestInfo to update with vts10 specific details.
97
98        Return:
99            TestInfo that is ready for the vts10 test runner.
100        """
101        test.test_runner = self._VTS_TEST_RUNNER
102        config_file = os.path.join(self.root_dir,
103                                   test.data[constants.TI_REL_CONFIG])
104        # Need to get out dir (special logic is to account for custom out dirs).
105        # The out dir is used to construct the build targets for the test deps.
106        out_dir = os.environ.get(constants.ANDROID_HOST_OUT)
107        custom_out_dir = os.environ.get(constants.ANDROID_OUT_DIR)
108        # If we're not an absolute custom out dir, get no-absolute out dir path.
109        if custom_out_dir is None or not os.path.isabs(custom_out_dir):
110            out_dir = os.path.relpath(out_dir, self.root_dir)
111        vts_out_dir = os.path.join(out_dir, 'vts10', 'android-vts10', 'testcases')
112        # Parse dependency of default staging plans.
113        xml_paths = test_finder_utils.search_integration_dirs(
114            constants.VTS_STAGING_PLAN,
115            self.module_info.get_paths(constants.VTS_TF_MODULE))
116        vts_xmls = set()
117        vts_xmls.add(config_file)
118        for xml_path in xml_paths:
119            vts_xmls |= test_finder_utils.get_plans_from_vts_xml(xml_path)
120        for config_file in vts_xmls:
121            # Add in vts10 test build targets.
122            test.build_targets |= test_finder_utils.get_targets_from_vts_xml(
123                config_file, vts_out_dir, self.module_info)
124        test.build_targets.add('vts-test-core')
125        test.build_targets.add(test.test_name)
126        return test
127
128    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
138        test.test_name = self.module_info.get_robolectric_test_name(test.test_name)
139        return test
140
141    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 vts10) and
145        update the test_info fields (like test_runner) appropriately.
146
147        Args:
148            test: TestInfo that has been filled out by a find method.
149
150        Return:
151            TestInfo that has been modified as needed and return None if
152            this module can't be found in the module_info.
153        """
154        module_name = test.test_name
155        mod_info = self.module_info.get_module_info(module_name)
156        if not mod_info:
157            return None
158        test.module_class = mod_info['class']
159        test.install_locations = test_finder_utils.get_install_locations(
160            mod_info['installed'])
161        # Check if this is only a vts10 module.
162        if self._is_vts_module(test.test_name):
163            return self._update_to_vts_test_info(test)
164        if self.module_info.is_robolectric_test(test.test_name):
165            return self._update_to_robolectric_test_info(test)
166        rel_config = test.data[constants.TI_REL_CONFIG]
167        test.build_targets = self._get_build_targets(module_name, rel_config)
168        return test
169
170    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()
181        if not self.module_info.is_auto_gen_test_config(module_name):
182            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)
185        if constants.VTS_CORE_SUITE in self.module_info.get_module_info(
186                module_name).get(constants.MODULE_COMPATIBILITY_SUITES, []):
187            targets.add(constants.VTS_CORE_TF_MODULE)
188        for module_path in self.module_info.get_paths(module_name):
189            mod_dir = module_path.replace('/', '-')
190            targets.add(_MODULES_IN % mod_dir)
191        # (b/156457698) Force add vts_kernel_tests as build target if our test
192        # belong to REQUIRED_KERNEL_TEST_MODULES due to required_module option
193        # not working for sh_test in soong.
194        if module_name in constants.REQUIRED_KERNEL_TEST_MODULES:
195            targets.add('vts_kernel_tests')
196        return targets
197
198    def _get_module_test_config(self, module_name, rel_config=None):
199        """Get the value of test_config in module_info.
200
201        Get the value of 'test_config' in module_info if its
202        auto_test_config is not true.
203        In this case, the test_config is specified by user.
204        If not, return rel_config.
205
206        Args:
207            module_name: A string of the test's module name.
208            rel_config: XML for the given test.
209
210        Returns:
211            A string of test_config path if found, else return rel_config.
212        """
213        mod_info = self.module_info.get_module_info(module_name)
214        if mod_info:
215            test_config = ''
216            test_config_list = mod_info.get(constants.MODULE_TEST_CONFIG, [])
217            if test_config_list:
218                test_config = test_config_list[0]
219            if not self.module_info.is_auto_gen_test_config(module_name) and test_config != '':
220                return test_config
221        return rel_config
222
223    def _get_test_info_filter(self, path, methods, **kwargs):
224        """Get test info filter.
225
226        Args:
227            path: A string of the test's path.
228            methods: A set of method name strings.
229            rel_module_dir: Optional. A string of the module dir no-absolute to
230                root.
231            class_name: Optional. A string of the class name.
232            is_native_test: Optional. A boolean variable of whether to search
233                for a native test or not.
234
235        Returns:
236            A set of test info filter.
237        """
238        _, file_name = test_finder_utils.get_dir_path_and_filename(path)
239        ti_filter = frozenset()
240        if kwargs.get('is_native_test', None):
241            ti_filter = frozenset([test_info.TestFilter(
242                test_finder_utils.get_cc_filter(
243                    kwargs.get('class_name', '*'), methods), frozenset())])
244        # Path to java file.
245        elif file_name and constants.JAVA_EXT_RE.match(file_name):
246            full_class_name = test_finder_utils.get_fully_qualified_class_name(
247                path)
248            ti_filter = frozenset(
249                [test_info.TestFilter(full_class_name, methods)])
250        # Path to cc file.
251        elif file_name and constants.CC_EXT_RE.match(file_name):
252            if not test_finder_utils.has_cc_class(path):
253                raise atest_error.MissingCCTestCaseError(
254                    "Can't find CC class in %s" % path)
255            if methods:
256                ti_filter = frozenset(
257                    [test_info.TestFilter(test_finder_utils.get_cc_filter(
258                        kwargs.get('class_name', '*'), methods), frozenset())])
259        # Path to non-module dir, treat as package.
260        elif (not file_name
261              and kwargs.get('rel_module_dir', None) !=
262              os.path.relpath(path, self.root_dir)):
263            dir_items = [os.path.join(path, f) for f in os.listdir(path)]
264            for dir_item in dir_items:
265                if constants.JAVA_EXT_RE.match(dir_item):
266                    package_name = test_finder_utils.get_package_name(dir_item)
267                    if package_name:
268                        # methods should be empty frozenset for package.
269                        if methods:
270                            raise atest_error.MethodWithoutClassError(
271                                '%s: Method filtering requires class'
272                                % str(methods))
273                        ti_filter = frozenset(
274                            [test_info.TestFilter(package_name, methods)])
275                        break
276        return ti_filter
277
278    def _get_rel_config(self, test_path):
279        """Get config file's no-absolute path.
280
281        Args:
282            test_path: A string of the test absolute path.
283
284        Returns:
285            A string of config's no-absolute path, else None.
286        """
287        test_dir = os.path.dirname(test_path)
288        rel_module_dir = test_finder_utils.find_parent_module_dir(
289            self.root_dir, test_dir, self.module_info)
290        if rel_module_dir:
291            return os.path.join(rel_module_dir, constants.MODULE_CONFIG)
292        return None
293
294    def _get_test_infos(self, test_path, rel_config, module_name, test_filter):
295        """Get test_info for test_path.
296
297        Args:
298            test_path: A string of the test path.
299            rel_config: A string of rel path of config.
300            module_name: A string of the module name to use.
301            test_filter: A test info filter.
302
303        Returns:
304            A list of TestInfo namedtuple if found, else None.
305        """
306        if not rel_config:
307            rel_config = self._get_rel_config(test_path)
308            if not rel_config:
309                return None
310        if module_name:
311            module_names = [module_name]
312        else:
313            module_names = self._determine_testable_module(
314                os.path.dirname(rel_config))
315        test_infos = []
316        if module_names:
317            for mname in module_names:
318                # The real test config might be record in module-info.
319                rel_config = self._get_module_test_config(mname,
320                                                          rel_config=rel_config)
321                mod_info = self.module_info.get_module_info(mname)
322                tinfo = self._process_test_info(test_info.TestInfo(
323                    test_name=mname,
324                    test_runner=self._TEST_RUNNER,
325                    build_targets=set(),
326                    data={constants.TI_FILTER: test_filter,
327                          constants.TI_REL_CONFIG: rel_config},
328                    compatibility_suites=mod_info.get(
329                        constants.MODULE_COMPATIBILITY_SUITES, [])))
330                if tinfo:
331                    test_infos.append(tinfo)
332        return test_infos
333
334    def find_test_by_module_name(self, module_name):
335        """Find test for the given module name.
336
337        Args:
338            module_name: A string of the test's module name.
339
340        Returns:
341            A list that includes only 1 populated TestInfo namedtuple
342            if found, otherwise None.
343        """
344        mod_info = self.module_info.get_module_info(module_name)
345        if self.module_info.is_testable_module(mod_info):
346            # path is a list with only 1 element.
347            rel_config = os.path.join(mod_info['path'][0],
348                                      constants.MODULE_CONFIG)
349            rel_config = self._get_module_test_config(module_name,
350                                                      rel_config=rel_config)
351            tinfo = self._process_test_info(test_info.TestInfo(
352                test_name=module_name,
353                test_runner=self._TEST_RUNNER,
354                build_targets=set(),
355                data={constants.TI_REL_CONFIG: rel_config,
356                      constants.TI_FILTER: frozenset()},
357                compatibility_suites=mod_info.get(
358                    constants.MODULE_COMPATIBILITY_SUITES, [])))
359            if tinfo:
360                return [tinfo]
361        return None
362
363    def find_test_by_kernel_class_name(self, module_name, class_name):
364        """Find kernel test for the given class name.
365
366        Args:
367            module_name: A string of the module name to use.
368            class_name: A string of the test's class name.
369
370        Returns:
371            A list of populated TestInfo namedtuple if test found, else None.
372        """
373        class_name, methods = test_finder_utils.split_methods(class_name)
374        test_config = self._get_module_test_config(module_name)
375        if not test_config:
376            return None
377        test_config_path = os.path.join(self.root_dir, test_config)
378        mod_info = self.module_info.get_module_info(module_name)
379        ti_filter = frozenset(
380            [test_info.TestFilter(class_name, methods)])
381        if test_finder_utils.is_test_from_kernel_xml(test_config_path, class_name):
382            tinfo = self._process_test_info(test_info.TestInfo(
383                test_name=module_name,
384                test_runner=self._TEST_RUNNER,
385                build_targets=set(),
386                data={constants.TI_REL_CONFIG: test_config,
387                      constants.TI_FILTER: ti_filter},
388                compatibility_suites=mod_info.get(
389                    constants.MODULE_COMPATIBILITY_SUITES, [])))
390            if tinfo:
391                return [tinfo]
392        return None
393
394    def find_test_by_class_name(self, class_name, module_name=None,
395                                rel_config=None, is_native_test=False):
396        """Find test files given a class name.
397
398        If module_name and rel_config not given it will calculate it determine
399        it by looking up the tree from the class file.
400
401        Args:
402            class_name: A string of the test's class name.
403            module_name: Optional. A string of the module name to use.
404            rel_config: Optional. A string of module dir no-absolute to repo root.
405            is_native_test: A boolean variable of whether to search for a
406            native test or not.
407
408        Returns:
409            A list of populated TestInfo namedtuple if test found, else None.
410        """
411        class_name, methods = test_finder_utils.split_methods(class_name)
412        search_class_name = class_name
413        # For parameterized gtest, test class will be automerged to
414        # $(class_prefix)/$(base_class) name. Using $(base_class) for searching
415        # matched TEST_P to make sure test class is matched.
416        if '/' in search_class_name:
417            search_class_name = str(search_class_name).split('/')[-1]
418        if rel_config:
419            search_dir = os.path.join(self.root_dir,
420                                      os.path.dirname(rel_config))
421        else:
422            search_dir = self.root_dir
423        test_paths = test_finder_utils.find_class_file(search_dir, search_class_name,
424                                                       is_native_test, methods)
425        if not test_paths and rel_config:
426            logging.info('Did not find class (%s) under module path (%s), '
427                         'researching from repo root.', class_name, rel_config)
428            test_paths = test_finder_utils.find_class_file(self.root_dir,
429                                                           search_class_name,
430                                                           is_native_test,
431                                                           methods)
432        if not test_paths:
433            return None
434        tinfos = []
435        for test_path in test_paths:
436            test_filter = self._get_test_info_filter(
437                test_path, methods, class_name=class_name,
438                is_native_test=is_native_test)
439            tinfo = self._get_test_infos(test_path, rel_config,
440                                         module_name, test_filter)
441            if tinfo:
442                tinfos.extend(tinfo)
443        return tinfos
444
445    def find_test_by_module_and_class(self, module_class):
446        """Find the test info given a MODULE:CLASS string.
447
448        Args:
449            module_class: A string of form MODULE:CLASS or MODULE:CLASS#METHOD.
450
451        Returns:
452            A list of populated TestInfo namedtuple if found, else None.
453        """
454        if ':' not in module_class:
455            return None
456        module_name, class_name = module_class.split(':')
457        # module_infos is a list with at most 1 element.
458        module_infos = self.find_test_by_module_name(module_name)
459        module_info = module_infos[0] if module_infos else None
460        if not module_info:
461            return None
462        find_result = None
463        # If the target module is NATIVE_TEST, search CC classes only.
464        if not self.module_info.is_native_test(module_name):
465            # Find by java class.
466            find_result = self.find_test_by_class_name(
467                class_name, module_info.test_name,
468                module_info.data.get(constants.TI_REL_CONFIG))
469        # kernel target test is also define as NATIVE_TEST in build system.
470        # TODO (b/157210083) Update find_test_by_kernel_class_name method to
471        # support gen_rule use case.
472        if not find_result:
473            find_result = self.find_test_by_kernel_class_name(
474                module_name, class_name)
475        # Find by cc class.
476        if not find_result:
477            find_result = self.find_test_by_cc_class_name(
478                class_name, module_info.test_name,
479                module_info.data.get(constants.TI_REL_CONFIG))
480        return find_result
481
482    def find_test_by_package_name(self, package, module_name=None,
483                                  rel_config=None):
484        """Find the test info given a PACKAGE string.
485
486        Args:
487            package: A string of the package name.
488            module_name: Optional. A string of the module name.
489            ref_config: Optional. A string of rel path of config.
490
491        Returns:
492            A list of populated TestInfo namedtuple if found, else None.
493        """
494        _, methods = test_finder_utils.split_methods(package)
495        if methods:
496            raise atest_error.MethodWithoutClassError('%s: Method filtering '
497                                                      'requires class' % (
498                                                          methods))
499        # Confirm that packages exists and get user input for multiples.
500        if rel_config:
501            search_dir = os.path.join(self.root_dir,
502                                      os.path.dirname(rel_config))
503        else:
504            search_dir = self.root_dir
505        package_paths = test_finder_utils.run_find_cmd(
506            test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir, package)
507        # Package path will be the full path to the dir represented by package.
508        if not package_paths:
509            return None
510        test_filter = frozenset([test_info.TestFilter(package, frozenset())])
511        test_infos = []
512        for package_path in package_paths:
513            tinfo = self._get_test_infos(package_path, rel_config,
514                                         module_name, test_filter)
515            if tinfo:
516                test_infos.extend(tinfo)
517        return test_infos
518
519    def find_test_by_module_and_package(self, module_package):
520        """Find the test info given a MODULE:PACKAGE string.
521
522        Args:
523            module_package: A string of form MODULE:PACKAGE
524
525        Returns:
526            A list of populated TestInfo namedtuple if found, else None.
527        """
528        module_name, package = module_package.split(':')
529        # module_infos is a list with at most 1 element.
530        module_infos = self.find_test_by_module_name(module_name)
531        module_info = module_infos[0] if module_infos else None
532        if not module_info:
533            return None
534        return self.find_test_by_package_name(
535            package, module_info.test_name,
536            module_info.data.get(constants.TI_REL_CONFIG))
537
538    def find_test_by_path(self, path):
539        """Find the first test info matching the given path.
540
541        Strategy:
542            path_to_java_file --> Resolve to CLASS
543            path_to_cc_file --> Resolve to CC CLASS
544            path_to_module_file -> Resolve to MODULE
545            path_to_module_dir -> Resolve to MODULE
546            path_to_dir_with_class_files--> Resolve to PACKAGE
547            path_to_any_other_dir --> Resolve as MODULE
548
549        Args:
550            path: A string of the test's path.
551
552        Returns:
553            A list of populated TestInfo namedtuple if test found, else None
554        """
555        logging.debug('Finding test by path: %s', path)
556        path, methods = test_finder_utils.split_methods(path)
557        # TODO: See if this can be generalized and shared with methods above
558        # create absolute path from cwd and remove symbolic links
559        path = os.path.realpath(path)
560        if not os.path.exists(path):
561            return None
562        if (methods and
563                not test_finder_utils.has_method_in_file(path, methods)):
564            return None
565        dir_path, _ = test_finder_utils.get_dir_path_and_filename(path)
566        # Module/Class
567        rel_module_dir = test_finder_utils.find_parent_module_dir(
568            self.root_dir, dir_path, self.module_info)
569        if not rel_module_dir:
570            return None
571        rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
572        test_filter = self._get_test_info_filter(path, methods,
573                                                 rel_module_dir=rel_module_dir)
574        return self._get_test_infos(path, rel_config, None, test_filter)
575
576    def find_test_by_cc_class_name(self, class_name, module_name=None,
577                                   rel_config=None):
578        """Find test files given a cc class name.
579
580        If module_name and rel_config not given, test will be determined
581        by looking up the tree for files which has input class.
582
583        Args:
584            class_name: A string of the test's class name.
585            module_name: Optional. A string of the module name to use.
586            rel_config: Optional. A string of module dir no-absolute to repo root.
587
588        Returns:
589            A list of populated TestInfo namedtuple if test found, else None.
590        """
591        # Check if class_name is prepended with file name. If so, trim the
592        # prefix and keep only the class_name.
593        if '.' in class_name:
594            # Assume the class name has a format of file_name.class_name
595            class_name = class_name[class_name.rindex('.')+1:]
596            logging.info('Search with updated class name: %s', class_name)
597        return self.find_test_by_class_name(
598            class_name, module_name, rel_config, is_native_test=True)
599
600    def get_testable_modules_with_ld(self, user_input, ld_range=0):
601        """Calculate the edit distances of the input and testable modules.
602
603        The user input will be calculated across all testable modules and
604        results in integers generated by Levenshtein Distance algorithm.
605        To increase the speed of the calculation, a bound can be applied to
606        this method to prevent from calculating every testable modules.
607
608        Guessing from typos, e.g. atest atest_unitests, implies a tangible range
609        of length that Atest only needs to search within it, and the default of
610        the bound is 2.
611
612        Guessing from keywords however, e.g. atest --search Camera, means that
613        the uncertainty of the module name is way higher, and Atest should walk
614        through all testable modules and return the highest possibilities.
615
616        Args:
617            user_input: A string of the user input.
618            ld_range: An integer that range the searching scope. If the length
619                      of user_input is 10, then Atest will calculate modules of
620                      which length is between 8 and 12. 0 is equivalent to
621                      unlimited.
622
623        Returns:
624            A List of LDs and possible module names. If the user_input is "fax",
625            the output will be like:
626            [[2, "fog"], [2, "Fix"], [4, "duck"], [7, "Duckies"]]
627
628            Which means the most lilely names of "fax" are fog and Fix(LD=2),
629            while Dickies is the most unlikely one(LD=7).
630        """
631        atest_utils.colorful_print('\nSearching for similar module names using '
632                                   'fuzzy search...', constants.CYAN)
633        testable_modules = sorted(self.module_info.get_testable_modules(),
634                                  key=len)
635        lower_bound = len(user_input) - ld_range
636        upper_bound = len(user_input) + ld_range
637        testable_modules_with_ld = []
638        for module_name in testable_modules:
639            # Dispose those too short or too lengthy.
640            if ld_range != 0:
641                if len(module_name) < lower_bound:
642                    continue
643                if len(module_name) > upper_bound:
644                    break
645            testable_modules_with_ld.append(
646                [test_finder_utils.get_levenshtein_distance(
647                    user_input, module_name), module_name])
648        return testable_modules_with_ld
649
650    def get_fuzzy_searching_results(self, user_input):
651        """Give results which have no more than allowance of edit distances.
652
653        Args:
654            user_input: the target module name for fuzzy searching.
655
656        Return:
657            A list of guessed modules.
658        """
659        modules_with_ld = self.get_testable_modules_with_ld(
660            user_input, ld_range=constants.LD_RANGE)
661        guessed_modules = []
662        for _distance, _module in modules_with_ld:
663            if _distance <= abs(constants.LD_RANGE):
664                guessed_modules.append(_module)
665        return guessed_modules
666