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