Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | # |
| 3 | # Copyright (C) 2015 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 | |
| 17 | """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 18 | Generate Java Main file from a classes.xml file. |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 19 | """ |
| 20 | |
| 21 | import os |
| 22 | import sys |
| 23 | from pathlib import Path |
| 24 | |
| 25 | BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") |
| 26 | if BUILD_TOP is None: |
| 27 | print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) |
| 28 | sys.exit(1) |
| 29 | |
| 30 | # Allow us to import utils and mixins. |
| 31 | sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) |
| 32 | |
| 33 | from testgen.utils import get_copyright |
| 34 | import testgen.mixins as mixins |
| 35 | |
| 36 | from collections import namedtuple |
| 37 | import itertools |
| 38 | import functools |
| 39 | import xml.etree.ElementTree as ET |
| 40 | |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 41 | class MainClass(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 42 | """ |
| 43 | A mainclass and main method for this test. |
| 44 | """ |
| 45 | |
| 46 | MAIN_CLASS_TEMPLATE = """{copyright} |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 47 | class Main {{ |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 48 | {test_groups} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 49 | {test_funcs} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 50 | {main_func} |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 51 | }} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 52 | """ |
| 53 | |
| 54 | MAIN_FUNCTION_TEMPLATE = """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 55 | public static void main(String[] args) {{ |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 56 | {test_group_invoke} |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 57 | }} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 58 | """ |
| 59 | |
| 60 | TEST_GROUP_INVOKE_TEMPLATE = """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 61 | {test_name}(); |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 62 | """ |
| 63 | |
| 64 | def __init__(self): |
| 65 | """ |
| 66 | Initialize this MainClass |
| 67 | """ |
| 68 | self.tests = set() |
| 69 | self.global_funcs = set() |
| 70 | |
| 71 | def add_instance(self, it): |
| 72 | """ |
| 73 | Add an instance test for the given class |
| 74 | """ |
| 75 | self.tests.add(it) |
| 76 | |
| 77 | def add_func(self, f): |
| 78 | """ |
| 79 | Add a function to the class |
| 80 | """ |
| 81 | self.global_funcs.add(f) |
| 82 | |
| 83 | def get_name(self): |
| 84 | """ |
| 85 | Get the name of this class |
| 86 | """ |
| 87 | return "Main" |
| 88 | |
| 89 | def __str__(self): |
| 90 | """ |
| 91 | Print this class |
| 92 | """ |
| 93 | all_tests = sorted(self.tests) |
| 94 | test_invoke = "" |
| 95 | test_groups = "" |
| 96 | for t in all_tests: |
| 97 | test_groups += str(t) |
| 98 | for t in sorted(all_tests): |
| 99 | test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name()) |
| 100 | main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke) |
| 101 | |
| 102 | funcs = "" |
| 103 | for f in self.global_funcs: |
| 104 | funcs += str(f) |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 105 | return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('java'), |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 106 | test_groups=test_groups, |
| 107 | main_func=main_func, test_funcs=funcs) |
| 108 | |
| 109 | |
| 110 | class InstanceTest(mixins.Named, mixins.NameComparableMixin): |
| 111 | """ |
| 112 | A method that runs tests for a particular concrete type, It calls the test |
| 113 | cases for running it in all possible ways. |
| 114 | """ |
| 115 | |
| 116 | INSTANCE_TEST_TEMPLATE = """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 117 | public static void {test_name}() {{ |
| 118 | System.out.println("Testing for type {ty}"); |
| 119 | String s = "{ty}"; |
| 120 | {ty} v = new {ty}(); |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 121 | |
| 122 | {invokes} |
| 123 | |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 124 | System.out.println("End testing for type {ty}"); |
| 125 | }} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 126 | """ |
| 127 | |
| 128 | TEST_INVOKE_TEMPLATE = """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 129 | {fname}(s, v); |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 130 | """ |
| 131 | |
| 132 | def __init__(self, main, ty): |
| 133 | """ |
| 134 | Initialize this test group for the given type |
| 135 | """ |
| 136 | self.ty = ty |
| 137 | self.main = main |
| 138 | self.funcs = set() |
| 139 | self.main.add_instance(self) |
| 140 | |
| 141 | def get_name(self): |
| 142 | """ |
| 143 | Get the name of this test group |
| 144 | """ |
| 145 | return "TEST_NAME_"+self.ty |
| 146 | |
| 147 | def add_func(self, f): |
| 148 | """ |
| 149 | Add a test function to this test group |
| 150 | """ |
| 151 | self.main.add_func(f) |
| 152 | self.funcs.add(f) |
| 153 | |
| 154 | def __str__(self): |
| 155 | """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 156 | Returns the java code for this function |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 157 | """ |
| 158 | func_invokes = "" |
| 159 | for f in sorted(self.funcs, key=lambda a: (a.func, a.farg)): |
| 160 | func_invokes += self.TEST_INVOKE_TEMPLATE.format(fname=f.get_name(), |
| 161 | farg=f.farg) |
| 162 | |
| 163 | return self.INSTANCE_TEST_TEMPLATE.format(test_name=self.get_name(), ty=self.ty, |
| 164 | invokes=func_invokes) |
| 165 | |
| 166 | class Func(mixins.Named, mixins.NameComparableMixin): |
| 167 | """ |
| 168 | A single test case that attempts to invoke a function on receiver of a given type. |
| 169 | """ |
| 170 | |
| 171 | TEST_FUNCTION_TEMPLATE = """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 172 | public static void {fname}(String s, {farg} v) {{ |
| 173 | try {{ |
| 174 | System.out.printf("%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n", s, v.{callfunc}()); |
| 175 | return; |
| 176 | }} catch (Error e) {{ |
| 177 | System.out.printf("%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n", s); |
Alex Light | 1f3925d | 2016-09-07 12:04:20 -0700 | [diff] [blame] | 178 | if (e instanceof IncompatibleClassChangeError) {{ |
| 179 | System.out.printf("Exception is of type %s\\n", e.getClass().getName()); |
| 180 | }} else {{ |
| 181 | e.printStackTrace(System.out); |
| 182 | }} |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 183 | }} |
| 184 | }} |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 185 | """ |
| 186 | |
| 187 | def __init__(self, func, farg, invoke): |
| 188 | """ |
| 189 | Initialize this test function for the given invoke type and argument |
| 190 | """ |
| 191 | self.func = func |
| 192 | self.farg = farg |
| 193 | self.invoke = invoke |
| 194 | |
| 195 | def get_name(self): |
| 196 | """ |
| 197 | Get the name of this test |
| 198 | """ |
| 199 | return "Test_Func_{}_{}_{}".format(self.func, self.farg, self.invoke) |
| 200 | |
| 201 | def __str__(self): |
| 202 | """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 203 | Get the java code for this test function |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 204 | """ |
| 205 | return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), |
| 206 | farg=self.farg, |
| 207 | invoke_type=self.invoke, |
| 208 | callfunc=self.func) |
| 209 | |
| 210 | def flatten_classes(classes, c): |
| 211 | """ |
| 212 | Iterate over all the classes 'c' can be used as |
| 213 | """ |
| 214 | while c: |
| 215 | yield c |
| 216 | c = classes.get(c.super_class) |
| 217 | |
| 218 | def flatten_class_methods(classes, c): |
| 219 | """ |
| 220 | Iterate over all the methods 'c' can call |
| 221 | """ |
| 222 | for c1 in flatten_classes(classes, c): |
| 223 | yield from c1.methods |
| 224 | |
| 225 | def flatten_interfaces(dat, c): |
| 226 | """ |
| 227 | Iterate over all the interfaces 'c' transitively implements |
| 228 | """ |
| 229 | def get_ifaces(cl): |
| 230 | for i2 in cl.implements: |
| 231 | yield dat.interfaces[i2] |
| 232 | yield from get_ifaces(dat.interfaces[i2]) |
| 233 | |
| 234 | for cl in flatten_classes(dat.classes, c): |
| 235 | yield from get_ifaces(cl) |
| 236 | |
| 237 | def flatten_interface_methods(dat, i): |
| 238 | """ |
| 239 | Iterate over all the interface methods 'c' can call |
| 240 | """ |
| 241 | yield from i.methods |
| 242 | for i2 in flatten_interfaces(dat, i): |
| 243 | yield from i2.methods |
| 244 | |
| 245 | def make_main_class(dat): |
| 246 | """ |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 247 | Creates a Main.java file that runs all the tests |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 248 | """ |
| 249 | m = MainClass() |
| 250 | for c in dat.classes.values(): |
| 251 | i = InstanceTest(m, c.name) |
| 252 | for clazz in flatten_classes(dat.classes, c): |
| 253 | for meth in flatten_class_methods(dat.classes, clazz): |
| 254 | i.add_func(Func(meth, clazz.name, 'virtual')) |
| 255 | for iface in flatten_interfaces(dat, clazz): |
| 256 | for meth in flatten_interface_methods(dat, iface): |
| 257 | i.add_func(Func(meth, clazz.name, 'virtual')) |
| 258 | i.add_func(Func(meth, iface.name, 'interface')) |
| 259 | return m |
| 260 | |
| 261 | class TestData(namedtuple("TestData", ['classes', 'interfaces'])): |
| 262 | """ |
| 263 | A class representing the classes.xml document. |
| 264 | """ |
| 265 | pass |
| 266 | |
| 267 | class Clazz(namedtuple("Clazz", ["name", "methods", "super_class", "implements"])): |
| 268 | """ |
| 269 | A class representing a class element in the classes.xml document. |
| 270 | """ |
| 271 | pass |
| 272 | |
| 273 | class IFace(namedtuple("IFace", ["name", "methods", "super_class", "implements"])): |
| 274 | """ |
| 275 | A class representing an interface element in the classes.xml document. |
| 276 | """ |
| 277 | pass |
| 278 | |
| 279 | def parse_xml(xml): |
| 280 | """ |
| 281 | Parse the xml description of this test. |
| 282 | """ |
| 283 | classes = dict() |
| 284 | ifaces = dict() |
| 285 | root = ET.fromstring(xml) |
| 286 | for iface in root.find("interfaces"): |
| 287 | name = iface.attrib['name'] |
| 288 | implements = [a.text for a in iface.find("implements")] |
| 289 | methods = [a.text for a in iface.find("methods")] |
| 290 | ifaces[name] = IFace(name = name, |
| 291 | super_class = iface.attrib['super'], |
| 292 | methods = methods, |
| 293 | implements = implements) |
| 294 | for clazz in root.find('classes'): |
| 295 | name = clazz.attrib['name'] |
| 296 | implements = [a.text for a in clazz.find("implements")] |
| 297 | methods = [a.text for a in clazz.find("methods")] |
| 298 | classes[name] = Clazz(name = name, |
| 299 | super_class = clazz.attrib['super'], |
| 300 | methods = methods, |
| 301 | implements = implements) |
| 302 | return TestData(classes, ifaces) |
| 303 | |
| 304 | def main(argv): |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 305 | java_dir = Path(argv[1]) |
| 306 | if not java_dir.exists() or not java_dir.is_dir(): |
| 307 | print("{} is not a valid java dir".format(java_dir), file=sys.stderr) |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 308 | sys.exit(1) |
Alex Light | d204ba5 | 2016-03-01 14:33:51 -0800 | [diff] [blame] | 309 | class_data = parse_xml((java_dir / "classes.xml").open().read()) |
| 310 | make_main_class(class_data).dump(java_dir) |
Alex Light | eb7c144 | 2015-08-31 13:17:42 -0700 | [diff] [blame] | 311 | |
| 312 | if __name__ == '__main__': |
| 313 | main(sys.argv) |