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 """ 18 Generate Java Main file from a classes.xml file. 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 41 class MainClass(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): 42 """ 43 A mainclass and main method for this test. 44 """ 45 46 MAIN_CLASS_TEMPLATE = """{copyright} 47 class 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 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 = """ 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 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 = """ 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 e.printStackTrace(System.out); 179 }} 180 }} 181 """ 182 183 def __init__(self, func, farg, invoke): 184 """ 185 Initialize this test function for the given invoke type and argument 186 """ 187 self.func = func 188 self.farg = farg 189 self.invoke = invoke 190 191 def get_name(self): 192 """ 193 Get the name of this test 194 """ 195 return "Test_Func_{}_{}_{}".format(self.func, self.farg, self.invoke) 196 197 def __str__(self): 198 """ 199 Get the java code for this test function 200 """ 201 return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), 202 farg=self.farg, 203 invoke_type=self.invoke, 204 callfunc=self.func) 205 206 def flatten_classes(classes, c): 207 """ 208 Iterate over all the classes 'c' can be used as 209 """ 210 while c: 211 yield c 212 c = classes.get(c.super_class) 213 214 def flatten_class_methods(classes, c): 215 """ 216 Iterate over all the methods 'c' can call 217 """ 218 for c1 in flatten_classes(classes, c): 219 yield from c1.methods 220 221 def flatten_interfaces(dat, c): 222 """ 223 Iterate over all the interfaces 'c' transitively implements 224 """ 225 def get_ifaces(cl): 226 for i2 in cl.implements: 227 yield dat.interfaces[i2] 228 yield from get_ifaces(dat.interfaces[i2]) 229 230 for cl in flatten_classes(dat.classes, c): 231 yield from get_ifaces(cl) 232 233 def flatten_interface_methods(dat, i): 234 """ 235 Iterate over all the interface methods 'c' can call 236 """ 237 yield from i.methods 238 for i2 in flatten_interfaces(dat, i): 239 yield from i2.methods 240 241 def make_main_class(dat): 242 """ 243 Creates a Main.java file that runs all the tests 244 """ 245 m = MainClass() 246 for c in dat.classes.values(): 247 i = InstanceTest(m, c.name) 248 for clazz in flatten_classes(dat.classes, c): 249 for meth in flatten_class_methods(dat.classes, clazz): 250 i.add_func(Func(meth, clazz.name, 'virtual')) 251 for iface in flatten_interfaces(dat, clazz): 252 for meth in flatten_interface_methods(dat, iface): 253 i.add_func(Func(meth, clazz.name, 'virtual')) 254 i.add_func(Func(meth, iface.name, 'interface')) 255 return m 256 257 class TestData(namedtuple("TestData", ['classes', 'interfaces'])): 258 """ 259 A class representing the classes.xml document. 260 """ 261 pass 262 263 class Clazz(namedtuple("Clazz", ["name", "methods", "super_class", "implements"])): 264 """ 265 A class representing a class element in the classes.xml document. 266 """ 267 pass 268 269 class IFace(namedtuple("IFace", ["name", "methods", "super_class", "implements"])): 270 """ 271 A class representing an interface element in the classes.xml document. 272 """ 273 pass 274 275 def parse_xml(xml): 276 """ 277 Parse the xml description of this test. 278 """ 279 classes = dict() 280 ifaces = dict() 281 root = ET.fromstring(xml) 282 for iface in root.find("interfaces"): 283 name = iface.attrib['name'] 284 implements = [a.text for a in iface.find("implements")] 285 methods = [a.text for a in iface.find("methods")] 286 ifaces[name] = IFace(name = name, 287 super_class = iface.attrib['super'], 288 methods = methods, 289 implements = implements) 290 for clazz in root.find('classes'): 291 name = clazz.attrib['name'] 292 implements = [a.text for a in clazz.find("implements")] 293 methods = [a.text for a in clazz.find("methods")] 294 classes[name] = Clazz(name = name, 295 super_class = clazz.attrib['super'], 296 methods = methods, 297 implements = implements) 298 return TestData(classes, ifaces) 299 300 def main(argv): 301 java_dir = Path(argv[1]) 302 if not java_dir.exists() or not java_dir.is_dir(): 303 print("{} is not a valid java dir".format(java_dir), file=sys.stderr) 304 sys.exit(1) 305 class_data = parse_xml((java_dir / "classes.xml").open().read()) 306 make_main_class(class_data).dump(java_dir) 307 308 if __name__ == '__main__': 309 main(sys.argv) 310