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