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