blob: 56ec428ab8a2c13d3d92b04bc15f37dbc162146c [file] [log] [blame]
/*
* Copyright (C) 2011 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.build;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* 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>/src/<for-each-package>/Main_testN1.java will be generated<br>
* (one Main class for each test method in the Test_... class
*/
public class BuildDalvikSuite extends BuildUtilBase {
public static final String TARGET_MAIN_FILE = "mains.jar";
private static String MAIN_SRC_OUTPUT_FOLDER = "";
// the folder for the generated junit-files for the cts host (which in turn
// execute the real vm tests using adb push/shell etc)
private static String HOSTJUNIT_SRC_OUTPUT_FOLDER = "";
private static String OUTPUT_FOLDER = "";
private static String COMPILED_CLASSES_FOLDER = "";
private static String CLASSES_OUTPUT_FOLDER = "";
private static String HOSTJUNIT_CLASSES_OUTPUT_FOLDER = "";
private static String CLASS_PATH = "";
private static final String TARGET_JAR_ROOT_PATH = "/data/local/tmp/vm-tests";
private String JAVASRC_FOLDER;
/**
* @param args
* args 0 must be the project root folder (where src, lib etc.
* resides)
* @throws IOException
*/
public static void main(String[] args) throws IOException {
BuildDalvikSuite cat = new BuildDalvikSuite();
if (!cat.parseArgs(args)) {
printUsage();
System.exit(-1);
}
long start = System.currentTimeMillis();
cat.run(null);
long end = System.currentTimeMillis();
System.out.println("elapsed seconds: " + (end - start) / 1000);
}
private boolean parseArgs(String[] args) {
if (args.length == 6) {
JAVASRC_FOLDER = args[0];
OUTPUT_FOLDER = args[1];
CLASS_PATH = args[2];
MAIN_SRC_OUTPUT_FOLDER = args[3];
CLASSES_OUTPUT_FOLDER = MAIN_SRC_OUTPUT_FOLDER + "/classes";
COMPILED_CLASSES_FOLDER = args[4];
HOSTJUNIT_SRC_OUTPUT_FOLDER = args[5];
HOSTJUNIT_CLASSES_OUTPUT_FOLDER = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/classes";
return true;
} else {
return false;
}
}
private static void printUsage() {
System.out.println("usage: java-src-folder output-folder classpath " +
"generated-main-files compiled_output generated-main-files " +
"[restrict-to-opcode]");
}
private SourceBuildStep hostJunitBuildStep;
private static class HostState {
private String fileName;
private StringBuilder fileData;
public HostState(String fileName) {
this.fileName = fileName;
fileData = new StringBuilder();
}
public void append(String s) {
fileData.append(s);
}
private void addCTSHostMethod(String pName, String method,
Collection<String> dependentTestClassNames) {
fileData.append("public void " + method + "() throws Exception {\n");
final String targetCoreJarPath = String.format("%s/dot/junit/dexcore.jar",
TARGET_JAR_ROOT_PATH);
String mainsJar = String.format("%s/%s", TARGET_JAR_ROOT_PATH, TARGET_MAIN_FILE);
String cp = String.format("%s:%s", targetCoreJarPath, mainsJar);
for (String depFqcn : dependentTestClassNames) {
String sourceName = depFqcn.replaceAll("\\.", "/") + ".jar";
String targetName= String.format("%s/%s", TARGET_JAR_ROOT_PATH,
sourceName);
cp += ":" + targetName;
// dot.junit.opcodes.invoke_interface_range.ITest
// -> dot/junit/opcodes/invoke_interface_range/ITest.jar
}
//"dot.junit.opcodes.add_double_2addr.Main_testN2";
String mainclass = pName + ".Main_" + method;
fileData.append(getShellExecJavaLine(cp, mainclass));
fileData.append("\n}\n\n");
}
public void end() {
fileData.append("\n}\n");
}
public File getFileToWrite() {
return new File(fileName);
}
public String getBuildStep() {
return new File(fileName).getAbsolutePath();
}
public String getData() {
return fileData.toString();
}
}
private void flushHostState(HostState state) {
state.end();
File toWrite = state.getFileToWrite();
writeToFileMkdir(toWrite, state.getData());
hostJunitBuildStep.addSourceFile(state.getBuildStep());
}
private HostState openCTSHostFileFor(String pName, String classOnlyName) {
String sourceName = classOnlyName;
String modPackage = pName;
{
// Given a class name of "Test_zzz" and a package of "xxx.yyy.zzz," strip
// "zzz" from the package to reduce duplication (and dashboard clutter).
int lastDot = modPackage.lastIndexOf('.');
if (lastDot > 0) {
String lastPackageComponent = modPackage.substring(lastDot + 1);
if (classOnlyName.equals("Test_" + lastPackageComponent)) {
// Drop the duplication.
modPackage = modPackage.substring(0, lastDot);
}
}
}
String fileName = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/" + modPackage.replaceAll("\\.", "/")
+ "/" + sourceName + ".java";
HostState newState = new HostState(fileName);
newState.append(getWarningMessage());
newState.append("package " + modPackage + ";\n");
newState.append("import java.io.IOException;\n" +
"import java.util.concurrent.TimeUnit;\n\n" +
"import com.android.tradefed.device.CollectingOutputReceiver;\n" +
"import com.android.tradefed.testtype.IAbi;\n" +
"import com.android.tradefed.testtype.IAbiReceiver;\n" +
"import com.android.tradefed.testtype.DeviceTestCase;\n" +
"import com.android.tradefed.util.AbiFormatter;\n" +
"\n");
newState.append("public class " + sourceName + " extends DeviceTestCase implements " +
"IAbiReceiver {\n");
newState.append("\n" +
"protected IAbi mAbi;\n" +
"@Override\n" +
"public void setAbi(IAbi abi) {\n" +
" mAbi = abi;\n" +
"}\n\n");
return newState;
}
private static String getShellExecJavaLine(String classpath, String mainclass) {
String cmd = String.format("ANDROID_DATA=%s dalvikvm|#ABI#| -Xmx512M -Xss32K -Xnodex2oat " +
"-Djava.io.tmpdir=%s -classpath %s %s", TARGET_JAR_ROOT_PATH, TARGET_JAR_ROOT_PATH,
classpath, mainclass);
StringBuilder code = new StringBuilder();
code.append(" String cmd = AbiFormatter.formatCmdForAbi(\"")
.append(cmd)
.append("\", mAbi.getBitness());\n")
.append(" CollectingOutputReceiver receiver = new CollectingOutputReceiver();\n")
.append(" getDevice().executeShellCommand(cmd, receiver, 6, TimeUnit.MINUTES, 1);\n")
.append(" // A sucessful adb shell command returns an empty string.\n")
.append(" assertEquals(cmd, \"\", receiver.getOutput());");
return code.toString();
}
private String getWarningMessage() {
return "//Autogenerated code by " + this.getClass().getName() + "; do not edit.\n";
}
class MyTestHandler implements TestHandler {
public String datafileContent = "";
Set<BuildStep> targets = new TreeSet<BuildStep>();
public SourceBuildStep srcBuildStep = new JavacBuildStep(CLASSES_OUTPUT_FOLDER, CLASS_PATH);
@Override
public void handleTest(String fqcn, List<String> methods) {
int lastDotPos = fqcn.lastIndexOf('.');
String pName = fqcn.substring(0, lastDotPos);
String classOnlyName = fqcn.substring(lastDotPos + 1);
HostState hostState = openCTSHostFileFor(pName, classOnlyName);
Collections.sort(methods, new Comparator<String>() {
@Override
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;
List<String> dependentTestClassNames = parseTestClassName(pName,
classOnlyName, methodContent);
hostState.addCTSHostMethod(pName, method, dependentTestClassNames);
if (dependentTestClassNames.isEmpty()) {
continue;
}
String content = getWarningMessage() +
"package " + pName + ";\n" +
"import " + pName + ".d.*;\n" +
"import dot.junit.*;\n" +
"public class Main_" + method + " extends DxAbstractMain {\n" +
" public static void main(String[] args) throws Exception {" +
methodContent + "\n}\n";
File sourceFile;
try {
sourceFile = getFileFromPackage(pName, method);
} catch (IOException e) {
throw new RuntimeException(e);
}
writeToFile(sourceFile, content);
srcBuildStep.addSourceFile(sourceFile.getAbsolutePath());
// 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,
// or 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 line = pName + ".Main_" + method + ";";
for (String className : dependentTestClassNames) {
line += className + " ";
}
// test description
String[] pparts = pName.split("\\.");
// detail e.g. add_double
String detail = pparts[pparts.length-1];
// type := opcode | verify
String type = pparts[pparts.length-2];
String description;
if ("format".equals(type)) {
description = "format";
} else if ("opcodes".equals(type)) {
// Beautify name, so it matches the actual mnemonic
detail = detail.replaceAll("_", "-");
detail = detail.replace("-from16", "/from16");
detail = detail.replace("-high16", "/high16");
detail = detail.replace("-lit8", "/lit8");
detail = detail.replace("-lit16", "/lit16");
detail = detail.replace("-4", "/4");
detail = detail.replace("-16", "/16");
detail = detail.replace("-32", "/32");
detail = detail.replace("-jumbo", "/jumbo");
detail = detail.replace("-range", "/range");
detail = detail.replace("-2addr", "/2addr");
// Unescape reserved words
detail = detail.replace("opc-", "");
description = detail;
} else if ("verify".equals(type)) {
description = "verifier";
} else {
description = type + " " + detail;
}
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 += ";" + description + ";" + comment + ";" + details;
datafileContent += line + "\n";
generateBuildStepFor(dependentTestClassNames, targets);
}
flushHostState(hostState);
}
}
@Override
protected void handleTests(JUnitTestCollector tests, TestHandler ignored) {
hostJunitBuildStep = new JavacBuildStep(
HOSTJUNIT_CLASSES_OUTPUT_FOLDER, CLASS_PATH);
MyTestHandler handler = new MyTestHandler();
super.handleTests(tests, handler);
D8BuildStep dexBuildStep = new D8BuildStep(
new BuildStep.BuildFile(new File(CLASSES_OUTPUT_FOLDER)),
new BuildStep.BuildFile(new File(OUTPUT_FOLDER + File.separator + TARGET_MAIN_FILE)),
false);
handler.targets.add(dexBuildStep);
File scriptDataDir = new File(OUTPUT_FOLDER + "/data/");
scriptDataDir.mkdirs();
writeToFile(new File(scriptDataDir, "scriptdata"), handler.datafileContent);
if (!hostJunitBuildStep.build()) {
System.out.println("main javac cts-host-hostjunit-classes build step failed");
System.exit(1);
}
if (!handler.srcBuildStep.build()) {
System.out.println("main src dalvik-cts-buildutil build step failed");
System.exit(1);
}
for (BuildStep buildStep : handler.targets) {
if (!buildStep.build()) {
System.out.println("building failed. buildStep: " +
buildStep.getClass().getName() + ", " + buildStep);
System.exit(1);
}
}
}
private void generateBuildStepFor(Collection<String> dependentTestClassNames,
Set<BuildStep> targets) {
for (String dependentTestClassName : dependentTestClassNames) {
generateBuildStepForDependant(dependentTestClassName, targets);
}
}
private void generateBuildStepForDependant(String dependentTestClassName,
Set<BuildStep> targets) {
File sourceFolder = new File(JAVASRC_FOLDER);
String fileName = dependentTestClassName.replace('.', '/').trim();
if (new File(sourceFolder, fileName + ".dfh").exists()) {
BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
JAVASRC_FOLDER, fileName + ".dfh");
BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".dex");
DFHBuildStep buildStep = new DFHBuildStep(inputFile, dexFile);
BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".jar");
JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
"classes.dex", jarFile, true);
jarBuildStep.addChild(buildStep);
targets.add(jarBuildStep);
return;
}
if (new File(sourceFolder, fileName + ".d").exists() &&
!new File(sourceFolder, fileName + ".smali").exists()) {
BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
JAVASRC_FOLDER, fileName + ".d");
BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".dex");
DasmBuildStep buildStep = new DasmBuildStep(inputFile, dexFile);
BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".jar");
JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
"classes.dex", jarFile, true);
jarBuildStep.addChild(buildStep);
targets.add(jarBuildStep);
return;
}
if (new File(sourceFolder, fileName + ".smali").exists()) {
BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
JAVASRC_FOLDER, fileName + ".smali");
BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".dex");
SmaliBuildStep buildStep = new SmaliBuildStep(inputFile, dexFile);
BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
OUTPUT_FOLDER, fileName + ".jar");
JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
"classes.dex", jarFile, true);
jarBuildStep.addChild(buildStep);
targets.add(jarBuildStep);
return;
}
File srcFile = new File(sourceFolder, fileName + ".java");
if (srcFile.exists()) {
BuildStep dexBuildStep;
dexBuildStep = generateDexBuildStep(
COMPILED_CLASSES_FOLDER, fileName);
targets.add(dexBuildStep);
return;
}
try {
if (Class.forName(dependentTestClassName) != null) {
BuildStep dexBuildStep = generateDexBuildStep(
COMPILED_CLASSES_FOLDER, fileName);
targets.add(dexBuildStep);
return;
}
} catch (ClassNotFoundException e) {
// do nothing
}
throw new RuntimeException("neither .dfh,.d,.java file of dependant test class found : " +
dependentTestClassName + ";" + fileName);
}
private BuildStep generateDexBuildStep(String classFileFolder,
String classFileName) {
BuildStep.BuildFile classFile = new BuildStep.BuildFile(
classFileFolder, classFileName + ".class");
BuildStep.BuildFile tmpJarFile = new BuildStep.BuildFile(
OUTPUT_FOLDER,
classFileName + "_tmp.jar");
JarBuildStep jarBuildStep = new JarBuildStep(classFile,
classFileName + ".class", tmpJarFile, false);
BuildStep.BuildFile outputFile = new BuildStep.BuildFile(
OUTPUT_FOLDER,
classFileName + ".jar");
D8BuildStep dexBuildStep = new D8BuildStep(tmpJarFile,
outputFile,
true);
dexBuildStep.addChild(jarBuildStep);
return dexBuildStep;
}
private File getFileFromPackage(String pname, String methodName)
throws IOException {
// e.g. dxc.junit.argsreturns.pargsreturn
String path = getFileName(pname, methodName, ".java");
String absPath = MAIN_SRC_OUTPUT_FOLDER + "/" + path;
File dirPath = new File(absPath);
File parent = dirPath.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
throw new IOException("failed to create directory: " + absPath);
}
return dirPath;
}
private String getFileName(String pname, String methodName,
String extension) {
String path = pname.replaceAll("\\.", "/");
return new File(path, "Main_" + methodName + extension).getPath();
}
}