• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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;
18 
19 import dxc.junit.AllTests;
20 
21 import junit.framework.TestCase;
22 import junit.framework.TestResult;
23 import junit.textui.TestRunner;
24 
25 import java.io.BufferedWriter;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.FileReader;
31 import java.io.IOException;
32 import java.io.OutputStreamWriter;
33 import java.text.MessageFormat;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Scanner;
40 import java.util.Set;
41 import java.util.TreeMap;
42 import java.util.Map.Entry;
43 import java.util.regex.MatchResult;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
46 
47 /**
48  * Main class to generate data from the test suite to later run from a shell
49  * script. the project's home folder.<br>
50  * <project-home>/src must contain the java sources<br>
51  * <project-home>/data/scriptdata will be generated<br>
52  * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
53  * (one Main class for each test method in the Test_... class
54  */
55 public class CollectAllTests {
56 
57     private static String PROJECT_FOLDER = "";
58     private static String PROJECT_FOLDER_OUT = "missing out folder!";
59     private static String JAVASRC_FOLDER = PROJECT_FOLDER + "/src";
60     private static HashSet<String> OPCODES = null;
61 
62     /*
63      * a map. key: fully qualified class name, value: a list of test methods for
64      * the given class
65      */
66     private TreeMap<String, List<String>> map = new TreeMap<String, List<String>>();
67 
68     private int testClassCnt = 0;
69     private int testMethodsCnt = 0;
70 
71     private class MethodData {
72         String methodBody, constraint, title;
73     }
74 
75     /**
76      * @param args
77      *            args 0 must be the project root folder (where src, lib etc.
78      *            resides)
79      *            args 1 must be the project out root folder (where the Main_*.java files
80      *            are put, and also data/scriptdata)
81      */
main(String[] args)82     public static void main(String[] args) {
83         if (args.length >= 2) {
84             PROJECT_FOLDER = args[0];
85             PROJECT_FOLDER_OUT = args[1];
86             JAVASRC_FOLDER = PROJECT_FOLDER + "/src";
87         } else {
88             System.out.println("usage: args 0 must be the project root folder (where src, lib etc. resides)" +
89                     "and args 1 must be the project out root folder (where the Main_*.java file" +
90                     " are put, and also data/scriptdata)");
91             return;
92         }
93 
94 
95         for (int i = 2; i < args.length; i++) {
96             if (OPCODES == null) {
97                 OPCODES = new HashSet<String>();
98             }
99             OPCODES.add(args[i]);
100         }
101 
102         System.out.println("using java src:"+JAVASRC_FOLDER);
103         CollectAllTests cat = new CollectAllTests();
104         cat.compose();
105     }
106 
compose()107     public void compose() {
108         System.out.println("Collecting all junit tests...");
109         new TestRunner() {
110             @Override
111             protected TestResult createTestResult() {
112                 return new TestResult() {
113                     @Override
114                     protected void run(TestCase test) {
115                         addToTests(test);
116                     }
117 
118                 };
119             }
120         }.doRun(AllTests.suite());
121 
122         // for each combination of TestClass and method, generate a Main_testN1
123         // etc.
124         // class in the respective package.
125         // for the report make sure all N... tests are called first, then B,
126         // then
127         // E, then VFE test methods.
128         // so we need x Main_xxxx methods in a package, and x entries in the
129         // global scriptdata file (read by a bash script for the tests)
130         // e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() ->
131         // File Main_testN1.java in package dxc.junit.opcodes.aaload
132         // and entry dxc.junit.opcodes.aaload.Main_testN1 in class execution
133         // table.
134         //
135         handleTests();
136     }
137 
138     private void addToTests(TestCase test) {
139 
140         String packageName = test.getClass().getPackage().getName();
141         packageName = packageName.substring(packageName.lastIndexOf('.')+1);
142         if (OPCODES != null && !OPCODES.contains(packageName)) {
143             return;
144         }
145 
146 
147         String method = test.getName(); // e.g. testVFE2
148         String fqcn = test.getClass().getName(); // e.g.
149         // dxc.junit.opcodes.iload_3.Test_iload_3
150         // order: take the order of the test-suites for the classes,
151         // TODO and for methods: take Nx, then Bx, then Ex, then VFEx
152         //System.out.println("collecting test:" + test.getName() + ", class "
153         //        + test.getClass().getName());
154         testMethodsCnt++;
155         List<String> li = map.get(fqcn);
156         if (li == null) {
157             testClassCnt++;
158             li = new ArrayList<String>();
159             map.put(fqcn, li);
160         }
161         li.add(method);
162     }
163 
164     private void handleTests() {
165         System.out.println("collected "+testMethodsCnt+" test methods in "+testClassCnt+" junit test classes");
166         String datafileContent = "";
167 
168         for (Entry<String, List<String>> entry : map.entrySet()) {
169 
170             String fqcn = entry.getKey();
171             int lastDotPos = fqcn.lastIndexOf('.');
172             String pName = fqcn.substring(0, lastDotPos);
173             String classOnlyName = fqcn.substring(lastDotPos + 1);
174             String instPrefix = "new " + classOnlyName + "()";
175 
176             String[] nameParts = pName.split("\\.");
177             if (nameParts.length != 4) {
178                 throw new RuntimeException(
179                         "package name does not comply to naming scheme: " + pName);
180             }
181 
182 
183             List<String> methods = entry.getValue();
184             Collections.sort(methods, new Comparator<String>() {
185                 public int compare(String s1, String s2) {
186                     // TODO sort according: test ... N, B, E, VFE
187                     return s1.compareTo(s2);
188                 }
189             });
190             for (String method : methods) {
191                 // e.g. testN1
192                 if (!method.startsWith("test")) {
193                     throw new RuntimeException("no test method: " + method);
194                 }
195 
196                 // generate the Main_xx java class
197 
198                 // a Main_testXXX.java contains:
199                 // package <packagenamehere>;
200                 // public class Main_testxxx {
201                 // public static void main(String[] args) {
202                 // new dxc.junit.opcodes.aaload.Test_aaload().testN1();
203                 // }
204                 // }
205 
206                 MethodData md = parseTestMethod(pName, classOnlyName, method);
207                 String methodContent = md.methodBody;
208 
209                 Set<String> dependentTestClassNames = parseTestClassName(pName,
210                         classOnlyName, methodContent);
211 
212                 if (dependentTestClassNames.isEmpty())
213                 {
214                     continue;
215                 }
216 
217 
218                 String content = "//autogenerated by "
219                         + this.getClass().getName()
220                         + ", do not change\n"
221                         + "package "
222                         + pName
223                         + ";\n"
224                         + "import "
225                         + pName
226                         + ".jm.*;\n"
227                         + "import dxc.junit.*;\n"
228                         + "public class Main_"
229                         + method
230                         + " extends DxAbstractMain {\n"
231                         + "public static void main(String[] args) throws Exception {\n"
232                         + "new Main_" + method + "()." + method + "();\n"
233                         + "}\n" + methodContent + "\n}\n";
234 
235                 writeToFile(getFileFromPackage(pName, method), content);
236 
237                 // prepare the entry in the data file for the bash script.
238                 // e.g.
239                 // main class to execute; opcode/constraint; test purpose
240                 // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
241                 // (#1)
242 
243                 char ca = method.charAt("test".length()); // either N,B,E, oradd_double
244                 // V (VFE)
245                 String comment;
246                 switch (ca) {
247                 case 'N':
248                     comment = "Normal #" + method.substring(5);
249                     break;
250                 case 'B':
251                     comment = "Boundary #" + method.substring(5);
252                     break;
253                 case 'E':
254                     comment = "Exception #" + method.substring(5);
255                     break;
256                 case 'V':
257                     comment = "Verifier #" + method.substring(7);
258                     break;
259                 default:
260                     throw new RuntimeException("unknown test abbreviation:"
261                             + method + " for " + fqcn);
262                 }
263 
264                 String opcConstr = pName.substring(pName.lastIndexOf('.') + 1);
265                 // beautify test title
266                 if (opcConstr.startsWith("t4")) {
267                     opcConstr = "verifier"; //  + opcConstr.substring(1);
268                 } else if (opcConstr.startsWith("pargs")) {
269                     opcConstr = "sanity";
270                 } else if (opcConstr.startsWith("opc_")) {
271                     // unescape reserved words
272                     opcConstr = opcConstr.substring(4);
273                 }
274 
275                 String line = pName + ".Main_" + method + ";";
276                 for (String className : dependentTestClassNames) {
277                     try {
278                         Class.forName(className);
279                     } catch (ClassNotFoundException e) {
280                         throw new RuntimeException(
281                                 "dependent class not found : " + className);
282                     } catch (Throwable e) {
283                         // ignore
284                     }
285 
286                     line += className + " ";
287                 }
288 
289                 String details = (md.title != null ? md.title : "");
290                 if (md.constraint != null) {
291                     details = "Constraint " + md.constraint + ", " + details;
292                 }
293                 if (details.length() != 0) {
294                     details = details.substring(0, 1).toUpperCase()
295                             + details.substring(1);
296                 }
297 
298                 line += ";" + opcConstr + ";"+ comment + ";" + details;
299 
300                 datafileContent += line + "\n";
301 
302             }
303 
304 
305         }
306         new File(PROJECT_FOLDER_OUT + "/data").mkdirs();
307         writeToFile(new File(PROJECT_FOLDER_OUT + "/data/scriptdata"),
308                 datafileContent);
309     }
310 
311 
312 
313     /**
314      *
315      * @param pName
316      * @param classOnlyName
317      * @param methodSource
318      * @return a set
319      */
320     private Set<String> parseTestClassName(String pName, String classOnlyName,
321             String methodSource) {
322         Set<String> entries = new HashSet<String>();
323         String opcodeName = classOnlyName.substring(5);
324 
325         Scanner scanner = new Scanner(methodSource);
326 
327         String[] patterns = new String[] {
328                 "new\\s(T_" + opcodeName + "\\w*)",
329                 "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"};
330 
331         String token = null;
332         for (String pattern : patterns) {
333             token = scanner.findWithinHorizon(pattern, methodSource.length());
334             if (token != null) {
335                 break;
336             }
337         }
338 
339         if (token == null) {
340             System.err.println("warning: failed to find dependent test class name: "+pName+", "+classOnlyName);
341             return entries;
342         }
343 
344         MatchResult result = scanner.match();
345 
346         entries.add((pName + ".jm." + result.group(1)).trim());
347 
348         // search additional @uses directives
349         Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
350         Matcher m = p.matcher(methodSource);
351         while (m.find()) {
352             String res = m.group(1);
353             entries.add(res.trim());
354         }
355 
356         //lines with the form @uses dx.junit.opcodes.add_double.jm.T_add_double_2
357         // one dependency per one @uses
358         //TODO
359 
360         return entries;
361     }
362 
363     private MethodData parseTestMethod(String pname, String classOnlyName,
364             String method) {
365 
366         String path = pname.replaceAll("\\.", "/");
367         String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName
368                 + ".java";
369         File f = new File(absPath);
370 
371         Scanner scanner;
372         try {
373             scanner = new Scanner(f);
374         } catch (FileNotFoundException e) {
375             throw new RuntimeException("error while reading from file: "
376                     + e.getClass().getName() + ", msg:" + e.getMessage());
377         }
378 
379         String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";
380 
381         String token = scanner.findWithinHorizon(methodPattern, (int) f
382                 .length());
383         if (token == null) {
384             throw new RuntimeException(
385                     "cannot find method source of 'public void" + method
386                             + "' in file '" + absPath + "'");
387         }
388 
389         MatchResult result = scanner.match();
390         result.start();
391         result.end();
392 
393         StringBuilder builder = new StringBuilder();
394         builder.append(token);
395 
396         try {
397             FileReader reader = new FileReader(f);
398             reader.skip(result.end());
399 
400             char currentChar;
401             int blocks = 1;
402             while ((currentChar = (char) reader.read()) != -1 && blocks > 0) {
403                 switch (currentChar) {
404                 case '}': {
405                     blocks--;
406                     builder.append(currentChar);
407                     break;
408                 }
409                 case '{': {
410                     blocks++;
411                     builder.append(currentChar);
412                     break;
413                 }
414                 default: {
415                     builder.append(currentChar);
416                     break;
417                 }
418                 }
419             }
420         } catch (Exception e) {
421             throw new RuntimeException("failed to parse", e);
422         }
423 
424         // find the @title/@constraint in javadoc comment for this method
425         Scanner scanner2;
426         try {
427             // using platform's default charset
428             scanner2 = new Scanner(f);
429         } catch (FileNotFoundException e) {
430             throw new RuntimeException("error while reading from file: "
431                     + e.getClass().getName() + ", msg:" + e.getMessage());
432         }
433 
434         // using platform's default charset
435         String all = new String(readFile(f));
436         // System.out.println("grepping javadoc found for method "+method +
437         // " in "+pname+","+classOnlyName);
438         String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
439         Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
440         Matcher m = p.matcher(all);
441         String title = null, constraint = null;
442         if (m.find()) {
443             String res = m.group(1);
444             // System.out.println("res: "+res);
445             // now grep @title and @constraint
446             Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
447                     .matcher(res);
448             if (titleM.find()) {
449                 title = titleM.group(1).replaceAll("\\n     \\*", "");
450                 title = title.replaceAll("\\n", " ");
451                 title = title.trim();
452                 // System.out.println("title: " + title);
453             } else {
454                 System.err.println("warning: no @title found for method "
455                         + method + " in " + pname + "," + classOnlyName);
456             }
457             // constraint can be one line only
458             Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
459                     res);
460             if (constraintM.find()) {
461                 constraint = constraintM.group(1);
462                 constraint = constraint.trim();
463                 // System.out.println("constraint: " + constraint);
464             } else if (method.contains("VFE")) {
465                 System.err
466                         .println("warning: no @constraint for for a VFE method:"
467                                 + method + " in " + pname + "," + classOnlyName);
468             }
469         } else {
470             System.err.println("warning: no javadoc found for method " + method
471                     + " in " + pname + "," + classOnlyName);
472         }
473         MethodData md = new MethodData();
474         md.methodBody = builder.toString();
475         md.constraint = constraint;
476         md.title = title;
477         return md;
478     }
479 
480     private void writeToFile(File file, String content) {
481         //System.out.println("writing file " + file.getAbsolutePath());
482         try {
483             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
484                     new FileOutputStream(file), "utf-8"));
485             bw.write(content);
486             bw.close();
487         } catch (Exception e) {
488             throw new RuntimeException("error while writing to file: "
489                     + e.getClass().getName() + ", msg:" + e.getMessage());
490         }
491     }
492 
493     private File getFileFromPackage(String pname, String methodName) {
494         // e.g. dxc.junit.argsreturns.pargsreturn
495         String path = pname.replaceAll("\\.", "/");
496         String absPath = PROJECT_FOLDER_OUT + "/" + path;
497         new File(absPath).mkdirs();
498         return new File(absPath + "/Main_" + methodName + ".java");
499     }
500 
501     private byte[] readFile(File file) {
502         int len = (int) file.length();
503         byte[] res = new byte[len];
504         try {
505             FileInputStream in = new FileInputStream(file);
506             int pos = 0;
507             while (len > 0) {
508                 int br = in.read(res, pos, len);
509                 if (br == -1) {
510                     throw new RuntimeException("unexpected EOF for file: "+file);
511                 }
512                 pos += br;
513                 len -= br;
514             }
515             in.close();
516         } catch (IOException ex) {
517             throw new RuntimeException("error reading file:"+file, ex);
518         }
519         return res;
520     }
521 }
522