blob: fa096625017cf0e3a97089b3ac8037f160e99b06 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package util;
import dxc.junit.AllTests;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.textui.TestRunner;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Main class to generate data from the test suite to later run from a shell
* script. the project's home folder.<br>
* <project-home>/src must contain the java sources<br>
* <project-home>/data/scriptdata will be generated<br>
* <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
* (one Main class for each test method in the Test_... class
*/
public class CollectAllTests {
private static String PROJECT_FOLDER = "";
private static String PROJECT_FOLDER_OUT = "missing out folder!";
private static String JAVASRC_FOLDER = PROJECT_FOLDER + "/src";
private static HashSet<String> OPCODES = null;
/*
* a map. key: fully qualified class name, value: a list of test methods for
* the given class
*/
private TreeMap<String, List<String>> map = new TreeMap<String, List<String>>();
private int testClassCnt = 0;
private int testMethodsCnt = 0;
private class MethodData {
String methodBody, constraint, title;
}
/**
* @param args
* args 0 must be the project root folder (where src, lib etc.
* resides)
* args 1 must be the project out root folder (where the Main_*.java files
* are put, and also data/scriptdata)
*/
public static void main(String[] args) {
if (args.length >= 2) {
PROJECT_FOLDER = args[0];
PROJECT_FOLDER_OUT = args[1];
JAVASRC_FOLDER = PROJECT_FOLDER + "/src";
} else {
System.out.println("usage: args 0 must be the project root folder (where src, lib etc. resides)" +
"and args 1 must be the project out root folder (where the Main_*.java file" +
" are put, and also data/scriptdata)");
return;
}
for (int i = 2; i < args.length; i++) {
if (OPCODES == null) {
OPCODES = new HashSet<String>();
}
OPCODES.add(args[i]);
}
System.out.println("using java src:"+JAVASRC_FOLDER);
CollectAllTests cat = new CollectAllTests();
cat.compose();
}
public void compose() {
System.out.println("Collecting all junit tests...");
new TestRunner() {
@Override
protected TestResult createTestResult() {
return new TestResult() {
@Override
protected void run(TestCase test) {
addToTests(test);
}
};
}
}.doRun(AllTests.suite());
// for each combination of TestClass and method, generate a Main_testN1
// etc.
// class in the respective package.
// for the report make sure all N... tests are called first, then B,
// then
// E, then VFE test methods.
// so we need x Main_xxxx methods in a package, and x entries in the
// global scriptdata file (read by a bash script for the tests)
// e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() ->
// File Main_testN1.java in package dxc.junit.opcodes.aaload
// and entry dxc.junit.opcodes.aaload.Main_testN1 in class execution
// table.
//
handleTests();
}
private void addToTests(TestCase test) {
String packageName = test.getClass().getPackage().getName();
packageName = packageName.substring(packageName.lastIndexOf('.')+1);
if (OPCODES != null && !OPCODES.contains(packageName)) {
return;
}
String method = test.getName(); // e.g. testVFE2
String fqcn = test.getClass().getName(); // e.g.
// dxc.junit.opcodes.iload_3.Test_iload_3
// order: take the order of the test-suites for the classes,
// TODO and for methods: take Nx, then Bx, then Ex, then VFEx
//System.out.println("collecting test:" + test.getName() + ", class "
// + test.getClass().getName());
testMethodsCnt++;
List<String> li = map.get(fqcn);
if (li == null) {
testClassCnt++;
li = new ArrayList<String>();
map.put(fqcn, li);
}
li.add(method);
}
private void handleTests() {
System.out.println("collected "+testMethodsCnt+" test methods in "+testClassCnt+" junit test classes");
String datafileContent = "";
for (Entry<String, List<String>> entry : map.entrySet()) {
String fqcn = entry.getKey();
int lastDotPos = fqcn.lastIndexOf('.');
String pName = fqcn.substring(0, lastDotPos);
String classOnlyName = fqcn.substring(lastDotPos + 1);
String instPrefix = "new " + classOnlyName + "()";
String[] nameParts = pName.split("\\.");
if (nameParts.length != 4) {
throw new RuntimeException(
"package name does not comply to naming scheme: " + pName);
}
List<String> methods = entry.getValue();
Collections.sort(methods, new Comparator<String>() {
public int compare(String s1, String s2) {
// TODO sort according: test ... N, B, E, VFE
return s1.compareTo(s2);
}
});
for (String method : methods) {
// e.g. testN1
if (!method.startsWith("test")) {
throw new RuntimeException("no test method: " + method);
}
// generate the Main_xx java class
// a Main_testXXX.java contains:
// package <packagenamehere>;
// public class Main_testxxx {
// public static void main(String[] args) {
// new dxc.junit.opcodes.aaload.Test_aaload().testN1();
// }
// }
MethodData md = parseTestMethod(pName, classOnlyName, method);
String methodContent = md.methodBody;
Set<String> dependentTestClassNames = parseTestClassName(pName,
classOnlyName, methodContent);
if (dependentTestClassNames.isEmpty())
{
continue;
}
String content = "//autogenerated by "
+ this.getClass().getName()
+ ", do not change\n"
+ "package "
+ pName
+ ";\n"
+ "import "
+ pName
+ ".jm.*;\n"
+ "import dxc.junit.*;\n"
+ "public class Main_"
+ method
+ " extends DxAbstractMain {\n"
+ "public static void main(String[] args) throws Exception {\n"
+ "new Main_" + method + "()." + method + "();\n"
+ "}\n" + methodContent + "\n}\n";
writeToFile(getFileFromPackage(pName, method), content);
// prepare the entry in the data file for the bash script.
// e.g.
// main class to execute; opcode/constraint; test purpose
// dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
// (#1)
char ca = method.charAt("test".length()); // either N,B,E, oradd_double
// V (VFE)
String comment;
switch (ca) {
case 'N':
comment = "Normal #" + method.substring(5);
break;
case 'B':
comment = "Boundary #" + method.substring(5);
break;
case 'E':
comment = "Exception #" + method.substring(5);
break;
case 'V':
comment = "Verifier #" + method.substring(7);
break;
default:
throw new RuntimeException("unknown test abbreviation:"
+ method + " for " + fqcn);
}
String opcConstr = pName.substring(pName.lastIndexOf('.') + 1);
// beautify test title
if (opcConstr.startsWith("t4")) {
opcConstr = "verifier"; // + opcConstr.substring(1);
} else if (opcConstr.startsWith("pargs")) {
opcConstr = "sanity";
} else if (opcConstr.startsWith("opc_")) {
// unescape reserved words
opcConstr = opcConstr.substring(4);
}
String line = pName + ".Main_" + method + ";";
for (String className : dependentTestClassNames) {
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(
"dependent class not found : " + className);
} catch (Throwable e) {
// ignore
}
line += className + " ";
}
String details = (md.title != null ? md.title : "");
if (md.constraint != null) {
details = "Constraint " + md.constraint + ", " + details;
}
if (details.length() != 0) {
details = details.substring(0, 1).toUpperCase()
+ details.substring(1);
}
line += ";" + opcConstr + ";"+ comment + ";" + details;
datafileContent += line + "\n";
}
}
new File(PROJECT_FOLDER_OUT + "/data").mkdirs();
writeToFile(new File(PROJECT_FOLDER_OUT + "/data/scriptdata"),
datafileContent);
}
/**
*
* @param pName
* @param classOnlyName
* @param methodSource
* @return a set
*/
private Set<String> parseTestClassName(String pName, String classOnlyName,
String methodSource) {
Set<String> entries = new HashSet<String>();
String opcodeName = classOnlyName.substring(5);
Scanner scanner = new Scanner(methodSource);
String[] patterns = new String[] {
"new\\s(T_" + opcodeName + "\\w*)",
"(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"};
String token = null;
for (String pattern : patterns) {
token = scanner.findWithinHorizon(pattern, methodSource.length());
if (token != null) {
break;
}
}
if (token == null) {
System.err.println("warning: failed to find dependent test class name: "+pName+", "+classOnlyName);
return entries;
}
MatchResult result = scanner.match();
entries.add((pName + ".jm." + result.group(1)).trim());
// search additional @uses directives
Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
Matcher m = p.matcher(methodSource);
while (m.find()) {
String res = m.group(1);
entries.add(res.trim());
}
//lines with the form @uses dx.junit.opcodes.add_double.jm.T_add_double_2
// one dependency per one @uses
//TODO
return entries;
}
private MethodData parseTestMethod(String pname, String classOnlyName,
String method) {
String path = pname.replaceAll("\\.", "/");
String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName
+ ".java";
File f = new File(absPath);
Scanner scanner;
try {
scanner = new Scanner(f);
} catch (FileNotFoundException e) {
throw new RuntimeException("error while reading from file: "
+ e.getClass().getName() + ", msg:" + e.getMessage());
}
String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";
String token = scanner.findWithinHorizon(methodPattern, (int) f
.length());
if (token == null) {
throw new RuntimeException(
"cannot find method source of 'public void" + method
+ "' in file '" + absPath + "'");
}
MatchResult result = scanner.match();
result.start();
result.end();
StringBuilder builder = new StringBuilder();
builder.append(token);
try {
FileReader reader = new FileReader(f);
reader.skip(result.end());
char currentChar;
int blocks = 1;
while ((currentChar = (char) reader.read()) != -1 && blocks > 0) {
switch (currentChar) {
case '}': {
blocks--;
builder.append(currentChar);
break;
}
case '{': {
blocks++;
builder.append(currentChar);
break;
}
default: {
builder.append(currentChar);
break;
}
}
}
} catch (Exception e) {
throw new RuntimeException("failed to parse", e);
}
// find the @title/@constraint in javadoc comment for this method
Scanner scanner2;
try {
// using platform's default charset
scanner2 = new Scanner(f);
} catch (FileNotFoundException e) {
throw new RuntimeException("error while reading from file: "
+ e.getClass().getName() + ", msg:" + e.getMessage());
}
// using platform's default charset
String all = new String(readFile(f));
// System.out.println("grepping javadoc found for method "+method +
// " in "+pname+","+classOnlyName);
String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
Matcher m = p.matcher(all);
String title = null, constraint = null;
if (m.find()) {
String res = m.group(1);
// System.out.println("res: "+res);
// now grep @title and @constraint
Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
.matcher(res);
if (titleM.find()) {
title = titleM.group(1).replaceAll("\\n \\*", "");
title = title.replaceAll("\\n", " ");
title = title.trim();
// System.out.println("title: " + title);
} else {
System.err.println("warning: no @title found for method "
+ method + " in " + pname + "," + classOnlyName);
}
// constraint can be one line only
Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
res);
if (constraintM.find()) {
constraint = constraintM.group(1);
constraint = constraint.trim();
// System.out.println("constraint: " + constraint);
} else if (method.contains("VFE")) {
System.err
.println("warning: no @constraint for for a VFE method:"
+ method + " in " + pname + "," + classOnlyName);
}
} else {
System.err.println("warning: no javadoc found for method " + method
+ " in " + pname + "," + classOnlyName);
}
MethodData md = new MethodData();
md.methodBody = builder.toString();
md.constraint = constraint;
md.title = title;
return md;
}
private void writeToFile(File file, String content) {
//System.out.println("writing file " + file.getAbsolutePath());
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file), "utf-8"));
bw.write(content);
bw.close();
} catch (Exception e) {
throw new RuntimeException("error while writing to file: "
+ e.getClass().getName() + ", msg:" + e.getMessage());
}
}
private File getFileFromPackage(String pname, String methodName) {
// e.g. dxc.junit.argsreturns.pargsreturn
String path = pname.replaceAll("\\.", "/");
String absPath = PROJECT_FOLDER_OUT + "/" + path;
new File(absPath).mkdirs();
return new File(absPath + "/Main_" + methodName + ".java");
}
private byte[] readFile(File file) {
int len = (int) file.length();
byte[] res = new byte[len];
try {
FileInputStream in = new FileInputStream(file);
int pos = 0;
while (len > 0) {
int br = in.read(res, pos, len);
if (br == -1) {
throw new RuntimeException("unexpected EOF for file: "+file);
}
pos += br;
len -= br;
}
in.close();
} catch (IOException ex) {
throw new RuntimeException("error reading file:"+file, ex);
}
return res;
}
}