• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2022 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 analyzing bootclasspath_fragment modules."""
17import os.path
18import shutil
19import tempfile
20import unittest
21import unittest.mock
22
23import sys
24
25import analyze_bcpf as ab
26
27_FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi"
28_MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt"
29_MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt"
30_MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt"
31_MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt"
32
33_MULTI_LINE_COMMENT = """
34Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo,
35bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem
36tincidunt luctus.""".replace("\n", " ").strip()
37
38
39class FakeBuildOperation(ab.BuildOperation):
40
41    def __init__(self, lines, return_code):
42        ab.BuildOperation.__init__(self, None)
43        self._lines = lines
44        self.returncode = return_code
45
46    def lines(self):
47        return iter(self._lines)
48
49    def wait(self, *args, **kwargs):
50        return
51
52
53class TestAnalyzeBcpf(unittest.TestCase):
54
55    def setUp(self):
56        # Create a temporary directory
57        self.test_dir = tempfile.mkdtemp()
58
59    def tearDown(self):
60        # Remove the directory after the test
61        shutil.rmtree(self.test_dir)
62
63    @staticmethod
64    def write_abs_file(abs_path, contents):
65        os.makedirs(os.path.dirname(abs_path), exist_ok=True)
66        with open(abs_path, "w", encoding="utf8") as f:
67            print(contents.removeprefix("\n"), file=f, end="")
68
69    def populate_fs(self, fs):
70        for path, contents in fs.items():
71            abs_path = os.path.join(self.test_dir, path)
72            self.write_abs_file(abs_path, contents)
73
74    def create_analyzer_for_test(self,
75                                 fs=None,
76                                 bcpf="bcpf",
77                                 apex="apex",
78                                 sdk="sdk",
79                                 fix=False):
80        if fs:
81            self.populate_fs(fs)
82
83        top_dir = self.test_dir
84        out_dir = os.path.join(self.test_dir, "out")
85        product_out_dir = "out/product"
86
87        bcpf_dir = f"{bcpf}-dir"
88        modules = {bcpf: {"path": [bcpf_dir]}}
89        module_info = ab.ModuleInfo(modules)
90
91        analyzer = ab.BcpfAnalyzer(
92            tool_path=os.path.join(out_dir, "bin"),
93            top_dir=top_dir,
94            out_dir=out_dir,
95            product_out_dir=product_out_dir,
96            bcpf=bcpf,
97            apex=apex,
98            sdk=sdk,
99            fix=fix,
100            module_info=module_info,
101        )
102        analyzer.load_all_flags()
103        return analyzer
104
105    def test_reformat_report_text(self):
106        lines = """
10799. An item in a numbered list
108that traverses multiple lines.
109
110   An indented example
111   that should not be reformatted.
112"""
113        reformatted = ab.BcpfAnalyzer.reformat_report_test(lines)
114        self.assertEqual(
115            """
11699. An item in a numbered list that traverses multiple lines.
117
118   An indented example
119   that should not be reformatted.
120""", reformatted)
121
122    def do_test_build_flags(self, fix):
123        lines = """
124ERROR: Hidden API flags are inconsistent:
125< out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv
126> out/soong/hiddenapi/hiddenapi-flags.csv
127
128< Lacme/test/Class;-><init>()V,blocked
129> Lacme/test/Class;-><init>()V,max-target-o
130
131< Lacme/test/Other;->getThing()Z,blocked
132> Lacme/test/Other;->getThing()Z,max-target-p
133
134< Lacme/test/Widget;-><init()V,blocked
135> Lacme/test/Widget;-><init()V,max-target-q
136
137< Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
138> Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r
13916:37:32 ninja failed with: exit status 1
140""".strip().splitlines()
141        operation = FakeBuildOperation(lines=lines, return_code=1)
142
143        fs = {
144            _MAX_TARGET_O:
145                """
146Lacme/items/Magnet;->size:I
147Lacme/test/Class;-><init>()V
148""",
149            _MAX_TARGET_P:
150                """
151Lacme/items/Rocket;->size:I
152Lacme/test/Other;->getThing()Z
153""",
154            _MAX_TARGET_Q:
155                """
156Lacme/items/Rock;->size:I
157Lacme/test/Widget;-><init()V
158""",
159            _MAX_TARGET_R:
160                """
161Lacme/items/Lever;->size:I
162Lacme/test/Gadget;->NAME:Ljava/lang/String;
163""",
164            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
165                """
166Lacme/old/Class;->getWidget()Lacme/test/Widget;
167""",
168            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
169                """
170Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
171Lacme/test/Widget;-><init()V,blocked
172Lacme/test/Class;-><init>()V,blocked
173Lacme/test/Other;->getThing()Z,blocked
174""",
175        }
176
177        analyzer = self.create_analyzer_for_test(fs, fix=fix)
178
179        # Override the build_file_read_output() method to just return a fake
180        # build operation.
181        analyzer.build_file_read_output = unittest.mock.Mock(
182            return_value=operation)
183
184        # Override the run_command() method to do nothing.
185        analyzer.run_command = unittest.mock.Mock()
186
187        result = ab.Result()
188
189        analyzer.build_monolithic_flags(result)
190        expected_diffs = {
191            "Lacme/test/Gadget;->NAME:Ljava/lang/String;":
192                (["blocked"], ["lo-prio", "max-target-r"]),
193            "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]),
194            "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]),
195            "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"])
196        }
197        self.assertEqual(expected_diffs, result.diffs, msg="flag differences")
198
199        expected_property_changes = [
200            ab.HiddenApiPropertyChange(
201                property_name="max_target_o_low_priority",
202                values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
203                property_comment=""),
204            ab.HiddenApiPropertyChange(
205                property_name="max_target_p",
206                values=["hiddenapi/hiddenapi-max-target-p.txt"],
207                property_comment=""),
208            ab.HiddenApiPropertyChange(
209                property_name="max_target_q",
210                values=["hiddenapi/hiddenapi-max-target-q.txt"],
211                property_comment=""),
212            ab.HiddenApiPropertyChange(
213                property_name="max_target_r_low_priority",
214                values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"],
215                property_comment=""),
216        ]
217        self.assertEqual(
218            expected_property_changes,
219            result.property_changes,
220            msg="property changes")
221
222        return result
223
224    def test_build_flags_report(self):
225        result = self.do_test_build_flags(fix=False)
226
227        expected_file_changes = [
228            ab.FileChange(
229                path="bcpf-dir/hiddenapi/"
230                "hiddenapi-max-target-o-low-priority.txt",
231                description="""Add the following entries:
232            Lacme/test/Class;-><init>()V
233""",
234            ),
235            ab.FileChange(
236                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
237                description="""Add the following entries:
238            Lacme/test/Other;->getThing()Z
239""",
240            ),
241            ab.FileChange(
242                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
243                description="""Add the following entries:
244            Lacme/test/Widget;-><init()V
245"""),
246            ab.FileChange(
247                path="bcpf-dir/hiddenapi/"
248                "hiddenapi-max-target-r-low-priority.txt",
249                description="""Add the following entries:
250            Lacme/test/Gadget;->NAME:Ljava/lang/String;
251"""),
252            ab.FileChange(
253                path="frameworks/base/boot/hiddenapi/"
254                "hiddenapi-max-target-o.txt",
255                description="""Remove the following entries:
256            Lacme/test/Class;-><init>()V
257"""),
258            ab.FileChange(
259                path="frameworks/base/boot/hiddenapi/"
260                "hiddenapi-max-target-p.txt",
261                description="""Remove the following entries:
262            Lacme/test/Other;->getThing()Z
263"""),
264            ab.FileChange(
265                path="frameworks/base/boot/hiddenapi/"
266                "hiddenapi-max-target-q.txt",
267                description="""Remove the following entries:
268            Lacme/test/Widget;-><init()V
269"""),
270            ab.FileChange(
271                path="frameworks/base/boot/hiddenapi/"
272                "hiddenapi-max-target-r-loprio.txt",
273                description="""Remove the following entries:
274            Lacme/test/Gadget;->NAME:Ljava/lang/String;
275""")
276        ]
277        result.file_changes.sort()
278        self.assertEqual(
279            expected_file_changes, result.file_changes, msg="file_changes")
280
281    def test_build_flags_fix(self):
282        result = self.do_test_build_flags(fix=True)
283
284        expected_file_changes = [
285            ab.FileChange(
286                path="bcpf-dir/hiddenapi/"
287                "hiddenapi-max-target-o-low-priority.txt",
288                description="Created with 'bcpf' specific entries"),
289            ab.FileChange(
290                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
291                description="Added 'bcpf' specific entries"),
292            ab.FileChange(
293                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
294                description="Created with 'bcpf' specific entries"),
295            ab.FileChange(
296                path="bcpf-dir/hiddenapi/"
297                "hiddenapi-max-target-r-low-priority.txt",
298                description="Created with 'bcpf' specific entries"),
299            ab.FileChange(
300                path=_MAX_TARGET_O,
301                description="Removed 'bcpf' specific entries"),
302            ab.FileChange(
303                path=_MAX_TARGET_P,
304                description="Removed 'bcpf' specific entries"),
305            ab.FileChange(
306                path=_MAX_TARGET_Q,
307                description="Removed 'bcpf' specific entries"),
308            ab.FileChange(
309                path=_MAX_TARGET_R,
310                description="Removed 'bcpf' specific entries")
311        ]
312
313        result.file_changes.sort()
314        self.assertEqual(
315            expected_file_changes, result.file_changes, msg="file_changes")
316
317        expected_file_contents = {
318            "bcpf-dir/hiddenapi/hiddenapi-max-target-o-low-priority.txt":
319                """
320Lacme/test/Class;-><init>()V
321""",
322            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
323                """
324Lacme/old/Class;->getWidget()Lacme/test/Widget;
325Lacme/test/Other;->getThing()Z
326""",
327            "bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt":
328                """
329Lacme/test/Widget;-><init()V
330""",
331            "bcpf-dir/hiddenapi/hiddenapi-max-target-r-low-priority.txt":
332                """
333Lacme/test/Gadget;->NAME:Ljava/lang/String;
334""",
335            _MAX_TARGET_O:
336                """
337Lacme/items/Magnet;->size:I
338""",
339            _MAX_TARGET_P:
340                """
341Lacme/items/Rocket;->size:I
342""",
343            _MAX_TARGET_Q:
344                """
345Lacme/items/Rock;->size:I
346""",
347            _MAX_TARGET_R:
348                """
349Lacme/items/Lever;->size:I
350""",
351        }
352        for file_change in result.file_changes:
353            path = file_change.path
354            expected_contents = expected_file_contents[path].lstrip()
355            abs_path = os.path.join(self.test_dir, path)
356            with open(abs_path, "r", encoding="utf8") as tio:
357                contents = tio.read()
358                self.assertEqual(
359                    expected_contents, contents, msg=f"{path} contents")
360
361    def test_compute_hiddenapi_package_properties(self):
362        fs = {
363            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
364                """
365La/b/C;->m()V
366La/b/c/D;->m()V
367La/b/c/E;->m()V
368Lb/c/D;->m()V
369Lb/c/E;->m()V
370Lb/c/d/E;->m()V
371""",
372            "out/soong/hiddenapi/hiddenapi-flags.csv":
373                """
374La/b/C;->m()V
375La/b/D;->m()V
376La/b/E;->m()V
377La/b/c/D;->m()V
378La/b/c/E;->m()V
379La/b/c/d/E;->m()V
380La/b/c/d/e/F;->m()V
381Lb/c/D;->m()V
382Lb/c/E;->m()V
383Lb/c/d/E;->m()V
384"""
385        }
386        analyzer = self.create_analyzer_for_test(fs)
387        analyzer.load_all_flags()
388
389        result = ab.Result()
390        analyzer.compute_hiddenapi_package_properties(result)
391        self.assertEqual(["a.b"], list(result.split_packages.keys()))
392
393        reason = result.split_packages["a.b"]
394        self.assertEqual(["a.b.C"], reason.bcpf)
395        self.assertEqual(["a.b.D", "a.b.E"], reason.other)
396
397        self.assertEqual(["a.b.c"], list(result.single_packages.keys()))
398
399        reason = result.single_packages["a.b.c"]
400        self.assertEqual(["a.b.c"], reason.bcpf)
401        self.assertEqual(["a.b.c.d", "a.b.c.d.e"], reason.other)
402
403        self.assertEqual(["b"], result.package_prefixes)
404
405
406class TestHiddenApiPropertyChange(unittest.TestCase):
407
408    def setUp(self):
409        # Create a temporary directory
410        self.test_dir = tempfile.mkdtemp()
411
412    def tearDown(self):
413        # Remove the directory after the test
414        shutil.rmtree(self.test_dir)
415
416    def check_change_fix(self, change, bpmodify_output, expected):
417        file = os.path.join(self.test_dir, "Android.bp")
418
419        with open(file, "w", encoding="utf8") as tio:
420            tio.write(bpmodify_output.strip("\n"))
421
422        bpmodify_runner = ab.BpModifyRunner(
423            os.path.join(os.path.dirname(sys.argv[0]), "bpmodify"))
424        change.fix_bp_file(file, "bcpf", bpmodify_runner)
425
426        with open(file, "r", encoding="utf8") as tio:
427            contents = tio.read()
428            self.assertEqual(expected.lstrip("\n"), contents)
429
430    def check_change_snippet(self, change, expected):
431        snippet = change.snippet("        ")
432        self.assertEqual(expected, snippet)
433
434    def test_change_property_with_value_no_comment(self):
435        change = ab.HiddenApiPropertyChange(
436            property_name="split_packages",
437            values=["android.provider"],
438        )
439
440        self.check_change_snippet(
441            change, """
442        split_packages: [
443            "android.provider",
444        ],
445""")
446
447        self.check_change_fix(
448            change, """
449bootclasspath_fragment {
450    name: "bcpf",
451
452    // modified by the Soong or platform compat team.
453    hidden_api: {
454        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
455        split_packages: [
456            "android.provider",
457        ],
458    },
459}
460""", """
461bootclasspath_fragment {
462    name: "bcpf",
463
464    // modified by the Soong or platform compat team.
465    hidden_api: {
466        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
467        split_packages: [
468            "android.provider",
469        ],
470    },
471}
472""")
473
474    def test_change_property_with_value_and_comment(self):
475        change = ab.HiddenApiPropertyChange(
476            property_name="split_packages",
477            values=["android.provider"],
478            property_comment=_MULTI_LINE_COMMENT,
479        )
480
481        self.check_change_snippet(
482            change, """
483        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
484        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
485        // ultricies sem tincidunt luctus.
486        split_packages: [
487            "android.provider",
488        ],
489""")
490
491        self.check_change_fix(
492            change, """
493bootclasspath_fragment {
494    name: "bcpf",
495
496    // modified by the Soong or platform compat team.
497    hidden_api: {
498        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
499        split_packages: [
500            "android.provider",
501        ],
502
503        single_packages: [
504            "android.system",
505        ],
506
507    },
508}
509""", """
510bootclasspath_fragment {
511    name: "bcpf",
512
513    // modified by the Soong or platform compat team.
514    hidden_api: {
515        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
516
517        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
518        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
519        // ultricies sem tincidunt luctus.
520        split_packages: [
521            "android.provider",
522        ],
523
524        single_packages: [
525            "android.system",
526        ],
527
528    },
529}
530""")
531
532    def test_set_property_with_value_and_comment(self):
533        change = ab.HiddenApiPropertyChange(
534            property_name="split_packages",
535            values=["another.provider", "other.system"],
536            property_comment=_MULTI_LINE_COMMENT,
537            action=ab.PropertyChangeAction.REPLACE,
538        )
539
540        self.check_change_snippet(
541            change, """
542        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
543        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
544        // ultricies sem tincidunt luctus.
545        split_packages: [
546            "another.provider",
547            "other.system",
548        ],
549""")
550
551        self.check_change_fix(
552            change, """
553bootclasspath_fragment {
554    name: "bcpf",
555
556    // modified by the Soong or platform compat team.
557    hidden_api: {
558        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
559        split_packages: [
560            "another.provider",
561            "other.system",
562        ],
563    },
564}
565""", """
566bootclasspath_fragment {
567    name: "bcpf",
568
569    // modified by the Soong or platform compat team.
570    hidden_api: {
571        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
572
573        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
574        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
575        // ultricies sem tincidunt luctus.
576        split_packages: [
577            "another.provider",
578            "other.system",
579        ],
580    },
581}
582""")
583
584    def test_set_property_with_no_value_or_comment(self):
585        change = ab.HiddenApiPropertyChange(
586            property_name="split_packages",
587            values=[],
588            action=ab.PropertyChangeAction.REPLACE,
589        )
590
591        self.check_change_snippet(change, """
592        split_packages: [],
593""")
594
595        self.check_change_fix(
596            change, """
597bootclasspath_fragment {
598    name: "bcpf",
599
600    // modified by the Soong or platform compat team.
601    hidden_api: {
602        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
603        split_packages: [
604            "another.provider",
605            "other.system",
606        ],
607        package_prefixes: ["android.provider"],
608    },
609}
610""", """
611bootclasspath_fragment {
612    name: "bcpf",
613
614    // modified by the Soong or platform compat team.
615    hidden_api: {
616        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
617        split_packages: [],
618        package_prefixes: ["android.provider"],
619    },
620}
621""")
622
623    def test_set_empty_property_with_no_value_or_comment(self):
624        change = ab.HiddenApiPropertyChange(
625            property_name="split_packages",
626            values=[],
627            action=ab.PropertyChangeAction.REPLACE,
628        )
629
630        self.check_change_snippet(change, """
631        split_packages: [],
632""")
633
634        self.check_change_fix(
635            change, """
636bootclasspath_fragment {
637    name: "bcpf",
638
639    // modified by the Soong or platform compat team.
640    hidden_api: {
641        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
642        split_packages: [],
643        package_prefixes: ["android.provider"],
644    },
645}
646""", """
647bootclasspath_fragment {
648    name: "bcpf",
649
650    // modified by the Soong or platform compat team.
651    hidden_api: {
652        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
653        split_packages: [],
654        package_prefixes: ["android.provider"],
655    },
656}
657""")
658
659
660if __name__ == "__main__":
661    unittest.main(verbosity=3)
662