• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2021, The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Unit tests for bazel_mode."""
18# pylint: disable=invalid-name
19# pylint: disable=missing-function-docstring
20# pylint: disable=too-many-lines
21
22import argparse
23import re
24import shlex
25import shutil
26import tempfile
27import unittest
28
29from pathlib import Path
30from typing import List
31from unittest import mock
32
33# pylint: disable=import-error
34from pyfakefs import fake_filesystem_unittest
35
36import bazel_mode
37import constants
38import module_info
39
40from test_finders import example_finder, test_finder_base, test_info
41from test_runners import atest_tf_test_runner
42
43
44ATEST_TF_RUNNER = atest_tf_test_runner.AtestTradefedTestRunner.NAME
45BAZEL_RUNNER = bazel_mode.BazelTestRunner.NAME
46MODULE_BUILD_TARGETS = {'foo1', 'foo2', 'foo3'}
47MODULE_NAME = 'foo'
48
49
50class GenerationTestFixture(fake_filesystem_unittest.TestCase):
51    """Fixture for workspace generation tests."""
52
53    def setUp(self):
54        self.setUpPyfakefs()
55
56        self.src_root_path = Path('/src')
57        self.out_dir_path = self.src_root_path.joinpath('out')
58        self.out_dir_path.mkdir(parents=True)
59        self.product_out_path = self.out_dir_path.joinpath('product')
60        self.host_out_path = self.out_dir_path.joinpath('host')
61        self.workspace_out_path = self.out_dir_path.joinpath('workspace')
62
63    def create_workspace_generator(self, modules=None, enabled_features=None):
64        mod_info = self.create_module_info(modules)
65
66        generator = bazel_mode.WorkspaceGenerator(
67            self.src_root_path,
68            self.workspace_out_path,
69            self.product_out_path,
70            self.host_out_path,
71            self.out_dir_path,
72            mod_info,
73            enabled_features=enabled_features,
74        )
75
76        return generator
77
78    def run_generator(self, mod_info, enabled_features=None):
79        generator = bazel_mode.WorkspaceGenerator(
80            self.src_root_path,
81            self.workspace_out_path,
82            self.product_out_path,
83            self.host_out_path,
84            self.out_dir_path,
85            mod_info,
86            enabled_features=enabled_features,
87        )
88
89        generator.generate()
90
91    # pylint: disable=protected-access
92    @mock.patch.dict('os.environ', {constants.ANDROID_BUILD_TOP: '/'})
93    def create_empty_module_info(self):
94        fake_temp_file_name = next(tempfile._get_candidate_names())
95        self.fs.create_file(fake_temp_file_name, contents='{}')
96        return module_info.ModuleInfo(module_file=fake_temp_file_name)
97
98    def create_module_info(self, modules=None):
99        mod_info = self.create_empty_module_info()
100        modules = modules or []
101
102        prerequisites = frozenset().union(
103            bazel_mode.TestTarget.DEVICE_TEST_PREREQUISITES,
104            bazel_mode.TestTarget.DEVICELESS_TEST_PREREQUISITES)
105
106        for module_name in prerequisites:
107            info = host_module(name=module_name, path='prebuilts')
108            mod_info.name_to_module_info[module_name] = info
109
110        for m in modules:
111            mod_info.name_to_module_info[m['module_name']] = m
112
113        return mod_info
114
115    def assertSymlinkTo(self, symlink_path, target_path):
116        self.assertEqual(symlink_path.resolve(strict=False), target_path)
117
118    def assertTargetInWorkspace(self, name, package=''):
119        build_file = self.workspace_out_path.joinpath(package, 'BUILD.bazel')
120        contents = build_file.read_text(encoding='utf8')
121        occurrences = len(self.find_target_by_name(name, contents))
122
123        if occurrences == 1:
124            return
125
126        cardinality = 'Multiple' if occurrences else 'Zero'
127        self.fail(
128            f'{cardinality} targets named \'{name}\' found in \'{contents}\''
129        )
130
131    def assertTargetNotInWorkspace(self, name, package=''):
132        build_file = self.workspace_out_path.joinpath(package, 'BUILD.bazel')
133
134        if not build_file.exists():
135            return
136
137        contents = build_file.read_text(encoding='utf8')
138        matches = self.find_target_by_name(name, contents)
139
140        if not matches:
141            return
142
143        self.fail(
144            f'Unexpectedly found target(s) named \'{name}\' in \'{contents}\''
145        )
146
147    def assertInBuildFile(self, substring, package=''):
148        build_file = self.workspace_out_path.joinpath(package, 'BUILD.bazel')
149        self.assertIn(substring, build_file.read_text(encoding='utf8'))
150
151    def assertNotInBuildFile(self, substring, package=''):
152        build_file = self.workspace_out_path.joinpath(package, 'BUILD.bazel')
153        self.assertNotIn(substring, build_file.read_text(encoding='utf8'))
154
155    def assertFileInWorkspace(self, relative_path, package=''):
156        path = self.workspace_out_path.joinpath(package, relative_path)
157        self.assertTrue(path.exists())
158
159    def assertFileNotInWorkspace(self, relative_path, package=''):
160        path = self.workspace_out_path.joinpath(package, relative_path)
161        self.assertFalse(path.exists())
162
163    def find_target_by_name(self, name: str, contents: str) -> List[str]:
164        return re.findall(rf'\bname\s*=\s*"{name}"', contents)
165
166
167class BasicWorkspaceGenerationTest(GenerationTestFixture):
168    """Tests for basic workspace generation and update."""
169
170    def test_generate_workspace_when_nonexistent(self):
171        workspace_generator = self.create_workspace_generator()
172        shutil.rmtree(workspace_generator.workspace_out_path,
173                      ignore_errors=True)
174
175        workspace_generator.generate()
176
177        self.assertTrue(workspace_generator.workspace_out_path.is_dir())
178
179    def test_regenerate_workspace_when_features_changed(self):
180        workspace_generator = self.create_workspace_generator(
181            enabled_features={bazel_mode.Features.NULL_FEATURE})
182        workspace_generator.generate()
183        workspace_stat = workspace_generator.workspace_out_path.stat()
184
185        workspace_generator = self.create_workspace_generator()
186        workspace_generator.generate()
187        new_workspace_stat = workspace_generator.workspace_out_path.stat()
188
189        self.assertNotEqual(workspace_stat, new_workspace_stat)
190
191    def test_not_regenerate_when_feature_does_not_affect_workspace(self):
192        workspace_generator = self.create_workspace_generator(
193            enabled_features={bazel_mode.Features.NULL_FEATURE})
194        workspace_generator.generate()
195        workspace_stat = workspace_generator.workspace_out_path.stat()
196
197        parser = argparse.ArgumentParser()
198        bazel_mode.add_parser_arguments(parser, dest='bazel_mode_features')
199        # pylint: disable=no-member
200        args = parser.parse_args([
201            bazel_mode.Features.NULL_FEATURE.arg_flag,
202            '--experimental-bes-publish'
203        ])
204        workspace_generator = self.create_workspace_generator(
205            enabled_features=set(args.bazel_mode_features))
206        workspace_generator.generate()
207        new_workspace_stat = workspace_generator.workspace_out_path.stat()
208
209        self.assertEqual(workspace_stat, new_workspace_stat)
210
211    def test_not_regenerate_workspace_when_features_unchanged(self):
212        workspace_generator = self.create_workspace_generator(
213            enabled_features={bazel_mode.Features.NULL_FEATURE})
214        workspace_generator.generate()
215        workspace_stat = workspace_generator.workspace_out_path.stat()
216
217        workspace_generator = self.create_workspace_generator(
218            enabled_features={bazel_mode.Features.NULL_FEATURE})
219        workspace_generator.generate()
220        new_workspace_stat = workspace_generator.workspace_out_path.stat()
221
222        self.assertEqual(workspace_stat, new_workspace_stat)
223
224    def test_regenerate_workspace_when_module_info_deleted(self):
225        workspace_generator = self.create_workspace_generator()
226        workspace_generator.generate()
227        workspace_stat = workspace_generator.workspace_out_path.stat()
228
229        workspace_generator.mod_info.mod_info_file_path.unlink()
230        workspace_generator = self.create_workspace_generator()
231        workspace_generator.generate()
232
233        new_workspace_stat = workspace_generator.workspace_out_path.stat()
234        self.assertNotEqual(workspace_stat, new_workspace_stat)
235
236    def test_not_regenerate_workspace_when_module_info_unchanged(self):
237        workspace_generator1 = self.create_workspace_generator()
238        workspace_generator1.generate()
239        workspace_stat = workspace_generator1.workspace_out_path.stat()
240
241        workspace_generator2 = self.create_workspace_generator()
242        workspace_generator2.generate()
243        new_workspace_stat = workspace_generator2.workspace_out_path.stat()
244
245        self.assertEqual(workspace_stat, new_workspace_stat)
246
247    def test_not_regenerate_workspace_when_module_only_touched(self):
248        workspace_generator = self.create_workspace_generator()
249        workspace_generator.generate()
250        workspace_stat = workspace_generator.workspace_out_path.stat()
251
252        Path(workspace_generator.mod_info.mod_info_file_path).touch()
253        workspace_generator = self.create_workspace_generator()
254        workspace_generator.generate()
255
256        new_workspace_stat = workspace_generator.workspace_out_path.stat()
257        self.assertEqual(workspace_stat, new_workspace_stat)
258
259    def test_regenerate_workspace_when_module_info_changed(self):
260        workspace_generator = self.create_workspace_generator()
261        workspace_generator.generate()
262        workspace_stat = workspace_generator.workspace_out_path.stat()
263
264        mod_info_file_path = workspace_generator.mod_info.mod_info_file_path
265        with open(mod_info_file_path, 'a', encoding='utf8') as f:
266            f.write(' ')
267        workspace_generator = self.create_workspace_generator()
268        workspace_generator.generate()
269
270        new_workspace_stat = workspace_generator.workspace_out_path.stat()
271        self.assertNotEqual(workspace_stat, new_workspace_stat)
272
273    def test_regenerate_workspace_when_md5_file_removed(self):
274        workspace_generator = self.create_workspace_generator()
275        workspace_generator.generate()
276        workspace_stat = workspace_generator.workspace_out_path.stat()
277
278        workspace_generator.mod_info.mod_info_file_path.unlink()
279        workspace_generator = self.create_workspace_generator()
280        workspace_generator.generate()
281
282        new_workspace_stat = workspace_generator.workspace_out_path.stat()
283        self.assertNotEqual(workspace_stat, new_workspace_stat)
284
285    def test_scrub_old_workspace_when_regenerating(self):
286        workspace_generator = self.create_workspace_generator()
287        workspace_generator.generate()
288        some_file = workspace_generator.workspace_out_path.joinpath('some_file')
289        some_file.touch()
290        self.assertTrue(some_file.is_file())
291
292        # Remove the md5 file to regenerate the workspace.
293        workspace_generator.mod_info.mod_info_file_path.unlink()
294        workspace_generator = self.create_workspace_generator()
295        workspace_generator.generate()
296
297        self.assertFalse(some_file.is_file())
298
299    def test_generate_workspace_file(self):
300        gen = self.create_workspace_generator()
301        workspace_path = gen.workspace_out_path.joinpath('WORKSPACE')
302
303        gen.generate()
304
305        self.assertSymlinkTo(
306            workspace_path,
307            self.src_root_path.joinpath('tools/asuite/atest/bazel/WORKSPACE')
308        )
309
310    def test_generate_bazelrc_file(self):
311        gen = self.create_workspace_generator()
312        bazelrc_path = gen.workspace_out_path.joinpath('.bazelrc')
313
314        gen.generate()
315
316        self.assertSymlinkTo(
317            bazelrc_path,
318            self.src_root_path.joinpath('tools/asuite/atest/bazel/bazelrc')
319        )
320
321    def test_generate_rules_dir(self):
322        gen = self.create_workspace_generator()
323        rules_dir_path = gen.workspace_out_path.joinpath('bazel/rules')
324
325        gen.generate()
326
327        self.assertSymlinkTo(
328            rules_dir_path,
329            self.src_root_path.joinpath('tools/asuite/atest/bazel/rules')
330        )
331
332    def test_generate_configs_dir(self):
333        gen = self.create_workspace_generator()
334        configs_dir_path = gen.workspace_out_path.joinpath('bazel/configs')
335
336        gen.generate()
337
338        self.assertSymlinkTo(
339            configs_dir_path,
340            self.src_root_path.joinpath('tools/asuite/atest/bazel/configs')
341        )
342
343    def test_generate_host_unit_test_module_target(self):
344        mod_info = self.create_module_info(modules=[
345            host_unit_test_module(name='hello_world_test')
346        ])
347
348        self.run_generator(mod_info)
349
350        self.assertTargetInWorkspace('hello_world_test_host')
351
352    def test_not_generate_host_test_module_target(self):
353        mod_info = self.create_module_info(modules=[
354            host_test_module(name='hello_world_test'),
355        ])
356
357        self.run_generator(mod_info)
358
359        self.assertTargetNotInWorkspace('hello_world_test')
360
361    def test_not_generate_test_module_target_with_invalid_installed_path(self):
362        mod_info = self.create_module_info(modules=[
363            test_module(name='hello_world_test', installed='out/invalid/path')
364        ])
365
366        self.run_generator(mod_info)
367
368        self.assertTargetNotInWorkspace('hello_world_test_device')
369        self.assertTargetNotInWorkspace('hello_world_test_host')
370
371    def test_generate_variable_file(self):
372        gen = self.create_workspace_generator()
373
374        gen.generate()
375
376        self.assertFileInWorkspace('BUILD.bazel')
377        self.assertFileInWorkspace('constants.bzl')
378
379
380class MultiConfigTestModuleTestTargetGenerationTest(GenerationTestFixture):
381    """Tests for test target generation of test modules with multi-configs."""
382
383    def test_generate_test_rule_imports(self):
384        mod_info = self.create_module_info(modules=[
385            multi_config(host_unit_suite(test_module(
386                name='hello_world_test', path='example/tests'))),
387        ])
388
389        self.run_generator(mod_info, enabled_features=set([
390            bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST]))
391
392        self.assertInBuildFile(
393            'load("//bazel/rules:tradefed_test.bzl",'
394            ' "tradefed_device_test", "tradefed_deviceless_test")\n',
395            package='example/tests',
396        )
397
398    def test_not_generate_device_test_import_when_feature_disabled(self):
399        mod_info = self.create_module_info(modules=[
400            multi_config(host_unit_suite(test_module(
401                name='hello_world_test', path='example/tests'))),
402        ])
403
404        self.run_generator(mod_info)
405
406        self.assertInBuildFile(
407            'load("//bazel/rules:tradefed_test.bzl",'
408            ' "tradefed_deviceless_test")\n',
409            package='example/tests',
410        )
411
412    def test_generate_test_targets(self):
413        mod_info = self.create_module_info(modules=[
414            multi_config(host_unit_suite(test_module(
415                name='hello_world_test', path='example/tests'))),
416        ])
417
418        self.run_generator(mod_info, enabled_features=set([
419            bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST]))
420
421        self.assertTargetInWorkspace('hello_world_test_device',
422                                     package='example/tests')
423        self.assertTargetInWorkspace('hello_world_test_host',
424                                     package='example/tests')
425
426    def test_not_generate_device_test_target_when_feature_disabled(self):
427        mod_info = self.create_module_info(modules=[
428            multi_config(host_unit_suite(test_module(
429                name='hello_world_test', path='example/tests'))),
430        ])
431
432        self.run_generator(mod_info)
433
434        self.assertTargetNotInWorkspace('hello_world_test_device',
435                                        package='example/tests')
436        self.assertTargetInWorkspace('hello_world_test_host',
437                                     package='example/tests')
438
439
440class DeviceTestModuleTestTargetGenerationTest(GenerationTestFixture):
441    """Tests for device test module test target generation."""
442
443    def test_generate_device_driven_test_target(self):
444        mod_info = self.create_module_info(modules=[
445            device_test_module(
446                name='hello_world_test', path='example/tests'),
447        ])
448
449        self.run_generator(mod_info, enabled_features=set([
450            bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST]))
451
452        self.assertInBuildFile(
453            'load("//bazel/rules:tradefed_test.bzl",'
454            ' "tradefed_device_test")\n',
455            package='example/tests',
456        )
457        self.assertTargetInWorkspace('hello_world_test_device',
458                                     package='example/tests')
459
460    def test_raise_when_prerequisite_not_in_module_info(self):
461        mod_info = self.create_module_info(modules=[
462            device_test_module(),
463        ])
464        del mod_info.name_to_module_info['aapt']
465
466        with self.assertRaises(Exception) as context:
467            self.run_generator(mod_info, enabled_features=set([
468                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST]))
469
470        self.assertIn('aapt', str(context.exception))
471
472
473class HostUnitTestModuleTestTargetGenerationTest(GenerationTestFixture):
474    """Tests for host unit test module test target generation."""
475
476    def test_generate_deviceless_test_import(self):
477        mod_info = self.create_module_info(modules=[
478            host_unit_test_module(name='hello_world_test'),
479        ])
480
481        self.run_generator(mod_info)
482
483        self.assertInBuildFile(
484            'load("//bazel/rules:tradefed_test.bzl",'
485            ' "tradefed_deviceless_test")\n'
486        )
487
488    def test_generate_deviceless_test_target(self):
489        mod_info = self.create_module_info(modules=[
490            host_unit_test_module(
491                name='hello_world_test', path='example/tests'),
492        ])
493
494        self.run_generator(mod_info)
495
496        self.assertInBuildFile(
497            'tradefed_deviceless_test(\n'
498            '    name = "hello_world_test_host",\n'
499            '    test = "//example/tests:hello_world_test",\n'
500            ')',
501            package='example/tests',
502        )
503
504    def test_generate_test_module_prebuilt(self):
505        mod_info = self.create_module_info(modules=[
506            host_unit_test_module(name='hello_world_test'),
507        ])
508
509        self.run_generator(mod_info)
510
511        self.assertTargetInWorkspace('hello_world_test')
512
513    def test_raise_when_prerequisite_not_in_module_info(self):
514        mod_info = self.create_module_info(modules=[
515            host_unit_test_module(),
516        ])
517        del mod_info.name_to_module_info['adb']
518
519        with self.assertRaises(Exception) as context:
520            self.run_generator(mod_info)
521
522        self.assertIn('adb', str(context.exception))
523
524    def test_raise_when_prerequisite_module_missing_path(self):
525        mod_info = self.create_module_info(modules=[
526            host_unit_test_module(),
527        ])
528        mod_info.name_to_module_info['adb'].get('path').clear()
529
530        with self.assertRaises(Exception) as context:
531            self.run_generator(mod_info)
532
533        self.assertIn('adb', str(context.exception))
534
535    def test_warning_when_prerequisite_module_has_multiple_path(self):
536        mod_info = self.create_module_info(modules=[
537            host_unit_test_module(),
538        ])
539        mod_info.name_to_module_info['adb'].get('path').append('the/2nd/path')
540
541        with self.assertWarns(Warning) as context:
542            self.run_generator(mod_info)
543
544        self.assertIn('adb', str(context.warnings[0].message))
545
546class ModulePrebuiltTargetGenerationTest(GenerationTestFixture):
547    """Tests for module prebuilt target generation."""
548
549    def test_generate_prebuilt_import(self):
550        mod_info = self.create_module_info(modules=[
551            supported_test_module(),
552        ])
553
554        self.run_generator(mod_info)
555
556        self.assertInBuildFile(
557            'load("//bazel/rules:soong_prebuilt.bzl", "soong_prebuilt")\n'
558        )
559
560    def test_generate_prebuilt_target_for_multi_config_test_module(self):
561        mod_info = self.create_module_info(modules=[
562            multi_config(supported_test_module(name='libhello')),
563        ])
564
565        self.run_generator(mod_info)
566
567        self.assertInBuildFile(
568            'soong_prebuilt(\n'
569            '    name = "libhello",\n'
570            '    module_name = "libhello",\n'
571            '    files = select({\n'
572            '        "//bazel/rules:device": glob(["libhello/device/**/*"]),\n'
573            '        "//bazel/rules:host": glob(["libhello/host/**/*"]),\n'
574            '    }),\n'
575            ')\n'
576        )
577
578    def test_create_symlinks_to_testcases_for_multi_config_test_module(self):
579        module_name = 'hello_world_test'
580        mod_info = self.create_module_info(modules=[
581            multi_config(supported_test_module(name=module_name))
582        ])
583        module_out_path = self.workspace_out_path.joinpath(module_name)
584
585        self.run_generator(mod_info)
586
587        self.assertSymlinkTo(
588            module_out_path.joinpath(f'host/testcases/{module_name}'),
589            self.host_out_path.joinpath(f'testcases/{module_name}'))
590        self.assertSymlinkTo(
591            module_out_path.joinpath(f'device/testcases/{module_name}'),
592            self.product_out_path.joinpath(f'testcases/{module_name}'))
593
594    def test_generate_files_for_host_only_test_module(self):
595        mod_info = self.create_module_info(modules=[
596            host_only_config(supported_test_module(name='test1')),
597        ])
598
599        self.run_generator(mod_info)
600
601        self.assertInBuildFile(
602            '    files = select({\n'
603            '        "//bazel/rules:host": glob(["test1/host/**/*"]),\n'
604            '    }),\n'
605        )
606
607    def test_generate_files_for_device_only_test_module(self):
608        mod_info = self.create_module_info(modules=[
609            device_only_config(supported_test_module(name='test1')),
610        ])
611
612        self.run_generator(mod_info)
613
614        self.assertInBuildFile(
615            '    files = select({\n'
616            '        "//bazel/rules:device": glob(["test1/device/**/*"]),\n'
617            '    }),\n'
618        )
619
620    def test_not_create_device_symlinks_for_host_only_test_module(self):
621        mod_info = self.create_module_info(modules=[
622            host_only_config(supported_test_module(name='test1')),
623        ])
624
625        self.run_generator(mod_info)
626
627        self.assertFileNotInWorkspace('test1/device')
628
629    def test_not_create_host_symlinks_for_device_test_module(self):
630        mod_info = self.create_module_info(modules=[
631            device_only_config(supported_test_module(name='test1')),
632        ])
633
634        self.run_generator(mod_info)
635
636        self.assertFileNotInWorkspace('test1/host')
637
638
639class ModuleSharedLibGenerationTest(GenerationTestFixture):
640    """Tests for module shared libs target generation."""
641
642    def test_not_generate_runtime_deps_when_all_configs_incompatible(self):
643        mod_info = self.create_module_info(modules=[
644            host_only_config(supported_test_module(shared_libs=['libdevice'])),
645            device_only_config(module(name='libdevice')),
646        ])
647
648        self.run_generator(mod_info)
649
650        self.assertNotInBuildFile('runtime_deps')
651
652    def test_generate_runtime_deps_when_configs_compatible(self):
653        mod_info = self.create_module_info(modules=[
654            multi_config(supported_test_module(shared_libs=['libmulti'])),
655            multi_config_module(name='libmulti'),
656        ])
657
658        self.run_generator(mod_info)
659
660        self.assertInBuildFile(
661            '    runtime_deps = select({\n'
662            '        "//bazel/rules:device": [\n'
663            '            "//:libmulti",\n'
664            '        ],\n'
665            '        "//bazel/rules:host": [\n'
666            '            "//:libmulti",\n'
667            '        ],\n'
668            '    }),\n'
669        )
670
671    def test_generate_runtime_deps_when_configs_partially_compatible(self):
672        mod_info = self.create_module_info(modules=[
673            multi_config(supported_test_module(shared_libs=[
674                'libhost',
675            ])),
676            host_module(name='libhost'),
677        ])
678
679        self.run_generator(mod_info)
680
681        self.assertInBuildFile(
682            '    runtime_deps = select({\n'
683            '        "//bazel/rules:device": [\n'
684            '        ],\n'
685            '        "//bazel/rules:host": [\n'
686            '            "//:libhost",\n'
687            '        ],\n'
688            '    }),\n'
689        )
690
691    def test_generate_runtime_deps_with_mixed_compatibility(self):
692        mod_info = self.create_module_info(modules=[
693            multi_config(supported_test_module(shared_libs=[
694                'libhost',
695                'libdevice',
696                'libmulti'
697            ])),
698            host_module(name='libhost'),
699            device_module(name='libdevice'),
700            multi_config_module(name='libmulti'),
701        ])
702
703        self.run_generator(mod_info)
704
705        self.assertInBuildFile(
706            '    runtime_deps = select({\n'
707            '        "//bazel/rules:device": [\n'
708            '            "//:libdevice",\n'
709            '            "//:libmulti",\n'
710            '        ],\n'
711            '        "//bazel/rules:host": [\n'
712            '            "//:libhost",\n'
713            '            "//:libmulti",\n'
714            '        ],\n'
715            '    }),\n'
716        )
717
718    def test_generate_runtime_deps_recursively(self):
719        mod_info = self.create_module_info(modules=[
720            multi_config(supported_test_module(shared_libs=[
721                'libdirect',
722            ])),
723            multi_config_module(name='libdirect', shared_libs=[
724                'libtransitive',
725            ]),
726            multi_config_module(name='libtransitive'),
727        ])
728
729        self.run_generator(mod_info)
730
731        self.assertTargetInWorkspace('libtransitive')
732
733    def test_generate_shared_runtime_deps_once(self):
734        mod_info = self.create_module_info(modules=[
735            multi_config(supported_test_module(shared_libs=[
736                'libleft',
737                'libright',
738            ])),
739            multi_config_module(name='libleft', shared_libs=[
740                'libshared',
741            ]),
742            multi_config_module(name='libright', shared_libs=[
743                'libshared',
744            ]),
745            multi_config_module(name='libshared'),
746        ])
747
748        self.run_generator(mod_info)
749
750        self.assertTargetInWorkspace('libshared')
751
752    def test_generate_runtime_deps_in_order(self):
753        mod_info = self.create_module_info(modules=[
754            supported_test_module(shared_libs=['libhello2', 'libhello1']),
755            host_module(name='libhello1'),
756            host_module(name='libhello2'),
757        ])
758
759        self.run_generator(mod_info)
760
761        self.assertInBuildFile(
762            '            "//:libhello1",\n'
763            '            "//:libhello2",\n'
764        )
765
766    def test_generate_target_for_shared_lib(self):
767        mod_info = self.create_module_info(modules=[
768            supported_test_module(shared_libs=['libhello']),
769            host_module(name='libhello'),
770        ])
771
772        self.run_generator(mod_info)
773
774        self.assertTargetInWorkspace('libhello')
775
776    def test_not_generate_for_missing_shared_lib_module(self):
777        mod_info = self.create_module_info(modules=[
778            supported_test_module(shared_libs=['libhello'])
779        ])
780
781        self.run_generator(mod_info)
782
783        self.assertNotInBuildFile('            "//:libhello",\n')
784        self.assertTargetNotInWorkspace('libhello')
785
786    def test_not_generate_when_shared_lib_uninstalled(self):
787        mod_info = self.create_module_info(modules=[
788            supported_test_module(shared_libs=['libhello']),
789            host_module(name='libhello', installed=[]),
790        ])
791
792        self.run_generator(mod_info)
793
794        self.assertNotInBuildFile('            "//:libhello",\n')
795        self.assertTargetNotInWorkspace('libhello')
796
797    def test_not_generate_when_shared_lib_installed_path_unsupported(self):
798        unsupported_install_path = 'out/other'
799        mod_info = self.create_module_info(modules=[
800            supported_test_module(shared_libs=['libhello']),
801            shared_lib(module('libhello',
802                              installed=[unsupported_install_path])),
803        ])
804
805        self.run_generator(mod_info)
806
807        self.assertNotInBuildFile('"//:libhello",\n')
808        self.assertTargetNotInWorkspace('libhello')
809
810    def test_not_generate_when_shared_lib_install_path_ambiguous(self):
811        ambiguous_install_path = 'out/f1'
812        mod_info = self.create_module_info(modules=[
813            supported_test_module(shared_libs=['libhello']),
814            module(name='libhello', installed=[ambiguous_install_path]),
815        ])
816
817        self.run_generator(mod_info)
818
819        self.assertNotInBuildFile('"//:libhello",\n')
820        self.assertTargetNotInWorkspace('libhello')
821
822    def test_generate_target_for_rlib_dependency(self):
823        mod_info = self.create_module_info(modules=[
824            supported_test_module(dependencies=['libhello']),
825            rlib(module(name='libhello'))
826        ])
827
828        self.run_generator(mod_info)
829
830        self.assertInBuildFile(
831            'soong_uninstalled_prebuilt(\n'
832            '    name = "libhello",\n'
833            '    module_name = "libhello",\n'
834            ')\n'
835        )
836
837    def test_generate_target_for_rlib_dylib_dependency(self):
838        mod_info = self.create_module_info(modules=[
839            supported_test_module(dependencies=['libhello']),
840            rlib(module(name='libhello', dependencies=['libworld'])),
841            host_only_config(dylib(module(name='libworld')))
842        ])
843
844        self.run_generator(mod_info)
845
846        self.assertTargetInWorkspace('libworld')
847
848    def test_generate_target_for_dylib_dependency(self):
849        mod_info = self.create_module_info(modules=[
850            supported_test_module(dependencies=['libhello']),
851            host_only_config(dylib(module(name='libhello')))
852        ])
853
854        self.run_generator(mod_info)
855
856        self.assertInBuildFile(
857            'soong_prebuilt(\n'
858            '    name = "libhello",\n'
859            '    module_name = "libhello",\n'
860        )
861
862    def test_generate_target_for_uninstalled_dylib_dependency(self):
863        mod_info = self.create_module_info(modules=[
864            supported_test_module(dependencies=['libhello']),
865            dylib(module(name='libhello', installed=[]))
866        ])
867
868        self.run_generator(mod_info)
869
870        self.assertInBuildFile(
871            'soong_uninstalled_prebuilt(\n'
872            '    name = "libhello",\n'
873            '    module_name = "libhello",\n'
874            ')\n'
875        )
876
877    def test_not_generate_target_for_non_runtime_dependency(self):
878        mod_info = self.create_module_info(modules=[
879            supported_test_module(dependencies=['libhello']),
880            host_module(name='libhello', classes=['NOT_SUPPORTED'])
881        ])
882
883        self.run_generator(mod_info)
884
885        self.assertNotInBuildFile('"//:libhello",\n')
886        self.assertTargetNotInWorkspace('libhello')
887
888
889    def test_generate_target_for_runtime_dependency(self):
890        mod_info = self.create_module_info(modules=[
891            supported_test_module(runtime_dependencies=['libhello']),
892            host_only_config(
893                module(name='libhello', classes=['SHARED_LIBRARIES']))
894        ])
895
896        self.run_generator(mod_info)
897
898        self.assertInBuildFile(
899            '    runtime_deps = select({\n'
900            '        "//bazel/rules:host": [\n'
901            '            "//:libhello",\n'
902            '        ],\n'
903            '    }),\n'
904        )
905
906class SharedLibPrebuiltTargetGenerationTest(GenerationTestFixture):
907    """Tests for runtime dependency module prebuilt target generation."""
908
909    def test_create_multi_config_target_symlinks(self):
910        host_file1 = self.host_out_path.joinpath('a/b/f1')
911        host_file2 = self.host_out_path.joinpath('a/c/f2')
912        device_file1 = self.product_out_path.joinpath('a/b/f1')
913        mod_info = self.create_module_info(modules=[
914            supported_test_module(shared_libs=['libhello']),
915            multi_config_module(
916                name='libhello',
917                installed=[str(host_file1), str(host_file2), str(device_file1)]
918            )
919        ])
920        package_path = self.workspace_out_path
921
922        self.run_generator(mod_info)
923
924        self.assertSymlinkTo(
925            package_path.joinpath('libhello/host/a/b/f1'), host_file1)
926        self.assertSymlinkTo(
927            package_path.joinpath('libhello/host/a/c/f2'), host_file2)
928        self.assertSymlinkTo(
929            package_path.joinpath('libhello/device/a/b/f1'), device_file1)
930
931    def test_create_symlinks_to_installed_path_for_non_tf_testable_deps(self):
932        host_file = self.host_out_path.joinpath('a/b/f1')
933        mod_info = self.create_module_info(modules=[
934            supported_test_module(shared_libs=['libhello']),
935            host_module(
936                name='libhello',
937                installed=[str(host_file)],
938                auto_test_config=['true']
939            )
940        ])
941        package_path = self.workspace_out_path
942
943        self.run_generator(mod_info)
944
945        self.assertSymlinkTo(
946            package_path.joinpath('libhello/host/a/b/f1'), host_file)
947
948    def test_create_symlinks_to_installed_path_for_lib_with_test_config(self):
949        host_file = self.host_out_path.joinpath('a/b/f1')
950        mod_info = self.create_module_info(modules=[
951            supported_test_module(shared_libs=['libhello']),
952            host_module(
953                name='libhello',
954                installed=[str(host_file)],
955                path='src/lib'
956            )
957        ])
958        self.fs.create_file(Path('src/lib/AndroidTest.xml'), contents='')
959        package_path = self.workspace_out_path
960
961        self.run_generator(mod_info)
962
963        self.assertSymlinkTo(
964            package_path.joinpath('src/lib/libhello/host/a/b/f1'), host_file)
965
966    def test_generate_for_host_only_shared_lib_dependency(self):
967        mod_info = self.create_module_info(modules=[
968            supported_test_module(shared_libs=['libhello']),
969            host_module(name='libhello'),
970        ])
971
972        self.run_generator(mod_info)
973
974        self.assertInBuildFile(
975            '    files = select({\n'
976            '        "//bazel/rules:host": glob(["libhello/host/**/*"]),\n'
977            '    }),\n'
978        )
979        self.assertFileNotInWorkspace('libhello/device')
980
981    def test_generate_for_device_only_shared_lib_dependency(self):
982        mod_info = self.create_module_info(modules=[
983            supported_test_module(shared_libs=['libhello']),
984            device_module(name='libhello'),
985        ])
986
987        self.run_generator(mod_info)
988
989        self.assertInBuildFile(
990            '    files = select({\n'
991            '        "//bazel/rules:device": glob(["libhello/device/**/*"]),\n'
992            '    }),\n'
993        )
994        self.assertFileNotInWorkspace('libhello/host')
995
996
997class DataDependenciesGenerationTest(GenerationTestFixture):
998    """Tests for module data dependencies target generation."""
999
1000    def test_generate_target_for_data_dependency(self):
1001        mod_info = self.create_module_info(modules=[
1002            supported_test_module(data_dependencies=['libdata']),
1003            host_module(name='libdata'),
1004        ])
1005
1006        self.run_generator(mod_info)
1007
1008        self.assertInBuildFile(
1009        '    data = select({\n'
1010        '        "//bazel/rules:host": [\n'
1011        '            "//:libdata",\n'
1012        '        ],\n'
1013        '    }),\n'
1014        )
1015        self.assertTargetInWorkspace('libdata')
1016
1017    def test_not_generate_target_for_data_file(self):
1018        # Data files are included in "data", but not in "data_dependencies".
1019        mod_info = self.create_module_info(modules=[
1020            supported_test_module(data=['libdata']),
1021            host_module(name='libdata'),
1022        ])
1023
1024        self.run_generator(mod_info)
1025
1026        self.assertTargetNotInWorkspace('libdata')
1027
1028
1029@mock.patch.dict('os.environ', {constants.ANDROID_BUILD_TOP: '/'})
1030def create_empty_module_info():
1031    with fake_filesystem_unittest.Patcher() as patcher:
1032        # pylint: disable=protected-access
1033        fake_temp_file_name = next(tempfile._get_candidate_names())
1034        patcher.fs.create_file(fake_temp_file_name, contents='{}')
1035        return module_info.ModuleInfo(module_file=fake_temp_file_name)
1036
1037
1038def create_module_info(modules=None):
1039    mod_info = create_empty_module_info()
1040    modules = modules or []
1041
1042    for m in modules:
1043        mod_info.name_to_module_info[m['module_name']] = m
1044
1045    return mod_info
1046
1047
1048def host_unit_test_module(**kwargs):
1049    return host_unit_suite(host_test_module(**kwargs))
1050
1051
1052# We use the below alias in situations where the actual type is irrelevant to
1053# the test as long as it is supported in Bazel mode.
1054supported_test_module = host_unit_test_module
1055
1056
1057def host_test_module(**kwargs):
1058    kwargs.setdefault('name', 'hello_world_test')
1059    return host_only_config(test_module(**kwargs))
1060
1061
1062def device_test_module(**kwargs):
1063    kwargs.setdefault('name', 'hello_world_test')
1064    return device_only_config(test_module(**kwargs))
1065
1066
1067def host_module(**kwargs):
1068    m = module(**kwargs)
1069
1070    if 'installed' in kwargs:
1071        return m
1072
1073    return host_only_config(m)
1074
1075
1076def device_module(**kwargs):
1077    m = module(**kwargs)
1078
1079    if 'installed' in kwargs:
1080        return m
1081
1082    return device_only_config(m)
1083
1084
1085def multi_config_module(**kwargs):
1086    m = module(**kwargs)
1087
1088    if 'installed' in kwargs:
1089        return m
1090
1091    return multi_config(m)
1092
1093
1094def test_module(**kwargs):
1095    kwargs.setdefault('name', 'hello_world_test')
1096    return test(module(**kwargs))
1097
1098
1099# pylint: disable=too-many-arguments
1100def module(
1101    name=None,
1102    path=None,
1103    installed=None,
1104    classes=None,
1105    auto_test_config=None,
1106    shared_libs=None,
1107    dependencies=None,
1108    runtime_dependencies=None,
1109    data=None,
1110    data_dependencies=None,
1111):
1112    name = name or 'libhello'
1113
1114    m = {}
1115
1116    m['module_name'] = name
1117    m['class'] = classes
1118    m['path'] = [path or '']
1119    m['installed'] = installed or []
1120    m['is_unit_test'] = 'false'
1121    m['auto_test_config'] = auto_test_config or []
1122    m['shared_libs'] = shared_libs or []
1123    m['runtime_dependencies'] = runtime_dependencies or []
1124    m['dependencies'] = dependencies or []
1125    m['data'] = data or []
1126    m['data_dependencies'] = data_dependencies or []
1127    return m
1128
1129
1130def test(info):
1131    info['auto_test_config'] = ['true']
1132    return info
1133
1134
1135def shared_lib(info):
1136    info['class'] = ['SHARED_LIBRARIES']
1137    return info
1138
1139
1140def rlib(info):
1141    info['class'] = ['RLIB_LIBRARIES']
1142    info['installed'] = []
1143    return info
1144
1145
1146def dylib(info):
1147    info['class'] = ['DYLIB_LIBRARIES']
1148    return info
1149
1150
1151def host_unit_suite(info):
1152    info = test(info)
1153    info.setdefault('compatibility_suites', []).append('host-unit-tests')
1154    return info
1155
1156
1157def multi_config(info):
1158    name = info.get('module_name', 'lib')
1159    info['installed'] = [
1160        f'out/host/linux-x86/{name}/{name}.jar',
1161        f'out/product/vsoc_x86/{name}/{name}.apk',
1162    ]
1163    info['supported_variants'] = [
1164        'DEVICE',
1165        'HOST',
1166    ]
1167    return info
1168
1169
1170def host_only_config(info):
1171    name = info.get('module_name', 'lib')
1172    info['installed'] = [
1173        f'out/host/linux-x86/{name}/{name}.jar',
1174    ]
1175    info['supported_variants'] = [
1176        'HOST',
1177    ]
1178    return info
1179
1180
1181def device_only_config(info):
1182    name = info.get('module_name', 'lib')
1183    info['installed'] = [
1184        f'out/product/vsoc_x86/{name}/{name}.jar',
1185    ]
1186    info['supported_variants'] = [
1187        'DEVICE',
1188    ]
1189    return info
1190
1191
1192class PackageTest(fake_filesystem_unittest.TestCase):
1193    """Tests for Package."""
1194
1195    class FakeTarget(bazel_mode.Target):
1196        """Fake target used for tests."""
1197
1198        def __init__(self, name, imports=None):
1199            self._name = name
1200            self._imports = imports or set()
1201
1202        def name(self):
1203            return self._name
1204
1205        def required_imports(self):
1206            return self._imports
1207
1208        def write_to_build_file(self, f):
1209            f.write(f'{self._name}\n')
1210
1211
1212    def setUp(self):
1213        self.setUpPyfakefs()
1214        self.workspace_out_path = Path('/workspace_out_path')
1215        self.workspace_out_path.mkdir()
1216
1217    def test_raise_when_adding_existing_target(self):
1218        target_name = '<fake_target>'
1219        package = bazel_mode.Package('p')
1220        package.add_target(self.FakeTarget(target_name))
1221
1222        with self.assertRaises(Exception) as context:
1223            package.add_target(self.FakeTarget(target_name))
1224
1225        self.assertIn(target_name, str(context.exception))
1226
1227    def test_write_build_file_in_package_dir(self):
1228        package_path = 'abc/def'
1229        package = bazel_mode.Package(package_path)
1230        expected_path = self.workspace_out_path.joinpath(
1231            package_path, 'BUILD.bazel')
1232
1233        package.generate(self.workspace_out_path)
1234
1235        self.assertTrue(expected_path.exists())
1236
1237    def test_write_load_statements_in_sorted_order(self):
1238        package = bazel_mode.Package('p')
1239        target1 = self.FakeTarget('target1', imports={
1240            bazel_mode.Import('z.bzl', 'symbol1'),
1241        })
1242        target2 = self.FakeTarget('target2', imports={
1243            bazel_mode.Import('a.bzl', 'symbol2'),
1244        })
1245        package.add_target(target1)
1246        package.add_target(target2)
1247
1248        package.generate(self.workspace_out_path)
1249
1250        self.assertIn('load("a.bzl", "symbol2")\nload("z.bzl", "symbol1")\n\n',
1251                      self.package_build_file_text(package))
1252
1253    def test_write_load_statements_with_symbols_grouped_by_bzl(self):
1254        package = bazel_mode.Package('p')
1255        target1 = self.FakeTarget('target1', imports={
1256            bazel_mode.Import('a.bzl', 'symbol1'),
1257            bazel_mode.Import('a.bzl', 'symbol3'),
1258        })
1259        target2 = self.FakeTarget('target2', imports={
1260            bazel_mode.Import('a.bzl', 'symbol2'),
1261        })
1262        package.add_target(target1)
1263        package.add_target(target2)
1264
1265        package.generate(self.workspace_out_path)
1266
1267        self.assertIn('load("a.bzl", "symbol1", "symbol2", "symbol3")\n\n',
1268                      self.package_build_file_text(package))
1269
1270    def test_write_targets_in_add_order(self):
1271        package = bazel_mode.Package('p')
1272        target1 = self.FakeTarget('target1')
1273        target2 = self.FakeTarget('target2')
1274        package.add_target(target2)  # Added out of order.
1275        package.add_target(target1)
1276
1277        package.generate(self.workspace_out_path)
1278
1279        self.assertIn('target2\n\ntarget1\n',
1280                      self.package_build_file_text(package))
1281
1282    def test_generate_parent_package_when_nested_exists(self):
1283        parent_path = Path('parent')
1284        parent = bazel_mode.Package(parent_path.name)
1285        nested = bazel_mode.Package(parent_path.joinpath('nested'))
1286        nested.generate(self.workspace_out_path)
1287
1288        parent.generate(self.workspace_out_path)
1289
1290        self.assertTrue(self.workspace_out_path.joinpath(parent_path).is_dir())
1291
1292    def package_build_file_text(self, package):
1293        return self.workspace_out_path.joinpath(
1294            package.path, 'BUILD.bazel').read_text(encoding='utf8')
1295
1296
1297class DecorateFinderMethodTest(GenerationTestFixture):
1298    """Tests for _decorate_find_method()."""
1299
1300    def setUp(self):
1301        self.setUpPyfakefs()
1302
1303    def test_host_unit_test_with_host_arg_runner_is_overridden(self):
1304        original_find_method = lambda obj, test_id:(
1305            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1306                                          runner=ATEST_TF_RUNNER))
1307        mod_info = self.create_module_info(modules=[
1308            host_unit_test_module(name=MODULE_NAME)
1309        ])
1310        original_finder = self.create_finder(mod_info, original_find_method)
1311        new_finder = bazel_mode.create_new_finder(
1312            mod_info, original_finder, host=True)
1313
1314        test_infos = new_finder.find_method(
1315            new_finder.test_finder_instance, MODULE_NAME)
1316
1317        self.assertEqual(len(test_infos), 1)
1318        self.assertEqual(test_infos[0].test_runner, BAZEL_RUNNER)
1319
1320    def test_host_unit_test_without_host_arg_runner_is_overridden(self):
1321        original_find_method = lambda obj, test_id:(
1322            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1323                                          runner=ATEST_TF_RUNNER))
1324        mod_info = self.create_module_info(modules=[
1325            host_unit_test_module(name=MODULE_NAME)
1326        ])
1327        original_finder = self.create_finder(mod_info, original_find_method)
1328        new_finder = bazel_mode.create_new_finder(
1329            mod_info, original_finder, host=False)
1330
1331        test_infos = new_finder.find_method(
1332            new_finder.test_finder_instance, MODULE_NAME)
1333
1334        self.assertEqual(len(test_infos), 1)
1335        self.assertEqual(test_infos[0].test_runner, BAZEL_RUNNER)
1336
1337    def test_device_test_with_host_arg_runner_is_preserved(self):
1338        original_find_method = lambda obj, test_id:(
1339            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1340                                          runner=ATEST_TF_RUNNER))
1341        mod_info = self.create_module_info(modules=[
1342            device_test_module(name=MODULE_NAME)
1343        ])
1344        original_finder = self.create_finder(mod_info, original_find_method)
1345        new_finder = bazel_mode.create_new_finder(
1346            mod_info,
1347            original_finder,
1348            host=True,
1349            enabled_features=[
1350                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST
1351            ]
1352        )
1353
1354        test_infos = new_finder.find_method(
1355            new_finder.test_finder_instance, MODULE_NAME)
1356
1357        self.assertEqual(len(test_infos), 1)
1358        self.assertEqual(test_infos[0].test_runner, ATEST_TF_RUNNER)
1359
1360    def test_device_test_without_host_arg_runner_is_overridden(self):
1361        original_find_method = lambda obj, test_id:(
1362            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1363                                          runner=ATEST_TF_RUNNER))
1364        mod_info = self.create_module_info(modules=[
1365            device_test_module(name=MODULE_NAME)
1366        ])
1367        original_finder = self.create_finder(mod_info, original_find_method)
1368        new_finder = bazel_mode.create_new_finder(
1369            mod_info,
1370            original_finder,
1371            host=False,
1372            enabled_features=[
1373                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST
1374            ]
1375        )
1376
1377        test_infos = new_finder.find_method(
1378            new_finder.test_finder_instance, MODULE_NAME)
1379
1380        self.assertEqual(len(test_infos), 1)
1381        self.assertEqual(test_infos[0].test_runner, BAZEL_RUNNER)
1382
1383    def test_multi_config_test_with_host_arg_runner_is_overridden(self):
1384        original_find_method = lambda obj, test_id:(
1385            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1386                                          runner=ATEST_TF_RUNNER))
1387        mod_info = self.create_module_info(modules=[
1388            multi_config(supported_test_module(name=MODULE_NAME))
1389        ])
1390        original_finder = self.create_finder(mod_info, original_find_method)
1391        new_finder = bazel_mode.create_new_finder(
1392            mod_info,
1393            original_finder,
1394            host=True,
1395            enabled_features=[
1396                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST
1397            ]
1398        )
1399
1400        test_infos = new_finder.find_method(
1401            new_finder.test_finder_instance, MODULE_NAME)
1402
1403        self.assertEqual(len(test_infos), 1)
1404        self.assertEqual(test_infos[0].test_runner, BAZEL_RUNNER)
1405
1406    def test_multi_config_test_without_host_arg_runner_is_overridden(self):
1407        original_find_method = lambda obj, test_id:(
1408            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1409                                          runner=ATEST_TF_RUNNER))
1410        mod_info = self.create_module_info(modules=[
1411            multi_config(supported_test_module(name=MODULE_NAME))
1412        ])
1413        original_finder = self.create_finder(mod_info, original_find_method)
1414        new_finder = bazel_mode.create_new_finder(
1415            mod_info,
1416            original_finder,
1417            host=False,
1418            enabled_features=[
1419                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST
1420            ]
1421        )
1422
1423        test_infos = new_finder.find_method(
1424            new_finder.test_finder_instance, MODULE_NAME)
1425
1426        self.assertEqual(len(test_infos), 1)
1427        self.assertEqual(test_infos[0].test_runner, BAZEL_RUNNER)
1428
1429    def test_host_non_unit_test_with_host_arg_runner_is_preserved(self):
1430        original_find_method = lambda obj, test_id:(
1431            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1432                                          runner=ATEST_TF_RUNNER))
1433        mod_info = self.create_module_info(modules=[
1434            host_test_module(name=MODULE_NAME)
1435        ])
1436        original_finder = self.create_finder(mod_info, original_find_method)
1437        new_finder = bazel_mode.create_new_finder(
1438            mod_info,
1439            original_finder,
1440            host=True,
1441            enabled_features=[
1442                bazel_mode.Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST
1443            ]
1444        )
1445
1446        test_infos = new_finder.find_method(
1447            new_finder.test_finder_instance, MODULE_NAME)
1448
1449        self.assertEqual(len(test_infos), 1)
1450        self.assertEqual(test_infos[0].test_runner, ATEST_TF_RUNNER)
1451
1452    def test_disable_device_driven_test_feature_runner_is_preserved(self):
1453        original_find_method = lambda obj, test_id:(
1454            self.create_single_test_infos(obj, test_id, test_name=MODULE_NAME,
1455                                          runner=ATEST_TF_RUNNER))
1456        mod_info = self.create_module_info(modules=[
1457            device_test_module(name=MODULE_NAME)
1458        ])
1459        original_finder = self.create_finder(mod_info, original_find_method)
1460        new_finder = bazel_mode.create_new_finder(
1461            mod_info, original_finder, host=False)
1462
1463        test_infos = new_finder.find_method(
1464            new_finder.test_finder_instance, MODULE_NAME)
1465
1466        self.assertEqual(len(test_infos), 1)
1467        self.assertEqual(test_infos[0].test_runner, ATEST_TF_RUNNER)
1468
1469    # pylint: disable=unused-argument
1470    def create_single_test_infos(self, obj, test_id, test_name=MODULE_NAME,
1471                                 runner=ATEST_TF_RUNNER):
1472        """Create list of test_info.TestInfo."""
1473        return [test_info.TestInfo(test_name, runner, MODULE_BUILD_TARGETS)]
1474
1475    def create_finder(self, mod_info, find_method):
1476        return test_finder_base.Finder(
1477            example_finder.ExampleFinder(mod_info),
1478            find_method, 'FINDER_NAME')
1479
1480class BazelTestRunnerTest(unittest.TestCase):
1481    """Tests for BazelTestRunner."""
1482
1483    def test_return_empty_build_reqs_when_no_test_infos(self):
1484        run_command = self.mock_run_command(side_effect=Exception(''))
1485        runner = self.create_bazel_test_runner(
1486            modules=[
1487                supported_test_module(name='test1', path='path1'),
1488            ],
1489            test_infos=[],
1490            run_command=run_command,
1491        )
1492
1493        reqs = runner.get_test_runner_build_reqs()
1494
1495        self.assertFalse(reqs)
1496
1497    def test_query_bazel_test_targets_deps_with_host_arg(self):
1498        run_command = self.mock_run_command()
1499        runner = self.create_bazel_test_runner(
1500            modules=[
1501                multi_config(host_unit_test_module(name='test1', path='path1')),
1502                multi_config(host_unit_test_module(name='test2', path='path2')),
1503            ],
1504            test_infos = [
1505                test_info_of('test2'),
1506                test_info_of('test1'),  # Intentionally out of order.
1507            ],
1508            run_command=run_command,
1509            host=True,
1510        )
1511
1512        runner.get_test_runner_build_reqs()
1513
1514        call_args = run_command.call_args[0][0]
1515        self.assertIn(
1516            'deps(tests(//path1:test1_host + //path2:test2_host))',
1517            call_args,
1518        )
1519
1520    def test_query_bazel_test_targets_deps_without_host_arg(self):
1521        run_command = self.mock_run_command()
1522        runner = self.create_bazel_test_runner(
1523            modules=[
1524                multi_config(host_unit_test_module(name='test1', path='path1')),
1525                host_unit_test_module(name='test2', path='path2'),
1526            ],
1527            test_infos = [
1528                test_info_of('test2'),
1529                test_info_of('test1'),
1530            ],
1531            run_command=run_command,
1532        )
1533
1534        runner.get_test_runner_build_reqs()
1535
1536        call_args = run_command.call_args[0][0]
1537        self.assertIn(
1538            'deps(tests(//path1:test1_device + //path2:test2_host))',
1539            call_args,
1540        )
1541
1542    def test_trim_whitespace_in_bazel_query_output(self):
1543        run_command = self.mock_run_command(
1544            return_value='\n'.join(['  test1  ', 'test2  ', '  ']))
1545        runner = self.create_bazel_test_runner(
1546            modules=[
1547                supported_test_module(name='test1', path='path1'),
1548            ],
1549            test_infos = [test_info_of('test1')],
1550            run_command=run_command,
1551        )
1552
1553        reqs = runner.get_test_runner_build_reqs()
1554
1555        self.assertSetEqual({'test1', 'test2'}, reqs)
1556
1557    def test_generate_single_run_command(self):
1558        test_infos = [test_info_of('test1')]
1559        runner = self.create_bazel_test_runner_for_tests(test_infos)
1560
1561        cmd = runner.generate_run_commands(test_infos, {})
1562
1563        self.assertEqual(1, len(cmd))
1564
1565    def test_generate_run_command_containing_targets_with_host_arg(self):
1566        test_infos = [test_info_of('test1'), test_info_of('test2')]
1567        runner = self.create_bazel_test_runner(
1568            [
1569                multi_config(host_unit_test_module(name='test1', path='path')),
1570                multi_config(host_unit_test_module(name='test2', path='path')),
1571            ],
1572            test_infos,
1573            host=True
1574        )
1575
1576        cmd = runner.generate_run_commands(test_infos, {})
1577
1578        self.assertTokensIn(['//path:test1_host', '//path:test2_host'], cmd[0])
1579
1580    def test_generate_run_command_containing_targets_without_host_arg(self):
1581        test_infos = [test_info_of('test1'), test_info_of('test2')]
1582        runner = self.create_bazel_test_runner(
1583            [
1584                multi_config(host_unit_test_module(name='test1', path='path')),
1585                host_unit_test_module(name='test2', path='path'),
1586            ],
1587            test_infos,
1588        )
1589
1590        cmd = runner.generate_run_commands(test_infos, {})
1591
1592        self.assertTokensIn(['//path:test1_device', '//path:test2_host'],
1593                            cmd[0])
1594
1595    def test_generate_run_command_with_multi_bazel_args(self):
1596        test_infos = [test_info_of('test1')]
1597        runner = self.create_bazel_test_runner_for_tests(test_infos)
1598        extra_args = {constants.BAZEL_ARG: [['--option1=value1'],
1599                                            ['--option2=value2']]}
1600
1601        cmd = runner.generate_run_commands(test_infos, extra_args)
1602
1603        self.assertTokensIn(['--option1=value1', '--option2=value2'], cmd[0])
1604
1605    def test_generate_run_command_with_multi_custom_args(self):
1606        test_infos = [test_info_of('test1')]
1607        runner = self.create_bazel_test_runner_for_tests(test_infos)
1608        extra_args = {constants.CUSTOM_ARGS: ['-hello', '--world=value']}
1609
1610        cmd = runner.generate_run_commands(test_infos, extra_args)
1611
1612        self.assertTokensIn(['--test_arg=-hello',
1613                             '--test_arg=--world=value'], cmd[0])
1614
1615    def test_generate_run_command_with_custom_and_bazel_args(self):
1616        test_infos = [test_info_of('test1')]
1617        runner = self.create_bazel_test_runner_for_tests(test_infos)
1618        extra_args = {constants.CUSTOM_ARGS: ['-hello', '--world=value'],
1619                      constants.BAZEL_ARG: [['--option1=value1']]}
1620
1621        cmd = runner.generate_run_commands(test_infos, extra_args)
1622
1623        self.assertTokensIn(['--test_arg=-hello',
1624                             '--test_arg=--world=value',
1625                             '--option1=value1'], cmd[0])
1626
1627    def test_generate_run_command_with_tf_supported_host_arg(self):
1628        test_infos = [test_info_of('test1')]
1629        runner = self.create_bazel_test_runner_for_tests(test_infos)
1630        extra_args = {constants.HOST: True}
1631
1632        cmd = runner.generate_run_commands(test_infos, extra_args)
1633
1634        self.assertTokensIn(['--test_arg=-n',
1635                             '--test_arg=--prioritize-host-config',
1636                             '--test_arg=--skip-host-arch-check'], cmd[0])
1637
1638    def test_generate_run_command_with_iterations_args(self):
1639        test_infos = [test_info_of('test1')]
1640        runner = self.create_bazel_test_runner_for_tests(test_infos)
1641        extra_args = {constants.ITERATIONS: 2}
1642
1643        cmd = runner.generate_run_commands(test_infos, extra_args)
1644
1645        self.assertTokensIn(['--runs_per_test=2'], cmd[0])
1646        self.assertNotIn('--test_arg=--retry-strategy', shlex.split(cmd[0]))
1647
1648    def test_generate_run_command_with_testinfo_filter(self):
1649        test_filter = test_filter_of('class1', ['method1'])
1650        test_infos = [test_info_of('test1', test_filters=[test_filter])]
1651        runner = self.create_bazel_test_runner_for_tests(test_infos)
1652
1653        cmd = runner.generate_run_commands(test_infos, {})
1654
1655        self.assertTokensIn(['--test_arg=--atest-include-filter',
1656                             '--test_arg=test1:class1#method1'], cmd[0])
1657
1658    def test_generate_run_command_with_bes_publish_enabled(self):
1659        test_infos = [test_info_of('test1')]
1660        extra_args = {
1661            constants.BAZEL_MODE_FEATURES: [
1662                bazel_mode.Features.EXPERIMENTAL_BES_PUBLISH
1663            ]
1664        }
1665        build_metadata = bazel_mode.BuildMetadata(
1666            'master', 'aosp_cf_x86_64_phone-userdebug')
1667        env = {
1668            'ATEST_BAZELRC': '/dir/atest.bazelrc',
1669            'ATEST_BAZEL_BES_PUBLISH_CONFIG': 'bes_publish'
1670        }
1671        runner = self.create_bazel_test_runner_for_tests(
1672            test_infos, build_metadata=build_metadata, env=env)
1673
1674        cmd = runner.generate_run_commands(
1675            test_infos,
1676            extra_args,
1677        )
1678
1679        self.assertTokensIn([
1680            '--bazelrc=/dir/atest.bazelrc',
1681            '--config=bes_publish',
1682            '--build_metadata=ab_branch=master',
1683            '--build_metadata=ab_target=aosp_cf_x86_64_phone-userdebug'
1684        ], cmd[0])
1685
1686    def create_bazel_test_runner(self,
1687                                 modules,
1688                                 test_infos,
1689                                 run_command=None,
1690                                 host=False,
1691                                 build_metadata=None,
1692                                 env=None):
1693        return bazel_mode.BazelTestRunner(
1694            'result_dir',
1695            mod_info=create_module_info(modules),
1696            test_infos=test_infos,
1697            src_top=Path('/src'),
1698            workspace_path=Path('/src/workspace'),
1699            run_command=run_command or self.mock_run_command(),
1700            extra_args={constants.HOST: host},
1701            build_metadata = build_metadata,
1702            env = env
1703        )
1704
1705    def create_bazel_test_runner_for_tests(self,
1706                                           test_infos,
1707                                           build_metadata=None,
1708                                           env=None):
1709        return self.create_bazel_test_runner(
1710            modules=[supported_test_module(name=t.test_name, path='path')
1711                     for t in test_infos],
1712            test_infos=test_infos,
1713            build_metadata=build_metadata,
1714            env=env
1715        )
1716
1717    def mock_run_command(self, **kwargs):
1718        return mock.create_autospec(bazel_mode.default_run_command, **kwargs)
1719
1720    def assertTokensIn(self, expected_tokens, s):
1721        tokens = shlex.split(s)
1722        for token in expected_tokens:
1723            self.assertIn(token, tokens)
1724
1725
1726class FeatureParserTest(unittest.TestCase):
1727    """Tests for parsing Bazel mode feature flags."""
1728
1729    def test_parse_args_with_bazel_mode_feature(self):
1730        parser = argparse.ArgumentParser()
1731        bazel_mode.add_parser_arguments(parser, dest='bazel_mode_features')
1732        # pylint: disable=no-member
1733        args = parser.parse_args([bazel_mode.Features.NULL_FEATURE.arg_flag])
1734
1735        self.assertListEqual([bazel_mode.Features.NULL_FEATURE],
1736                             args.bazel_mode_features)
1737
1738    def test_parse_args_without_bazel_mode_feature(self):
1739        parser = argparse.ArgumentParser()
1740        parser.add_argument('--foo',
1741                            action='append_const',
1742                            const='foo',
1743                            dest='foo')
1744        bazel_mode.add_parser_arguments(parser, dest='bazel_mode_features')
1745        args = parser.parse_args(['--foo'])
1746
1747        self.assertIsNone(args.bazel_mode_features)
1748
1749
1750def test_info_of(module_name, test_filters=None):
1751    return test_info.TestInfo(
1752        module_name, BAZEL_RUNNER, [],
1753        data={constants.TI_FILTER: frozenset(test_filters)}
1754        if test_filters else None)
1755
1756
1757def test_filter_of(class_name, methods=None):
1758    return test_info.TestFilter(
1759        class_name, frozenset(methods) if methods else frozenset())
1760
1761
1762if __name__ == '__main__':
1763    unittest.main()
1764