Home | History | Annotate | Download | only in build
      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.build;
     18 
     19 import com.android.dx.util.FileUtils;
     20 
     21 import dot.junit.AllTests;
     22 
     23 import junit.framework.TestCase;
     24 import junit.framework.TestResult;
     25 import junit.framework.TestSuite;
     26 import junit.textui.TestRunner;
     27 
     28 import java.io.BufferedWriter;
     29 import java.io.File;
     30 import java.io.FileNotFoundException;
     31 import java.io.FileOutputStream;
     32 import java.io.FileReader;
     33 import java.io.IOException;
     34 import java.io.OutputStreamWriter;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.Comparator;
     38 import java.util.HashSet;
     39 import java.util.Iterator;
     40 import java.util.LinkedHashMap;
     41 import java.util.List;
     42 import java.util.Scanner;
     43 import java.util.Set;
     44 import java.util.TreeSet;
     45 import java.util.Map.Entry;
     46 import java.util.regex.MatchResult;
     47 import java.util.regex.Matcher;
     48 import java.util.regex.Pattern;
     49 
     50 /**
     51  * Main class to generate data from the test suite to later run from a shell
     52  * script. the project's home folder.<br>
     53  * <project-home>/src must contain the java sources<br>
     54  * <project-home>/data/scriptdata will be generated<br>
     55  * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
     56  * (one Main class for each test method in the Test_... class
     57  */
     58 public class BuildDalvikSuite {
     59 
     60     public static boolean DEBUG = true;
     61 
     62     private static String JAVASRC_FOLDER = "";
     63     private static String MAIN_SRC_OUTPUT_FOLDER = "";
     64 
     65     // the folder for the generated junit-files for the cts host (which in turn
     66     // execute the real vm tests using adb push/shell etc)
     67     private static String HOSTJUNIT_SRC_OUTPUT_FOLDER = "";
     68     private static String OUTPUT_FOLDER = "";
     69     private static String COMPILED_CLASSES_FOLDER = "";
     70 
     71     private static String CLASSES_OUTPUT_FOLDER = "";
     72     private static String HOSTJUNIT_CLASSES_OUTPUT_FOLDER = "";
     73 
     74     private static String CLASS_PATH = "";
     75 
     76     private static String restrictTo = null; // e.g. restrict to
     77     // "opcodes.add_double"
     78 
     79     private static final String TARGET_JAR_ROOT_PATH = "/data/local/tmp";
     80 
     81     private int testClassCnt = 0;
     82     private int testMethodsCnt = 0;
     83 
     84 
     85     /*
     86      * using a linked hashmap to keep the insertion order for iterators.
     87      * the junit suite/tests adding order is used to generate the order of the
     88      * report.
     89      * a map. key: fully qualified class name, value: a list of test methods for
     90      * the given class
     91      */
     92     private LinkedHashMap<String, List<String>> map = new LinkedHashMap<String,
     93             List<String>>();
     94 
     95 
     96     private class MethodData {
     97         String methodBody, constraint, title;
     98     }
     99 
    100     /**
    101      * @param args
    102      *            args 0 must be the project root folder (where src, lib etc.
    103      *            resides)
    104      * @throws IOException
    105      */
    106     public static void main(String[] args) throws IOException {
    107 
    108         if (args.length > 5) {
    109             JAVASRC_FOLDER = args[0];
    110             OUTPUT_FOLDER = args[1];
    111             CLASS_PATH = args[2];
    112             MAIN_SRC_OUTPUT_FOLDER = args[3];
    113             CLASSES_OUTPUT_FOLDER = MAIN_SRC_OUTPUT_FOLDER + "/classes";
    114 
    115             COMPILED_CLASSES_FOLDER = args[4];
    116 
    117             HOSTJUNIT_SRC_OUTPUT_FOLDER = args[5];
    118             HOSTJUNIT_CLASSES_OUTPUT_FOLDER = HOSTJUNIT_SRC_OUTPUT_FOLDER
    119                     + "/classes";
    120 
    121             if (args.length > 6) {
    122                 // optional: restrict to e.g. "opcodes.add_double"
    123                 restrictTo = args[6];
    124                 System.out.println("restricting build to: "+restrictTo);
    125             }
    126 
    127         } else {
    128             System.out
    129                     .println("usage: java-src-folder output-folder classpath "
    130                            + "generated-main-files compiled_output "
    131                            + "generated-main-files [restrict-to-opcode]");
    132             System.exit(-1);
    133         }
    134 
    135         long start = System.currentTimeMillis();
    136         BuildDalvikSuite cat = new BuildDalvikSuite();
    137         cat.compose();
    138         long end = System.currentTimeMillis();
    139 
    140         System.out.println("elapsed seconds: " + (end - start) / 1000);
    141     }
    142 
    143     public void compose() throws IOException {
    144         System.out.println("Collecting all junit tests...");
    145         new TestRunner() {
    146             @Override
    147             protected TestResult createTestResult() {
    148                 return new TestResult() {
    149                     @Override
    150                     protected void run(TestCase test) {
    151                         addToTests(test);
    152                     }
    153 
    154                 };
    155             }
    156         }.doRun(AllTests.suite());
    157 
    158         // for each combination of TestClass and method, generate a Main_testN1
    159         // etc.
    160         // class in the respective package.
    161         // for the report make sure all N... tests are called first, then B,
    162         // then
    163         // E, then VFE test methods.
    164         // so we need x Main_xxxx methods in a package, and x entries in the
    165         // global scriptdata file (read by a bash script for the tests)
    166         // e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() ->
    167         // File Main_testN1.java in package dxc.junit.opcodes.aaload
    168         // and entry dxc.junit.opcodes.aaload.Main_testN1 in class execution
    169         // table.
    170         //
    171         handleTests();
    172     }
    173 
    174     private void addToTests(TestCase test) {
    175 
    176         String packageName = test.getClass().getPackage().getName();
    177         packageName = packageName.substring(packageName.lastIndexOf('.'));
    178 
    179 
    180         String method = test.getName(); // e.g. testVFE2
    181         String fqcn = test.getClass().getName(); // e.g.
    182         // dxc.junit.opcodes.iload_3.Test_iload_3
    183 
    184         // ignore all tests not belonging to the given restriction
    185         if (restrictTo != null && !fqcn.contains(restrictTo)) return;
    186 
    187         testMethodsCnt++;
    188         List<String> li = map.get(fqcn);
    189         if (li == null) {
    190             testClassCnt++;
    191             li = new ArrayList<String>();
    192             map.put(fqcn, li);
    193         }
    194         li.add(method);
    195     }
    196 
    197     private static final String ctsAllTestsB =
    198         "package dot.junit;\n" +
    199         "import junit.framework.Test;\n" +
    200         "import junit.framework.TestSuite;\n" +
    201         "import com.android.hosttest.DeviceTestSuite;\n" +
    202         "\n" +
    203         "public class AllJunitHostTests extends DeviceTestSuite {\n" +
    204         "    public static final Test suite() {\n" +
    205         "        TestSuite suite = new TestSuite(\"CTS Host tests for all " +
    206         " dalvik vm opcodes\");\n";
    207 
    208     private static final String ctsAllTestsE =
    209         "    }"+
    210         "}";
    211 
    212     private static final String curFileDataE = "}\n";
    213 
    214 
    215     private String curAllTestsData = ctsAllTestsB;
    216     private String curJunitFileName = null;
    217     private String curJunitFileData = "";
    218 
    219     private JavacBuildStep javacHostJunitBuildStep;
    220 
    221     private void flushHostJunitFile() {
    222         if (curJunitFileName != null) {
    223         	File toWrite = new File(curJunitFileName);
    224             String absPath = toWrite.getAbsolutePath();
    225             // add to java source files for later compilation
    226             javacHostJunitBuildStep.addSourceFile(absPath);
    227             // write file
    228             curJunitFileData+="\n}\n";
    229             writeToFileMkdir(toWrite, curJunitFileData);
    230             curJunitFileName = null;
    231             curJunitFileData = "";
    232         }
    233     }
    234 
    235     private void ctsFinish() {
    236     	flushHostJunitFile();
    237     	curAllTestsData+="return suite;\n}\n}\n";
    238     	// suite is in package dot.junit.
    239     	String allTestsFileName = HOSTJUNIT_SRC_OUTPUT_FOLDER
    240     	        + "/dot/junit/AllJunitHostTests.java";
    241     	File toWrite = new File(allTestsFileName);
    242     	writeToFileMkdir(toWrite, curAllTestsData);
    243     	javacHostJunitBuildStep.addSourceFile(toWrite.getAbsolutePath());
    244     	javacHostJunitBuildStep.addSourceFile(new File(
    245     	        HOSTJUNIT_SRC_OUTPUT_FOLDER + "/dot/junit/DeviceUtil.java").
    246     	        getAbsolutePath());
    247     }
    248 
    249     private void openCTSHostFileFor(String pName, String classOnlyName) {
    250         String sourceName = "JUnit_"+classOnlyName;
    251         // Add to AllTests.java
    252         String suiteline = "suite.addTestSuite("+pName+"." + sourceName +
    253                 ".class);\n";
    254         curAllTestsData += suiteline;
    255         // flush previous JunitFile
    256         flushHostJunitFile();
    257 
    258         // prepare current testcase-file
    259         curJunitFileName = HOSTJUNIT_SRC_OUTPUT_FOLDER+"/"
    260                 + pName.replaceAll("\\.","/")+"/"+sourceName+".java";
    261         curJunitFileData =
    262             "package "+pName+";\n"+
    263             "import java.io.IOException;\n"+
    264             "import junit.framework.TestCase;\n"+
    265             "import com.android.hosttest.DeviceTestCase;\n"+
    266             "import dot.junit.DeviceUtil;\n" +
    267             "\n" +
    268             "public class "+sourceName+" extends DeviceTestCase {\n";
    269     }
    270 
    271     private String getADBPushJavaLine(String source, String target) {
    272         return "DeviceUtil.adbPush(getDevice(), \"" + source + "\", \"" + target + "\");";
    273     }
    274 
    275     private String getADBExecJavaLine(String classpath, String mainclass) {
    276         return "DeviceUtil.adbExec(getDevice(), \"" + classpath + "\", \"" +
    277                 mainclass + "\");";
    278     }
    279 
    280     private void addCTSHostMethod(String pName, String method, MethodData md,
    281             Set<String> dependentTestClassNames) {
    282     	final String targetCoreJarPath = String.format("%s/dexcore.jar", TARGET_JAR_ROOT_PATH);
    283     	curJunitFileData+="public void "+method+ "() throws Exception {\n";
    284         curJunitFileData+= "    "+getADBPushJavaLine("dot/junit/dexcore.jar",
    285         		targetCoreJarPath);
    286 
    287         // push class with Main jar.
    288         String mjar = "Main_"+method+".jar";
    289         String mainJar = String.format("%s/%s", TARGET_JAR_ROOT_PATH, mjar);
    290         String pPath = pName.replaceAll("\\.","/");
    291         //System.out.println("adb push "+pPath+"/"+mjar +" "+mainJar);
    292         curJunitFileData+= "    "+getADBPushJavaLine(pPath+"/"+mjar, mainJar);
    293 
    294         // for each dependency:
    295         // adb push dot/junit/opcodes/add_double_2addr/Main_testN2.jar
    296         // /data/local/tmp/Main_testN2.jar
    297         String cp = String.format("%s:%s", targetCoreJarPath, mainJar);
    298         for (String depFqcn : dependentTestClassNames) {
    299             int lastDotPos = depFqcn.lastIndexOf('.');
    300             String targetName= String.format("%s/%s.jar", TARGET_JAR_ROOT_PATH,
    301                     depFqcn.substring(lastDotPos +1));
    302             String sourceName = depFqcn.replaceAll("\\.", "/")+".jar";
    303             //System.out.println("adb push "+sourceName+" "+targetName);
    304             curJunitFileData+= "    "+getADBPushJavaLine(sourceName, targetName);
    305             cp+= ":"+targetName;
    306             // dot.junit.opcodes.invoke_interface_range.ITest
    307             // -> dot/junit/opcodes/invoke_interface_range/ITest.jar
    308         }
    309 
    310         //"dot.junit.opcodes.add_double_2addr.Main_testN2";
    311         String mainclass = pName + ".Main_" + method;
    312         curJunitFileData+= "    "+getADBExecJavaLine(cp, mainclass);
    313         curJunitFileData+= "}\n\n";
    314     }
    315 
    316     private void handleTests() throws IOException {
    317         System.out.println("collected "+testMethodsCnt+" test methods in " +
    318                 testClassCnt+" junit test classes");
    319         String datafileContent = "";
    320         Set<BuildStep> targets = new TreeSet<BuildStep>();
    321 
    322         javacHostJunitBuildStep = new JavacBuildStep(
    323         		HOSTJUNIT_CLASSES_OUTPUT_FOLDER, CLASS_PATH);
    324 
    325 
    326         JavacBuildStep javacBuildStep = new JavacBuildStep(
    327                 CLASSES_OUTPUT_FOLDER, CLASS_PATH);
    328 
    329         for (Entry<String, List<String>> entry : map.entrySet()) {
    330 
    331             String fqcn = entry.getKey();
    332             int lastDotPos = fqcn.lastIndexOf('.');
    333             String pName = fqcn.substring(0, lastDotPos);
    334             String classOnlyName = fqcn.substring(lastDotPos + 1);
    335             String instPrefix = "new " + classOnlyName + "()";
    336 
    337             openCTSHostFileFor(pName, classOnlyName);
    338 
    339             List<String> methods = entry.getValue();
    340             Collections.sort(methods, new Comparator<String>() {
    341                 public int compare(String s1, String s2) {
    342                     // TODO sort according: test ... N, B, E, VFE
    343                     return s1.compareTo(s2);
    344                 }
    345             });
    346             for (String method : methods) {
    347                 // e.g. testN1
    348                 if (!method.startsWith("test")) {
    349                     throw new RuntimeException("no test method: " + method);
    350                 }
    351 
    352                 // generate the Main_xx java class
    353 
    354                 // a Main_testXXX.java contains:
    355                 // package <packagenamehere>;
    356                 // public class Main_testxxx {
    357                 // public static void main(String[] args) {
    358                 // new dxc.junit.opcodes.aaload.Test_aaload().testN1();
    359                 // }
    360                 // }
    361                 MethodData md = parseTestMethod(pName, classOnlyName, method);
    362                 String methodContent = md.methodBody;
    363 
    364                 Set<String> dependentTestClassNames = parseTestClassName(pName,
    365                         classOnlyName, methodContent);
    366 
    367                 addCTSHostMethod(pName, method, md, dependentTestClassNames);
    368 
    369 
    370                 if (dependentTestClassNames.isEmpty()) {
    371                     continue;
    372                 }
    373 
    374 
    375                 String content = "//autogenerated by "
    376                         + this.getClass().getName()
    377                         + ", do not change\n"
    378                         + "package "
    379                         + pName
    380                         + ";\n"
    381                         + "import "
    382                         + pName
    383                         + ".d.*;\n"
    384                         + "import dot.junit.*;\n"
    385                         + "public class Main_"
    386                         + method
    387                         + " extends DxAbstractMain {\n"
    388                         + "    public static void main(String[] args) "
    389                         + "throws Exception {"
    390                         + methodContent + "\n}\n";
    391 
    392                 String fileName = getFileName(pName, method, ".java");
    393                 File sourceFile = getFileFromPackage(pName, method);
    394 
    395                 File classFile = new File(CLASSES_OUTPUT_FOLDER + "/"
    396                         + getFileName(pName, method, ".class"));
    397                 // if (sourceFile.lastModified() > classFile.lastModified()) {
    398                 writeToFile(sourceFile, content);
    399                 javacBuildStep.addSourceFile(sourceFile.getAbsolutePath());
    400 
    401                 BuildStep dexBuildStep = generateDexBuildStep(
    402                         CLASSES_OUTPUT_FOLDER, getFileName(pName, method, ""));
    403                 targets.add(dexBuildStep);
    404                 // }
    405 
    406 
    407                 // prepare the entry in the data file for the bash script.
    408                 // e.g.
    409                 // main class to execute; opcode/constraint; test purpose
    410                 // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
    411                 // (#1)
    412 
    413                 char ca = method.charAt("test".length()); // either N,B,E,
    414                 // or V (VFE)
    415                 String comment;
    416                 switch (ca) {
    417                 case 'N':
    418                     comment = "Normal #" + method.substring(5);
    419                     break;
    420                 case 'B':
    421                     comment = "Boundary #" + method.substring(5);
    422                     break;
    423                 case 'E':
    424                     comment = "Exception #" + method.substring(5);
    425                     break;
    426                 case 'V':
    427                     comment = "Verifier #" + method.substring(7);
    428                     break;
    429                 default:
    430                     throw new RuntimeException("unknown test abbreviation:"
    431                             + method + " for " + fqcn);
    432                 }
    433 
    434                 String line = pName + ".Main_" + method + ";";
    435                 for (String className : dependentTestClassNames) {
    436                     line += className + " ";
    437                 }
    438 
    439 
    440                 // test description
    441                 String[] pparts = pName.split("\\.");
    442                 // detail e.g. add_double
    443                 String detail = pparts[pparts.length-1];
    444                 // type := opcode | verify
    445                 String type = pparts[pparts.length-2];
    446 
    447                 String description;
    448                 if ("format".equals(type)) {
    449                     description = "format";
    450                 } else if ("opcodes".equals(type)) {
    451                     // Beautify name, so it matches the actual mnemonic
    452                     detail = detail.replaceAll("_", "-");
    453                     detail = detail.replace("-from16", "/from16");
    454                     detail = detail.replace("-high16", "/high16");
    455                     detail = detail.replace("-lit8", "/lit8");
    456                     detail = detail.replace("-lit16", "/lit16");
    457                     detail = detail.replace("-4", "/4");
    458                     detail = detail.replace("-16", "/16");
    459                     detail = detail.replace("-32", "/32");
    460                     detail = detail.replace("-jumbo", "/jumbo");
    461                     detail = detail.replace("-range", "/range");
    462                     detail = detail.replace("-2addr", "/2addr");
    463 
    464                     // Unescape reserved words
    465                     detail = detail.replace("opc-", "");
    466 
    467                     description = detail;
    468                 } else if ("verify".equals(type)) {
    469                     description = "verifier";
    470                 } else {
    471                     description = type + " " + detail;
    472                 }
    473 
    474                 String details = (md.title != null ? md.title : "");
    475                 if (md.constraint != null) {
    476                     details = " Constraint " + md.constraint + ", " + details;
    477                 }
    478                 if (details.length() != 0) {
    479                     details = details.substring(0, 1).toUpperCase()
    480                             + details.substring(1);
    481                 }
    482 
    483                 line += ";" + description + ";" + comment + ";" + details;
    484 
    485                 datafileContent += line + "\n";
    486                 generateBuildStepFor(pName, method, dependentTestClassNames,
    487                         targets);
    488             }
    489 
    490 
    491         }
    492 
    493         // write latest HOSTJUNIT generated file and AllTests.java
    494         ctsFinish();
    495 
    496         File scriptDataDir = new File(OUTPUT_FOLDER + "/data/");
    497         scriptDataDir.mkdirs();
    498         writeToFile(new File(scriptDataDir, "scriptdata"), datafileContent);
    499 
    500         if (!javacHostJunitBuildStep.build()) {
    501             System.out.println("main javac cts-host-hostjunit-classes build " +
    502                     "step failed");
    503             System.exit(1);
    504         }
    505 
    506         if (javacBuildStep.build()) {
    507             for (BuildStep buildStep : targets) {
    508                 if (!buildStep.build()) {
    509                     System.out.println("building failed. buildStep: " +
    510                             buildStep.getClass().getName()+", "+buildStep);
    511                     System.exit(1);
    512                 }
    513             }
    514         } else {
    515             System.out.println("main javac dalvik-cts-buildutil build step " +
    516                     "failed");
    517             System.exit(1);
    518         }
    519     }
    520 
    521     private void generateBuildStepFor(String pName, String method,
    522             Set<String> dependentTestClassNames, Set<BuildStep> targets) {
    523 
    524 
    525         for (String dependentTestClassName : dependentTestClassNames) {
    526             generateBuildStepForDependant(dependentTestClassName, targets);
    527         }
    528     }
    529 
    530     private void generateBuildStepForDependant(String dependentTestClassName,
    531             Set<BuildStep> targets) {
    532 
    533         File sourceFolder = new File(JAVASRC_FOLDER);
    534         String fileName = dependentTestClassName.replace('.', '/').trim();
    535 
    536         if (new File(sourceFolder, fileName + ".dfh").exists()) {
    537 
    538             BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
    539                     JAVASRC_FOLDER, fileName + ".dfh");
    540             BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
    541                     OUTPUT_FOLDER, fileName + ".dex");
    542 
    543             DFHBuildStep buildStep = new DFHBuildStep(inputFile, dexFile);
    544 
    545             BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
    546                     OUTPUT_FOLDER, fileName + ".jar");
    547             JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
    548                     "classes.dex", jarFile, true);
    549             jarBuildStep.addChild(buildStep);
    550 
    551             targets.add(jarBuildStep);
    552             return;
    553         }
    554 
    555         if (new File(sourceFolder, fileName + ".d").exists()) {
    556 
    557             BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
    558                     JAVASRC_FOLDER, fileName + ".d");
    559             BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
    560                     OUTPUT_FOLDER, fileName + ".dex");
    561 
    562             DasmBuildStep buildStep = new DasmBuildStep(inputFile, dexFile);
    563 
    564             BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
    565                     OUTPUT_FOLDER, fileName + ".jar");
    566 
    567             JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
    568                     "classes.dex", jarFile, true);
    569             jarBuildStep.addChild(buildStep);
    570             targets.add(jarBuildStep);
    571             return;
    572         }
    573 
    574         if (new File(sourceFolder, fileName + ".java").exists()) {
    575 
    576             BuildStep dexBuildStep = generateDexBuildStep(
    577                     COMPILED_CLASSES_FOLDER, fileName);
    578             targets.add(dexBuildStep);
    579             return;
    580         }
    581 
    582         try {
    583             if (Class.forName(dependentTestClassName) != null) {
    584 
    585                 BuildStep dexBuildStep = generateDexBuildStep(
    586                         COMPILED_CLASSES_FOLDER, fileName);
    587                 targets.add(dexBuildStep);
    588                 return;
    589             }
    590         } catch (ClassNotFoundException e) {
    591             // do nothing
    592         }
    593 
    594         throw new RuntimeException(
    595                 "neither .dfh,.d,.java file of dependant test class found : "
    596                         + dependentTestClassName + ";" + fileName);
    597     }
    598 
    599     private BuildStep generateDexBuildStep(String classFileFolder,
    600             String classFileName) {
    601         BuildStep.BuildFile classFile = new BuildStep.BuildFile(
    602                 classFileFolder, classFileName + ".class");
    603 
    604         BuildStep.BuildFile tmpJarFile = new BuildStep.BuildFile(OUTPUT_FOLDER,
    605                 classFileName + "_tmp.jar");
    606 
    607         JarBuildStep jarBuildStep = new JarBuildStep(classFile, classFileName
    608                 + ".class", tmpJarFile, false);
    609 
    610         BuildStep.BuildFile outputFile = new BuildStep.BuildFile(OUTPUT_FOLDER,
    611                 classFileName + ".jar");
    612 
    613         DexBuildStep dexBuildStep = new DexBuildStep(tmpJarFile, outputFile,
    614                 true);
    615 
    616         dexBuildStep.addChild(jarBuildStep);
    617         return dexBuildStep;
    618 
    619     }
    620 
    621     /**
    622      * @param pName
    623      * @param classOnlyName
    624      * @param methodSource
    625      * @return testclass names
    626      */
    627     private Set<String> parseTestClassName(String pName, String classOnlyName,
    628             String methodSource) {
    629         Set<String> entries = new HashSet<String>();
    630         String opcodeName = classOnlyName.substring(5);
    631 
    632         Scanner scanner = new Scanner(methodSource);
    633 
    634         String[] patterns = new String[] {
    635                 "new\\s(T_" + opcodeName + "\\w*)",
    636                 "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"};
    637 
    638         String token = null;
    639         for (String pattern : patterns) {
    640             token = scanner.findWithinHorizon(pattern, methodSource.length());
    641             if (token != null) {
    642                 break;
    643             }
    644         }
    645 
    646         if (token == null) {
    647             System.err
    648                     .println("warning: failed to find dependent test class name: "
    649                             + pName
    650                             + ", "
    651                             + classOnlyName
    652                             + " in methodSource:\n" + methodSource);
    653             return entries;
    654         }
    655 
    656         MatchResult result = scanner.match();
    657 
    658         entries.add((pName + ".d." + result.group(1)).trim());
    659 
    660         // search additional @uses directives
    661         Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
    662         Matcher m = p.matcher(methodSource);
    663         while (m.find()) {
    664             String res = m.group(1);
    665             entries.add(res.trim());
    666         }
    667 
    668         // lines with the form @uses
    669         // dot.junit.opcodes.add_double.jm.T_add_double_2
    670         // one dependency per one @uses
    671         // TODO
    672 
    673         return entries;
    674     }
    675 
    676     private MethodData parseTestMethod(String pname, String classOnlyName,
    677             String method) {
    678 
    679         String path = pname.replaceAll("\\.", "/");
    680         String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName
    681                 + ".java";
    682         File f = new File(absPath);
    683 
    684         Scanner scanner;
    685         try {
    686             scanner = new Scanner(f);
    687         } catch (FileNotFoundException e) {
    688             throw new RuntimeException("error while reading to file: "
    689                     + e.getClass().getName() + ", msg:" + e.getMessage());
    690         }
    691 
    692 
    693         String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";
    694 
    695         String token = scanner.findWithinHorizon(methodPattern, (int) f
    696                 .length());
    697         if (token == null) {
    698             throw new RuntimeException(
    699                     "cannot find method source of 'public void" + method
    700                             + "' in file '" + absPath + "'");
    701         }
    702 
    703         MatchResult result = scanner.match();
    704         result.start();
    705         result.end();
    706 
    707         StringBuilder builder = new StringBuilder();
    708         //builder.append(token);
    709 
    710         try {
    711             FileReader reader = new FileReader(f);
    712             reader.skip(result.end());
    713 
    714             char currentChar;
    715             int blocks = 1;
    716             while ((currentChar = (char) reader.read()) != -1 && blocks > 0) {
    717                 switch (currentChar) {
    718                 case '}': {
    719                     blocks--;
    720                     builder.append(currentChar);
    721                     break;
    722                 }
    723                 case '{': {
    724                     blocks++;
    725                     builder.append(currentChar);
    726                     break;
    727                 }
    728                 default: {
    729                     builder.append(currentChar);
    730                     break;
    731                 }
    732                 }
    733             }
    734 	    if (reader != null) {
    735 		reader.close();
    736 	    }
    737         } catch (Exception e) {
    738             throw new RuntimeException("failed to parse", e);
    739         }
    740 
    741         // find the @title/@constraint in javadoc comment for this method
    742         Scanner scanner2;
    743         try {
    744             // using platform's default charset
    745             scanner2 = new Scanner(f);
    746         } catch (FileNotFoundException e) {
    747             throw new RuntimeException("error while reading to file: "
    748                     + e.getClass().getName() + ", msg:" + e.getMessage());
    749         }
    750         // using platform's default charset
    751         String all = new String(FileUtils.readFile(f));
    752         // System.out.println("grepping javadoc found for method "+method +
    753         // " in "+pname+","+classOnlyName);
    754         String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
    755         Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
    756         Matcher m = p.matcher(all);
    757         String title = null, constraint = null;
    758         if (m.find()) {
    759             String res = m.group(1);
    760             // System.out.println("res: "+res);
    761             // now grep @title and @constraint
    762             Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
    763                     .matcher(res);
    764             if (titleM.find()) {
    765                 title = titleM.group(1).replaceAll("\\n     \\*", "");
    766                 title = title.replaceAll("\\n", " ");
    767                 title = title.trim();
    768                 // System.out.println("title: " + title);
    769             } else {
    770                 System.err.println("warning: no @title found for method "
    771                         + method + " in " + pname + "," + classOnlyName);
    772             }
    773             // constraint can be one line only
    774             Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
    775                     res);
    776             if (constraintM.find()) {
    777                 constraint = constraintM.group(1);
    778                 constraint = constraint.trim();
    779                 // System.out.println("constraint: " + constraint);
    780             } else if (method.contains("VFE")) {
    781                 System.err
    782                         .println("warning: no @constraint for for a VFE method:"
    783                                 + method + " in " + pname + "," + classOnlyName);
    784             }
    785         } else {
    786             System.err.println("warning: no javadoc found for method " + method
    787                     + " in " + pname + "," + classOnlyName);
    788         }
    789         MethodData md = new MethodData();
    790         md.methodBody = builder.toString();
    791         md.constraint = constraint;
    792         md.title = title;
    793 	if (scanner != null) {
    794 	    scanner.close();
    795 	}
    796 	if (scanner2 != null) {
    797 	    scanner.close();
    798 	}
    799         return md;
    800     }
    801 
    802     private void writeToFileMkdir(File file, String content) {
    803 	    File parent = file.getParentFile();
    804 	    if (!parent.exists() && !parent.mkdirs()) {
    805 	        throw new RuntimeException("failed to create directory: " +
    806 	                parent.getAbsolutePath());
    807 	    }
    808 	    writeToFile(file, content);
    809     }
    810 
    811     private void writeToFile(File file, String content) {
    812         try {
    813             if (file.length() == content.length()) {
    814                 FileReader reader = new FileReader(file);
    815                 char[] charContents = new char[(int) file.length()];
    816                 reader.read(charContents);
    817                 String contents = new String(charContents);
    818                 if (contents.equals(content)) {
    819                     // System.out.println("skipping identical: "
    820                     // + file.getAbsolutePath());
    821                     return;
    822                 }
    823             }
    824 
    825             //System.out.println("writing file " + file.getAbsolutePath());
    826 
    827             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
    828                     new FileOutputStream(file), "utf-8"));
    829             bw.write(content);
    830             bw.close();
    831         } catch (Exception e) {
    832             throw new RuntimeException("error while writing to file: "
    833                     + e.getClass().getName() + ", msg:" + e.getMessage());
    834         }
    835     }
    836 
    837     private File getFileFromPackage(String pname, String methodName)
    838             throws IOException {
    839         // e.g. dxc.junit.argsreturns.pargsreturn
    840         String path = getFileName(pname, methodName, ".java");
    841         String absPath = MAIN_SRC_OUTPUT_FOLDER + "/" + path;
    842         File dirPath = new File(absPath);
    843         File parent = dirPath.getParentFile();
    844         if (!parent.exists() && !parent.mkdirs()) {
    845             throw new IOException("failed to create directory: " + absPath);
    846         }
    847         return dirPath;
    848     }
    849 
    850     private String getFileName(String pname, String methodName,
    851             String extension) {
    852         String path = pname.replaceAll("\\.", "/");
    853         return new File(path, "Main_" + methodName + extension).getPath();
    854     }
    855 }
    856