• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17import io
18import re
19import unittest
20import xml.etree.ElementTree as ET
21import zipfile
22
23import api_versions_trimmer
24
25
26def create_in_memory_zip_file(files):
27  f = io.BytesIO()
28  with zipfile.ZipFile(f, "w") as z:
29    for fname in files:
30      with z.open(fname, mode="w") as class_file:
31        class_file.write(b"")
32  return f
33
34
35def indent(elem, level=0):
36  i = "\n" + level * "  "
37  j = "\n" + (level - 1) * "  "
38  if len(elem):
39    if not elem.text or not elem.text.strip():
40      elem.text = i + "  "
41      if not elem.tail or not elem.tail.strip():
42        elem.tail = i
43        for subelem in elem:
44          indent(subelem, level + 1)
45        if not elem.tail or not elem.tail.strip():
46          elem.tail = j
47    else:
48      if level and (not elem.tail or not elem.tail.strip()):
49        elem.tail = j
50    return elem
51
52
53def pretty_print(s):
54  tree = ET.parse(io.StringIO(s))
55  el = indent(tree.getroot())
56  res = ET.tostring(el).decode("utf-8")
57  # remove empty lines inside the result because this still breaks some
58  # comparisons
59  return re.sub(r"\n\s*\n", "\n", res, re.MULTILINE)
60
61
62class ApiVersionsTrimmerUnittests(unittest.TestCase):
63
64  def setUp(self):
65    # so it prints diffs in long strings (xml files)
66    self.maxDiff = None
67
68  def test_read_classes(self):
69    f = create_in_memory_zip_file(
70        ["a/b/C.class",
71         "a/b/D.class",
72        ]
73    )
74    res = api_versions_trimmer.read_classes(f)
75    self.assertEqual({"a/b/C", "a/b/D"}, res)
76
77  def test_read_classes_ignore_dex(self):
78    f = create_in_memory_zip_file(
79        ["a/b/C.class",
80         "a/b/D.class",
81         "a/b/E.dex",
82         "f.dex",
83        ]
84    )
85    res = api_versions_trimmer.read_classes(f)
86    self.assertEqual({"a/b/C", "a/b/D"}, res)
87
88  def test_read_classes_ignore_manifest(self):
89    f = create_in_memory_zip_file(
90        ["a/b/C.class",
91         "a/b/D.class",
92         "META-INFO/G.class"
93        ]
94    )
95    res = api_versions_trimmer.read_classes(f)
96    self.assertEqual({"a/b/C", "a/b/D"}, res)
97
98  def test_filter_method_signature(self):
99    xml = """
100    <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
101    """
102    method = ET.fromstring(xml)
103    classes_to_remove = {"android/accessibilityservice/GestureDescription"}
104    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
105    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
106    self.assertEqual(expected, method.get("name"))
107
108  def test_filter_method_signature_with_L_in_method(self):
109    xml = """
110    <method name="dispatchLeftGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
111    """
112    method = ET.fromstring(xml)
113    classes_to_remove = {"android/accessibilityservice/GestureDescription"}
114    expected = "dispatchLeftGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
115    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
116    self.assertEqual(expected, method.get("name"))
117
118  def test_filter_method_signature_with_L_in_class(self):
119    xml = """
120    <method name="dispatchGesture(Landroid/accessibilityservice/LeftGestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
121    """
122    method = ET.fromstring(xml)
123    classes_to_remove = {"android/accessibilityservice/LeftGestureDescription"}
124    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
125    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
126    self.assertEqual(expected, method.get("name"))
127
128  def test_filter_method_signature_with_inner_class(self):
129    xml = """
130    <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription$Inner;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
131    """
132    method = ET.fromstring(xml)
133    classes_to_remove = {"android/accessibilityservice/GestureDescription$Inner"}
134    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
135    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
136    self.assertEqual(expected, method.get("name"))
137
138  def _run_filter_db_test(self, database_str, expected):
139    """Performs the pattern of testing the filter_lint_database method.
140
141    Filters instances of the class "a/b/C" (hard-coded) from the database string
142    and compares the result with the expected result (performs formatting of
143    the xml of both inputs)
144
145    Args:
146      database_str: string, the contents of the lint database (api-versions.xml)
147      expected: string, the expected result after filtering the original
148    database
149    """
150    database = io.StringIO(database_str)
151    classes_to_remove = {"a/b/C"}
152    output = io.BytesIO()
153    api_versions_trimmer.filter_lint_database(
154        database,
155        classes_to_remove,
156        output
157    )
158    expected = pretty_print(expected)
159    res = pretty_print(output.getvalue().decode("utf-8"))
160    self.assertEqual(expected, res)
161
162  def test_filter_lint_database_updates_method_signature_params(self):
163    self._run_filter_db_test(
164        database_str="""
165    <api version="2">
166      <!-- will be removed -->
167      <class name="a/b/C" since="1">
168        <extends name="java/lang/Object"/>
169      </class>
170
171      <class name="a/b/E" since="1">
172        <!-- extends will be modified -->
173        <extends name="a/b/C"/>
174        <!-- first parameter will be modified -->
175        <method name="dispatchGesture(La/b/C;Landroid/os/Handler;)Z" since="24"/>
176        <!-- second should remain untouched -->
177        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
178sultCallback;Landroid/os/Handler;)Z" since="24"/>
179      </class>
180    </api>
181    """,
182        expected="""
183    <api version="2">
184      <class name="a/b/E" since="1">
185        <extends name="java/lang/Object"/>
186        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
187        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
188sultCallback;Landroid/os/Handler;)Z" since="24"/>
189      </class>
190    </api>
191    """)
192
193  def test_filter_lint_database_updates_method_signature_return(self):
194    self._run_filter_db_test(
195        database_str="""
196    <api version="2">
197      <!-- will be removed -->
198      <class name="a/b/C" since="1">
199        <extends name="java/lang/Object"/>
200      </class>
201
202      <class name="a/b/E" since="1">
203        <!-- extends will be modified -->
204        <extends name="a/b/C"/>
205        <!-- return type should be changed -->
206        <method name="gestureIdToString(I)La/b/C;" since="24"/>
207      </class>
208    </api>
209    """,
210        expected="""
211    <api version="2">
212      <class name="a/b/E" since="1">
213
214        <extends name="java/lang/Object"/>
215
216        <method name="gestureIdToString(I)Ljava/lang/Object;" since="24"/>
217      </class>
218    </api>
219    """)
220
221  def test_filter_lint_database_removes_implements(self):
222    self._run_filter_db_test(
223        database_str="""
224    <api version="2">
225      <!-- will be removed -->
226      <class name="a/b/C" since="1">
227        <extends name="java/lang/Object"/>
228      </class>
229
230      <class name="a/b/D" since="1">
231        <extends name="java/lang/Object"/>
232        <implements name="a/b/C"/>
233        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
234sultCallback;Landroid/os/Handler;)Z" since="24"/>
235      </class>
236    </api>
237    """,
238        expected="""
239    <api version="2">
240
241      <class name="a/b/D" since="1">
242        <extends name="java/lang/Object"/>
243        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
244sultCallback;Landroid/os/Handler;)Z" since="24"/>
245      </class>
246    </api>
247    """)
248
249  def test_filter_lint_database_updates_extends(self):
250    self._run_filter_db_test(
251        database_str="""
252    <api version="2">
253      <!-- will be removed -->
254      <class name="a/b/C" since="1">
255        <extends name="java/lang/Object"/>
256      </class>
257
258      <class name="a/b/E" since="1">
259        <!-- extends will be modified -->
260        <extends name="a/b/C"/>
261        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
262        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
263sultCallback;Landroid/os/Handler;)Z" since="24"/>
264      </class>
265    </api>
266    """,
267        expected="""
268    <api version="2">
269      <class name="a/b/E" since="1">
270        <extends name="java/lang/Object"/>
271        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
272        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
273sultCallback;Landroid/os/Handler;)Z" since="24"/>
274      </class>
275    </api>
276    """)
277
278  def test_filter_lint_database_removes_class(self):
279    self._run_filter_db_test(
280        database_str="""
281    <api version="2">
282      <!-- will be removed -->
283      <class name="a/b/C" since="1">
284        <extends name="java/lang/Object"/>
285      </class>
286
287      <class name="a/b/D" since="1">
288        <extends name="java/lang/Object"/>
289        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
290sultCallback;Landroid/os/Handler;)Z" since="24"/>
291      </class>
292    </api>
293    """,
294        expected="""
295    <api version="2">
296
297      <class name="a/b/D" since="1">
298        <extends name="java/lang/Object"/>
299        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
300sultCallback;Landroid/os/Handler;)Z" since="24"/>
301      </class>
302    </api>
303    """)
304
305
306if __name__ == "__main__":
307  unittest.main()
308