| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2021 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import io |
| import re |
| import unittest |
| import xml.etree.ElementTree as ET |
| import zipfile |
| |
| import api_versions_trimmer |
| |
| |
| def create_in_memory_zip_file(files): |
| f = io.BytesIO() |
| with zipfile.ZipFile(f, "w") as z: |
| for fname in files: |
| with z.open(fname, mode="w") as class_file: |
| class_file.write(b"") |
| return f |
| |
| |
| def indent(elem, level=0): |
| i = "\n" + level * " " |
| j = "\n" + (level - 1) * " " |
| if len(elem): |
| if not elem.text or not elem.text.strip(): |
| elem.text = i + " " |
| if not elem.tail or not elem.tail.strip(): |
| elem.tail = i |
| for subelem in elem: |
| indent(subelem, level + 1) |
| if not elem.tail or not elem.tail.strip(): |
| elem.tail = j |
| else: |
| if level and (not elem.tail or not elem.tail.strip()): |
| elem.tail = j |
| return elem |
| |
| |
| def pretty_print(s): |
| tree = ET.parse(io.StringIO(s)) |
| el = indent(tree.getroot()) |
| res = ET.tostring(el).decode("utf-8") |
| # remove empty lines inside the result because this still breaks some |
| # comparisons |
| return re.sub(r"\n\s*\n", "\n", res, re.MULTILINE) |
| |
| |
| class ApiVersionsTrimmerUnittests(unittest.TestCase): |
| |
| def setUp(self): |
| # so it prints diffs in long strings (xml files) |
| self.maxDiff = None |
| |
| def test_read_classes(self): |
| f = create_in_memory_zip_file( |
| ["a/b/C.class", |
| "a/b/D.class", |
| ] |
| ) |
| res = api_versions_trimmer.read_classes(f) |
| self.assertEqual({"a/b/C", "a/b/D"}, res) |
| |
| def test_read_classes_ignore_dex(self): |
| f = create_in_memory_zip_file( |
| ["a/b/C.class", |
| "a/b/D.class", |
| "a/b/E.dex", |
| "f.dex", |
| ] |
| ) |
| res = api_versions_trimmer.read_classes(f) |
| self.assertEqual({"a/b/C", "a/b/D"}, res) |
| |
| def test_read_classes_ignore_manifest(self): |
| f = create_in_memory_zip_file( |
| ["a/b/C.class", |
| "a/b/D.class", |
| "META-INFO/G.class" |
| ] |
| ) |
| res = api_versions_trimmer.read_classes(f) |
| self.assertEqual({"a/b/C", "a/b/D"}, res) |
| |
| def test_filter_method_signature(self): |
| xml = """ |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> |
| """ |
| method = ET.fromstring(xml) |
| classes_to_remove = {"android/accessibilityservice/GestureDescription"} |
| expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" |
| api_versions_trimmer.filter_method_tag(method, classes_to_remove) |
| self.assertEqual(expected, method.get("name")) |
| |
| def test_filter_method_signature_with_L_in_method(self): |
| xml = """ |
| <method name="dispatchLeftGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> |
| """ |
| method = ET.fromstring(xml) |
| classes_to_remove = {"android/accessibilityservice/GestureDescription"} |
| expected = "dispatchLeftGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" |
| api_versions_trimmer.filter_method_tag(method, classes_to_remove) |
| self.assertEqual(expected, method.get("name")) |
| |
| def test_filter_method_signature_with_L_in_class(self): |
| xml = """ |
| <method name="dispatchGesture(Landroid/accessibilityservice/LeftGestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> |
| """ |
| method = ET.fromstring(xml) |
| classes_to_remove = {"android/accessibilityservice/LeftGestureDescription"} |
| expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" |
| api_versions_trimmer.filter_method_tag(method, classes_to_remove) |
| self.assertEqual(expected, method.get("name")) |
| |
| def test_filter_method_signature_with_inner_class(self): |
| xml = """ |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription$Inner;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> |
| """ |
| method = ET.fromstring(xml) |
| classes_to_remove = {"android/accessibilityservice/GestureDescription$Inner"} |
| expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" |
| api_versions_trimmer.filter_method_tag(method, classes_to_remove) |
| self.assertEqual(expected, method.get("name")) |
| |
| def _run_filter_db_test(self, database_str, expected): |
| """Performs the pattern of testing the filter_lint_database method. |
| |
| Filters instances of the class "a/b/C" (hard-coded) from the database string |
| and compares the result with the expected result (performs formatting of |
| the xml of both inputs) |
| |
| Args: |
| database_str: string, the contents of the lint database (api-versions.xml) |
| expected: string, the expected result after filtering the original |
| database |
| """ |
| database = io.StringIO(database_str) |
| classes_to_remove = {"a/b/C"} |
| output = io.BytesIO() |
| api_versions_trimmer.filter_lint_database( |
| database, |
| classes_to_remove, |
| output |
| ) |
| expected = pretty_print(expected) |
| res = pretty_print(output.getvalue().decode("utf-8")) |
| self.assertEqual(expected, res) |
| |
| def test_filter_lint_database_updates_method_signature_params(self): |
| self._run_filter_db_test( |
| database_str=""" |
| <api version="2"> |
| <!-- will be removed --> |
| <class name="a/b/C" since="1"> |
| <extends name="java/lang/Object"/> |
| </class> |
| |
| <class name="a/b/E" since="1"> |
| <!-- extends will be modified --> |
| <extends name="a/b/C"/> |
| <!-- first parameter will be modified --> |
| <method name="dispatchGesture(La/b/C;Landroid/os/Handler;)Z" since="24"/> |
| <!-- second should remain untouched --> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """, |
| expected=""" |
| <api version="2"> |
| <class name="a/b/E" since="1"> |
| <extends name="java/lang/Object"/> |
| <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """) |
| |
| def test_filter_lint_database_updates_method_signature_return(self): |
| self._run_filter_db_test( |
| database_str=""" |
| <api version="2"> |
| <!-- will be removed --> |
| <class name="a/b/C" since="1"> |
| <extends name="java/lang/Object"/> |
| </class> |
| |
| <class name="a/b/E" since="1"> |
| <!-- extends will be modified --> |
| <extends name="a/b/C"/> |
| <!-- return type should be changed --> |
| <method name="gestureIdToString(I)La/b/C;" since="24"/> |
| </class> |
| </api> |
| """, |
| expected=""" |
| <api version="2"> |
| <class name="a/b/E" since="1"> |
| |
| <extends name="java/lang/Object"/> |
| |
| <method name="gestureIdToString(I)Ljava/lang/Object;" since="24"/> |
| </class> |
| </api> |
| """) |
| |
| def test_filter_lint_database_removes_implements(self): |
| self._run_filter_db_test( |
| database_str=""" |
| <api version="2"> |
| <!-- will be removed --> |
| <class name="a/b/C" since="1"> |
| <extends name="java/lang/Object"/> |
| </class> |
| |
| <class name="a/b/D" since="1"> |
| <extends name="java/lang/Object"/> |
| <implements name="a/b/C"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """, |
| expected=""" |
| <api version="2"> |
| |
| <class name="a/b/D" since="1"> |
| <extends name="java/lang/Object"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """) |
| |
| def test_filter_lint_database_updates_extends(self): |
| self._run_filter_db_test( |
| database_str=""" |
| <api version="2"> |
| <!-- will be removed --> |
| <class name="a/b/C" since="1"> |
| <extends name="java/lang/Object"/> |
| </class> |
| |
| <class name="a/b/E" since="1"> |
| <!-- extends will be modified --> |
| <extends name="a/b/C"/> |
| <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """, |
| expected=""" |
| <api version="2"> |
| <class name="a/b/E" since="1"> |
| <extends name="java/lang/Object"/> |
| <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """) |
| |
| def test_filter_lint_database_removes_class(self): |
| self._run_filter_db_test( |
| database_str=""" |
| <api version="2"> |
| <!-- will be removed --> |
| <class name="a/b/C" since="1"> |
| <extends name="java/lang/Object"/> |
| </class> |
| |
| <class name="a/b/D" since="1"> |
| <extends name="java/lang/Object"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """, |
| expected=""" |
| <api version="2"> |
| |
| <class name="a/b/D" since="1"> |
| <extends name="java/lang/Object"/> |
| <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe |
| sultCallback;Landroid/os/Handler;)Z" since="24"/> |
| </class> |
| </api> |
| """) |
| |
| |
| if __name__ == "__main__": |
| unittest.main(verbosity=2) |