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