1 /* 2 * Copyright (C) 2016 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.build.IBuildInfo; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 22 import com.android.tradefed.invoker.logger.TfObjectTracker; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.result.FileInputStreamSource; 25 import com.android.tradefed.result.ILogSaverListener; 26 import com.android.tradefed.result.ITestInvocationListener; 27 import com.android.tradefed.result.InputStreamSource; 28 import com.android.tradefed.result.LogDataType; 29 import com.android.tradefed.result.LogFile; 30 import com.android.tradefed.result.TestDescription; 31 import com.android.tradefed.util.SubprocessEventHelper.BaseTestEventInfo; 32 import com.android.tradefed.util.SubprocessEventHelper.FailedTestEventInfo; 33 import com.android.tradefed.util.SubprocessEventHelper.InvocationEndedEventInfo; 34 import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo; 35 import com.android.tradefed.util.SubprocessEventHelper.InvocationStartedEventInfo; 36 import com.android.tradefed.util.SubprocessEventHelper.LogAssociationEventInfo; 37 import com.android.tradefed.util.SubprocessEventHelper.TestEndedEventInfo; 38 import com.android.tradefed.util.SubprocessEventHelper.TestLogEventInfo; 39 import com.android.tradefed.util.SubprocessEventHelper.TestModuleStartedEventInfo; 40 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo; 41 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo; 42 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo; 43 import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo; 44 import com.android.tradefed.util.proto.TfMetricProtoUtil; 45 46 import com.google.common.base.Splitter; 47 import com.google.common.base.Strings; 48 49 import org.json.JSONException; 50 import org.json.JSONObject; 51 52 import java.io.BufferedReader; 53 import java.io.Closeable; 54 import java.io.File; 55 import java.io.FileNotFoundException; 56 import java.io.FileOutputStream; 57 import java.io.FileReader; 58 import java.io.IOException; 59 import java.io.InputStreamReader; 60 import java.net.ServerSocket; 61 import java.net.Socket; 62 import java.util.ArrayList; 63 import java.util.HashMap; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Set; 68 import java.util.concurrent.Semaphore; 69 import java.util.concurrent.TimeUnit; 70 import java.util.regex.Matcher; 71 import java.util.regex.Pattern; 72 73 /** 74 * Extends {@link FileOutputStream} to parse the output before writing to the file so we can 75 * generate the test events on the launcher side. 76 */ 77 public class SubprocessTestResultsParser implements Closeable { 78 79 private ITestInvocationListener mListener; 80 81 private TestDescription mCurrentTest = null; 82 private IInvocationContext mCurrentModuleContext = null; 83 84 private Pattern mPattern = null; 85 private Map<String, EventHandler> mHandlerMap = null; 86 private EventReceiverThread mEventReceiver = null; 87 private IInvocationContext mContext = null; 88 private Long mStartTime = null; 89 // Ignore the testLog events, rely only on logAssociation 90 private boolean mIgnoreTestLog = true; 91 // Keep track of which files we received TEST_LOG event from. 92 private Set<String> mTestLogged = new HashSet<>(); 93 94 /** Relevant test status keys. */ 95 public static class StatusKeys { 96 public static final String INVOCATION_FAILED = "INVOCATION_FAILED"; 97 public static final String TEST_ASSUMPTION_FAILURE = "TEST_ASSUMPTION_FAILURE"; 98 public static final String TEST_ENDED = "TEST_ENDED"; 99 public static final String TEST_FAILED = "TEST_FAILED"; 100 public static final String TEST_IGNORED = "TEST_IGNORED"; 101 public static final String TEST_STARTED = "TEST_STARTED"; 102 public static final String TEST_RUN_ENDED = "TEST_RUN_ENDED"; 103 public static final String TEST_RUN_FAILED = "TEST_RUN_FAILED"; 104 public static final String TEST_RUN_STARTED = "TEST_RUN_STARTED"; 105 public static final String TEST_MODULE_STARTED = "TEST_MODULE_STARTED"; 106 public static final String TEST_MODULE_ENDED = "TEST_MODULE_ENDED"; 107 public static final String TEST_LOG = "TEST_LOG"; 108 public static final String LOG_ASSOCIATION = "LOG_ASSOCIATION"; 109 public static final String INVOCATION_STARTED = "INVOCATION_STARTED"; 110 public static final String INVOCATION_ENDED = "INVOCATION_ENDED"; 111 } 112 113 /** 114 * Internal receiver thread class with a socket. 115 */ 116 private class EventReceiverThread extends Thread { 117 private ServerSocket mSocket; 118 // initial state: 1 permit available, joins that don't wait for connection will succeed 119 private Semaphore mSemaphore = new Semaphore(1); 120 private boolean mShouldParse = true; 121 EventReceiverThread()122 public EventReceiverThread() throws IOException { 123 super("EventReceiverThread"); 124 mSocket = new ServerSocket(0); 125 } 126 getLocalPort()127 protected int getLocalPort() { 128 return mSocket.getLocalPort(); 129 } 130 131 /** @return True if parsing completes before timeout (optionally waiting for connection). */ await(long millis, boolean waitForConnection)132 boolean await(long millis, boolean waitForConnection) throws InterruptedException { 133 // As only 1 permit is available prior to connecting, changing the number of permits 134 // requested controls whether the receiver will wait for a connection. 135 int permits = waitForConnection ? 2 : 1; 136 if (mSemaphore.tryAcquire(permits, millis, TimeUnit.MILLISECONDS)) { 137 mSemaphore.release(permits); 138 return true; 139 } 140 return false; 141 } 142 cancel()143 public void cancel() throws IOException { 144 if (mSocket != null) { 145 mSocket.close(); 146 } 147 } 148 149 /** 150 * When reaching some issues, we might want to terminate the buffer of the socket to spy 151 * which events are still in the pipe. 152 */ stopParsing()153 public void stopParsing() { 154 mShouldParse = false; 155 } 156 157 @Override run()158 public void run() { 159 Socket client = null; 160 BufferedReader in = null; 161 try { 162 client = mSocket.accept(); 163 mSemaphore.acquire(); // connected: 0 permits available, all joins will wait 164 in = new BufferedReader(new InputStreamReader(client.getInputStream())); 165 String event = null; 166 while ((event = in.readLine()) != null) { 167 try { 168 if (mShouldParse) { 169 CLog.d("received event: '%s'", event); 170 parse(event); 171 } else { 172 CLog.d("Skipping parsing of event: '%s'", event); 173 } 174 } catch (JSONException e) { 175 CLog.e(e); 176 } 177 } 178 } catch (IOException | InterruptedException e) { 179 CLog.e(e); 180 } finally { 181 StreamUtil.close(in); 182 mSemaphore.release(2); // finished: 2 permits available, all joins succeed 183 } 184 CLog.d("EventReceiverThread done."); 185 } 186 } 187 188 /** 189 * Wait for the event receiver to finish processing events. Will wait even if a connection 190 * wasn't established, i.e. processing hasn't begun yet. 191 * 192 * @param millis timeout in milliseconds. 193 * @return True if receiver thread terminate before timeout, False otherwise. 194 */ joinReceiver(long millis)195 public boolean joinReceiver(long millis) { 196 return joinReceiver(millis, true); 197 } 198 199 /** 200 * Wait for the event receiver to finish processing events. 201 * 202 * @param millis timeout in milliseconds. 203 * @param waitForConnection False to skip waiting if a connection was never established. 204 * @return True if receiver thread terminate before timeout, False otherwise. 205 */ joinReceiver(long millis, boolean waitForConnection)206 public boolean joinReceiver(long millis, boolean waitForConnection) { 207 if (mEventReceiver != null) { 208 try { 209 CLog.i("Waiting for events to finish being processed."); 210 if (!mEventReceiver.await(millis, waitForConnection)) { 211 mEventReceiver.stopParsing(); 212 CLog.e("Event receiver thread did not complete. Some events may be missing."); 213 return false; 214 } 215 } catch (InterruptedException e) { 216 CLog.e(e); 217 throw new RuntimeException(e); 218 } 219 } 220 return true; 221 } 222 223 /** 224 * Returns the socket receiver that was open. -1 if none. 225 */ getSocketServerPort()226 public int getSocketServerPort() { 227 if (mEventReceiver != null) { 228 return mEventReceiver.getLocalPort(); 229 } 230 return -1; 231 } 232 233 /** Whether or not to ignore testLog events and only rely on logAssociation. */ setIgnoreTestLog(boolean ignoreTestLog)234 public void setIgnoreTestLog(boolean ignoreTestLog) { 235 mIgnoreTestLog = ignoreTestLog; 236 } 237 238 @Override close()239 public void close() throws IOException { 240 if (mEventReceiver != null) { 241 mEventReceiver.cancel(); 242 } 243 } 244 245 /** 246 * Constructor for the result parser 247 * 248 * @param listener {@link ITestInvocationListener} where to report the results 249 * @param streaming if True, a socket receiver will be open to receive results. 250 * @param context a {@link IInvocationContext} information about the invocation 251 */ SubprocessTestResultsParser( ITestInvocationListener listener, boolean streaming, IInvocationContext context)252 public SubprocessTestResultsParser( 253 ITestInvocationListener listener, boolean streaming, IInvocationContext context) 254 throws IOException { 255 this(listener, context); 256 if (streaming) { 257 mEventReceiver = new EventReceiverThread(); 258 mEventReceiver.start(); 259 } 260 } 261 262 /** 263 * Constructor for the result parser 264 * 265 * @param listener {@link ITestInvocationListener} where to report the results 266 * @param context a {@link IInvocationContext} information about the invocation 267 */ SubprocessTestResultsParser( ITestInvocationListener listener, IInvocationContext context)268 public SubprocessTestResultsParser( 269 ITestInvocationListener listener, IInvocationContext context) { 270 mListener = listener; 271 mContext = context; 272 StringBuilder sb = new StringBuilder(); 273 sb.append(StatusKeys.INVOCATION_FAILED).append("|"); 274 sb.append(StatusKeys.TEST_ASSUMPTION_FAILURE).append("|"); 275 sb.append(StatusKeys.TEST_ENDED).append("|"); 276 sb.append(StatusKeys.TEST_FAILED).append("|"); 277 sb.append(StatusKeys.TEST_IGNORED).append("|"); 278 sb.append(StatusKeys.TEST_STARTED).append("|"); 279 sb.append(StatusKeys.TEST_RUN_ENDED).append("|"); 280 sb.append(StatusKeys.TEST_RUN_FAILED).append("|"); 281 sb.append(StatusKeys.TEST_RUN_STARTED).append("|"); 282 sb.append(StatusKeys.TEST_MODULE_STARTED).append("|"); 283 sb.append(StatusKeys.TEST_MODULE_ENDED).append("|"); 284 sb.append(StatusKeys.TEST_LOG).append("|"); 285 sb.append(StatusKeys.LOG_ASSOCIATION).append("|"); 286 sb.append(StatusKeys.INVOCATION_STARTED).append("|"); 287 sb.append(StatusKeys.INVOCATION_ENDED); 288 String patt = String.format("(.*)(%s)( )(.*)", sb.toString()); 289 mPattern = Pattern.compile(patt); 290 291 // Create Handler map for each event 292 mHandlerMap = new HashMap<String, EventHandler>(); 293 mHandlerMap.put(StatusKeys.INVOCATION_FAILED, new InvocationFailedEventHandler()); 294 mHandlerMap.put(StatusKeys.TEST_ASSUMPTION_FAILURE, 295 new TestAssumptionFailureEventHandler()); 296 mHandlerMap.put(StatusKeys.TEST_ENDED, new TestEndedEventHandler()); 297 mHandlerMap.put(StatusKeys.TEST_FAILED, new TestFailedEventHandler()); 298 mHandlerMap.put(StatusKeys.TEST_IGNORED, new TestIgnoredEventHandler()); 299 mHandlerMap.put(StatusKeys.TEST_STARTED, new TestStartedEventHandler()); 300 mHandlerMap.put(StatusKeys.TEST_RUN_ENDED, new TestRunEndedEventHandler()); 301 mHandlerMap.put(StatusKeys.TEST_RUN_FAILED, new TestRunFailedEventHandler()); 302 mHandlerMap.put(StatusKeys.TEST_RUN_STARTED, new TestRunStartedEventHandler()); 303 mHandlerMap.put(StatusKeys.TEST_MODULE_STARTED, new TestModuleStartedEventHandler()); 304 mHandlerMap.put(StatusKeys.TEST_MODULE_ENDED, new TestModuleEndedEventHandler()); 305 mHandlerMap.put(StatusKeys.TEST_LOG, new TestLogEventHandler()); 306 mHandlerMap.put(StatusKeys.LOG_ASSOCIATION, new LogAssociationEventHandler()); 307 mHandlerMap.put(StatusKeys.INVOCATION_STARTED, new InvocationStartedEventHandler()); 308 mHandlerMap.put(StatusKeys.INVOCATION_ENDED, new InvocationEndedEventHandler()); 309 } 310 parseFile(File file)311 public void parseFile(File file) { 312 BufferedReader reader = null; 313 try { 314 reader = new BufferedReader(new FileReader(file)); 315 } catch (FileNotFoundException e) { 316 CLog.e(e); 317 throw new RuntimeException(e); 318 } 319 ArrayList<String> listString = new ArrayList<String>(); 320 String line = null; 321 try { 322 while ((line = reader.readLine()) != null) { 323 listString.add(line); 324 } 325 reader.close(); 326 } catch (IOException e) { 327 CLog.e(e); 328 throw new RuntimeException(e); 329 } 330 processNewLines(listString.toArray(new String[listString.size()])); 331 } 332 333 /** 334 * call parse on each line of the array to extract the events if any. 335 */ processNewLines(String[] lines)336 public void processNewLines(String[] lines) { 337 for (String line : lines) { 338 try { 339 parse(line); 340 } catch (JSONException e) { 341 CLog.e("Exception while parsing"); 342 CLog.e(e); 343 throw new RuntimeException(e); 344 } 345 } 346 } 347 348 /** 349 * Parse a line, if it matches one of the events, handle it. 350 */ parse(String line)351 private void parse(String line) throws JSONException { 352 Matcher matcher = mPattern.matcher(line); 353 if (matcher.find()) { 354 EventHandler handler = mHandlerMap.get(matcher.group(2)); 355 if (handler != null) { 356 handler.handleEvent(matcher.group(4)); 357 } else { 358 CLog.w("No handler found matching: %s", matcher.group(2)); 359 } 360 } 361 } 362 checkCurrentTestId(String className, String testName)363 private void checkCurrentTestId(String className, String testName) { 364 if (mCurrentTest == null) { 365 mCurrentTest = new TestDescription(className, testName); 366 CLog.w("Calling a test event without having called testStarted."); 367 } 368 } 369 370 /** 371 * Interface for event handling 372 */ 373 interface EventHandler { handleEvent(String eventJson)374 public void handleEvent(String eventJson) throws JSONException; 375 } 376 377 private class TestRunStartedEventHandler implements EventHandler { 378 @Override handleEvent(String eventJson)379 public void handleEvent(String eventJson) throws JSONException { 380 TestRunStartedEventInfo rsi = new TestRunStartedEventInfo(new JSONObject(eventJson)); 381 if (rsi.mAttempt != null) { 382 mListener.testRunStarted( 383 rsi.mRunName, rsi.mTestCount, rsi.mAttempt, rsi.mStartTime); 384 } else { 385 mListener.testRunStarted(rsi.mRunName, rsi.mTestCount); 386 } 387 } 388 } 389 390 private class TestRunFailedEventHandler implements EventHandler { 391 @Override handleEvent(String eventJson)392 public void handleEvent(String eventJson) throws JSONException { 393 TestRunFailedEventInfo rfi = new TestRunFailedEventInfo(new JSONObject(eventJson)); 394 if (rfi.mFailure != null) { 395 mListener.testRunFailed(rfi.mFailure); 396 } else { 397 mListener.testRunFailed(rfi.mReason); 398 } 399 } 400 } 401 402 private class TestRunEndedEventHandler implements EventHandler { 403 @Override handleEvent(String eventJson)404 public void handleEvent(String eventJson) throws JSONException { 405 try { 406 TestRunEndedEventInfo rei = new TestRunEndedEventInfo(new JSONObject(eventJson)); 407 // TODO: Parse directly as proto. 408 mListener.testRunEnded( 409 rei.mTime, TfMetricProtoUtil.upgradeConvert(rei.mRunMetrics)); 410 } finally { 411 mCurrentTest = null; 412 } 413 } 414 } 415 416 private class InvocationFailedEventHandler implements EventHandler { 417 @Override handleEvent(String eventJson)418 public void handleEvent(String eventJson) throws JSONException { 419 InvocationFailedEventInfo ifi = 420 new InvocationFailedEventInfo(new JSONObject(eventJson)); 421 if (ifi.mFailure != null) { 422 mListener.invocationFailed(ifi.mFailure); 423 } else { 424 mListener.invocationFailed(ifi.mCause); 425 } 426 } 427 } 428 429 private class TestStartedEventHandler implements EventHandler { 430 @Override handleEvent(String eventJson)431 public void handleEvent(String eventJson) throws JSONException { 432 TestStartedEventInfo bti = new TestStartedEventInfo(new JSONObject(eventJson)); 433 mCurrentTest = new TestDescription(bti.mClassName, bti.mTestName); 434 if (bti.mStartTime != null) { 435 mListener.testStarted(mCurrentTest, bti.mStartTime); 436 } else { 437 mListener.testStarted(mCurrentTest); 438 } 439 } 440 } 441 442 private class TestFailedEventHandler implements EventHandler { 443 @Override handleEvent(String eventJson)444 public void handleEvent(String eventJson) throws JSONException { 445 FailedTestEventInfo fti = new FailedTestEventInfo(new JSONObject(eventJson)); 446 checkCurrentTestId(fti.mClassName, fti.mTestName); 447 mListener.testFailed(mCurrentTest, fti.mTrace); 448 } 449 } 450 451 private class TestEndedEventHandler implements EventHandler { 452 @Override handleEvent(String eventJson)453 public void handleEvent(String eventJson) throws JSONException { 454 try { 455 TestEndedEventInfo tei = new TestEndedEventInfo(new JSONObject(eventJson)); 456 checkCurrentTestId(tei.mClassName, tei.mTestName); 457 if (tei.mEndTime != null) { 458 mListener.testEnded( 459 mCurrentTest, 460 tei.mEndTime, 461 TfMetricProtoUtil.upgradeConvert(tei.mRunMetrics)); 462 } else { 463 mListener.testEnded( 464 mCurrentTest, TfMetricProtoUtil.upgradeConvert(tei.mRunMetrics)); 465 } 466 } finally { 467 mCurrentTest = null; 468 } 469 } 470 } 471 472 private class TestIgnoredEventHandler implements EventHandler { 473 @Override handleEvent(String eventJson)474 public void handleEvent(String eventJson) throws JSONException { 475 BaseTestEventInfo baseTestIgnored = new BaseTestEventInfo(new JSONObject(eventJson)); 476 checkCurrentTestId(baseTestIgnored.mClassName, baseTestIgnored.mTestName); 477 mListener.testIgnored(mCurrentTest); 478 } 479 } 480 481 private class TestAssumptionFailureEventHandler implements EventHandler { 482 @Override handleEvent(String eventJson)483 public void handleEvent(String eventJson) throws JSONException { 484 FailedTestEventInfo FailedAssumption = 485 new FailedTestEventInfo(new JSONObject(eventJson)); 486 checkCurrentTestId(FailedAssumption.mClassName, FailedAssumption.mTestName); 487 mListener.testAssumptionFailure(mCurrentTest, FailedAssumption.mTrace); 488 } 489 } 490 491 private class TestModuleStartedEventHandler implements EventHandler { 492 @Override handleEvent(String eventJson)493 public void handleEvent(String eventJson) throws JSONException { 494 TestModuleStartedEventInfo module = 495 new TestModuleStartedEventInfo(new JSONObject(eventJson)); 496 mCurrentModuleContext = module.mModuleContext; 497 mListener.testModuleStarted(module.mModuleContext); 498 } 499 } 500 501 private class TestModuleEndedEventHandler implements EventHandler { 502 @Override handleEvent(String eventJson)503 public void handleEvent(String eventJson) throws JSONException { 504 if (mCurrentModuleContext == null) { 505 CLog.w("Calling testModuleEnded when testModuleStarted was not called."); 506 } 507 mListener.testModuleEnded(); 508 mCurrentModuleContext = null; 509 } 510 } 511 512 private class TestLogEventHandler implements EventHandler { 513 @Override handleEvent(String eventJson)514 public void handleEvent(String eventJson) throws JSONException { 515 TestLogEventInfo logInfo = new TestLogEventInfo(new JSONObject(eventJson)); 516 if (mIgnoreTestLog) { 517 FileUtil.deleteFile(logInfo.mDataFile); 518 return; 519 } 520 String name = String.format("subprocess-%s", logInfo.mDataName); 521 try (InputStreamSource data = new FileInputStreamSource(logInfo.mDataFile, true)) { 522 mListener.testLog(name, logInfo.mLogType, data); 523 mTestLogged.add(logInfo.mDataName); 524 } 525 } 526 } 527 528 private class LogAssociationEventHandler implements EventHandler { 529 @Override handleEvent(String eventJson)530 public void handleEvent(String eventJson) throws JSONException { 531 LogAssociationEventInfo assosInfo = 532 new LogAssociationEventInfo(new JSONObject(eventJson)); 533 LogFile file = assosInfo.mLoggedFile; 534 if (Strings.isNullOrEmpty(file.getPath())) { 535 CLog.e("Log '%s' was registered but without a path.", assosInfo.mDataName); 536 return; 537 } 538 File path = new File(file.getPath()); 539 String name = String.format("subprocess-%s", assosInfo.mDataName); 540 if (Strings.isNullOrEmpty(file.getUrl()) && path.exists()) { 541 if (mTestLogged.contains(assosInfo.mDataName)) { 542 CLog.d( 543 "Already called testLog on %s, ignoring the logAssociation.", 544 assosInfo.mDataName); 545 return; 546 } 547 LogDataType type = file.getType(); 548 // File might have already been compressed 549 File toLog = path; 550 File extractedDir = null; 551 if (file.getPath().endsWith(LogDataType.ZIP.getFileExt())) { 552 // If file type is compressed, then keep that type. 553 if (!file.getType().isCompressed()) { 554 try { 555 extractedDir = ZipUtil2.extractZipToTemp(path, assosInfo.mDataName); 556 File[] files = extractedDir.listFiles(); 557 if (files.length == 1) { 558 toLog = files[0]; 559 } else { 560 type = LogDataType.ZIP; 561 } 562 } catch (IOException e) { 563 CLog.e(e); 564 } 565 } 566 } 567 try (InputStreamSource source = new FileInputStreamSource(toLog)) { 568 CLog.d("Logging %s from subprocess: %s ", assosInfo.mDataName, toLog.getPath()); 569 mListener.testLog(name, type, source); 570 } 571 FileUtil.recursiveDelete(extractedDir); 572 FileUtil.deleteFile(path); 573 } else { 574 CLog.d( 575 "Logging %s from subprocess. url: %s, path: %s", 576 name, file.getUrl(), file.getPath()); 577 if (mListener instanceof ILogSaverListener) { 578 ((ILogSaverListener) mListener).logAssociation(name, assosInfo.mLoggedFile); 579 } 580 } 581 } 582 } 583 584 private class InvocationStartedEventHandler implements EventHandler { 585 @Override handleEvent(String eventJson)586 public void handleEvent(String eventJson) throws JSONException { 587 InvocationStartedEventInfo eventStart = 588 new InvocationStartedEventInfo(new JSONObject(eventJson)); 589 if (mContext.getTestTag() == null || "stub".equals(mContext.getTestTag())) { 590 mContext.setTestTag(eventStart.mTestTag); 591 } 592 mStartTime = eventStart.mStartTime; 593 } 594 } 595 596 private class InvocationEndedEventHandler implements EventHandler { 597 @Override handleEvent(String eventJson)598 public void handleEvent(String eventJson) throws JSONException { 599 JSONObject json = new JSONObject(eventJson); 600 InvocationEndedEventInfo eventEnd = new InvocationEndedEventInfo(json); 601 // Add build attributes to the primary build (the first build 602 // provider of the running configuration). 603 List<IBuildInfo> infos = mContext.getBuildInfos(); 604 if (!infos.isEmpty()) { 605 Map<String, String> attributes = eventEnd.mBuildAttributes; 606 for (InvocationMetricKey key : InvocationMetricKey.values()) { 607 if (!attributes.containsKey(key.toString())) { 608 continue; 609 } 610 String val = attributes.remove(key.toString()); 611 if (key.shouldAdd()) { 612 try { 613 InvocationMetricLogger.addInvocationMetrics(key, Long.parseLong(val)); 614 } catch (NumberFormatException e) { 615 CLog.d("Key %s doesn't have a number value, was: %s.", key, val); 616 // If it's not a number then, let the string concatenate 617 InvocationMetricLogger.addInvocationMetrics(key, val); 618 } 619 } else { 620 InvocationMetricLogger.addInvocationMetrics(key, val); 621 } 622 } 623 if (attributes.containsKey(TfObjectTracker.TF_OBJECTS_TRACKING_KEY)) { 624 String val = attributes.get(TfObjectTracker.TF_OBJECTS_TRACKING_KEY); 625 for (String pair : Splitter.on(",").split(val)) { 626 if (!pair.contains("=")) { 627 continue; 628 } 629 String[] pairSplit = pair.split("="); 630 try { 631 TfObjectTracker.directCount(pairSplit[0], Long.parseLong(pairSplit[1])); 632 } catch (NumberFormatException e) { 633 CLog.e(e); 634 continue; 635 } 636 } 637 attributes.remove(TfObjectTracker.TF_OBJECTS_TRACKING_KEY); 638 } 639 infos.get(0).addBuildAttributes(attributes); 640 } 641 } 642 } 643 644 /** 645 * Returns the start time associated with the invocation start event from the subprocess 646 * invocation. 647 */ getStartTime()648 public Long getStartTime() { 649 return mStartTime; 650 } 651 652 /** Returns the test that is currently in progress. */ getCurrentTest()653 public TestDescription getCurrentTest() { 654 return mCurrentTest; 655 } 656 } 657