blob: a32ffd0237fc89e894c1fd7fe1f2814fe0b50849 [file] [log] [blame]
Paul Duffin4dcf6592022-02-28 19:22:12 +00001#!/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
Paul Duffin26f19912022-03-28 16:09:27 +010023import sys
24
Paul Duffin4dcf6592022-02-28 19:22:12 +000025import 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",
Paul Duffin26f19912022-03-28 16:09:27 +010078 sdk="sdk",
79 fix=False):
Paul Duffin4dcf6592022-02-28 19:22:12 +000080 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(
Paul Duffin26f19912022-03-28 16:09:27 +010092 tool_path=os.path.join(out_dir, "bin"),
Paul Duffin4dcf6592022-02-28 19:22:12 +000093 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,
Paul Duffin26f19912022-03-28 16:09:27 +010099 fix=fix,
Paul Duffin4dcf6592022-02-28 19:22:12 +0000100 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
Paul Duffin26f19912022-03-28 16:09:27 +0100122 def do_test_build_flags(self, fix):
Paul Duffin4dcf6592022-02-28 19:22:12 +0000123 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
Paul Duffin26f19912022-03-28 16:09:27 +0100177 analyzer = self.create_analyzer_for_test(fs, fix=fix)
Paul Duffin4dcf6592022-02-28 19:22:12 +0000178
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
Paul Duffin26f19912022-03-28 16:09:27 +0100222 return result
223
224 def test_build_flags_report(self):
225 result = self.do_test_build_flags(fix=False)
226
Paul Duffin4dcf6592022-02-28 19:22:12 +0000227 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
Paul Duffin26f19912022-03-28 16:09:27 +0100281 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
Paul Duffindd97fd22022-02-28 19:22:12 +0000361 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
Paul Duffinb99d4802022-04-04 11:26:45 +0100380La/b/c/d/e/F;->m()V
Paul Duffindd97fd22022-02-28 19:22:12 +0000381Lb/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
Paul Duffinb99d4802022-04-04 11:26:45 +0100389 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)
Paul Duffindd97fd22022-02-28 19:22:12 +0000404
Paul Duffin4dcf6592022-02-28 19:22:12 +0000405
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
Paul Duffin26f19912022-03-28 16:09:27 +0100416 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
Paul Duffin4dcf6592022-02-28 19:22:12 +0000430 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
Paul Duffin26f19912022-03-28 16:09:27 +0100447 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
Paul Duffin4dcf6592022-02-28 19:22:12 +0000474 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
Paul Duffin26f19912022-03-28 16:09:27 +0100491 self.check_change_fix(
Paul Duffin4dcf6592022-02-28 19:22:12 +0000492 change, """
Paul Duffin26f19912022-03-28 16:09:27 +0100493bootclasspath_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}
Paul Duffin4dcf6592022-02-28 19:22:12 +0000530""")
531
Paul Duffindd97fd22022-02-28 19:22:12 +0000532 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
Paul Duffin4dcf6592022-02-28 19:22:12 +0000659
660if __name__ == "__main__":
661 unittest.main(verbosity=3)