1 /* 2 * Copyright (C) 2023 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.testtype.binary; 17 18 import com.android.tradefed.config.Option; 19 import com.android.tradefed.config.OptionClass; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.DeviceRuntimeException; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.invoker.TestInformation; 24 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.FailureDescription; 27 import com.android.tradefed.result.ITestInvocationListener; 28 import com.android.tradefed.result.TestDescription; 29 import com.android.tradefed.result.error.DeviceErrorIdentifier; 30 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 31 import com.android.tradefed.util.CommandResult; 32 import com.android.tradefed.util.CommandStatus; 33 34 import java.io.IOException; 35 import java.util.HashMap; 36 import java.util.LinkedHashMap; 37 import java.util.Map; 38 import java.util.regex.Matcher; 39 import java.util.regex.Pattern; 40 41 /** Test runner for executable running on the target and parsing tesult of kernel test. */ 42 @OptionClass(alias = "kernel-target-test") 43 public class KernelTargetTest extends ExecutableTargetTest { 44 private Integer mCurrKver = null; 45 private Pattern mKverPattern = Pattern.compile("(\\d+)\\.(\\d+)(?:\\.(\\d+))?"); 46 47 /** 48 * @deprecated use skip-binary-check instead. Left for backwards compatibility. 49 */ 50 @Deprecated 51 @Option( 52 name = "ignore-binary-check", 53 description = "Deprecated - use skip-binary-check instead.") 54 private boolean mIgnoreBinaryCheck = false; 55 56 @Option(name = "exit-code-skip", description = "Exit code for skipped tests.") 57 private Integer mExitCodeSkip = null; 58 59 @Option( 60 name = "min-kernel-version", 61 description = 62 "The minimum kernel version needed to run a test. The test name should be the" 63 + " key and the minimum kernel version should be the value. Should contain" 64 + " at least the kernel version and major revision, and optionally the" 65 + " minor revision, separated by periods, e.g. 5.4 or 4.19.1") 66 private Map<String, String> mTestMinKernelVersion = new LinkedHashMap<>(); 67 68 @Option(name = "parse-ktap", description = "Parse test outputs in KTAP format") 69 private boolean mParseKTAP = false; 70 71 @Override doesRunBinaryGenerateTestResults()72 protected boolean doesRunBinaryGenerateTestResults() { 73 return mParseKTAP; 74 } 75 76 /** 77 * Skips the binary check in findBinary. Redundant with mSkipBinaryCheck but needed for 78 * backwards compatibility. 79 */ 80 @Override findBinary(String binary)81 public String findBinary(String binary) throws DeviceNotAvailableException { 82 if (mIgnoreBinaryCheck) return binary; 83 return super.findBinary(binary); 84 } 85 86 /** 87 * Parse the kernel version, major revision, and, optionally, the minimum revision from a 88 * version string into a single integer that can used for numerical comparison. 89 * 90 * @param version linux version string. 91 */ parseKernelVersion(String version)92 public Integer parseKernelVersion(String version) { 93 Matcher m = mKverPattern.matcher(version); 94 if (m.find()) { 95 Integer v1 = Integer.valueOf(m.group(1)); 96 Integer v2 = Integer.valueOf(m.group(2)); 97 Integer v3 = m.group(3) == null ? 0 : Integer.valueOf(m.group(3)); 98 return (v1 << 20) + (v2 << 10) + v3; 99 } 100 throw new IllegalArgumentException("Invalid kernel version string: " + version); 101 } 102 103 /** 104 * Check if the kernel version meets or exceeds the minimum kernel version for this test. 105 * 106 * @param minKernelVersion the min version string from the config. 107 */ compareKernelVersion(String minKernelVersion)108 public boolean compareKernelVersion(String minKernelVersion) { 109 Integer minKver = parseKernelVersion(minKernelVersion); 110 return mCurrKver >= minKver; 111 } 112 113 /** Get the device kernel version with uname -r. */ getDeviceKernelVersion()114 public Integer getDeviceKernelVersion() throws DeviceNotAvailableException { 115 ITestDevice device = getDevice(); 116 if (device == null) { 117 throw new IllegalArgumentException("Device has not been set"); 118 } 119 CommandResult result = device.executeShellV2Command("uname -r"); 120 if (result.getStatus() != CommandStatus.SUCCESS) { 121 throw new DeviceRuntimeException( 122 "Failed to get the kernel version with uname", 123 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 124 } 125 return parseKernelVersion(result.getStdout()); 126 } 127 128 @Override runBinary( String binaryPath, ITestInvocationListener listener, TestDescription description)129 public void runBinary( 130 String binaryPath, ITestInvocationListener listener, TestDescription description) 131 throws DeviceNotAvailableException, IOException { 132 String testName = description.getTestName(); 133 if (mTestMinKernelVersion.containsKey(testName) 134 && !compareKernelVersion(mTestMinKernelVersion.get(testName))) { 135 listener.testIgnored(description); 136 } else { 137 super.runBinary(binaryPath, listener, description); 138 } 139 } 140 141 @Override run(TestInformation testInfo, ITestInvocationListener listener)142 public void run(TestInformation testInfo, ITestInvocationListener listener) 143 throws DeviceNotAvailableException { 144 if (!mTestMinKernelVersion.isEmpty()) { 145 mCurrKver = getDeviceKernelVersion(); 146 } 147 super.run(testInfo, listener); 148 } 149 150 /** 151 * Check the result of the test command. 152 * 153 * @param result test result of the command {@link CommandResult} 154 * @param listener the {@link ITestInvocationListener} 155 * @param description The test in progress. 156 */ 157 @Override checkCommandResult( CommandResult result, ITestInvocationListener listener, TestDescription description)158 protected void checkCommandResult( 159 CommandResult result, ITestInvocationListener listener, TestDescription description) { 160 if (mParseKTAP) { 161 if (result.getExitCode().equals(mExitCodeSkip)) { 162 listener.testStarted(description); 163 listener.testIgnored(description); 164 listener.testEnded(description, new HashMap<String, Metric>()); 165 return; 166 } 167 try { 168 KTapResultParser.applyKTapResultToListener( 169 listener, 170 description.getTestName(), 171 result.getStdout(), 172 KTapResultParser.ParseResolution.AGGREGATED_TOP_LEVEL); 173 } catch (RuntimeException exception) { 174 CLog.e("KTAP parse error: %s", exception.toString()); 175 listener.testStarted(description); 176 listener.testFailed( 177 description, 178 FailureDescription.create(exception.toString()) 179 .setFailureStatus(FailureStatus.TEST_FAILURE)); 180 listener.testEnded(description, new HashMap<String, Metric>()); 181 } 182 return; 183 } 184 if (result.getExitCode().equals(mExitCodeSkip)) { 185 listener.testIgnored(description); 186 return; 187 } 188 super.checkCommandResult(result, listener, description); 189 } 190 } 191