From d204ba5ac9c5488880d85dc198e7b6aefea2f0bb Mon Sep 17 00:00:00 2001 From: Alex Light Date: Tue, 1 Mar 2016 14:33:51 -0800 Subject: Move some default-methods tests to Java from Smali. Move all smali tests for default method behavior in non-source-incompatible contexts to java. Also move some of the simpler tests for source and binary incompatibilities into java as well when possible. Bug: 27310767 Change-Id: I753196f19849494825953c1bf06f15b7132f459b --- test/utils/python/generate_java_main.py | 309 +++++++++++++++++++++++++ test/utils/python/generate_smali_main.py | 376 ------------------------------- 2 files changed, 309 insertions(+), 376 deletions(-) create mode 100755 test/utils/python/generate_java_main.py delete mode 100755 test/utils/python/generate_smali_main.py (limited to 'test/utils/python') diff --git a/test/utils/python/generate_java_main.py b/test/utils/python/generate_java_main.py new file mode 100755 index 0000000000..f66d0dd372 --- /dev/null +++ b/test/utils/python/generate_java_main.py @@ -0,0 +1,309 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 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. + +""" +Generate Java Main file from a classes.xml file. +""" + +import os +import sys +from pathlib import Path + +BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") +if BUILD_TOP is None: + print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) + sys.exit(1) + +# Allow us to import utils and mixins. +sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) + +from testgen.utils import get_copyright +import testgen.mixins as mixins + +from collections import namedtuple +import itertools +import functools +import xml.etree.ElementTree as ET + +class MainClass(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): + """ + A mainclass and main method for this test. + """ + + MAIN_CLASS_TEMPLATE = """{copyright} +class Main {{ +{test_groups} +{test_funcs} +{main_func} +}} +""" + + MAIN_FUNCTION_TEMPLATE = """ + public static void main(String[] args) {{ + {test_group_invoke} + }} +""" + + TEST_GROUP_INVOKE_TEMPLATE = """ + {test_name}(); +""" + + def __init__(self): + """ + Initialize this MainClass + """ + self.tests = set() + self.global_funcs = set() + + def add_instance(self, it): + """ + Add an instance test for the given class + """ + self.tests.add(it) + + def add_func(self, f): + """ + Add a function to the class + """ + self.global_funcs.add(f) + + def get_name(self): + """ + Get the name of this class + """ + return "Main" + + def __str__(self): + """ + Print this class + """ + all_tests = sorted(self.tests) + test_invoke = "" + test_groups = "" + for t in all_tests: + test_groups += str(t) + for t in sorted(all_tests): + test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name()) + main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke) + + funcs = "" + for f in self.global_funcs: + funcs += str(f) + return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('java'), + test_groups=test_groups, + main_func=main_func, test_funcs=funcs) + + +class InstanceTest(mixins.Named, mixins.NameComparableMixin): + """ + A method that runs tests for a particular concrete type, It calls the test + cases for running it in all possible ways. + """ + + INSTANCE_TEST_TEMPLATE = """ + public static void {test_name}() {{ + System.out.println("Testing for type {ty}"); + String s = "{ty}"; + {ty} v = new {ty}(); + + {invokes} + + System.out.println("End testing for type {ty}"); + }} +""" + + TEST_INVOKE_TEMPLATE = """ + {fname}(s, v); +""" + + def __init__(self, main, ty): + """ + Initialize this test group for the given type + """ + self.ty = ty + self.main = main + self.funcs = set() + self.main.add_instance(self) + + def get_name(self): + """ + Get the name of this test group + """ + return "TEST_NAME_"+self.ty + + def add_func(self, f): + """ + Add a test function to this test group + """ + self.main.add_func(f) + self.funcs.add(f) + + def __str__(self): + """ + Returns the java code for this function + """ + func_invokes = "" + for f in sorted(self.funcs, key=lambda a: (a.func, a.farg)): + func_invokes += self.TEST_INVOKE_TEMPLATE.format(fname=f.get_name(), + farg=f.farg) + + return self.INSTANCE_TEST_TEMPLATE.format(test_name=self.get_name(), ty=self.ty, + invokes=func_invokes) + +class Func(mixins.Named, mixins.NameComparableMixin): + """ + A single test case that attempts to invoke a function on receiver of a given type. + """ + + TEST_FUNCTION_TEMPLATE = """ + public static void {fname}(String s, {farg} v) {{ + try {{ + System.out.printf("%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n", s, v.{callfunc}()); + return; + }} catch (Error e) {{ + System.out.printf("%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n", s); + e.printStackTrace(System.out); + }} + }} +""" + + def __init__(self, func, farg, invoke): + """ + Initialize this test function for the given invoke type and argument + """ + self.func = func + self.farg = farg + self.invoke = invoke + + def get_name(self): + """ + Get the name of this test + """ + return "Test_Func_{}_{}_{}".format(self.func, self.farg, self.invoke) + + def __str__(self): + """ + Get the java code for this test function + """ + return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), + farg=self.farg, + invoke_type=self.invoke, + callfunc=self.func) + +def flatten_classes(classes, c): + """ + Iterate over all the classes 'c' can be used as + """ + while c: + yield c + c = classes.get(c.super_class) + +def flatten_class_methods(classes, c): + """ + Iterate over all the methods 'c' can call + """ + for c1 in flatten_classes(classes, c): + yield from c1.methods + +def flatten_interfaces(dat, c): + """ + Iterate over all the interfaces 'c' transitively implements + """ + def get_ifaces(cl): + for i2 in cl.implements: + yield dat.interfaces[i2] + yield from get_ifaces(dat.interfaces[i2]) + + for cl in flatten_classes(dat.classes, c): + yield from get_ifaces(cl) + +def flatten_interface_methods(dat, i): + """ + Iterate over all the interface methods 'c' can call + """ + yield from i.methods + for i2 in flatten_interfaces(dat, i): + yield from i2.methods + +def make_main_class(dat): + """ + Creates a Main.java file that runs all the tests + """ + m = MainClass() + for c in dat.classes.values(): + i = InstanceTest(m, c.name) + for clazz in flatten_classes(dat.classes, c): + for meth in flatten_class_methods(dat.classes, clazz): + i.add_func(Func(meth, clazz.name, 'virtual')) + for iface in flatten_interfaces(dat, clazz): + for meth in flatten_interface_methods(dat, iface): + i.add_func(Func(meth, clazz.name, 'virtual')) + i.add_func(Func(meth, iface.name, 'interface')) + return m + +class TestData(namedtuple("TestData", ['classes', 'interfaces'])): + """ + A class representing the classes.xml document. + """ + pass + +class Clazz(namedtuple("Clazz", ["name", "methods", "super_class", "implements"])): + """ + A class representing a class element in the classes.xml document. + """ + pass + +class IFace(namedtuple("IFace", ["name", "methods", "super_class", "implements"])): + """ + A class representing an interface element in the classes.xml document. + """ + pass + +def parse_xml(xml): + """ + Parse the xml description of this test. + """ + classes = dict() + ifaces = dict() + root = ET.fromstring(xml) + for iface in root.find("interfaces"): + name = iface.attrib['name'] + implements = [a.text for a in iface.find("implements")] + methods = [a.text for a in iface.find("methods")] + ifaces[name] = IFace(name = name, + super_class = iface.attrib['super'], + methods = methods, + implements = implements) + for clazz in root.find('classes'): + name = clazz.attrib['name'] + implements = [a.text for a in clazz.find("implements")] + methods = [a.text for a in clazz.find("methods")] + classes[name] = Clazz(name = name, + super_class = clazz.attrib['super'], + methods = methods, + implements = implements) + return TestData(classes, ifaces) + +def main(argv): + java_dir = Path(argv[1]) + if not java_dir.exists() or not java_dir.is_dir(): + print("{} is not a valid java dir".format(java_dir), file=sys.stderr) + sys.exit(1) + class_data = parse_xml((java_dir / "classes.xml").open().read()) + make_main_class(class_data).dump(java_dir) + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/utils/python/generate_smali_main.py b/test/utils/python/generate_smali_main.py deleted file mode 100755 index d796d313c6..0000000000 --- a/test/utils/python/generate_smali_main.py +++ /dev/null @@ -1,376 +0,0 @@ -#!/usr/bin/python3 -# -# Copyright (C) 2015 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. - -""" -Generate Smali Main file from a classes.xml file. -""" - -import os -import sys -from pathlib import Path - -BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") -if BUILD_TOP is None: - print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) - sys.exit(1) - -# Allow us to import utils and mixins. -sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) - -from testgen.utils import get_copyright -import testgen.mixins as mixins - -from collections import namedtuple -import itertools -import functools -import xml.etree.ElementTree as ET - -class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin): - """ - A mainclass and main method for this test. - """ - - MAIN_CLASS_TEMPLATE = """{copyright} -.class public LMain; -.super Ljava/lang/Object; - -# class Main {{ - -.method public constructor ()V - .registers 1 - invoke-direct {{p0}}, Ljava/lang/Object;->()V - return-void -.end method - -{test_groups} - -{test_funcs} - -{main_func} - -# }} -""" - - MAIN_FUNCTION_TEMPLATE = """ -# public static void main(String[] args) {{ -.method public static main([Ljava/lang/String;)V - .locals 2 - sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; - - {test_group_invoke} - - return-void -.end method -# }} -""" - - TEST_GROUP_INVOKE_TEMPLATE = """ -# {test_name}(); - invoke-static {{}}, {test_name}()V -""" - - def __init__(self): - """ - Initialize this MainClass - """ - self.tests = set() - self.global_funcs = set() - - def add_instance(self, it): - """ - Add an instance test for the given class - """ - self.tests.add(it) - - def add_func(self, f): - """ - Add a function to the class - """ - self.global_funcs.add(f) - - def get_name(self): - """ - Get the name of this class - """ - return "Main" - - def __str__(self): - """ - Print this class - """ - all_tests = sorted(self.tests) - test_invoke = "" - test_groups = "" - for t in all_tests: - test_groups += str(t) - for t in sorted(all_tests): - test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name()) - main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke) - - funcs = "" - for f in self.global_funcs: - funcs += str(f) - return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('smali'), - test_groups=test_groups, - main_func=main_func, test_funcs=funcs) - - -class InstanceTest(mixins.Named, mixins.NameComparableMixin): - """ - A method that runs tests for a particular concrete type, It calls the test - cases for running it in all possible ways. - """ - - INSTANCE_TEST_TEMPLATE = """ -# public static void {test_name}() {{ -# System.out.println("Testing for type {ty}"); -# String s = "{ty}"; -# {ty} v = new {ty}(); -.method public static {test_name}()V - .locals 3 - sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; - const-string v0, "Testing for type {ty}" - invoke-virtual {{v2,v0}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V - - const-string v0, "{ty}" - new-instance v1, L{ty}; - invoke-direct {{v1}}, L{ty};->()V - - {invokes} - - const-string v0, "End testing for type {ty}" - invoke-virtual {{v2,v0}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V - return-void -.end method -# System.out.println("End testing for type {ty}"); -# }} -""" - - TEST_INVOKE_TEMPLATE = """ -# {fname}(s, v); - invoke-static {{v0, v1}}, {fname}(Ljava/lang/String;L{farg};)V -""" - - def __init__(self, main, ty): - """ - Initialize this test group for the given type - """ - self.ty = ty - self.main = main - self.funcs = set() - self.main.add_instance(self) - - def get_name(self): - """ - Get the name of this test group - """ - return "TEST_NAME_"+self.ty - - def add_func(self, f): - """ - Add a test function to this test group - """ - self.main.add_func(f) - self.funcs.add(f) - - def __str__(self): - """ - Returns the smali code for this function - """ - func_invokes = "" - for f in sorted(self.funcs, key=lambda a: (a.func, a.farg)): - func_invokes += self.TEST_INVOKE_TEMPLATE.format(fname=f.get_name(), - farg=f.farg) - - return self.INSTANCE_TEST_TEMPLATE.format(test_name=self.get_name(), ty=self.ty, - invokes=func_invokes) - -class Func(mixins.Named, mixins.NameComparableMixin): - """ - A single test case that attempts to invoke a function on receiver of a given type. - """ - - TEST_FUNCTION_TEMPLATE = """ -# public static void {fname}(String s, {farg} v) {{ -# try {{ -# System.out.printf("%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n", s, v.{callfunc}()); -# return; -# }} catch (Error e) {{ -# System.out.printf("%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n", s); -# e.printStackTrace(System.out); -# }} -# }} -.method public static {fname}(Ljava/lang/String;L{farg};)V - .locals 7 - :call_{fname}_try_start - const/4 v0, 2 - new-array v1,v0, [Ljava/lang/Object; - const/4 v0, 0 - aput-object p0,v1,v0 - - sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; - const-string v3, "%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n" - - invoke-{invoke_type} {{p1}}, L{farg};->{callfunc}()Ljava/lang/String; - move-result-object v4 - const/4 v0, 1 - aput-object v4, v1, v0 - - invoke-virtual {{v2,v3,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; - return-void - :call_{fname}_try_end - .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start - :error_{fname}_start - move-exception v3 - const/4 v0, 1 - new-array v1,v0, [Ljava/lang/Object; - const/4 v0, 0 - aput-object p0, v1, v0 - sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; - const-string v4, "%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n" - invoke-virtual {{v2,v4,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; - invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V - return-void -.end method -""" - - def __init__(self, func, farg, invoke): - """ - Initialize this test function for the given invoke type and argument - """ - self.func = func - self.farg = farg - self.invoke = invoke - - def get_name(self): - """ - Get the name of this test - """ - return "Test_Func_{}_{}_{}".format(self.func, self.farg, self.invoke) - - def __str__(self): - """ - Get the smali code for this test function - """ - return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), - farg=self.farg, - invoke_type=self.invoke, - callfunc=self.func) - -def flatten_classes(classes, c): - """ - Iterate over all the classes 'c' can be used as - """ - while c: - yield c - c = classes.get(c.super_class) - -def flatten_class_methods(classes, c): - """ - Iterate over all the methods 'c' can call - """ - for c1 in flatten_classes(classes, c): - yield from c1.methods - -def flatten_interfaces(dat, c): - """ - Iterate over all the interfaces 'c' transitively implements - """ - def get_ifaces(cl): - for i2 in cl.implements: - yield dat.interfaces[i2] - yield from get_ifaces(dat.interfaces[i2]) - - for cl in flatten_classes(dat.classes, c): - yield from get_ifaces(cl) - -def flatten_interface_methods(dat, i): - """ - Iterate over all the interface methods 'c' can call - """ - yield from i.methods - for i2 in flatten_interfaces(dat, i): - yield from i2.methods - -def make_main_class(dat): - """ - Creates a Main.smali file that runs all the tests - """ - m = MainClass() - for c in dat.classes.values(): - i = InstanceTest(m, c.name) - for clazz in flatten_classes(dat.classes, c): - for meth in flatten_class_methods(dat.classes, clazz): - i.add_func(Func(meth, clazz.name, 'virtual')) - for iface in flatten_interfaces(dat, clazz): - for meth in flatten_interface_methods(dat, iface): - i.add_func(Func(meth, clazz.name, 'virtual')) - i.add_func(Func(meth, iface.name, 'interface')) - return m - -class TestData(namedtuple("TestData", ['classes', 'interfaces'])): - """ - A class representing the classes.xml document. - """ - pass - -class Clazz(namedtuple("Clazz", ["name", "methods", "super_class", "implements"])): - """ - A class representing a class element in the classes.xml document. - """ - pass - -class IFace(namedtuple("IFace", ["name", "methods", "super_class", "implements"])): - """ - A class representing an interface element in the classes.xml document. - """ - pass - -def parse_xml(xml): - """ - Parse the xml description of this test. - """ - classes = dict() - ifaces = dict() - root = ET.fromstring(xml) - for iface in root.find("interfaces"): - name = iface.attrib['name'] - implements = [a.text for a in iface.find("implements")] - methods = [a.text for a in iface.find("methods")] - ifaces[name] = IFace(name = name, - super_class = iface.attrib['super'], - methods = methods, - implements = implements) - for clazz in root.find('classes'): - name = clazz.attrib['name'] - implements = [a.text for a in clazz.find("implements")] - methods = [a.text for a in clazz.find("methods")] - classes[name] = Clazz(name = name, - super_class = clazz.attrib['super'], - methods = methods, - implements = implements) - return TestData(classes, ifaces) - -def main(argv): - smali_dir = Path(argv[1]) - if not smali_dir.exists() or not smali_dir.is_dir(): - print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr) - sys.exit(1) - class_data = parse_xml((smali_dir / "classes.xml").open().read()) - make_main_class(class_data).dump(smali_dir) - -if __name__ == '__main__': - main(sys.argv) -- cgit v1.2.3-59-g8ed1b