Home | History | Annotate | Download | only in util
      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