• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 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"""Unit tests for mainline_modules_sdks.py."""
17import dataclasses
18import re
19import typing
20from pathlib import Path
21import os
22import shutil
23import tempfile
24import unittest
25import zipfile
26import json
27from unittest import mock
28
29import mainline_modules_sdks as mm
30
31MAINLINE_MODULES_BY_APEX = dict(
32    (m.apex, m) for m in (mm.MAINLINE_MODULES + mm.BUNDLED_MAINLINE_MODULES +
33                          mm.PLATFORM_SDKS_FOR_MAINLINE))
34
35
36@dataclasses.dataclass()
37class FakeSnapshotBuilder(mm.SnapshotBuilder):
38    """A fake snapshot builder that does not run the build.
39
40    This skips the whole build process and just creates some fake sdk
41    modules.
42    """
43
44    snapshots: typing.List[typing.Any] = dataclasses.field(default_factory=list)
45
46    @staticmethod
47    def create_sdk_library_files(z, name):
48        z.writestr(f"sdk_library/public/{name}-removed.txt", "")
49        z.writestr(f"sdk_library/public/{name}.srcjar", "")
50        z.writestr(f"sdk_library/public/{name}-stubs.jar", "")
51        z.writestr(f"sdk_library/public/{name}.txt",
52                   "method public int testMethod(int);")
53
54    def create_snapshot_file(self, out_dir, name, for_r_build):
55        zip_file = Path(mm.sdk_snapshot_zip_file(out_dir, name))
56        with zipfile.ZipFile(zip_file, "w") as z:
57            z.writestr("Android.bp", "")
58            if name.endswith("-sdk"):
59                if for_r_build:
60                    for library in for_r_build.sdk_libraries:
61                        self.create_sdk_library_files(z, library.name)
62                else:
63                    self.create_sdk_library_files(z, re.sub(r"-.*$", "", name))
64
65    def build_snapshots(self, build_release, modules):
66        self.snapshots.append((build_release.name, build_release.soong_env,
67                               [m.apex for m in modules]))
68        # Create input file structure.
69        sdks_out_dir = Path(self.mainline_sdks_dir).joinpath("test")
70        sdks_out_dir.mkdir(parents=True, exist_ok=True)
71        # Create a fake sdk zip file for each module.
72        for module in modules:
73            for sdk in module.sdks:
74                self.create_snapshot_file(sdks_out_dir, sdk, module.for_r_build)
75        return sdks_out_dir
76
77    def get_art_module_info_file_data(self, sdk):
78        info_file_data = f"""[
79  {{
80    "@type": "java_sdk_library",
81    "@name": "art.module.public.api",
82    "@deps": [
83      "libcore_license"
84    ],
85    "dist_stem": "art",
86    "scopes": {{
87      "public": {{
88        "current_api": "sdk_library/public/{re.sub(r"-.*$", "", sdk)}.txt",
89        "latest_api": "{Path(self.mainline_sdks_dir).joinpath("test")}/prebuilts/sdk/art.api.public.latest/gen/art.api.public.latest",
90        "latest_removed_api": "{Path(self.mainline_sdks_dir).joinpath("test")}/prebuilts/sdk/art-removed.api.public.latest/gen/art-removed.api.public.latest",
91        "removed_api": "sdk_library/public/{re.sub(r"-.*$", "", sdk)}-removed.txt"
92      }}
93    }}
94  }}
95]
96"""
97        return info_file_data
98
99    @staticmethod
100    def write_data_to_file(file, data):
101        with open(file, "w", encoding="utf8") as fd:
102            fd.write(data)
103
104    def create_snapshot_info_file(self, module, sdk_info_file, sdk):
105        if module == MAINLINE_MODULES_BY_APEX["com.android.art"]:
106            self.write_data_to_file(sdk_info_file,
107                                    self.get_art_module_info_file_data(sdk))
108        else:
109            # For rest of the modules, generate an empty .info file.
110            self.write_data_to_file(sdk_info_file, "[]")
111
112    def get_module_extension_version(self):
113        # Return any integer value indicating the module extension version for testing.
114        return 5
115
116    def build_sdk_scope_targets(self, build_release, modules):
117        target_paths = []
118        target_dict = {}
119        for module in modules:
120            for sdk in module.sdks:
121                if "host-exports" in sdk or "test-exports" in sdk:
122                    continue
123
124                sdk_info_file = mm.sdk_snapshot_info_file(
125                    Path(self.mainline_sdks_dir).joinpath("test"), sdk)
126                self.create_snapshot_info_file(module, sdk_info_file, sdk)
127                paths, dict_item = self.latest_api_file_targets(sdk_info_file)
128                target_paths.extend(paths)
129                target_dict[sdk_info_file] = dict_item
130
131        for target_path in target_paths:
132            os.makedirs(os.path.split(target_path)[0])
133            self.write_data_to_file(target_path, "")
134
135        return target_dict
136
137
138class TestProduceDist(unittest.TestCase):
139
140    def setUp(self):
141        self.tmp_dir = tempfile.mkdtemp()
142        self.tmp_out_dir = os.path.join(self.tmp_dir, "out")
143        os.mkdir(self.tmp_out_dir)
144        self.tmp_dist_dir = os.path.join(self.tmp_dir, "dist")
145        os.mkdir(self.tmp_dist_dir)
146
147    def tearDown(self):
148        shutil.rmtree(self.tmp_dir, ignore_errors=True)
149
150    def produce_dist(self, modules, build_releases):
151        subprocess_runner = mm.SubprocessRunner()
152        snapshot_builder = FakeSnapshotBuilder(
153            tool_path="path/to/mainline_modules_sdks.sh",
154            subprocess_runner=subprocess_runner,
155            out_dir=self.tmp_out_dir,
156        )
157        producer = mm.SdkDistProducer(
158            subprocess_runner=subprocess_runner,
159            snapshot_builder=snapshot_builder,
160            dist_dir=self.tmp_dist_dir,
161        )
162        producer.produce_dist(modules, build_releases)
163
164    def list_files_in_dir(self, tmp_dist_dir):
165        files = []
166        for abs_dir, _, filenames in os.walk(tmp_dist_dir):
167            rel_dir = os.path.relpath(abs_dir, tmp_dist_dir)
168            if rel_dir == ".":
169                rel_dir = ""
170            for f in filenames:
171                files.append(os.path.join(rel_dir, f))
172        return files
173
174    def test_unbundled_modules(self):
175        # Create the out/soong/build_number.txt file that is copied into the
176        # snapshots.
177        self.create_build_number_file()
178
179        modules = [
180            MAINLINE_MODULES_BY_APEX["com.android.art"],
181            MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
182            MAINLINE_MODULES_BY_APEX["com.android.tethering"],
183            # Create a google specific module.
184            mm.aosp_to_google(MAINLINE_MODULES_BY_APEX["com.android.wifi"]),
185        ]
186        build_releases = [
187            mm.Q,
188            mm.R,
189            mm.S,
190            mm.LATEST,
191        ]
192        self.produce_dist(modules, build_releases)
193
194        # pylint: disable=line-too-long
195        self.assertEqual(
196            [
197                # Build specific snapshots.
198                "mainline-sdks/for-R-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
199                "mainline-sdks/for-R-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
200                "mainline-sdks/for-R-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
201                "mainline-sdks/for-S-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
202                "mainline-sdks/for-S-build/current/com.android.art/sdk/art-module-sdk-current.zip",
203                "mainline-sdks/for-S-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
204                "mainline-sdks/for-S-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
205                "mainline-sdks/for-S-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
206                "mainline-sdks/for-S-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
207                "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json",
208                "mainline-sdks/for-latest-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
209                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt",
210                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current.zip",
211                "mainline-sdks/for-latest-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
212                "mainline-sdks/for-latest-build/current/com.android.ipsec/gantry-metadata.json",
213                "mainline-sdks/for-latest-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current-api-diff.txt",
214                "mainline-sdks/for-latest-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
215                "mainline-sdks/for-latest-build/current/com.android.tethering/gantry-metadata.json",
216                "mainline-sdks/for-latest-build/current/com.android.tethering/sdk/tethering-module-sdk-current-api-diff.txt",
217                "mainline-sdks/for-latest-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
218                "mainline-sdks/for-latest-build/current/com.google.android.wifi/gantry-metadata.json",
219                "mainline-sdks/for-latest-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current-api-diff.txt",
220                "mainline-sdks/for-latest-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
221            ],
222            sorted(self.list_files_in_dir(self.tmp_dist_dir)))
223
224        r_snaphot_dir = os.path.join(self.tmp_out_dir,
225                                     "soong/mainline-sdks/test/for-R-build")
226        aosp_ipsec_r_bp_file = "com.android.ipsec/sdk_library/Android.bp"
227        aosp_tethering_r_bp_file = "com.android.tethering/sdk_library/Android.bp"
228        google_wifi_android_bp = "com.google.android.wifi/sdk_library/Android.bp"
229        self.assertEqual([
230            aosp_ipsec_r_bp_file,
231            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike-removed.txt",
232            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike-stubs.jar",
233            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike.srcjar",
234            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike.txt",
235            "com.android.ipsec/snapshot-creation-build-number.txt",
236            aosp_tethering_r_bp_file,
237            "com.android.tethering/sdk_library/public/framework-tethering-removed.txt",
238            "com.android.tethering/sdk_library/public/framework-tethering-stubs.jar",
239            "com.android.tethering/sdk_library/public/framework-tethering.srcjar",
240            "com.android.tethering/sdk_library/public/framework-tethering.txt",
241            "com.android.tethering/snapshot-creation-build-number.txt",
242            google_wifi_android_bp,
243            "com.google.android.wifi/sdk_library/public/framework-wifi-removed.txt",
244            "com.google.android.wifi/sdk_library/public/framework-wifi-stubs.jar",
245            "com.google.android.wifi/sdk_library/public/framework-wifi.srcjar",
246            "com.google.android.wifi/sdk_library/public/framework-wifi.txt",
247            "com.google.android.wifi/snapshot-creation-build-number.txt",
248            "ipsec-module-sdk-current.zip",
249            "tethering-module-sdk-current.zip",
250            "wifi-module-sdk-current.zip",
251        ], sorted(self.list_files_in_dir(r_snaphot_dir)))
252
253        def read_r_snapshot_contents(path):
254            abs_path = os.path.join(r_snaphot_dir, path)
255            with open(abs_path, "r", encoding="utf8") as file:
256                return file.read()
257
258        # Check the contents of the AOSP ipsec module
259        ipsec_contents = read_r_snapshot_contents(aosp_ipsec_r_bp_file)
260        expected = read_test_data("ipsec_for_r_Android.bp")
261        self.assertEqual(expected, ipsec_contents)
262
263        # Check the contents of the AOSP tethering module
264        tethering_contents = read_r_snapshot_contents(aosp_tethering_r_bp_file)
265        expected = read_test_data("tethering_for_r_Android.bp")
266        self.assertEqual(expected, tethering_contents)
267
268        # Check the contents of the Google ipsec module
269        wifi_contents = read_r_snapshot_contents(google_wifi_android_bp)
270        expected = read_test_data("google_wifi_for_r_Android.bp")
271        self.assertEqual(expected, wifi_contents)
272
273    def test_old_release(self):
274        modules = [
275            MAINLINE_MODULES_BY_APEX["com.android.art"],  # An unnbundled module
276            MAINLINE_MODULES_BY_APEX["com.android.runtime"],  # A bundled module
277            MAINLINE_MODULES_BY_APEX["platform-mainline"],  # Platform SDK
278        ]
279        build_releases = [mm.S]
280        self.produce_dist(modules, build_releases)
281
282        # pylint: disable=line-too-long
283        self.assertEqual([
284            "mainline-sdks/for-S-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
285            "mainline-sdks/for-S-build/current/com.android.art/sdk/art-module-sdk-current.zip",
286            "mainline-sdks/for-S-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
287        ], sorted(self.list_files_in_dir(self.tmp_dist_dir)))
288
289    def test_latest_release(self):
290        modules = [
291            MAINLINE_MODULES_BY_APEX["com.android.art"],  # An unnbundled module
292            MAINLINE_MODULES_BY_APEX["com.android.runtime"],  # A bundled module
293            MAINLINE_MODULES_BY_APEX["platform-mainline"],  # Platform SDK
294        ]
295        build_releases = [mm.LATEST]
296        self.produce_dist(modules, build_releases)
297
298        # pylint: disable=line-too-long
299        self.assertEqual(
300            [
301                # Bundled modules and platform SDKs.
302                "bundled-mainline-sdks/com.android.runtime/host-exports/runtime-module-host-exports-current.zip",
303                "bundled-mainline-sdks/com.android.runtime/sdk/runtime-module-sdk-current.zip",
304                "bundled-mainline-sdks/platform-mainline/sdk/platform-mainline-sdk-current.zip",
305                "bundled-mainline-sdks/platform-mainline/test-exports/platform-mainline-test-exports-current.zip",
306                # Unbundled (normal) modules.
307                "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json",
308                "mainline-sdks/for-latest-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
309                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt",
310                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current.zip",
311                "mainline-sdks/for-latest-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
312            ],
313            sorted(self.list_files_in_dir(self.tmp_dist_dir)))
314
315        art_api_diff_file = os.path.join(
316            self.tmp_dist_dir,
317            "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt"
318        )
319        self.assertNotEqual(
320            os.path.getsize(art_api_diff_file),
321            0,
322            msg="Api diff file should not be empty for the art module")
323
324        art_gantry_metadata_json_file = os.path.join(
325            self.tmp_dist_dir,
326            "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json"
327        )
328
329        with open(art_gantry_metadata_json_file, "r",
330                  encoding="utf8") as gantry_metadata_json_file_object:
331            json_data = json.load(gantry_metadata_json_file_object)
332
333        self.assertEqual(
334            json_data["api_diff_file"],
335            "art-module-sdk-current-api-diff.txt",
336            msg="Incorrect api-diff file name.")
337        self.assertEqual(
338            json_data["api_diff_file_size"],
339            267,
340            msg="Incorrect api-diff file size.")
341        self.assertEqual(
342            json_data["module_extension_version"],
343            5,
344            msg="The module extension version does not match the expected value."
345        )
346
347    def create_build_number_file(self):
348        soong_dir = os.path.join(self.tmp_out_dir, "soong")
349        os.makedirs(soong_dir, exist_ok=True)
350        build_number_file = os.path.join(soong_dir, "build_number.txt")
351        with open(build_number_file, "w", encoding="utf8") as f:
352            f.write("build-number")
353
354    def test_snapshot_build_order(self):
355        # Create the out/soong/build_number.txt file that is copied into the
356        # snapshots.
357        self.create_build_number_file()
358
359        subprocess_runner = unittest.mock.Mock(mm.SubprocessRunner)
360        snapshot_builder = FakeSnapshotBuilder(
361            tool_path="path/to/mainline_modules_sdks.sh",
362            subprocess_runner=subprocess_runner,
363            out_dir=self.tmp_out_dir,
364        )
365        producer = mm.SdkDistProducer(
366            subprocess_runner=subprocess_runner,
367            snapshot_builder=snapshot_builder,
368            dist_dir=self.tmp_dist_dir,
369        )
370
371        modules = [
372            MAINLINE_MODULES_BY_APEX["com.android.art"],
373            MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
374            # Create a google specific module.
375            mm.aosp_to_google(MAINLINE_MODULES_BY_APEX["com.android.wifi"]),
376        ]
377        build_releases = [
378            mm.Q,
379            mm.R,
380            mm.S,
381            mm.LATEST,
382        ]
383
384        producer.produce_dist(modules, build_releases)
385
386        # Check the order in which the snapshots are built.
387        self.assertEqual([
388            (
389                "R",
390                {},
391                ["com.android.ipsec", "com.google.android.wifi"],
392            ),
393            (
394                "latest",
395                {},
396                [
397                    "com.android.art", "com.android.ipsec",
398                    "com.google.android.wifi"
399                ],
400            ),
401            (
402                "S",
403                {
404                    "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S"
405                },
406                [
407                    "com.android.art", "com.android.ipsec",
408                    "com.google.android.wifi"
409                ],
410            ),
411        ], snapshot_builder.snapshots)
412
413
414def path_to_test_data(relative_path):
415    """Construct a path to a test data file.
416
417    The relative_path is relative to the location of this file.
418    """
419    this_file = __file__
420    # When running as a python_test_host (name=<x>) with an embedded launcher
421    # the __file__ points to .../<x>/<x>.py but the .../<x> is not a directory
422    # it is a binary with the launcher and the python file embedded inside. In
423    # that case a test data file <rel> is at .../<x>_data/<rel>, not
424    # .../<x>/<x>_data/<rel> so it is necessary to trim the base name (<x>.py)
425    # from the file.
426    if not os.path.isfile(this_file):
427        this_file = os.path.dirname(this_file)
428    # When the python file is at .../<x>.py (or in the case of an embedded
429    # launcher at .../<x>/<x>.py) then the test data is at .../<x>_data/<rel>.
430    this_file_without_ext, _ = os.path.splitext(this_file)
431    return os.path.join(this_file_without_ext + "_data", relative_path)
432
433
434def read_test_data(relative_path):
435    with open(path_to_test_data(relative_path), "r", encoding="utf8") as f:
436        return f.read()
437
438
439class TestAndroidBpTransformations(unittest.TestCase):
440
441    def apply_transformations(self, src, transformations, build_release, expected):
442        producer = mm.SdkDistProducer(
443            subprocess_runner=mock.Mock(mm.SubprocessRunner),
444            snapshot_builder=mock.Mock(mm.SnapshotBuilder),
445            script=self._testMethodName,
446        )
447
448        with tempfile.TemporaryDirectory() as tmp_dir:
449            path = os.path.join(tmp_dir, "Android.bp")
450            with open(path, "w", encoding="utf8") as f:
451                f.write(src)
452
453            mm.apply_transformations(
454                producer, tmp_dir, transformations, build_release)
455
456            with open(path, "r", encoding="utf8") as f:
457                result = f.read()
458
459        self.maxDiff = None
460        self.assertEqual(expected, result)
461
462    def test_common_mainline_module(self):
463        """Tests the transformations applied to a common mainline sdk on S.
464
465        This uses ipsec as an example of a common mainline sdk. This checks
466        that the general Soong config module types and variables are used.
467        """
468        src = read_test_data("ipsec_Android.bp.input")
469
470        expected = read_test_data("ipsec_Android.bp.expected")
471
472        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
473        transformations = module.transformations(mm.S, mm.Sdk)
474
475        self.apply_transformations(src, transformations, mm.S, expected)
476
477    def test_common_mainline_module_tiramisu(self):
478        """Tests the transformations applied to a common mainline sdk on T.
479
480        This uses ipsec as an example of a common mainline sdk. This checks
481        that the use_source_config_var property is inserted.
482        """
483        src = read_test_data("ipsec_Android.bp.input")
484
485        expected = read_test_data("ipsec_tiramisu_Android.bp.expected")
486
487        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
488        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
489
490        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
491
492    def test_optional_mainline_module(self):
493        """Tests the transformations applied to an optional mainline sdk on S.
494
495        This uses wifi as an example of a optional mainline sdk. This checks
496        that the module specific Soong config module types and variables are
497        used.
498        """
499        src = read_test_data("wifi_Android.bp.input")
500
501        expected = read_test_data("wifi_Android.bp.expected")
502
503        module = MAINLINE_MODULES_BY_APEX["com.android.wifi"]
504        transformations = module.transformations(mm.S, mm.Sdk)
505
506        self.apply_transformations(src, transformations, mm.S, expected)
507
508    def test_optional_mainline_module_tiramisu(self):
509        """Tests the transformations applied to an optional mainline sdk on T.
510
511        This uses wifi as an example of a optional mainline sdk. This checks
512        that the use_source_config_var property is inserted.
513        """
514        src = read_test_data("wifi_Android.bp.input")
515
516        expected = read_test_data("wifi_tiramisu_Android.bp.expected")
517
518        module = MAINLINE_MODULES_BY_APEX["com.android.wifi"]
519        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
520
521        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
522
523    def test_art(self):
524        """Tests the transformations applied to a the ART mainline module.
525
526        The ART mainline module uses a different Soong config setup to the
527        common mainline modules. This checks that the ART specific Soong config
528        module types, and variables are used.
529        """
530        src = read_test_data("art_Android.bp.input")
531
532        expected = read_test_data("art_Android.bp.expected")
533
534        module = MAINLINE_MODULES_BY_APEX["com.android.art"]
535        transformations = module.transformations(mm.S, mm.Sdk)
536
537        self.apply_transformations(src, transformations, mm.S, expected)
538
539    def test_art_module_exports(self):
540        """Tests the transformations applied to a the ART mainline module.
541
542        The ART mainline module uses a different Soong config setup to the
543        common mainline modules. This checks that the ART specific Soong config
544        module types, and variables are used.
545        """
546        src = read_test_data("art_Android.bp.input")
547
548        expected = read_test_data("art_host_exports_Android.bp.expected")
549
550        module = MAINLINE_MODULES_BY_APEX["com.android.art"]
551        transformations = module.transformations(mm.S, mm.HostExports)
552
553        self.apply_transformations(src, transformations, mm.S, expected)
554
555    def test_r_build(self):
556        """Tests the transformations that are applied for the R build.
557
558        This uses ipsec as an example of a common mainline module. That would
559        usually apply the mm.SoongConfigBoilerplateInserter transformation but
560        because this is being run for build R that transformation should not be
561        applied.
562        """
563        src = read_test_data("ipsec_for_r_Android.bp")
564
565        # There should be no changes made.
566        expected = src
567
568        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
569        transformations = module.transformations(mm.R, mm.Sdk)
570
571        self.apply_transformations(src, transformations, mm.R, expected)
572
573    def test_additional_transformation(self):
574        """Tests additional transformation.
575
576        This uses ipsec as an example of a common case for adding information
577        in Android.bp file.
578        This checks will append the information in Android.bp for a regular module.
579        """
580
581        @dataclasses.dataclass(frozen=True)
582        class TestTransformation(mm.FileTransformation):
583            """Transforms an Android.bp file by appending testing message."""
584
585            test_content: str = ""
586
587            def apply(self, producer, path, build_release):
588                with open(path, "a+", encoding="utf8") as file:
589                    self._apply_transformation(producer, file, build_release)
590
591            def _apply_transformation(self, producer, file, build_release):
592                if build_release >= mm.Tiramisu:
593                    file.write(self.test_content)
594
595        src = read_test_data("ipsec_Android.bp.input")
596
597        expected = read_test_data(
598            "ipsec_tiramisu_Android.bp.additional.expected")
599        test_transformation = TestTransformation(
600            "Android.bp", test_content="\n// Adding by test")
601        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
602        module = dataclasses.replace(
603            module, apex=module.apex,
604            first_release=module.first_release,
605            additional_transformations=[test_transformation])
606        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
607        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
608
609
610class TestFilterModules(unittest.TestCase):
611
612    def test_no_filter(self):
613        all_modules = mm.MAINLINE_MODULES + mm.BUNDLED_MAINLINE_MODULES
614        modules = mm.filter_modules(all_modules, None)
615        self.assertEqual(modules, all_modules)
616
617    def test_with_filter(self):
618        modules = mm.filter_modules(mm.MAINLINE_MODULES, "com.android.art")
619        expected = MAINLINE_MODULES_BY_APEX["com.android.art"]
620        self.assertEqual(modules, [expected])
621
622
623class TestModuleProperties(unittest.TestCase):
624
625    def test_unbundled(self):
626        for module in mm.MAINLINE_MODULES:
627            with self.subTest(module=module):
628                self.assertFalse(module.is_bundled())
629
630    def test_bundled(self):
631        for module in (mm.BUNDLED_MAINLINE_MODULES +
632                       mm.PLATFORM_SDKS_FOR_MAINLINE):
633            with self.subTest(module=module):
634                self.assertTrue(module.is_bundled())
635                self.assertEqual(module.first_release, mm.LATEST)
636
637
638if __name__ == "__main__":
639    unittest.main(verbosity=2)
640