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            if ".latest.extension_version" in target_path:
134                self.write_data_to_file(
135                    target_path, str(self.get_module_extension_version()))
136            else:
137               self.write_data_to_file(target_path, "")
138
139        return target_dict
140
141
142class TestProduceDist(unittest.TestCase):
143
144    def setUp(self):
145        self.tmp_dir = tempfile.mkdtemp()
146        self.tmp_out_dir = os.path.join(self.tmp_dir, "out")
147        os.mkdir(self.tmp_out_dir)
148        self.tmp_dist_dir = os.path.join(self.tmp_dir, "dist")
149        os.mkdir(self.tmp_dist_dir)
150        os.makedirs(
151            os.path.join(self.tmp_dir, "prebuilts/module_sdk/AdServices"),
152            exist_ok=True,
153        )
154        os.makedirs(
155            os.path.join(self.tmp_dir, "prebuilts/module_sdk/Connectivity"),
156            exist_ok=True,
157        )
158
159    def tearDown(self):
160        shutil.rmtree(self.tmp_dir, ignore_errors=True)
161
162    def produce_dist(self, modules, build_releases):
163        subprocess_runner = mm.SubprocessRunner()
164        snapshot_builder = FakeSnapshotBuilder(
165            tool_path="path/to/mainline_modules_sdks.sh",
166            subprocess_runner=subprocess_runner,
167            out_dir=self.tmp_out_dir,
168        )
169        producer = mm.SdkDistProducer(
170            subprocess_runner=subprocess_runner,
171            snapshot_builder=snapshot_builder,
172            dist_dir=self.tmp_dist_dir,
173        )
174        producer.produce_dist(modules, build_releases)
175
176    def list_files_in_dir(self, tmp_dist_dir):
177        files = []
178        for abs_dir, _, filenames in os.walk(tmp_dist_dir):
179            rel_dir = os.path.relpath(abs_dir, tmp_dist_dir)
180            if rel_dir == ".":
181                rel_dir = ""
182            for f in filenames:
183                files.append(os.path.join(rel_dir, f))
184        return files
185
186    def test_unbundled_modules(self):
187        # Create the out/soong/build_number.txt file that is copied into the
188        # snapshots.
189        self.create_build_number_file()
190
191        modules = [
192            MAINLINE_MODULES_BY_APEX["com.android.art"],
193            MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
194            MAINLINE_MODULES_BY_APEX["com.android.tethering"],
195            # Create a google specific module.
196            mm.aosp_to_google(MAINLINE_MODULES_BY_APEX["com.android.wifi"]),
197        ]
198        build_releases = [
199            mm.Q,
200            mm.R,
201            mm.S,
202            mm.LATEST,
203        ]
204        self.produce_dist(modules, build_releases)
205
206        # pylint: disable=line-too-long
207        self.assertEqual(
208            [
209                # Build specific snapshots.
210                "mainline-sdks/for-R-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
211                "mainline-sdks/for-R-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
212                "mainline-sdks/for-R-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
213                "mainline-sdks/for-S-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
214                "mainline-sdks/for-S-build/current/com.android.art/sdk/art-module-sdk-current.zip",
215                "mainline-sdks/for-S-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
216                "mainline-sdks/for-S-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
217                "mainline-sdks/for-S-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
218                "mainline-sdks/for-S-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
219                "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json",
220                "mainline-sdks/for-latest-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
221                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt",
222                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current.zip",
223                "mainline-sdks/for-latest-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
224                "mainline-sdks/for-latest-build/current/com.android.ipsec/gantry-metadata.json",
225                "mainline-sdks/for-latest-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current-api-diff.txt",
226                "mainline-sdks/for-latest-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
227                "mainline-sdks/for-latest-build/current/com.android.tethering/gantry-metadata.json",
228                "mainline-sdks/for-latest-build/current/com.android.tethering/sdk/tethering-module-sdk-current-api-diff.txt",
229                "mainline-sdks/for-latest-build/current/com.android.tethering/sdk/tethering-module-sdk-current.zip",
230                "mainline-sdks/for-latest-build/current/com.google.android.wifi/gantry-metadata.json",
231                "mainline-sdks/for-latest-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current-api-diff.txt",
232                "mainline-sdks/for-latest-build/current/com.google.android.wifi/sdk/wifi-module-sdk-current.zip",
233            ],
234            sorted(self.list_files_in_dir(self.tmp_dist_dir)))
235
236        r_snaphot_dir = os.path.join(self.tmp_out_dir,
237                                     "soong/mainline-sdks/test/for-R-build")
238        aosp_ipsec_r_bp_file = "com.android.ipsec/sdk_library/Android.bp"
239        aosp_tethering_r_bp_file = "com.android.tethering/sdk_library/Android.bp"
240        google_wifi_android_bp = "com.google.android.wifi/sdk_library/Android.bp"
241        self.assertEqual([
242            aosp_ipsec_r_bp_file,
243            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike-removed.txt",
244            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike-stubs.jar",
245            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike.srcjar",
246            "com.android.ipsec/sdk_library/public/android.net.ipsec.ike.txt",
247            "com.android.ipsec/snapshot-creation-build-number.txt",
248            aosp_tethering_r_bp_file,
249            "com.android.tethering/sdk_library/public/framework-tethering-removed.txt",
250            "com.android.tethering/sdk_library/public/framework-tethering-stubs.jar",
251            "com.android.tethering/sdk_library/public/framework-tethering.srcjar",
252            "com.android.tethering/sdk_library/public/framework-tethering.txt",
253            "com.android.tethering/snapshot-creation-build-number.txt",
254            google_wifi_android_bp,
255            "com.google.android.wifi/sdk_library/public/framework-wifi-removed.txt",
256            "com.google.android.wifi/sdk_library/public/framework-wifi-stubs.jar",
257            "com.google.android.wifi/sdk_library/public/framework-wifi.srcjar",
258            "com.google.android.wifi/sdk_library/public/framework-wifi.txt",
259            "com.google.android.wifi/snapshot-creation-build-number.txt",
260            "ipsec-module-sdk-current.zip",
261            "tethering-module-sdk-current.zip",
262            "wifi-module-sdk-current.zip",
263        ], sorted(self.list_files_in_dir(r_snaphot_dir)))
264
265        def read_r_snapshot_contents(path):
266            abs_path = os.path.join(r_snaphot_dir, path)
267            with open(abs_path, "r", encoding="utf8") as file:
268                return file.read()
269
270        # Check the contents of the AOSP ipsec module
271        ipsec_contents = read_r_snapshot_contents(aosp_ipsec_r_bp_file)
272        expected = read_test_data("ipsec_for_r_Android.bp")
273        self.assertEqual(expected, ipsec_contents)
274
275        # Check the contents of the AOSP tethering module
276        tethering_contents = read_r_snapshot_contents(aosp_tethering_r_bp_file)
277        expected = read_test_data("tethering_for_r_Android.bp")
278        self.assertEqual(expected, tethering_contents)
279
280        # Check the contents of the Google ipsec module
281        wifi_contents = read_r_snapshot_contents(google_wifi_android_bp)
282        expected = read_test_data("google_wifi_for_r_Android.bp")
283        self.assertEqual(expected, wifi_contents)
284
285    def test_old_release(self):
286        modules = [
287            MAINLINE_MODULES_BY_APEX["com.android.art"],  # An unnbundled module
288            MAINLINE_MODULES_BY_APEX["com.android.runtime"],  # A bundled module
289            MAINLINE_MODULES_BY_APEX["platform-mainline"],  # Platform SDK
290        ]
291        build_releases = [mm.S]
292        self.produce_dist(modules, build_releases)
293
294        # pylint: disable=line-too-long
295        self.assertEqual([
296            "mainline-sdks/for-S-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
297            "mainline-sdks/for-S-build/current/com.android.art/sdk/art-module-sdk-current.zip",
298            "mainline-sdks/for-S-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
299        ], sorted(self.list_files_in_dir(self.tmp_dist_dir)))
300
301    def test_latest_release(self):
302        modules = [
303            MAINLINE_MODULES_BY_APEX["com.android.art"],  # An unnbundled module
304            MAINLINE_MODULES_BY_APEX["com.android.runtime"],  # A bundled module
305            MAINLINE_MODULES_BY_APEX["platform-mainline"],  # Platform SDK
306        ]
307        build_releases = [mm.LATEST]
308        self.produce_dist(modules, build_releases)
309
310        # pylint: disable=line-too-long
311        self.assertEqual(
312            [
313                # Bundled modules and platform SDKs.
314                "bundled-mainline-sdks/com.android.runtime/host-exports/runtime-module-host-exports-current.zip",
315                "bundled-mainline-sdks/com.android.runtime/sdk/runtime-module-sdk-current.zip",
316                "bundled-mainline-sdks/platform-mainline/sdk/platform-mainline-sdk-current.zip",
317                "bundled-mainline-sdks/platform-mainline/test-exports/platform-mainline-test-exports-current.zip",
318                # Unbundled (normal) modules.
319                "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json",
320                "mainline-sdks/for-latest-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
321                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt",
322                "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current.zip",
323                "mainline-sdks/for-latest-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
324            ],
325            sorted(self.list_files_in_dir(self.tmp_dist_dir)))
326
327        art_api_diff_file = os.path.join(
328            self.tmp_dist_dir,
329            "mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current-api-diff.txt"
330        )
331        self.assertNotEqual(
332            os.path.getsize(art_api_diff_file),
333            0,
334            msg="Api diff file should not be empty for the art module")
335
336        art_gantry_metadata_json_file = os.path.join(
337            self.tmp_dist_dir,
338            "mainline-sdks/for-latest-build/current/com.android.art/gantry-metadata.json"
339        )
340
341        with open(art_gantry_metadata_json_file, "r",
342                  encoding="utf8") as gantry_metadata_json_file_object:
343            json_data = json.load(gantry_metadata_json_file_object)
344
345        self.assertEqual(
346            json_data["api_diff_file"],
347            "art-module-sdk-current-api-diff.txt",
348            msg="Incorrect api-diff file name.")
349        self.assertEqual(
350            json_data["api_diff_file_size"],
351            267,
352            msg="Incorrect api-diff file size.")
353        self.assertEqual(
354            json_data["module_extension_version"],
355            5,
356            msg="The module extension version does not match the expected value."
357        )
358        self.assertEqual(
359            json_data["last_finalized_version"],
360            5,
361            msg="The last finalized version does not match the expected value."
362        )
363
364    def create_build_number_file(self):
365        soong_dir = os.path.join(self.tmp_out_dir, "soong")
366        os.makedirs(soong_dir, exist_ok=True)
367        build_number_file = os.path.join(soong_dir, "build_number.txt")
368        with open(build_number_file, "w", encoding="utf8") as f:
369            f.write("build-number")
370
371    def test_snapshot_build_order(self):
372        # Create the out/soong/build_number.txt file that is copied into the
373        # snapshots.
374        self.create_build_number_file()
375
376        subprocess_runner = unittest.mock.Mock(mm.SubprocessRunner)
377        snapshot_builder = FakeSnapshotBuilder(
378            tool_path="path/to/mainline_modules_sdks.sh",
379            subprocess_runner=subprocess_runner,
380            out_dir=self.tmp_out_dir,
381        )
382        producer = mm.SdkDistProducer(
383            subprocess_runner=subprocess_runner,
384            snapshot_builder=snapshot_builder,
385            dist_dir=self.tmp_dist_dir,
386        )
387
388        modules = [
389            MAINLINE_MODULES_BY_APEX["com.android.art"],
390            MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
391            # Create a google specific module.
392            mm.aosp_to_google(MAINLINE_MODULES_BY_APEX["com.android.wifi"]),
393        ]
394        build_releases = [
395            mm.Q,
396            mm.R,
397            mm.S,
398            mm.LATEST,
399        ]
400
401        producer.produce_dist(modules, build_releases)
402
403        # Check the order in which the snapshots are built.
404        self.assertEqual([
405            (
406                "R",
407                {},
408                ["com.android.ipsec", "com.google.android.wifi"],
409            ),
410            (
411                "latest",
412                {},
413                [
414                    "com.android.art", "com.android.ipsec",
415                    "com.google.android.wifi"
416                ],
417            ),
418            (
419                "S",
420                {
421                    "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S"
422                },
423                [
424                    "com.android.art", "com.android.ipsec",
425                    "com.google.android.wifi"
426                ],
427            ),
428        ], snapshot_builder.snapshots)
429
430    def test_generate_sdk_supported_modules_file(self):
431        subprocess_runner = mm.SubprocessRunner()
432        snapshot_builder = FakeSnapshotBuilder(
433            tool_path="path/to/mainline_modules_sdks.sh",
434            subprocess_runner=subprocess_runner,
435            out_dir=self.tmp_out_dir,
436        )
437        producer = mm.SdkDistProducer(
438            subprocess_runner=subprocess_runner,
439            snapshot_builder=snapshot_builder,
440            dist_dir=self.tmp_dist_dir,
441        )
442
443        # Contains only sdk modules.
444        modules = [
445            MAINLINE_MODULES_BY_APEX["com.android.adservices"],
446            MAINLINE_MODULES_BY_APEX["com.android.art"],
447            MAINLINE_MODULES_BY_APEX["com.android.mediaprovider"],
448        ]
449        producer.dist_generate_sdk_supported_modules_file(modules)
450        with open(os.path.join(self.tmp_dist_dir, "sdk-modules.txt"), "r",
451                  encoding="utf8") as sdk_modules_file:
452            sdk_modules = sdk_modules_file.readlines()
453
454        self.assertTrue("com.google.android.adservices\n" in sdk_modules)
455        self.assertTrue("com.google.android.art\n" in sdk_modules)
456        self.assertTrue("com.google.android.mediaprovider\n" in sdk_modules)
457
458        # Contains only non-sdk modules.
459        modules = [
460            mm.MainlineModule(
461                apex="com.android.adbd",
462                sdks=[],
463                first_release="",
464            ),
465            mm.MainlineModule(
466                apex="com.android.extservices",
467                sdks=[],
468                first_release="",
469            ),
470        ]
471        producer.dist_generate_sdk_supported_modules_file(modules)
472        with open(os.path.join(self.tmp_dist_dir, "sdk-modules.txt"), "r",
473                  encoding="utf8") as sdk_modules_file:
474            sdk_modules = sdk_modules_file.readlines()
475
476        self.assertEqual(len(sdk_modules), 0)
477
478        # Contains mixture of sdk and non-sdk modules.
479        modules = [
480            MAINLINE_MODULES_BY_APEX["com.android.adservices"],
481            MAINLINE_MODULES_BY_APEX["com.android.mediaprovider"],
482            mm.MainlineModule(
483                apex="com.android.adbd",
484                sdks=[],
485                first_release="",
486            ),
487            mm.MainlineModule(
488                apex="com.android.extservices",
489                sdks=[],
490                first_release="",
491            ),
492        ]
493        producer.dist_generate_sdk_supported_modules_file(modules)
494        with open(os.path.join(self.tmp_dist_dir, "sdk-modules.txt"), "r",
495                  encoding="utf8") as sdk_modules_file:
496            sdk_modules = sdk_modules_file.readlines()
497
498        self.assertTrue("com.google.android.adservices\n" in sdk_modules)
499        self.assertTrue("com.google.android.mediaprovider\n" in sdk_modules)
500        self.assertFalse("com.google.android.adbd\n" in sdk_modules)
501        self.assertFalse("com.google.android.extservices\n" in sdk_modules)
502
503    def test_generate_mainline_modules_info_file(self):
504        subprocess_runner = mm.SubprocessRunner()
505        snapshot_builder = FakeSnapshotBuilder(
506            tool_path="path/to/mainline_modules_sdks.sh",
507            subprocess_runner=subprocess_runner,
508            out_dir=self.tmp_out_dir,
509        )
510        producer = mm.SdkDistProducer(
511            subprocess_runner=subprocess_runner,
512            snapshot_builder=snapshot_builder,
513            dist_dir=self.tmp_dist_dir,
514        )
515
516        modules = [
517            MAINLINE_MODULES_BY_APEX["com.android.adservices"],
518            MAINLINE_MODULES_BY_APEX["com.android.tethering"],
519            mm.MainlineModule(
520                apex="com.android.adbd",
521                sdks=[],
522                first_release="",
523            ),
524        ]
525
526        producer.generate_mainline_modules_info_file(modules, self.tmp_dir)
527        with open(
528            os.path.join(self.tmp_dist_dir, "mainline-modules-info.json"),
529            "r",
530            encoding="utf8",
531        ) as mainline_modules_info_file:
532            mainline_modules_info = json.load(mainline_modules_info_file)
533
534        module_json = mainline_modules_info["com.google.android.adservices"]
535        self.assertEqual(
536            module_json["module_sdk_project"], "prebuilts/module_sdk/AdServices"
537        )
538        self.assertEqual(module_json["module_proto_key"], "AD_SERVICES")
539        self.assertEqual(module_json["sdk_name"], "adservices-module-sdk")
540
541        module_json = mainline_modules_info["com.google.android.tethering"]
542        self.assertEqual(
543            module_json["module_sdk_project"],
544            "prebuilts/module_sdk/Connectivity",
545        )
546        self.assertEqual(module_json["module_proto_key"], "TETHERING")
547        self.assertEqual(module_json["sdk_name"], "tethering-module-sdk")
548
549        self.assertFalse("adbd" in mainline_modules_info)
550
551
552def path_to_test_data(relative_path):
553    """Construct a path to a test data file.
554
555    The relative_path is relative to the location of this file.
556    """
557    this_file = __file__
558    # When running as a python_test_host (name=<x>) with an embedded launcher
559    # the __file__ points to .../<x>/<x>.py but the .../<x> is not a directory
560    # it is a binary with the launcher and the python file embedded inside. In
561    # that case a test data file <rel> is at .../<x>_data/<rel>, not
562    # .../<x>/<x>_data/<rel> so it is necessary to trim the base name (<x>.py)
563    # from the file.
564    if not os.path.isfile(this_file):
565        this_file = os.path.dirname(this_file)
566    # When the python file is at .../<x>.py (or in the case of an embedded
567    # launcher at .../<x>/<x>.py) then the test data is at .../<x>_data/<rel>.
568    this_file_without_ext, _ = os.path.splitext(this_file)
569    return os.path.join(this_file_without_ext + "_data", relative_path)
570
571
572def read_test_data(relative_path):
573    with open(path_to_test_data(relative_path), "r", encoding="utf8") as f:
574        return f.read()
575
576
577class TestAndroidBpTransformations(unittest.TestCase):
578
579    def apply_transformations(self, src, transformations, build_release, expected):
580        producer = mm.SdkDistProducer(
581            subprocess_runner=mock.Mock(mm.SubprocessRunner),
582            snapshot_builder=mock.Mock(mm.SnapshotBuilder),
583            script=self._testMethodName,
584        )
585
586        with tempfile.TemporaryDirectory() as tmp_dir:
587            path = os.path.join(tmp_dir, "Android.bp")
588            with open(path, "w", encoding="utf8") as f:
589                f.write(src)
590
591            mm.apply_transformations(
592                producer, tmp_dir, transformations, build_release)
593
594            with open(path, "r", encoding="utf8") as f:
595                result = f.read()
596
597        self.maxDiff = None
598        self.assertEqual(expected, result)
599
600    def test_common_mainline_module(self):
601        """Tests the transformations applied to a common mainline sdk on S.
602
603        This uses ipsec as an example of a common mainline sdk. This checks
604        that the general Soong config module types and variables are used.
605        """
606        src = read_test_data("ipsec_Android.bp.input")
607
608        expected = read_test_data("ipsec_Android.bp.expected")
609
610        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
611        transformations = module.transformations(mm.S, mm.Sdk)
612
613        self.apply_transformations(src, transformations, mm.S, expected)
614
615    def test_common_mainline_module_tiramisu(self):
616        """Tests the transformations applied to a common mainline sdk on T.
617
618        This uses ipsec as an example of a common mainline sdk. This checks
619        that the use_source_config_var property is inserted.
620        """
621        src = read_test_data("ipsec_Android.bp.input")
622
623        expected = read_test_data("ipsec_tiramisu_Android.bp.expected")
624
625        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
626        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
627
628        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
629
630    def test_optional_mainline_module(self):
631        """Tests the transformations applied to an optional mainline sdk on S.
632
633        This uses wifi as an example of a optional mainline sdk. This checks
634        that the module specific Soong config module types and variables are
635        used.
636        """
637        src = read_test_data("wifi_Android.bp.input")
638
639        expected = read_test_data("wifi_Android.bp.expected")
640
641        module = MAINLINE_MODULES_BY_APEX["com.android.wifi"]
642        transformations = module.transformations(mm.S, mm.Sdk)
643
644        self.apply_transformations(src, transformations, mm.S, expected)
645
646    def test_optional_mainline_module_tiramisu(self):
647        """Tests the transformations applied to an optional mainline sdk on T.
648
649        This uses wifi as an example of a optional mainline sdk. This checks
650        that the use_source_config_var property is inserted.
651        """
652        src = read_test_data("wifi_Android.bp.input")
653
654        expected = read_test_data("wifi_tiramisu_Android.bp.expected")
655
656        module = MAINLINE_MODULES_BY_APEX["com.android.wifi"]
657        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
658
659        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
660
661    def test_optional_mainline_module_latest(self):
662        """Tests the transformations applied to an optional mainline sdk LATEST.
663
664        This uses wifi as an example of a optional mainline sdk. This checks
665        that the use_source_config_var property is inserted.
666        """
667        src = read_test_data("wifi_Android.bp.input")
668
669        expected = read_test_data("wifi_latest_Android.bp.expected")
670
671        module = MAINLINE_MODULES_BY_APEX["com.android.wifi"]
672        transformations = module.transformations(mm.LATEST, mm.Sdk)
673
674        self.apply_transformations(src, transformations, mm.LATEST, expected)
675
676    def test_art(self):
677        """Tests the transformations applied to a the ART mainline module.
678
679        The ART mainline module uses a different Soong config setup to the
680        common mainline modules. This checks that the ART specific Soong config
681        module types, and variables are used.
682        """
683        src = read_test_data("art_Android.bp.input")
684
685        expected = read_test_data("art_Android.bp.expected")
686
687        module = MAINLINE_MODULES_BY_APEX["com.android.art"]
688        transformations = module.transformations(mm.S, mm.Sdk)
689
690        self.apply_transformations(src, transformations, mm.S, expected)
691
692    def test_art_module_exports(self):
693        """Tests the transformations applied to a the ART mainline module.
694
695        The ART mainline module uses a different Soong config setup to the
696        common mainline modules. This checks that the ART specific Soong config
697        module types, and variables are used.
698        """
699        src = read_test_data("art_Android.bp.input")
700
701        expected = read_test_data("art_host_exports_Android.bp.expected")
702
703        module = MAINLINE_MODULES_BY_APEX["com.android.art"]
704        transformations = module.transformations(mm.S, mm.HostExports)
705
706        self.apply_transformations(src, transformations, mm.S, expected)
707
708    def test_r_build(self):
709        """Tests the transformations that are applied for the R build.
710
711        This uses ipsec as an example of a common mainline module. That would
712        usually apply the mm.SoongConfigBoilerplateInserter transformation but
713        because this is being run for build R that transformation should not be
714        applied.
715        """
716        src = read_test_data("ipsec_for_r_Android.bp")
717
718        # There should be no changes made.
719        expected = src
720
721        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
722        transformations = module.transformations(mm.R, mm.Sdk)
723
724        self.apply_transformations(src, transformations, mm.R, expected)
725
726    def test_additional_transformation(self):
727        """Tests additional transformation.
728
729        This uses ipsec as an example of a common case for adding information
730        in Android.bp file.
731        This checks will append the information in Android.bp for a regular module.
732        """
733
734        @dataclasses.dataclass(frozen=True)
735        class TestTransformation(mm.FileTransformation):
736            """Transforms an Android.bp file by appending testing message."""
737
738            test_content: str = ""
739
740            def apply(self, producer, path, build_release):
741                with open(path, "a+", encoding="utf8") as file:
742                    self._apply_transformation(producer, file, build_release)
743
744            def _apply_transformation(self, producer, file, build_release):
745                if build_release >= mm.Tiramisu:
746                    file.write(self.test_content)
747
748        src = read_test_data("ipsec_Android.bp.input")
749
750        expected = read_test_data(
751            "ipsec_tiramisu_Android.bp.additional.expected")
752        test_transformation = TestTransformation(
753            "Android.bp", test_content="\n// Adding by test")
754        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
755        module = dataclasses.replace(
756            module, apex=module.apex,
757            first_release=module.first_release,
758            additional_transformations=[test_transformation])
759        transformations = module.transformations(mm.Tiramisu, mm.Sdk)
760        self.apply_transformations(src, transformations, mm.Tiramisu, expected)
761
762
763class TestFilterModules(unittest.TestCase):
764
765    def test_no_filter(self):
766        all_modules = mm.MAINLINE_MODULES + mm.BUNDLED_MAINLINE_MODULES
767        modules = mm.filter_modules(all_modules, None)
768        self.assertEqual(modules, all_modules)
769
770    def test_with_filter(self):
771        modules = mm.filter_modules(mm.MAINLINE_MODULES, "com.android.art")
772        expected = MAINLINE_MODULES_BY_APEX["com.android.art"]
773        self.assertEqual(modules, [expected])
774
775
776class TestModuleProperties(unittest.TestCase):
777
778    def test_unbundled(self):
779        for module in mm.MAINLINE_MODULES:
780            with self.subTest(module=module):
781                self.assertFalse(module.is_bundled())
782
783    def test_bundled(self):
784        for module in (mm.BUNDLED_MAINLINE_MODULES +
785                       mm.PLATFORM_SDKS_FOR_MAINLINE):
786            with self.subTest(module=module):
787                self.assertTrue(module.is_bundled())
788                self.assertEqual(module.first_release, mm.LATEST)
789
790
791if __name__ == "__main__":
792    unittest.main(verbosity=2)
793