1 /* 2 * Copyright (C) 2025 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.result.resultdb; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.config.ConfigurationDescriptor; 20 import com.android.tradefed.invoker.IInvocationContext; 21 import com.android.tradefed.invoker.InvocationContext; 22 import com.android.tradefed.metrics.proto.MetricMeasurement; 23 import com.android.tradefed.result.FailureDescription; 24 import com.android.tradefed.result.ILogSaverListener; 25 import com.android.tradefed.result.ITestSummaryListener; 26 import com.android.tradefed.result.LogFile; 27 import com.android.tradefed.result.TestDescription; 28 import com.android.tradefed.result.TestSummary; 29 import com.android.tradefed.result.skipped.SkipReason; 30 import com.android.tradefed.util.MultiMap; 31 32 import java.time.LocalDateTime; 33 import java.time.ZoneId; 34 import java.time.ZonedDateTime; 35 import java.time.format.DateTimeFormatter; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 43 /** Invocation simulator for testing. */ 44 public class InvocationSimulator { 45 46 public enum TestStatus { 47 PASS("pass"), 48 FAIL("fail"), 49 IGNORED("ignored"), 50 ASSUMPTION_FAILURE("assumptionFailure"), 51 TEST_ERROR("testError"), 52 TEST_SKIPPED("testSkipped"); 53 54 private final String value; 55 TestStatus(String value)56 private TestStatus(String value) { 57 this.value = value; 58 } 59 60 @Override toString()61 public String toString() { 62 return value; 63 } 64 } 65 66 private String mModuleName = "example-module"; 67 private MultiMap<String, String> mModuleMetadata = new MultiMap<>(); 68 private Map<String, String> mModuleAttributes = new HashMap<>(); 69 private List<TestDescription> mTests; 70 private List<TestStatus> mTestOutcomes; 71 private List<HashMap<String, MetricMeasurement.Metric>> mTestMetricsList; 72 private Map<TestDescription, FailureDescription> mFailureDescriptions; 73 private String mSummary = ""; 74 private String mTestRunError = ""; 75 private FailureDescription mTestRunDescription; 76 private HashMap<String, MetricMeasurement.Metric> mTestRunMetrics; 77 private String mInvocationId; 78 private String mWorkUnitId; 79 private String mLegacyResultId; 80 private Map<String, String> mInvocationAttributes; 81 private List<IBuildInfo> mBuildInfos; 82 private List<LogFile> mTestLogs; 83 private List<LogFile> mTestRunLogs; 84 private List<LogFile> mInvocationLogs; 85 private boolean mIgnoreExceptions = false; 86 private boolean mSkipTestRuns = false; 87 create()88 public static InvocationSimulator create() { 89 return new InvocationSimulator(); 90 } 91 InvocationSimulator()92 private InvocationSimulator() { 93 mTests = new ArrayList<>(); 94 mTestOutcomes = new ArrayList<>(); 95 mTestMetricsList = new ArrayList<>(); 96 mFailureDescriptions = new HashMap<>(); 97 mTestRunMetrics = new HashMap<>(); 98 mBuildInfos = new ArrayList<>(); 99 mTestLogs = new ArrayList<>(); 100 mTestRunLogs = new ArrayList<>(); 101 mInvocationLogs = new ArrayList<>(); 102 mInvocationAttributes = new HashMap<>(); 103 } 104 setInvocationId(String invocationId)105 public InvocationSimulator setInvocationId(String invocationId) { 106 mInvocationId = invocationId; 107 return this; 108 } 109 setWorkUnitId(String workUnitId)110 public InvocationSimulator setWorkUnitId(String workUnitId) { 111 mWorkUnitId = workUnitId; 112 return this; 113 } 114 setLegacyResultId(String id)115 public InvocationSimulator setLegacyResultId(String id) { 116 mLegacyResultId = id; 117 return this; 118 } 119 withBuildInfo(IBuildInfo info)120 public InvocationSimulator withBuildInfo(IBuildInfo info) { 121 mBuildInfos.add(info); 122 return this; 123 } 124 withInvocationAttribute(String name, String value)125 public InvocationSimulator withInvocationAttribute(String name, String value) { 126 mInvocationAttributes.put(name, value); 127 return this; 128 } 129 getModule()130 private IInvocationContext getModule() { 131 ConfigurationDescriptor module = new ConfigurationDescriptor(); 132 module.setModuleName(mModuleName); 133 if (!mModuleMetadata.isEmpty()) { 134 module.setMetaData(mModuleMetadata); 135 } 136 InvocationContext moduleContext = createInvocation(); 137 moduleContext.setConfigurationDescriptor(module); 138 for (Map.Entry<String, String> attribute : mModuleAttributes.entrySet()) { 139 moduleContext.addInvocationAttribute(attribute.getKey(), attribute.getValue()); 140 } 141 return moduleContext; 142 } 143 getStartTime()144 private long getStartTime() { 145 String in = "2018-09-07 15:23:45"; 146 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 147 ZonedDateTime date = formatter.parse(in, LocalDateTime::from).atZone(ZoneId.of("UTC")); 148 return date.toInstant().toEpochMilli(); 149 } 150 withTest(String clazz, String method)151 public InvocationSimulator withTest(String clazz, String method) { 152 return withTest(clazz, method, TestStatus.PASS); 153 } 154 withTest(String clazz, String method, TestStatus outcome)155 public InvocationSimulator withTest(String clazz, String method, TestStatus outcome) { 156 return withTest(clazz, method, outcome, new HashMap<String, MetricMeasurement.Metric>()); 157 } 158 withTestFailure( String clazz, String method, FailureDescription failure)159 public InvocationSimulator withTestFailure( 160 String clazz, String method, FailureDescription failure) { 161 mFailureDescriptions.put(new TestDescription(clazz, method), failure); 162 return this; 163 } 164 withTest( String clazz, String method, TestStatus outcome, HashMap<String, MetricMeasurement.Metric> metrics)165 public InvocationSimulator withTest( 166 String clazz, 167 String method, 168 TestStatus outcome, 169 HashMap<String, MetricMeasurement.Metric> metrics) { 170 mTests.add(new TestDescription(clazz, method)); 171 mTestOutcomes.add(outcome); 172 mTestMetricsList.add(metrics); 173 return this; 174 } 175 withModule(String module)176 public InvocationSimulator withModule(String module) { 177 mModuleName = module; 178 return this; 179 } 180 withModuleMetadata(String name, String value)181 public InvocationSimulator withModuleMetadata(String name, String value) { 182 mModuleMetadata.put(name, value); 183 return this; 184 } 185 withModuleAttribute(String name, String value)186 public InvocationSimulator withModuleAttribute(String name, String value) { 187 mModuleAttributes.put(name, value); 188 return this; 189 } 190 withoutModules()191 public InvocationSimulator withoutModules() { 192 mModuleName = ""; 193 return this; 194 } 195 withSummary(String summary)196 public InvocationSimulator withSummary(String summary) { 197 mSummary = summary; 198 return this; 199 } 200 withTestRunMetrics( HashMap<String, MetricMeasurement.Metric> runMetrics)201 public InvocationSimulator withTestRunMetrics( 202 HashMap<String, MetricMeasurement.Metric> runMetrics) { 203 mTestRunMetrics = runMetrics; 204 return this; 205 } 206 withTestRunFailure(String message)207 public InvocationSimulator withTestRunFailure(String message) { 208 mTestRunError = message; 209 return this; 210 } 211 withTestRunFailure(FailureDescription failure)212 public InvocationSimulator withTestRunFailure(FailureDescription failure) { 213 mTestRunDescription = failure; 214 return this; 215 } 216 withTestLog(LogFile log)217 public InvocationSimulator withTestLog(LogFile log) { 218 mTestLogs.add(log); 219 return this; 220 } 221 withTestRunLog(LogFile log)222 public InvocationSimulator withTestRunLog(LogFile log) { 223 mTestRunLogs.add(log); 224 return this; 225 } 226 withInvocationLog(LogFile log)227 public InvocationSimulator withInvocationLog(LogFile log) { 228 mInvocationLogs.add(log); 229 return this; 230 } 231 ignoreExceptions()232 public InvocationSimulator ignoreExceptions() { 233 mIgnoreExceptions = true; 234 return this; 235 } 236 skipTestRuns()237 public InvocationSimulator skipTestRuns() { 238 mSkipTestRuns = true; 239 return this; 240 } 241 simulateInvocation( T reporter)242 public <T extends ILogSaverListener & ITestSummaryListener> void simulateInvocation( 243 T reporter) { 244 if (!mSummary.isEmpty()) { 245 reporter.putEarlySummary(Arrays.asList(new TestSummary(mSummary))); 246 } 247 try { 248 reporter.invocationStarted(createInvocation()); 249 } catch (Exception e) { 250 if (!mIgnoreExceptions) { 251 throw e; 252 } 253 } 254 try { 255 if (!mModuleName.isEmpty()) { 256 reporter.testModuleStarted(getModule()); 257 } 258 } catch (Exception e) { 259 if (!mIgnoreExceptions) { 260 throw e; 261 } 262 } 263 try { 264 if (!mSkipTestRuns) { 265 reporter.testRunStarted("TestRun1", 1, 1); 266 } 267 } catch (Exception e) { 268 if (!mIgnoreExceptions) { 269 throw e; 270 } 271 } 272 273 simulateTests(reporter); 274 if (!mTestRunError.isEmpty()) { 275 reporter.testRunFailed(mTestRunError); 276 } 277 if (mTestRunDescription != null) { 278 reporter.testRunFailed(mTestRunDescription); 279 } 280 simulateLogs("test-run-", mTestRunLogs, reporter); 281 if (!mSkipTestRuns) { 282 reporter.testRunEnded(1000L, mTestRunMetrics); 283 } 284 if (!mModuleName.isEmpty()) { 285 reporter.testModuleEnded(); 286 } 287 simulateLogs("invocation-log-", mInvocationLogs, reporter); 288 reporter.invocationEnded(100); 289 } 290 createInvocation()291 private InvocationContext createInvocation() { 292 InvocationContext context = new InvocationContext(); 293 context.setTestTag("test tag"); 294 context.addInvocationAttribute("invocation_id", mInvocationId); 295 context.addInvocationAttribute("work_unit_id", mWorkUnitId); 296 context.addInvocationAttribute("test_result_id", mLegacyResultId); 297 for (int i = 0; i < mBuildInfos.size(); i++) { 298 context.addDeviceBuildInfo(String.format("device_%d", i), mBuildInfos.get(i)); 299 } 300 for (Map.Entry<String, String> attr : mInvocationAttributes.entrySet()) { 301 context.addInvocationAttribute(attr.getKey(), attr.getValue()); 302 } 303 return context; 304 } 305 simulateTests(T reporter)306 private <T extends ILogSaverListener & ITestSummaryListener> void simulateTests(T reporter) { 307 long startTime = getStartTime(); 308 for (int i = 0; i < mTests.size(); i++) { 309 reporter.testStarted(mTests.get(i), startTime); 310 startTime += 100; 311 switch (mTestOutcomes.get(i)) { 312 case FAIL: 313 reporter.testFailed(mTests.get(i), "Fail Trace"); 314 break; 315 case ASSUMPTION_FAILURE: 316 reporter.testAssumptionFailure(mTests.get(i), "Assumption Fail Trace"); 317 break; 318 case IGNORED: 319 reporter.testIgnored(mTests.get(i)); 320 break; 321 case TEST_SKIPPED: 322 reporter.testSkipped( 323 mTests.get(i), 324 new SkipReason("skip reason message", "skip trigger", "bugId")); 325 break; 326 default: 327 break; 328 } 329 simulateLogs("test-log-", mTestLogs, reporter); 330 reporter.testEnded(mTests.get(i), startTime, mTestMetricsList.get(i)); 331 startTime += 100; 332 } 333 for (Map.Entry<TestDescription, FailureDescription> entry : 334 mFailureDescriptions.entrySet()) { 335 reporter.testStarted(entry.getKey(), startTime); 336 startTime += 100; 337 reporter.testFailed(entry.getKey(), entry.getValue()); 338 reporter.testEnded(entry.getKey(), startTime, Collections.emptyMap()); 339 startTime += 100; 340 } 341 } 342 simulateLogs(String prefix, List<LogFile> logs, ILogSaverListener reporter)343 private void simulateLogs(String prefix, List<LogFile> logs, ILogSaverListener reporter) { 344 for (int i = 0; i < logs.size(); i++) { 345 String dataName = String.format("%s%d", prefix, i); 346 reporter.logAssociation(dataName, logs.get(i)); 347 } 348 } 349 } 350