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