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 com.google.common.base.Strings;
26 
27 import java.io.BufferedInputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.util.Set;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarOutputStream;
35 import java.util.jar.Manifest;
36 
37 /**
38  * A class to build a wrapper configuration file to use subprocess results reporter for a cluster
39  * command.
40  */
41 public class SubprocessReportingHelper {
42     private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar";
43     private static final String CLASS_FILTER =
44             String.format(
45                     "(^%s|^%s|^%s|^%s|^%s).*class$",
46                     "LegacySubprocessResultsReporter",
47                     "SubprocessTestResultsParser",
48                     "SubprocessEventHelper",
49                     "SubprocessResultsReporter",
50                     "ISupportGranularResults");
51 
52     private String mCommandLine;
53     private String mClasspath;
54     private File mWorkDir;
55     private String mPort;
56 
SubprocessReportingHelper( String commandLine, String classpath, File workDir, String port)57     public SubprocessReportingHelper(
58             String commandLine, String classpath, File workDir, String port) {
59         mCommandLine = commandLine;
60         mClasspath = classpath;
61         mWorkDir = workDir;
62         mPort = port;
63     }
64 
65     /**
66      * Dynamically generate extract .class file from tradefed.jar and generate new subprocess
67      * results reporter jar.
68      *
69      * @return a subprocess result reporter jar to inject.
70      * @throws IOException
71      */
buildSubprocessReporterJar()72     public File buildSubprocessReporterJar() throws IOException {
73         // Generate a patched config file.
74         final String[] tokens = QuotationAwareTokenizer.tokenizeLine(mCommandLine);
75         final String configName = tokens[0];
76         final SubprocessConfigBuilder builder = new SubprocessConfigBuilder();
77         builder.setWorkingDir(mWorkDir)
78                 .setOriginalConfig(configName)
79                 .setClasspath(mClasspath)
80                 .setPort(mPort);
81         final File patchedConfigFile = builder.build();
82         LogUtil.CLog.i(
83                 "Generating new configuration:\n %s",
84                 FileUtil.readStringFromFile(patchedConfigFile));
85 
86         final File reporterJar = new File(mWorkDir, REPORTER_JAR_NAME);
87         final File tfJar =
88                 new File(
89                         LegacySubprocessResultsReporter.class
90                                 .getProtectionDomain()
91                                 .getCodeSource()
92                                 .getLocation()
93                                 .getPath());
94         final String ext = FileUtil.getExtension(configName);
95         final String configFileName = Strings.isNullOrEmpty(ext) ? configName + ".xml" : configName;
96         // tfJar is directory of .class file when running JUnit test from Eclipse IDE
97         if (tfJar.isDirectory()) {
98             Set<File> classFiles = FileUtil.findFilesObject(tfJar, CLASS_FILTER);
99             Manifest manifest = new Manifest();
100             createJar(reporterJar, manifest, classFiles, configFileName, patchedConfigFile);
101         } else {
102             // tfJar is the tradefed.jar when running with tradefed.
103             File extractedJar = ZipUtil2.extractZipToTemp(tfJar, "tmp-jar");
104             try {
105                 Set<File> classFiles = FileUtil.findFilesObject(extractedJar, CLASS_FILTER);
106                 File mf = FileUtil.findFile(extractedJar, "MANIFEST.MF");
107                 Manifest manifest = new Manifest(new FileInputStream(mf));
108                 createJar(reporterJar, manifest, classFiles, configFileName, patchedConfigFile);
109             } finally {
110                 FileUtil.recursiveDelete(extractedJar);
111             }
112         }
113         return reporterJar;
114     }
115 
116     /**
117      * Create jar file.
118      *
119      * @param jar jar file to be created.
120      * @param manifest manifest file.
121      * @throws IOException
122      */
createJar( File jar, Manifest manifest, Set<File> classFiles, String configName, File configFile)123     private void createJar(
124             File jar, Manifest manifest, Set<File> classFiles, String configName, File configFile)
125             throws IOException {
126         try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(jar), manifest)) {
127             for (File file : classFiles) {
128                 try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
129                     String path = file.getPath();
130                     JarEntry entry = new JarEntry(path.substring(path.indexOf("com")));
131                     entry.setTime(file.lastModified());
132                     jarOutput.putNextEntry(entry);
133                     StreamUtil.copyStreams(in, jarOutput);
134                     jarOutput.closeEntry();
135                 }
136             }
137             try (BufferedInputStream in =
138                     new BufferedInputStream(new FileInputStream(configFile))) {
139                 JarEntry entry = new JarEntry(String.format("config/%s", configName));
140                 entry.setTime(configFile.lastModified());
141                 jarOutput.putNextEntry(entry);
142                 StreamUtil.copyStreams(in, jarOutput);
143                 jarOutput.closeEntry();
144             }
145         }
146     }
147 }
148