1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package util; 18 19 import dxc.junit.AllTests; 20 21 import junit.framework.TestCase; 22 import junit.framework.TestResult; 23 import junit.textui.TestRunner; 24 25 import java.io.BufferedWriter; 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.FileNotFoundException; 29 import java.io.FileOutputStream; 30 import java.io.FileReader; 31 import java.io.IOException; 32 import java.io.OutputStreamWriter; 33 import java.text.MessageFormat; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Scanner; 40 import java.util.Set; 41 import java.util.TreeMap; 42 import java.util.Map.Entry; 43 import java.util.regex.MatchResult; 44 import java.util.regex.Matcher; 45 import java.util.regex.Pattern; 46 47 /** 48 * Main class to generate data from the test suite to later run from a shell 49 * script. the project's home folder.<br> 50 * <project-home>/src must contain the java sources<br> 51 * <project-home>/data/scriptdata will be generated<br> 52 * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br> 53 * (one Main class for each test method in the Test_... class 54 */ 55 public class CollectAllTests { 56 57 private static String PROJECT_FOLDER = ""; 58 private static String PROJECT_FOLDER_OUT = "missing out folder!"; 59 private static String JAVASRC_FOLDER = PROJECT_FOLDER + "/src"; 60 private static HashSet<String> OPCODES = null; 61 62 /* 63 * a map. key: fully qualified class name, value: a list of test methods for 64 * the given class 65 */ 66 private TreeMap<String, List<String>> map = new TreeMap<String, List<String>>(); 67 68 private int testClassCnt = 0; 69 private int testMethodsCnt = 0; 70 71 private class MethodData { 72 String methodBody, constraint, title; 73 } 74 75 /** 76 * @param args 77 * args 0 must be the project root folder (where src, lib etc. 78 * resides) 79 * args 1 must be the project out root folder (where the Main_*.java files 80 * are put, and also data/scriptdata) 81 */ 82 public static void main(String[] args) { 83 if (args.length >= 2) { 84 PROJECT_FOLDER = args[0]; 85 PROJECT_FOLDER_OUT = args[1]; 86 JAVASRC_FOLDER = PROJECT_FOLDER + "/src"; 87 } else { 88 System.out.println("usage: args 0 must be the project root folder (where src, lib etc. resides)" + 89 "and args 1 must be the project out root folder (where the Main_*.java file" + 90 " are put, and also data/scriptdata)"); 91 return; 92 } 93 94 95 for (int i = 2; i < args.length; i++) { 96 if (OPCODES == null) { 97 OPCODES = new HashSet<String>(); 98 } 99 OPCODES.add(args[i]); 100 } 101 102 System.out.println("using java src:"+JAVASRC_FOLDER); 103 CollectAllTests cat = new CollectAllTests(); 104 cat.compose(); 105 } 106 107 public void compose() { 108 System.out.println("Collecting all junit tests..."); 109 new TestRunner() { 110 @Override 111 protected TestResult createTestResult() { 112 return new TestResult() { 113 @Override 114 protected void run(TestCase test) { 115 addToTests(test); 116 } 117 118 }; 119 } 120 }.doRun(AllTests.suite()); 121 122 // for each combination of TestClass and method, generate a Main_testN1 123 // etc. 124 // class in the respective package. 125 // for the report make sure all N... tests are called first, then B, 126 // then 127 // E, then VFE test methods. 128 // so we need x Main_xxxx methods in a package, and x entries in the 129 // global scriptdata file (read by a bash script for the tests) 130 // e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() -> 131 // File Main_testN1.java in package dxc.junit.opcodes.aaload 132 // and entry dxc.junit.opcodes.aaload.Main_testN1 in class execution 133 // table. 134 // 135 handleTests(); 136 } 137 138 private void addToTests(TestCase test) { 139 140 String packageName = test.getClass().getPackage().getName(); 141 packageName = packageName.substring(packageName.lastIndexOf('.')+1); 142 if (OPCODES != null && !OPCODES.contains(packageName)) { 143 return; 144 } 145 146 147 String method = test.getName(); // e.g. testVFE2 148 String fqcn = test.getClass().getName(); // e.g. 149 // dxc.junit.opcodes.iload_3.Test_iload_3 150 // order: take the order of the test-suites for the classes, 151 // TODO and for methods: take Nx, then Bx, then Ex, then VFEx 152 //System.out.println("collecting test:" + test.getName() + ", class " 153 // + test.getClass().getName()); 154 testMethodsCnt++; 155 List<String> li = map.get(fqcn); 156 if (li == null) { 157 testClassCnt++; 158 li = new ArrayList<String>(); 159 map.put(fqcn, li); 160 } 161 li.add(method); 162 } 163 164 private void handleTests() { 165 System.out.println("collected "+testMethodsCnt+" test methods in "+testClassCnt+" junit test classes"); 166 String datafileContent = ""; 167 168 for (Entry<String, List<String>> entry : map.entrySet()) { 169 170 String fqcn = entry.getKey(); 171 int lastDotPos = fqcn.lastIndexOf('.'); 172 String pName = fqcn.substring(0, lastDotPos); 173 String classOnlyName = fqcn.substring(lastDotPos + 1); 174 String instPrefix = "new " + classOnlyName + "()"; 175 176 String[] nameParts = pName.split("\\."); 177 if (nameParts.length != 4) { 178 throw new RuntimeException( 179 "package name does not comply to naming scheme: " + pName); 180 } 181 182 183 List<String> methods = entry.getValue(); 184 Collections.sort(methods, new Comparator<String>() { 185 public int compare(String s1, String s2) { 186 // TODO sort according: test ... N, B, E, VFE 187 return s1.compareTo(s2); 188 } 189 }); 190 for (String method : methods) { 191 // e.g. testN1 192 if (!method.startsWith("test")) { 193 throw new RuntimeException("no test method: " + method); 194 } 195 196 // generate the Main_xx java class 197 198 // a Main_testXXX.java contains: 199 // package <packagenamehere>; 200 // public class Main_testxxx { 201 // public static void main(String[] args) { 202 // new dxc.junit.opcodes.aaload.Test_aaload().testN1(); 203 // } 204 // } 205 206 MethodData md = parseTestMethod(pName, classOnlyName, method); 207 String methodContent = md.methodBody; 208 209 Set<String> dependentTestClassNames = parseTestClassName(pName, 210 classOnlyName, methodContent); 211 212 if (dependentTestClassNames.isEmpty()) 213 { 214 continue; 215 } 216 217 218 String content = "//autogenerated by " 219 + this.getClass().getName() 220 + ", do not change\n" 221 + "package " 222 + pName 223 + ";\n" 224 + "import " 225 + pName 226 + ".jm.*;\n" 227 + "import dxc.junit.*;\n" 228 + "public class Main_" 229 + method 230 + " extends DxAbstractMain {\n" 231 + "public static void main(String[] args) throws Exception {\n" 232 + "new Main_" + method + "()." + method + "();\n" 233 + "}\n" + methodContent + "\n}\n"; 234 235 writeToFile(getFileFromPackage(pName, method), content); 236 237 // prepare the entry in the data file for the bash script. 238 // e.g. 239 // main class to execute; opcode/constraint; test purpose 240 // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test 241 // (#1) 242 243 char ca = method.charAt("test".length()); // either N,B,E, oradd_double 244 // V (VFE) 245 String comment; 246 switch (ca) { 247 case 'N': 248 comment = "Normal #" + method.substring(5); 249 break; 250 case 'B': 251 comment = "Boundary #" + method.substring(5); 252 break; 253 case 'E': 254 comment = "Exception #" + method.substring(5); 255 break; 256 case 'V': 257 comment = "Verifier #" + method.substring(7); 258 break; 259 default: 260 throw new RuntimeException("unknown test abbreviation:" 261 + method + " for " + fqcn); 262 } 263 264 String opcConstr = pName.substring(pName.lastIndexOf('.') + 1); 265 // beautify test title 266 if (opcConstr.startsWith("t4")) { 267 opcConstr = "verifier"; // + opcConstr.substring(1); 268 } else if (opcConstr.startsWith("pargs")) { 269 opcConstr = "sanity"; 270 } else if (opcConstr.startsWith("opc_")) { 271 // unescape reserved words 272 opcConstr = opcConstr.substring(4); 273 } 274 275 String line = pName + ".Main_" + method + ";"; 276 for (String className : dependentTestClassNames) { 277 try { 278 Class.forName(className); 279 } catch (ClassNotFoundException e) { 280 throw new RuntimeException( 281 "dependent class not found : " + className); 282 } catch (Throwable e) { 283 // ignore 284 } 285 286 line += className + " "; 287 } 288 289 String details = (md.title != null ? md.title : ""); 290 if (md.constraint != null) { 291 details = "Constraint " + md.constraint + ", " + details; 292 } 293 if (details.length() != 0) { 294 details = details.substring(0, 1).toUpperCase() 295 + details.substring(1); 296 } 297 298 line += ";" + opcConstr + ";"+ comment + ";" + details; 299 300 datafileContent += line + "\n"; 301 302 } 303 304 305 } 306 new File(PROJECT_FOLDER_OUT + "/data").mkdirs(); 307 writeToFile(new File(PROJECT_FOLDER_OUT + "/data/scriptdata"), 308 datafileContent); 309 } 310 311 312 313 /** 314 * 315 * @param pName 316 * @param classOnlyName 317 * @param methodSource 318 * @return a set 319 */ 320 private Set<String> parseTestClassName(String pName, String classOnlyName, 321 String methodSource) { 322 Set<String> entries = new HashSet<String>(); 323 String opcodeName = classOnlyName.substring(5); 324 325 Scanner scanner = new Scanner(methodSource); 326 327 String[] patterns = new String[] { 328 "new\\s(T_" + opcodeName + "\\w*)", 329 "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"}; 330 331 String token = null; 332 for (String pattern : patterns) { 333 token = scanner.findWithinHorizon(pattern, methodSource.length()); 334 if (token != null) { 335 break; 336 } 337 } 338 339 if (token == null) { 340 System.err.println("warning: failed to find dependent test class name: "+pName+", "+classOnlyName); 341 return entries; 342 } 343 344 MatchResult result = scanner.match(); 345 346 entries.add((pName + ".jm." + result.group(1)).trim()); 347 348 // search additional @uses directives 349 Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE); 350 Matcher m = p.matcher(methodSource); 351 while (m.find()) { 352 String res = m.group(1); 353 entries.add(res.trim()); 354 } 355 356 //lines with the form @uses dx.junit.opcodes.add_double.jm.T_add_double_2 357 // one dependency per one @uses 358 //TODO 359 360 return entries; 361 } 362 363 private MethodData parseTestMethod(String pname, String classOnlyName, 364 String method) { 365 366 String path = pname.replaceAll("\\.", "/"); 367 String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName 368 + ".java"; 369 File f = new File(absPath); 370 371 Scanner scanner; 372 try { 373 scanner = new Scanner(f); 374 } catch (FileNotFoundException e) { 375 throw new RuntimeException("error while reading from file: " 376 + e.getClass().getName() + ", msg:" + e.getMessage()); 377 } 378 379 String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{"; 380 381 String token = scanner.findWithinHorizon(methodPattern, (int) f 382 .length()); 383 if (token == null) { 384 throw new RuntimeException( 385 "cannot find method source of 'public void" + method 386 + "' in file '" + absPath + "'"); 387 } 388 389 MatchResult result = scanner.match(); 390 result.start(); 391 result.end(); 392 393 StringBuilder builder = new StringBuilder(); 394 builder.append(token); 395 396 try { 397 FileReader reader = new FileReader(f); 398 reader.skip(result.end()); 399 400 char currentChar; 401 int blocks = 1; 402 while ((currentChar = (char) reader.read()) != -1 && blocks > 0) { 403 switch (currentChar) { 404 case '}': { 405 blocks--; 406 builder.append(currentChar); 407 break; 408 } 409 case '{': { 410 blocks++; 411 builder.append(currentChar); 412 break; 413 } 414 default: { 415 builder.append(currentChar); 416 break; 417 } 418 } 419 } 420 if (reader != null) { 421 reader.close(); 422 } 423 } catch (Exception e) { 424 throw new RuntimeException("failed to parse", e); 425 } 426 427 // find the @title/@constraint in javadoc comment for this method 428 Scanner scanner2; 429 try { 430 // using platform's default charset 431 scanner2 = new Scanner(f); 432 } catch (FileNotFoundException e) { 433 throw new RuntimeException("error while reading from file: " 434 + e.getClass().getName() + ", msg:" + e.getMessage()); 435 } 436 437 // using platform's default charset 438 String all = new String(readFile(f)); 439 // System.out.println("grepping javadoc found for method "+method + 440 // " in "+pname+","+classOnlyName); 441 String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern; 442 Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL); 443 Matcher m = p.matcher(all); 444 String title = null, constraint = null; 445 if (m.find()) { 446 String res = m.group(1); 447 // System.out.println("res: "+res); 448 // now grep @title and @constraint 449 Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL) 450 .matcher(res); 451 if (titleM.find()) { 452 title = titleM.group(1).replaceAll("\\n \\*", ""); 453 title = title.replaceAll("\\n", " "); 454 title = title.trim(); 455 // System.out.println("title: " + title); 456 } else { 457 System.err.println("warning: no @title found for method " 458 + method + " in " + pname + "," + classOnlyName); 459 } 460 // constraint can be one line only 461 Matcher constraintM = Pattern.compile("@constraint (.*)").matcher( 462 res); 463 if (constraintM.find()) { 464 constraint = constraintM.group(1); 465 constraint = constraint.trim(); 466 // System.out.println("constraint: " + constraint); 467 } else if (method.contains("VFE")) { 468 System.err 469 .println("warning: no @constraint for for a VFE method:" 470 + method + " in " + pname + "," + classOnlyName); 471 } 472 } else { 473 System.err.println("warning: no javadoc found for method " + method 474 + " in " + pname + "," + classOnlyName); 475 } 476 MethodData md = new MethodData(); 477 md.methodBody = builder.toString(); 478 md.constraint = constraint; 479 md.title = title; 480 if (scanner != null) { 481 scanner.close(); 482 } 483 if (scanner2 != null) { 484 scanner2.close(); 485 } 486 return md; 487 } 488 489 private void writeToFile(File file, String content) { 490 //System.out.println("writing file " + file.getAbsolutePath()); 491 try { 492 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( 493 new FileOutputStream(file), "utf-8")); 494 bw.write(content); 495 bw.close(); 496 } catch (Exception e) { 497 throw new RuntimeException("error while writing to file: " 498 + e.getClass().getName() + ", msg:" + e.getMessage()); 499 } 500 } 501 502 private File getFileFromPackage(String pname, String methodName) { 503 // e.g. dxc.junit.argsreturns.pargsreturn 504 String path = pname.replaceAll("\\.", "/"); 505 String absPath = PROJECT_FOLDER_OUT + "/" + path; 506 new File(absPath).mkdirs(); 507 return new File(absPath + "/Main_" + methodName + ".java"); 508 } 509 510 private byte[] readFile(File file) { 511 int len = (int) file.length(); 512 byte[] res = new byte[len]; 513 try { 514 FileInputStream in = new FileInputStream(file); 515 int pos = 0; 516 while (len > 0) { 517 int br = in.read(res, pos, len); 518 if (br == -1) { 519 throw new RuntimeException("unexpected EOF for file: "+file); 520 } 521 pos += br; 522 len -= br; 523 } 524 in.close(); 525 } catch (IOException ex) { 526 throw new RuntimeException("error reading file:"+file, ex); 527 } 528 return res; 529 } 530 } 531