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