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