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      */
main(String[] args)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 
parseArgs(String[] args)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 
printUsage()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 
BuildDalvikSuite(boolean useJack)151     public BuildDalvikSuite(boolean useJack) {
152       this.useJack = useJack;
153     }
154 
compose()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