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""" 18Generate Java Main file from a classes.xml file. 19""" 20 21import os 22import sys 23from pathlib import Path 24 25BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") 26if 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. 31sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) 32 33from testgen.utils import get_copyright 34import testgen.mixins as mixins 35 36from collections import namedtuple 37import itertools 38import functools 39import xml.etree.ElementTree as ET 40 41class MainClass(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): 42 """ 43 A mainclass and main method for this test. 44 """ 45 46 MAIN_CLASS_TEMPLATE = """{copyright} 47class Main {{ 48{test_groups} 49{test_funcs} 50{main_func} 51}} 52""" 53 54 MAIN_FUNCTION_TEMPLATE = """ 55 public static void main(String[] args) {{ 56 {test_group_invoke} 57 }} 58""" 59 60 TEST_GROUP_INVOKE_TEMPLATE = """ 61 {test_name}(); 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) 105 return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('java'), 106 test_groups=test_groups, 107 main_func=main_func, test_funcs=funcs) 108 109 110class 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 = """ 117 public static void {test_name}() {{ 118 System.out.println("Testing for type {ty}"); 119 String s = "{ty}"; 120 {ty} v = new {ty}(); 121 122 {invokes} 123 124 System.out.println("End testing for type {ty}"); 125 }} 126""" 127 128 TEST_INVOKE_TEMPLATE = """ 129 {fname}(s, v); 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 """ 156 Returns the java code for this function 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 166class 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 = """ 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); 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 }} 183 }} 184 }} 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 """ 203 Get the java code for this test function 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 210def 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 218def 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 225def 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 237def 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 245def make_main_class(dat): 246 """ 247 Creates a Main.java file that runs all the tests 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 261class TestData(namedtuple("TestData", ['classes', 'interfaces'])): 262 """ 263 A class representing the classes.xml document. 264 """ 265 pass 266 267class 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 273class 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 279def 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 304def main(argv): 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) 308 sys.exit(1) 309 class_data = parse_xml((java_dir / "classes.xml").open().read()) 310 make_main_class(class_data).dump(java_dir) 311 312if __name__ == '__main__': 313 main(sys.argv) 314