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 java.io.File;
20 import java.io.IOException;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.List;
25 
26 /**
27  * Main class to generate data from the test suite to later run from a shell
28  * script. the project's home folder.<br>
29  * <project-home>/src must contain the java sources<br>
30  * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
31  * (one Main class for each test method in the Test_... class
32  */
33 public class BuildCTSHostSources extends BuildUtilBase {
34 
35     public static final String TARGET_MAIN_FILE = "mains.jar";
36 
37     // the folder for the generated junit-files for the cts host (which in turn
38     // execute the real vm tests using adb push/shell etc)
39     private static String HOSTJUNIT_SRC_OUTPUT_FOLDER = "";
40 
41     private static final String TARGET_JAR_ROOT_PATH = "/data/local/tmp/vm-tests";
42     private static final String APEX_ART_BIN_PATH = "/apex/com.android.art/bin";
43 
44     /**
45      * @param args
46      *            args 0 must be the project root folder (where src, lib etc.
47      *            resides)
48      * @throws IOException
49      */
main(String[] args)50     public static void main(String[] args) throws IOException {
51         BuildCTSHostSources cat = new BuildCTSHostSources();
52 
53         if (!cat.parseArgs(args)) {
54           printUsage();
55           System.exit(-1);
56         }
57 
58         long start = System.currentTimeMillis();
59         cat.run(cat::handleTest);
60         long end = System.currentTimeMillis();
61 
62         System.out.println("elapsed seconds: " + (end - start) / 1000);
63     }
64 
parseArgs(String[] args)65     private boolean parseArgs(String[] args) {
66       if (args.length == 1) {
67           HOSTJUNIT_SRC_OUTPUT_FOLDER = args[0];
68           return true;
69       } else {
70           return false;
71       }
72     }
73 
printUsage()74     private static void printUsage() {
75         System.out.println("usage: java-src-folder output-folder classpath " +
76                            "generated-main-files compiled_output generated-main-files " +
77                            "[restrict-to-opcode]");
78     }
79 
80     private static class HostState {
81         private String fileName;
82         private StringBuilder fileData;
83 
HostState(String fileName)84         public HostState(String fileName) {
85             this.fileName = fileName;
86             fileData = new StringBuilder();
87         }
88 
append(String s)89         public void append(String s) {
90             fileData.append(s);
91         }
92 
addCTSHostMethod(String pName, String method, Collection<String> dependentTestClassNames)93         private void addCTSHostMethod(String pName, String method,
94                 Collection<String> dependentTestClassNames) {
95             fileData.append("public void " + method + "() throws Exception {\n");
96             final String targetCoreJarPath = String.format("%s/dot/junit/dexcore.jar",
97                     TARGET_JAR_ROOT_PATH);
98 
99             String mainsJar = String.format("%s/%s", TARGET_JAR_ROOT_PATH, TARGET_MAIN_FILE);
100 
101             String cp = String.format("%s:%s", targetCoreJarPath, mainsJar);
102             for (String depFqcn : dependentTestClassNames) {
103                 String sourceName = depFqcn.replaceAll("\\.", "/") + ".jar";
104                 String targetName= String.format("%s/%s", TARGET_JAR_ROOT_PATH,
105                         sourceName);
106                 cp += ":" + targetName;
107                 // dot.junit.opcodes.invoke_interface_range.ITest
108                 // -> dot/junit/opcodes/invoke_interface_range/ITest.jar
109             }
110 
111             //"dot.junit.opcodes.add_double_2addr.Main_testN2";
112             String mainclass = pName + ".Main_" + method;
113             fileData.append(getShellExecJavaLine(cp, mainclass));
114             fileData.append("\n}\n\n");
115         }
116 
end()117         public void end() {
118             fileData.append("\n}\n");
119         }
120 
getFileToWrite()121         public File getFileToWrite() {
122             return new File(fileName);
123         }
getData()124         public String getData() {
125             return fileData.toString();
126         }
127     }
128 
flushHostState(HostState state)129     private void flushHostState(HostState state) {
130         state.end();
131 
132         File toWrite = state.getFileToWrite();
133         writeToFileMkdir(toWrite, state.getData());
134     }
135 
openCTSHostFileFor(String pName, String classOnlyName)136     private HostState openCTSHostFileFor(String pName, String classOnlyName) {
137         String sourceName = classOnlyName;
138 
139         String modPackage = pName;
140         {
141             // Given a class name of "Test_zzz" and a package of "xxx.yyy.zzz," strip
142             // "zzz" from the package to reduce duplication (and dashboard clutter).
143             int lastDot = modPackage.lastIndexOf('.');
144             if (lastDot > 0) {
145                 String lastPackageComponent = modPackage.substring(lastDot + 1);
146                 if (classOnlyName.equals("Test_" + lastPackageComponent)) {
147                     // Drop the duplication.
148                     modPackage = modPackage.substring(0, lastDot);
149                 }
150             }
151         }
152 
153         String fileName = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/" + modPackage.replaceAll("\\.", "/")
154                 + "/" + sourceName + ".java";
155 
156         HostState newState = new HostState(fileName);
157 
158         newState.append(getWarningMessage());
159         newState.append("package " + modPackage + ";\n");
160         newState.append("import java.io.IOException;\n" +
161                 "import java.util.concurrent.TimeUnit;\n\n" +
162                 "import com.android.tradefed.device.CollectingOutputReceiver;\n" +
163                 "import com.android.tradefed.testtype.IAbi;\n" +
164                 "import com.android.tradefed.testtype.IAbiReceiver;\n" +
165                 "import com.android.tradefed.testtype.DeviceTestCase;\n" +
166                 "import com.android.tradefed.util.AbiFormatter;\n" +
167                 "\n");
168         newState.append("public class " + sourceName + " extends DeviceTestCase implements " +
169                 "IAbiReceiver {\n");
170 
171         newState.append("\n" +
172                 "protected IAbi mAbi;\n" +
173                 "@Override\n" +
174                 "public void setAbi(IAbi abi) {\n" +
175                 "    mAbi = abi;\n" +
176                 "}\n\n");
177 
178         return newState;
179     }
180 
getShellExecJavaLine(String classpath, String mainclass)181     private static String getShellExecJavaLine(String classpath, String mainclass) {
182       String cmd = String.format("ANDROID_DATA=%s %s/dalvikvm|#ABI#| -Xmx512M -Xss32K " +
183               "-Djava.io.tmpdir=%s -classpath %s %s", TARGET_JAR_ROOT_PATH, APEX_ART_BIN_PATH,
184               TARGET_JAR_ROOT_PATH, classpath, mainclass);
185       StringBuilder code = new StringBuilder();
186       code.append("    String cmd = AbiFormatter.formatCmdForAbi(\"")
187           .append(cmd)
188           .append("\", mAbi.getBitness());\n")
189           .append("    CollectingOutputReceiver receiver = new CollectingOutputReceiver();\n")
190           .append("    getDevice().executeShellCommand(cmd, receiver, 6, TimeUnit.MINUTES, 1);\n")
191           .append("    // A sucessful adb shell command returns an empty string.\n")
192           .append("    assertEquals(cmd, \"\", receiver.getOutput());");
193       return code.toString();
194     }
195 
getWarningMessage()196     private String getWarningMessage() {
197         return "//Autogenerated code by " + this.getClass().getName() + "; do not edit.\n";
198     }
199 
handleTest(String fqcn, List<String> methods)200     private void handleTest(String fqcn, List<String> methods) {
201         int lastDotPos = fqcn.lastIndexOf('.');
202         String pName = fqcn.substring(0, lastDotPos);
203         String classOnlyName = fqcn.substring(lastDotPos + 1);
204 
205         HostState hostState = openCTSHostFileFor(pName, classOnlyName);
206 
207         Collections.sort(methods, new Comparator<String>() {
208             @Override
209             public int compare(String s1, String s2) {
210                 // TODO sort according: test ... N, B, E, VFE
211                 return s1.compareTo(s2);
212             }
213         });
214         for (String method : methods) {
215             // e.g. testN1
216             if (!method.startsWith("test")) {
217                 throw new RuntimeException("no test method: " + method);
218             }
219 
220             // generate the Main_xx java class
221 
222             // a Main_testXXX.java contains:
223             // package <packagenamehere>;
224             // public class Main_testxxx {
225             // public static void main(String[] args) {
226             // new dxc.junit.opcodes.aaload.Test_aaload().testN1();
227             // }
228             // }
229             MethodData md = parseTestMethod(pName, classOnlyName, method);
230             String methodContent = md.methodBody;
231 
232             List<String> dependentTestClassNames = parseTestClassName(pName,
233                     classOnlyName, methodContent);
234 
235             hostState.addCTSHostMethod(pName, method, dependentTestClassNames);
236         }
237 
238         flushHostState(hostState);
239     }
240 }
241