1 /*
2  * Copyright (C) 2019 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 package com.android.tradefed.cluster;
17 
18 import com.android.tradefed.log.LogUtil;
19 import com.android.tradefed.result.LegacySubprocessResultsReporter;
20 import com.android.tradefed.util.FileUtil;
21 import com.android.tradefed.util.QuotationAwareTokenizer;
22 import com.android.tradefed.util.StreamUtil;
23 import com.android.tradefed.util.ZipUtil2;
24 
25 import java.io.BufferedInputStream;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.util.Set;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarOutputStream;
33 import java.util.jar.Manifest;
34 
35 /**
36  * A class to build a wrapper configuration file to use subprocess results reporter for a cluster
37  * command.
38  */
39 public class SubprocessReportingHelper {
40     private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar";
41     private static final String CLASS_FILTER =
42             String.format(
43                     "(^%s|^%s|^%s|^%s|^%s).*class$",
44                     "LegacySubprocessResultsReporter",
45                     "SubprocessTestResultsParser",
46                     "SubprocessEventHelper",
47                     "SubprocessResultsReporter",
48                     "ISupportGranularResults");
49 
50     /**
51      * Dynamically generate extract .class file from tradefed.jar and generate new subprocess
52      * results reporter jar.
53      *
54      * @param parentDir parent directory of subprocess results reporter jar.
55      * @return subprocess result reporter jar.
56      * @throws IOException
57      */
createSubprocessReporterJar(File parentDir)58     public File createSubprocessReporterJar(File parentDir) throws IOException {
59         File reporterJar = new File(parentDir, REPORTER_JAR_NAME);
60         File tfJar =
61                 new File(
62                         LegacySubprocessResultsReporter.class
63                                 .getProtectionDomain()
64                                 .getCodeSource()
65                                 .getLocation()
66                                 .getPath());
67         // tfJar is directory of .class file when running JUnit test from Eclipse IDE
68         if (tfJar.isDirectory()) {
69             Set<File> classFiles = FileUtil.findFilesObject(tfJar, CLASS_FILTER);
70             Manifest manifest = new Manifest();
71             createJar(reporterJar, manifest, classFiles);
72         }
73         // tfJar is the tradefed.jar when running with tradefed.
74         else {
75             File extractedJar = ZipUtil2.extractZipToTemp(tfJar, "tmp-jar");
76             try {
77                 Set<File> classFiles = FileUtil.findFilesObject(extractedJar, CLASS_FILTER);
78                 File mf = FileUtil.findFile(extractedJar, "MANIFEST.MF");
79                 Manifest manifest = new Manifest(new FileInputStream(mf));
80                 createJar(reporterJar, manifest, classFiles);
81             } finally {
82                 FileUtil.recursiveDelete(extractedJar);
83             }
84         }
85         return reporterJar;
86     }
87 
88     /**
89      * Create jar file.
90      *
91      * @param jar jar file to be created.
92      * @param manifest manifest file.
93      * @throws IOException
94      */
createJar(File jar, Manifest manifest, Set<File> classFiles)95     private void createJar(File jar, Manifest manifest, Set<File> classFiles) throws IOException {
96         try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(jar), manifest)) {
97             for (File file : classFiles) {
98                 try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
99                     String path = file.getPath();
100                     JarEntry entry = new JarEntry(path.substring(path.indexOf("com")));
101                     entry.setTime(file.lastModified());
102                     jarOutput.putNextEntry(entry);
103                     StreamUtil.copyStreams(in, jarOutput);
104                     jarOutput.closeEntry();
105                 }
106             }
107         }
108     }
109 
110     /**
111      * Get a new command line whose configuration argument is replaced by a newly-created wrapper
112      * configuration.
113      *
114      * <p>The resulting command line will reference a generate XML file in parentDir and needs to
115      * run from parentDir.
116      *
117      * @param commandLine old command line that will be run by subprocess.
118      * @param port port number that subprocess should use to report results.
119      * @param parentDir parent directory of new wrapper configuration.
120      * @return new command line, whose first argument is wrapper config.
121      * @throws IOException
122      */
buildNewCommandConfig(String commandLine, String port, File parentDir)123     public String buildNewCommandConfig(String commandLine, String port, File parentDir)
124             throws IOException {
125         String[] tokens = QuotationAwareTokenizer.tokenizeLine(commandLine);
126         SubprocessConfigBuilder builder = new SubprocessConfigBuilder();
127         builder.setWorkingDir(parentDir).setOriginalConfig(tokens[0]).setPort(port);
128         File f = builder.build();
129         LogUtil.CLog.i("Generating new configuration:\n %s", FileUtil.readStringFromFile(f));
130         tokens[0] = f.getName();
131         return QuotationAwareTokenizer.combineTokens(tokens);
132     }
133 }
134