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