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