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