1 /* 2 * Copyright (C) 2020 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.util; 17 18 import com.android.tradefed.device.DeviceNotAvailableException; 19 import com.android.tradefed.error.HarnessRuntimeException; 20 import com.android.tradefed.error.IHarnessException; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.result.error.ErrorIdentifier; 23 import com.android.tradefed.result.error.InfraErrorIdentifier; 24 import com.android.tradefed.sandbox.TradefedSandboxRunner; 25 26 import org.json.JSONException; 27 import org.json.JSONObject; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 /** Helper to handle the exception output from standard Tradefed command runners. */ 35 public class SubprocessExceptionParser { 36 37 public static final String EVENT_THREAD_JOIN = "Event receiver thread did not complete."; 38 39 /** Extract the file path of the serialized exception. */ getPathFromStderr(String stderr)40 public static String getPathFromStderr(String stderr) { 41 String patternString = String.format(".*%s.*", TradefedSandboxRunner.EXCEPTION_KEY); 42 Pattern pattern = Pattern.compile(patternString); 43 for (String line : stderr.split("\n")) { 44 Matcher m = pattern.matcher(line); 45 if (m.matches()) { 46 try { 47 JSONObject json = new JSONObject(line); 48 String filePath = json.getString(TradefedSandboxRunner.EXCEPTION_KEY); 49 return filePath; 50 } catch (JSONException e) { 51 // Ignore 52 CLog.w("Could not parse the stderr as a particular exception."); 53 } 54 } else { 55 CLog.w("'%s' doesn't match pattern '%s'", line, patternString); 56 } 57 } 58 return null; 59 } 60 61 /** Attempt to extract a proper exception from stderr, if not stick to RuntimeException. */ handleStderrException(CommandResult result)62 public static void handleStderrException(CommandResult result) 63 throws DeviceNotAvailableException { 64 String stderr = result.getStderr(); 65 String filePath = getPathFromStderr(stderr); 66 Integer exitCode = result.getExitCode(); 67 String message = 68 String.format( 69 "Subprocess finished with error exit code: %s.\nStderr: %s", 70 exitCode, stderr); 71 if (filePath != null) { 72 try { 73 File exception = new File(filePath); 74 Throwable obj = (Throwable) SerializationUtil.deserialize(exception, true); 75 if (obj instanceof DeviceNotAvailableException) { 76 throw (DeviceNotAvailableException) obj; 77 } 78 if (obj instanceof IHarnessException) { 79 throw new HarnessRuntimeException(message, (IHarnessException) obj); 80 } 81 throw new HarnessRuntimeException(message, obj, InfraErrorIdentifier.UNDETERMINED); 82 } catch (IOException e) { 83 // Ignore 84 CLog.w( 85 "Could not parse the stderr as a particular exception. " 86 + "Using HarnessRuntimeException instead."); 87 } 88 } 89 ErrorIdentifier id = InfraErrorIdentifier.UNDETERMINED; 90 if (CommandStatus.TIMED_OUT.equals(result.getStatus())) { 91 id = InfraErrorIdentifier.INVOCATION_TIMEOUT; 92 } 93 // If we reach here and it's an event issue, set the id 94 if (stderr.startsWith(EVENT_THREAD_JOIN)) { 95 id = InfraErrorIdentifier.EVENT_PROCESSING_TIMEOUT; 96 } 97 throw new HarnessRuntimeException(message, id); 98 } 99 } 100