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