blob: 8e34671b072584a9b222d3dc89891182594c4ea0 [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
23import analyze_bcpf as ab
24
25_FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi"
26_MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt"
27_MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt"
28_MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt"
29_MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt"
30
31_MULTI_LINE_COMMENT = """
32Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo,
33bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem
34tincidunt luctus.""".replace("\n", " ").strip()
35
36
37class FakeBuildOperation(ab.BuildOperation):
38
39 def __init__(self, lines, return_code):
40 ab.BuildOperation.__init__(self, None)
41 self._lines = lines
42 self.returncode = return_code
43
44 def lines(self):
45 return iter(self._lines)
46
47 def wait(self, *args, **kwargs):
48 return
49
50
51class TestAnalyzeBcpf(unittest.TestCase):
52
53 def setUp(self):
54 # Create a temporary directory
55 self.test_dir = tempfile.mkdtemp()
56
57 def tearDown(self):
58 # Remove the directory after the test
59 shutil.rmtree(self.test_dir)
60
61 @staticmethod
62 def write_abs_file(abs_path, contents):
63 os.makedirs(os.path.dirname(abs_path), exist_ok=True)
64 with open(abs_path, "w", encoding="utf8") as f:
65 print(contents.removeprefix("\n"), file=f, end="")
66
67 def populate_fs(self, fs):
68 for path, contents in fs.items():
69 abs_path = os.path.join(self.test_dir, path)
70 self.write_abs_file(abs_path, contents)
71
72 def create_analyzer_for_test(self,
73 fs=None,
74 bcpf="bcpf",
75 apex="apex",
76 sdk="sdk"):
77 if fs:
78 self.populate_fs(fs)
79
80 top_dir = self.test_dir
81 out_dir = os.path.join(self.test_dir, "out")
82 product_out_dir = "out/product"
83
84 bcpf_dir = f"{bcpf}-dir"
85 modules = {bcpf: {"path": [bcpf_dir]}}
86 module_info = ab.ModuleInfo(modules)
87
88 analyzer = ab.BcpfAnalyzer(
89 top_dir=top_dir,
90 out_dir=out_dir,
91 product_out_dir=product_out_dir,
92 bcpf=bcpf,
93 apex=apex,
94 sdk=sdk,
95 module_info=module_info,
96 )
97 analyzer.load_all_flags()
98 return analyzer
99
100 def test_reformat_report_text(self):
101 lines = """
10299. An item in a numbered list
103that traverses multiple lines.
104
105 An indented example
106 that should not be reformatted.
107"""
108 reformatted = ab.BcpfAnalyzer.reformat_report_test(lines)
109 self.assertEqual(
110 """
11199. An item in a numbered list that traverses multiple lines.
112
113 An indented example
114 that should not be reformatted.
115""", reformatted)
116
117 def test_build_flags(self):
118 lines = """
119ERROR: Hidden API flags are inconsistent:
120< out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv
121> out/soong/hiddenapi/hiddenapi-flags.csv
122
123< Lacme/test/Class;-><init>()V,blocked
124> Lacme/test/Class;-><init>()V,max-target-o
125
126< Lacme/test/Other;->getThing()Z,blocked
127> Lacme/test/Other;->getThing()Z,max-target-p
128
129< Lacme/test/Widget;-><init()V,blocked
130> Lacme/test/Widget;-><init()V,max-target-q
131
132< Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
133> Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r
13416:37:32 ninja failed with: exit status 1
135""".strip().splitlines()
136 operation = FakeBuildOperation(lines=lines, return_code=1)
137
138 fs = {
139 _MAX_TARGET_O:
140 """
141Lacme/items/Magnet;->size:I
142Lacme/test/Class;-><init>()V
143""",
144 _MAX_TARGET_P:
145 """
146Lacme/items/Rocket;->size:I
147Lacme/test/Other;->getThing()Z
148""",
149 _MAX_TARGET_Q:
150 """
151Lacme/items/Rock;->size:I
152Lacme/test/Widget;-><init()V
153""",
154 _MAX_TARGET_R:
155 """
156Lacme/items/Lever;->size:I
157Lacme/test/Gadget;->NAME:Ljava/lang/String;
158""",
159 "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
160 """
161Lacme/old/Class;->getWidget()Lacme/test/Widget;
162""",
163 "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
164 """
165Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
166Lacme/test/Widget;-><init()V,blocked
167Lacme/test/Class;-><init>()V,blocked
168Lacme/test/Other;->getThing()Z,blocked
169""",
170 }
171
172 analyzer = self.create_analyzer_for_test(fs)
173
174 # Override the build_file_read_output() method to just return a fake
175 # build operation.
176 analyzer.build_file_read_output = unittest.mock.Mock(
177 return_value=operation)
178
179 # Override the run_command() method to do nothing.
180 analyzer.run_command = unittest.mock.Mock()
181
182 result = ab.Result()
183
184 analyzer.build_monolithic_flags(result)
185 expected_diffs = {
186 "Lacme/test/Gadget;->NAME:Ljava/lang/String;":
187 (["blocked"], ["lo-prio", "max-target-r"]),
188 "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]),
189 "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]),
190 "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"])
191 }
192 self.assertEqual(expected_diffs, result.diffs, msg="flag differences")
193
194 expected_property_changes = [
195 ab.HiddenApiPropertyChange(
196 property_name="max_target_o_low_priority",
197 values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
198 property_comment=""),
199 ab.HiddenApiPropertyChange(
200 property_name="max_target_p",
201 values=["hiddenapi/hiddenapi-max-target-p.txt"],
202 property_comment=""),
203 ab.HiddenApiPropertyChange(
204 property_name="max_target_q",
205 values=["hiddenapi/hiddenapi-max-target-q.txt"],
206 property_comment=""),
207 ab.HiddenApiPropertyChange(
208 property_name="max_target_r_low_priority",
209 values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"],
210 property_comment=""),
211 ]
212 self.assertEqual(
213 expected_property_changes,
214 result.property_changes,
215 msg="property changes")
216
217 expected_file_changes = [
218 ab.FileChange(
219 path="bcpf-dir/hiddenapi/"
220 "hiddenapi-max-target-o-low-priority.txt",
221 description="""Add the following entries:
222 Lacme/test/Class;-><init>()V
223""",
224 ),
225 ab.FileChange(
226 path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
227 description="""Add the following entries:
228 Lacme/test/Other;->getThing()Z
229""",
230 ),
231 ab.FileChange(
232 path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
233 description="""Add the following entries:
234 Lacme/test/Widget;-><init()V
235"""),
236 ab.FileChange(
237 path="bcpf-dir/hiddenapi/"
238 "hiddenapi-max-target-r-low-priority.txt",
239 description="""Add the following entries:
240 Lacme/test/Gadget;->NAME:Ljava/lang/String;
241"""),
242 ab.FileChange(
243 path="frameworks/base/boot/hiddenapi/"
244 "hiddenapi-max-target-o.txt",
245 description="""Remove the following entries:
246 Lacme/test/Class;-><init>()V
247"""),
248 ab.FileChange(
249 path="frameworks/base/boot/hiddenapi/"
250 "hiddenapi-max-target-p.txt",
251 description="""Remove the following entries:
252 Lacme/test/Other;->getThing()Z
253"""),
254 ab.FileChange(
255 path="frameworks/base/boot/hiddenapi/"
256 "hiddenapi-max-target-q.txt",
257 description="""Remove the following entries:
258 Lacme/test/Widget;-><init()V
259"""),
260 ab.FileChange(
261 path="frameworks/base/boot/hiddenapi/"
262 "hiddenapi-max-target-r-loprio.txt",
263 description="""Remove the following entries:
264 Lacme/test/Gadget;->NAME:Ljava/lang/String;
265""")
266 ]
267 result.file_changes.sort()
268 self.assertEqual(
269 expected_file_changes, result.file_changes, msg="file_changes")
270
271
272class TestHiddenApiPropertyChange(unittest.TestCase):
273
274 def setUp(self):
275 # Create a temporary directory
276 self.test_dir = tempfile.mkdtemp()
277
278 def tearDown(self):
279 # Remove the directory after the test
280 shutil.rmtree(self.test_dir)
281
282 def check_change_snippet(self, change, expected):
283 snippet = change.snippet(" ")
284 self.assertEqual(expected, snippet)
285
286 def test_change_property_with_value_no_comment(self):
287 change = ab.HiddenApiPropertyChange(
288 property_name="split_packages",
289 values=["android.provider"],
290 )
291
292 self.check_change_snippet(
293 change, """
294 split_packages: [
295 "android.provider",
296 ],
297""")
298
299 def test_change_property_with_value_and_comment(self):
300 change = ab.HiddenApiPropertyChange(
301 property_name="split_packages",
302 values=["android.provider"],
303 property_comment=_MULTI_LINE_COMMENT,
304 )
305
306 self.check_change_snippet(
307 change, """
308 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
309 // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
310 // ultricies sem tincidunt luctus.
311 split_packages: [
312 "android.provider",
313 ],
314""")
315
316 def test_change_without_value_or_empty_comment(self):
317 change = ab.HiddenApiPropertyChange(
318 property_name="split_packages",
319 values=[],
320 property_comment="Single line comment.",
321 )
322
323 self.check_change_snippet(
324 change, """
325 // Single line comment.
326 split_packages: [],
327""")
328
329
330if __name__ == "__main__":
331 unittest.main(verbosity=3)