|  | #!/usr/bin/env python | 
|  | # | 
|  | # Copyright (C) 2022 The Android Open Source Project | 
|  | # | 
|  | # Licensed under the Apache License, Version 2.0 (the 'License'); | 
|  | # you may not use this file except in compliance with the License. | 
|  | # You may obtain a copy of the License at | 
|  | # | 
|  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, software | 
|  | # distributed under the License is distributed on an 'AS IS' BASIS, | 
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | # See the License for the specific language governing permissions and | 
|  | # limitations under the License. | 
|  | """Unit tests for analyzing bootclasspath_fragment modules.""" | 
|  | import os.path | 
|  | import shutil | 
|  | import tempfile | 
|  | import unittest | 
|  | import unittest.mock | 
|  |  | 
|  | import sys | 
|  |  | 
|  | import analyze_bcpf as ab | 
|  |  | 
|  | _FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi" | 
|  | _MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt" | 
|  | _MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt" | 
|  | _MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt" | 
|  | _MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt" | 
|  |  | 
|  | _MULTI_LINE_COMMENT = """ | 
|  | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo, | 
|  | bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem | 
|  | tincidunt luctus.""".replace("\n", " ").strip() | 
|  |  | 
|  |  | 
|  | class FakeBuildOperation(ab.BuildOperation): | 
|  |  | 
|  | def __init__(self, lines, return_code): | 
|  | ab.BuildOperation.__init__(self, None) | 
|  | self._lines = lines | 
|  | self.returncode = return_code | 
|  |  | 
|  | def lines(self): | 
|  | return iter(self._lines) | 
|  |  | 
|  | def wait(self, *args, **kwargs): | 
|  | return | 
|  |  | 
|  |  | 
|  | class TestAnalyzeBcpf(unittest.TestCase): | 
|  |  | 
|  | def setUp(self): | 
|  | # Create a temporary directory | 
|  | self.test_dir = tempfile.mkdtemp() | 
|  |  | 
|  | def tearDown(self): | 
|  | # Remove the directory after the test | 
|  | shutil.rmtree(self.test_dir) | 
|  |  | 
|  | @staticmethod | 
|  | def write_abs_file(abs_path, contents): | 
|  | os.makedirs(os.path.dirname(abs_path), exist_ok=True) | 
|  | with open(abs_path, "w", encoding="utf8") as f: | 
|  | print(contents.removeprefix("\n"), file=f, end="") | 
|  |  | 
|  | def populate_fs(self, fs): | 
|  | for path, contents in fs.items(): | 
|  | abs_path = os.path.join(self.test_dir, path) | 
|  | self.write_abs_file(abs_path, contents) | 
|  |  | 
|  | def create_analyzer_for_test(self, | 
|  | fs=None, | 
|  | bcpf="bcpf", | 
|  | apex="apex", | 
|  | sdk="sdk", | 
|  | fix=False): | 
|  | if fs: | 
|  | self.populate_fs(fs) | 
|  |  | 
|  | top_dir = self.test_dir | 
|  | out_dir = os.path.join(self.test_dir, "out") | 
|  | product_out_dir = "out/product" | 
|  |  | 
|  | bcpf_dir = f"{bcpf}-dir" | 
|  | modules = {bcpf: {"path": [bcpf_dir]}} | 
|  | module_info = ab.ModuleInfo(modules) | 
|  |  | 
|  | analyzer = ab.BcpfAnalyzer( | 
|  | tool_path=os.path.join(out_dir, "bin"), | 
|  | top_dir=top_dir, | 
|  | out_dir=out_dir, | 
|  | product_out_dir=product_out_dir, | 
|  | bcpf=bcpf, | 
|  | apex=apex, | 
|  | sdk=sdk, | 
|  | fix=fix, | 
|  | module_info=module_info, | 
|  | ) | 
|  | analyzer.load_all_flags() | 
|  | return analyzer | 
|  |  | 
|  | def test_reformat_report_text(self): | 
|  | lines = """ | 
|  | 99. An item in a numbered list | 
|  | that traverses multiple lines. | 
|  |  | 
|  | An indented example | 
|  | that should not be reformatted. | 
|  | """ | 
|  | reformatted = ab.BcpfAnalyzer.reformat_report_test(lines) | 
|  | self.assertEqual( | 
|  | """ | 
|  | 99. An item in a numbered list that traverses multiple lines. | 
|  |  | 
|  | An indented example | 
|  | that should not be reformatted. | 
|  | """, reformatted) | 
|  |  | 
|  | def do_test_build_flags(self, fix): | 
|  | lines = """ | 
|  | ERROR: Hidden API flags are inconsistent: | 
|  | < out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv | 
|  | > out/soong/hiddenapi/hiddenapi-flags.csv | 
|  |  | 
|  | < Lacme/test/Class;-><init>()V,blocked | 
|  | > Lacme/test/Class;-><init>()V,max-target-o | 
|  |  | 
|  | < Lacme/test/Other;->getThing()Z,blocked | 
|  | > Lacme/test/Other;->getThing()Z,max-target-p | 
|  |  | 
|  | < Lacme/test/Widget;-><init()V,blocked | 
|  | > Lacme/test/Widget;-><init()V,max-target-q | 
|  |  | 
|  | < Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked | 
|  | > Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r | 
|  | 16:37:32 ninja failed with: exit status 1 | 
|  | """.strip().splitlines() | 
|  | operation = FakeBuildOperation(lines=lines, return_code=1) | 
|  |  | 
|  | fs = { | 
|  | _MAX_TARGET_O: | 
|  | """ | 
|  | Lacme/items/Magnet;->size:I | 
|  | Lacme/test/Class;-><init>()V | 
|  | """, | 
|  | _MAX_TARGET_P: | 
|  | """ | 
|  | Lacme/items/Rocket;->size:I | 
|  | Lacme/test/Other;->getThing()Z | 
|  | """, | 
|  | _MAX_TARGET_Q: | 
|  | """ | 
|  | Lacme/items/Rock;->size:I | 
|  | Lacme/test/Widget;-><init()V | 
|  | """, | 
|  | _MAX_TARGET_R: | 
|  | """ | 
|  | Lacme/items/Lever;->size:I | 
|  | Lacme/test/Gadget;->NAME:Ljava/lang/String; | 
|  | """, | 
|  | "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt": | 
|  | """ | 
|  | Lacme/old/Class;->getWidget()Lacme/test/Widget; | 
|  | """, | 
|  | "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv": | 
|  | """ | 
|  | Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked | 
|  | Lacme/test/Widget;-><init()V,blocked | 
|  | Lacme/test/Class;-><init>()V,blocked | 
|  | Lacme/test/Other;->getThing()Z,blocked | 
|  | """, | 
|  | } | 
|  |  | 
|  | analyzer = self.create_analyzer_for_test(fs, fix=fix) | 
|  |  | 
|  | # Override the build_file_read_output() method to just return a fake | 
|  | # build operation. | 
|  | analyzer.build_file_read_output = unittest.mock.Mock( | 
|  | return_value=operation) | 
|  |  | 
|  | # Override the run_command() method to do nothing. | 
|  | analyzer.run_command = unittest.mock.Mock() | 
|  |  | 
|  | result = ab.Result() | 
|  |  | 
|  | analyzer.build_monolithic_flags(result) | 
|  | expected_diffs = { | 
|  | "Lacme/test/Gadget;->NAME:Ljava/lang/String;": | 
|  | (["blocked"], ["lo-prio", "max-target-r"]), | 
|  | "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]), | 
|  | "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]), | 
|  | "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"]) | 
|  | } | 
|  | self.assertEqual(expected_diffs, result.diffs, msg="flag differences") | 
|  |  | 
|  | expected_property_changes = [ | 
|  | ab.HiddenApiPropertyChange( | 
|  | property_name="max_target_o_low_priority", | 
|  | values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | property_comment=""), | 
|  | ab.HiddenApiPropertyChange( | 
|  | property_name="max_target_p", | 
|  | values=["hiddenapi/hiddenapi-max-target-p.txt"], | 
|  | property_comment=""), | 
|  | ab.HiddenApiPropertyChange( | 
|  | property_name="max_target_q", | 
|  | values=["hiddenapi/hiddenapi-max-target-q.txt"], | 
|  | property_comment=""), | 
|  | ab.HiddenApiPropertyChange( | 
|  | property_name="max_target_r_low_priority", | 
|  | values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"], | 
|  | property_comment=""), | 
|  | ] | 
|  | self.assertEqual( | 
|  | expected_property_changes, | 
|  | result.property_changes, | 
|  | msg="property changes") | 
|  |  | 
|  | return result | 
|  |  | 
|  | def test_build_flags_report(self): | 
|  | result = self.do_test_build_flags(fix=False) | 
|  |  | 
|  | expected_file_changes = [ | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/" | 
|  | "hiddenapi-max-target-o-low-priority.txt", | 
|  | description="""Add the following entries: | 
|  | Lacme/test/Class;-><init>()V | 
|  | """, | 
|  | ), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt", | 
|  | description="""Add the following entries: | 
|  | Lacme/test/Other;->getThing()Z | 
|  | """, | 
|  | ), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt", | 
|  | description="""Add the following entries: | 
|  | Lacme/test/Widget;-><init()V | 
|  | """), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/" | 
|  | "hiddenapi-max-target-r-low-priority.txt", | 
|  | description="""Add the following entries: | 
|  | Lacme/test/Gadget;->NAME:Ljava/lang/String; | 
|  | """), | 
|  | ab.FileChange( | 
|  | path="frameworks/base/boot/hiddenapi/" | 
|  | "hiddenapi-max-target-o.txt", | 
|  | description="""Remove the following entries: | 
|  | Lacme/test/Class;-><init>()V | 
|  | """), | 
|  | ab.FileChange( | 
|  | path="frameworks/base/boot/hiddenapi/" | 
|  | "hiddenapi-max-target-p.txt", | 
|  | description="""Remove the following entries: | 
|  | Lacme/test/Other;->getThing()Z | 
|  | """), | 
|  | ab.FileChange( | 
|  | path="frameworks/base/boot/hiddenapi/" | 
|  | "hiddenapi-max-target-q.txt", | 
|  | description="""Remove the following entries: | 
|  | Lacme/test/Widget;-><init()V | 
|  | """), | 
|  | ab.FileChange( | 
|  | path="frameworks/base/boot/hiddenapi/" | 
|  | "hiddenapi-max-target-r-loprio.txt", | 
|  | description="""Remove the following entries: | 
|  | Lacme/test/Gadget;->NAME:Ljava/lang/String; | 
|  | """) | 
|  | ] | 
|  | result.file_changes.sort() | 
|  | self.assertEqual( | 
|  | expected_file_changes, result.file_changes, msg="file_changes") | 
|  |  | 
|  | def test_build_flags_fix(self): | 
|  | result = self.do_test_build_flags(fix=True) | 
|  |  | 
|  | expected_file_changes = [ | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/" | 
|  | "hiddenapi-max-target-o-low-priority.txt", | 
|  | description="Created with 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt", | 
|  | description="Added 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt", | 
|  | description="Created with 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path="bcpf-dir/hiddenapi/" | 
|  | "hiddenapi-max-target-r-low-priority.txt", | 
|  | description="Created with 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path=_MAX_TARGET_O, | 
|  | description="Removed 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path=_MAX_TARGET_P, | 
|  | description="Removed 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path=_MAX_TARGET_Q, | 
|  | description="Removed 'bcpf' specific entries"), | 
|  | ab.FileChange( | 
|  | path=_MAX_TARGET_R, | 
|  | description="Removed 'bcpf' specific entries") | 
|  | ] | 
|  |  | 
|  | result.file_changes.sort() | 
|  | self.assertEqual( | 
|  | expected_file_changes, result.file_changes, msg="file_changes") | 
|  |  | 
|  | expected_file_contents = { | 
|  | "bcpf-dir/hiddenapi/hiddenapi-max-target-o-low-priority.txt": | 
|  | """ | 
|  | Lacme/test/Class;-><init>()V | 
|  | """, | 
|  | "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt": | 
|  | """ | 
|  | Lacme/old/Class;->getWidget()Lacme/test/Widget; | 
|  | Lacme/test/Other;->getThing()Z | 
|  | """, | 
|  | "bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt": | 
|  | """ | 
|  | Lacme/test/Widget;-><init()V | 
|  | """, | 
|  | "bcpf-dir/hiddenapi/hiddenapi-max-target-r-low-priority.txt": | 
|  | """ | 
|  | Lacme/test/Gadget;->NAME:Ljava/lang/String; | 
|  | """, | 
|  | _MAX_TARGET_O: | 
|  | """ | 
|  | Lacme/items/Magnet;->size:I | 
|  | """, | 
|  | _MAX_TARGET_P: | 
|  | """ | 
|  | Lacme/items/Rocket;->size:I | 
|  | """, | 
|  | _MAX_TARGET_Q: | 
|  | """ | 
|  | Lacme/items/Rock;->size:I | 
|  | """, | 
|  | _MAX_TARGET_R: | 
|  | """ | 
|  | Lacme/items/Lever;->size:I | 
|  | """, | 
|  | } | 
|  | for file_change in result.file_changes: | 
|  | path = file_change.path | 
|  | expected_contents = expected_file_contents[path].lstrip() | 
|  | abs_path = os.path.join(self.test_dir, path) | 
|  | with open(abs_path, "r", encoding="utf8") as tio: | 
|  | contents = tio.read() | 
|  | self.assertEqual( | 
|  | expected_contents, contents, msg=f"{path} contents") | 
|  |  | 
|  | def test_compute_hiddenapi_package_properties(self): | 
|  | fs = { | 
|  | "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv": | 
|  | """ | 
|  | La/b/C;->m()V | 
|  | La/b/c/D;->m()V | 
|  | La/b/c/E;->m()V | 
|  | Lb/c/D;->m()V | 
|  | Lb/c/E;->m()V | 
|  | Lb/c/d/E;->m()V | 
|  | """, | 
|  | "out/soong/hiddenapi/hiddenapi-flags.csv": | 
|  | """ | 
|  | La/b/C;->m()V | 
|  | La/b/D;->m()V | 
|  | La/b/E;->m()V | 
|  | La/b/c/D;->m()V | 
|  | La/b/c/E;->m()V | 
|  | La/b/c/d/E;->m()V | 
|  | La/b/c/d/e/F;->m()V | 
|  | Lb/c/D;->m()V | 
|  | Lb/c/E;->m()V | 
|  | Lb/c/d/E;->m()V | 
|  | """ | 
|  | } | 
|  | analyzer = self.create_analyzer_for_test(fs) | 
|  | analyzer.load_all_flags() | 
|  |  | 
|  | result = ab.Result() | 
|  | analyzer.compute_hiddenapi_package_properties(result) | 
|  | self.assertEqual(["a.b"], list(result.split_packages.keys())) | 
|  |  | 
|  | reason = result.split_packages["a.b"] | 
|  | self.assertEqual(["a.b.C"], reason.bcpf) | 
|  | self.assertEqual(["a.b.D", "a.b.E"], reason.other) | 
|  |  | 
|  | self.assertEqual(["a.b.c"], list(result.single_packages.keys())) | 
|  |  | 
|  | reason = result.single_packages["a.b.c"] | 
|  | self.assertEqual(["a.b.c"], reason.bcpf) | 
|  | self.assertEqual(["a.b.c.d", "a.b.c.d.e"], reason.other) | 
|  |  | 
|  | self.assertEqual(["b"], result.package_prefixes) | 
|  |  | 
|  |  | 
|  | class TestHiddenApiPropertyChange(unittest.TestCase): | 
|  |  | 
|  | def setUp(self): | 
|  | # Create a temporary directory | 
|  | self.test_dir = tempfile.mkdtemp() | 
|  |  | 
|  | def tearDown(self): | 
|  | # Remove the directory after the test | 
|  | shutil.rmtree(self.test_dir) | 
|  |  | 
|  | def check_change_fix(self, change, bpmodify_output, expected): | 
|  | file = os.path.join(self.test_dir, "Android.bp") | 
|  |  | 
|  | with open(file, "w", encoding="utf8") as tio: | 
|  | tio.write(bpmodify_output.strip("\n")) | 
|  |  | 
|  | bpmodify_runner = ab.BpModifyRunner( | 
|  | os.path.join(os.path.dirname(sys.argv[0]), "bpmodify")) | 
|  | change.fix_bp_file(file, "bcpf", bpmodify_runner) | 
|  |  | 
|  | with open(file, "r", encoding="utf8") as tio: | 
|  | contents = tio.read() | 
|  | self.assertEqual(expected.lstrip("\n"), contents) | 
|  |  | 
|  | def check_change_snippet(self, change, expected): | 
|  | snippet = change.snippet("        ") | 
|  | self.assertEqual(expected, snippet) | 
|  |  | 
|  | def test_change_property_with_value_no_comment(self): | 
|  | change = ab.HiddenApiPropertyChange( | 
|  | property_name="split_packages", | 
|  | values=["android.provider"], | 
|  | ) | 
|  |  | 
|  | self.check_change_snippet( | 
|  | change, """ | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  | """) | 
|  |  | 
|  | self.check_change_fix( | 
|  | change, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  | }, | 
|  | } | 
|  | """, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  | }, | 
|  | } | 
|  | """) | 
|  |  | 
|  | def test_change_property_with_value_and_comment(self): | 
|  | change = ab.HiddenApiPropertyChange( | 
|  | property_name="split_packages", | 
|  | values=["android.provider"], | 
|  | property_comment=_MULTI_LINE_COMMENT, | 
|  | ) | 
|  |  | 
|  | self.check_change_snippet( | 
|  | change, """ | 
|  | // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu | 
|  | // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida | 
|  | // ultricies sem tincidunt luctus. | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  | """) | 
|  |  | 
|  | self.check_change_fix( | 
|  | change, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  |  | 
|  | single_packages: [ | 
|  | "android.system", | 
|  | ], | 
|  |  | 
|  | }, | 
|  | } | 
|  | """, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  |  | 
|  | // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu | 
|  | // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida | 
|  | // ultricies sem tincidunt luctus. | 
|  | split_packages: [ | 
|  | "android.provider", | 
|  | ], | 
|  |  | 
|  | single_packages: [ | 
|  | "android.system", | 
|  | ], | 
|  |  | 
|  | }, | 
|  | } | 
|  | """) | 
|  |  | 
|  | def test_set_property_with_value_and_comment(self): | 
|  | change = ab.HiddenApiPropertyChange( | 
|  | property_name="split_packages", | 
|  | values=["another.provider", "other.system"], | 
|  | property_comment=_MULTI_LINE_COMMENT, | 
|  | action=ab.PropertyChangeAction.REPLACE, | 
|  | ) | 
|  |  | 
|  | self.check_change_snippet( | 
|  | change, """ | 
|  | // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu | 
|  | // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida | 
|  | // ultricies sem tincidunt luctus. | 
|  | split_packages: [ | 
|  | "another.provider", | 
|  | "other.system", | 
|  | ], | 
|  | """) | 
|  |  | 
|  | self.check_change_fix( | 
|  | change, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [ | 
|  | "another.provider", | 
|  | "other.system", | 
|  | ], | 
|  | }, | 
|  | } | 
|  | """, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  |  | 
|  | // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu | 
|  | // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida | 
|  | // ultricies sem tincidunt luctus. | 
|  | split_packages: [ | 
|  | "another.provider", | 
|  | "other.system", | 
|  | ], | 
|  | }, | 
|  | } | 
|  | """) | 
|  |  | 
|  | def test_set_property_with_no_value_or_comment(self): | 
|  | change = ab.HiddenApiPropertyChange( | 
|  | property_name="split_packages", | 
|  | values=[], | 
|  | action=ab.PropertyChangeAction.REPLACE, | 
|  | ) | 
|  |  | 
|  | self.check_change_snippet(change, """ | 
|  | split_packages: [], | 
|  | """) | 
|  |  | 
|  | self.check_change_fix( | 
|  | change, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [ | 
|  | "another.provider", | 
|  | "other.system", | 
|  | ], | 
|  | package_prefixes: ["android.provider"], | 
|  | }, | 
|  | } | 
|  | """, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [], | 
|  | package_prefixes: ["android.provider"], | 
|  | }, | 
|  | } | 
|  | """) | 
|  |  | 
|  | def test_set_empty_property_with_no_value_or_comment(self): | 
|  | change = ab.HiddenApiPropertyChange( | 
|  | property_name="split_packages", | 
|  | values=[], | 
|  | action=ab.PropertyChangeAction.REPLACE, | 
|  | ) | 
|  |  | 
|  | self.check_change_snippet(change, """ | 
|  | split_packages: [], | 
|  | """) | 
|  |  | 
|  | self.check_change_fix( | 
|  | change, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [], | 
|  | package_prefixes: ["android.provider"], | 
|  | }, | 
|  | } | 
|  | """, """ | 
|  | bootclasspath_fragment { | 
|  | name: "bcpf", | 
|  |  | 
|  | // modified by the Soong or platform compat team. | 
|  | hidden_api: { | 
|  | max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], | 
|  | split_packages: [], | 
|  | package_prefixes: ["android.provider"], | 
|  | }, | 
|  | } | 
|  | """) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | unittest.main(verbosity=3) |