Home | History | Annotate | Download | only in util
      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.invoker.IInvocationContext;
     19 import com.android.tradefed.log.LogUtil.CLog;
     20 import com.android.tradefed.result.FileInputStreamSource;
     21 import com.android.tradefed.result.ILogSaverListener;
     22 import com.android.tradefed.result.ITestInvocationListener;
     23 import com.android.tradefed.result.InputStreamSource;
     24 import com.android.tradefed.result.TestDescription;
     25 import com.android.tradefed.util.SubprocessEventHelper.BaseTestEventInfo;
     26 import com.android.tradefed.util.SubprocessEventHelper.FailedTestEventInfo;
     27 import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo;
     28 import com.android.tradefed.util.SubprocessEventHelper.InvocationStartedEventInfo;
     29 import com.android.tradefed.util.SubprocessEventHelper.LogAssociationEventInfo;
     30 import com.android.tradefed.util.SubprocessEventHelper.TestEndedEventInfo;
     31 import com.android.tradefed.util.SubprocessEventHelper.TestLogEventInfo;
     32 import com.android.tradefed.util.SubprocessEventHelper.TestModuleStartedEventInfo;
     33 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo;
     34 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo;
     35 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo;
     36 import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo;
     37 import com.android.tradefed.util.proto.TfMetricProtoUtil;
     38 
     39 import org.json.JSONException;
     40 import org.json.JSONObject;
     41 
     42 import java.io.BufferedReader;
     43 import java.io.Closeable;
     44 import java.io.File;
     45 import java.io.FileNotFoundException;
     46 import java.io.FileOutputStream;
     47 import java.io.FileReader;
     48 import java.io.IOException;
     49 import java.io.InputStreamReader;
     50 import java.net.ServerSocket;
     51 import java.net.Socket;
     52 import java.util.ArrayList;
     53 import java.util.HashMap;
     54 import java.util.Map;
     55 import java.util.concurrent.CountDownLatch;
     56 import java.util.concurrent.TimeUnit;
     57 import java.util.regex.Matcher;
     58 import java.util.regex.Pattern;
     59 
     60 /**
     61  * Extends {@link FileOutputStream} to parse the output before writing to the file so we can
     62  * generate the test events on the launcher side.
     63  */
     64 public class SubprocessTestResultsParser implements Closeable {
     65 
     66     private ITestInvocationListener mListener;
     67 
     68     private TestDescription mCurrentTest = null;
     69     private IInvocationContext mCurrentModuleContext = null;
     70 
     71     private Pattern mPattern = null;
     72     private Map<String, EventHandler> mHandlerMap = null;
     73     private EventReceiverThread mEventReceiver = null;
     74     private IInvocationContext mContext = null;
     75     private Long mStartTime = null;
     76 
     77     /** Relevant test status keys. */
     78     public static class StatusKeys {
     79         public static final String INVOCATION_FAILED = "INVOCATION_FAILED";
     80         public static final String TEST_ASSUMPTION_FAILURE = "TEST_ASSUMPTION_FAILURE";
     81         public static final String TEST_ENDED = "TEST_ENDED";
     82         public static final String TEST_FAILED = "TEST_FAILED";
     83         public static final String TEST_IGNORED = "TEST_IGNORED";
     84         public static final String TEST_STARTED = "TEST_STARTED";
     85         public static final String TEST_RUN_ENDED = "TEST_RUN_ENDED";
     86         public static final String TEST_RUN_FAILED = "TEST_RUN_FAILED";
     87         public static final String TEST_RUN_STARTED = "TEST_RUN_STARTED";
     88         public static final String TEST_MODULE_STARTED = "TEST_MODULE_STARTED";
     89         public static final String TEST_MODULE_ENDED = "TEST_MODULE_ENDED";
     90         public static final String TEST_LOG = "TEST_LOG";
     91         public static final String LOG_ASSOCIATION = "LOG_ASSOCIATION";
     92         public static final String INVOCATION_STARTED = "INVOCATION_STARTED";
     93     }
     94 
     95     /**
     96      * Internal receiver thread class with a socket.
     97      */
     98     private class EventReceiverThread extends Thread {
     99         private ServerSocket mSocket;
    100         private CountDownLatch mCountDown;
    101 
    102         public EventReceiverThread() throws IOException {
    103             super("EventReceiverThread");
    104             mSocket = new ServerSocket(0);
    105             mCountDown = new CountDownLatch(1);
    106         }
    107 
    108         protected int getLocalPort() {
    109             return mSocket.getLocalPort();
    110         }
    111 
    112         protected CountDownLatch getCountDown() {
    113             return mCountDown;
    114         }
    115 
    116         public void cancel() throws IOException {
    117             if (mSocket != null) {
    118                 mSocket.close();
    119             }
    120         }
    121 
    122         @Override
    123         public void run() {
    124             Socket client = null;
    125             BufferedReader in = null;
    126             try {
    127                 client = mSocket.accept();
    128                 in = new BufferedReader(new InputStreamReader(client.getInputStream()));
    129                 String event = null;
    130                 while ((event = in.readLine()) != null) {
    131                     try {
    132                         CLog.d("received event: '%s'", event);
    133                         parse(event);
    134                     } catch (JSONException e) {
    135                         CLog.e(e);
    136                     }
    137                 }
    138             } catch (IOException e) {
    139                 CLog.e(e);
    140             } finally {
    141                 StreamUtil.close(in);
    142                 mCountDown.countDown();
    143             }
    144             CLog.d("EventReceiverThread done.");
    145         }
    146     }
    147 
    148     /**
    149      * If the event receiver is being used, ensure that we wait for it to terminate.
    150      * @param millis timeout in milliseconds.
    151      * @return True if receiver thread terminate before timeout, False otherwise.
    152      */
    153     public boolean joinReceiver(long millis) {
    154         if (mEventReceiver != null) {
    155             try {
    156                 CLog.i("Waiting for events to finish being processed.");
    157                 if (!mEventReceiver.getCountDown().await(millis, TimeUnit.MILLISECONDS)) {
    158                     CLog.e("Event receiver thread did not complete. Some events may be missing.");
    159                     return false;
    160                 }
    161             } catch (InterruptedException e) {
    162                 CLog.e(e);
    163                 throw new RuntimeException(e);
    164             }
    165         }
    166         return true;
    167     }
    168 
    169     /**
    170      * Returns the socket receiver that was open. -1 if none.
    171      */
    172     public int getSocketServerPort() {
    173         if (mEventReceiver != null) {
    174             return mEventReceiver.getLocalPort();
    175         }
    176         return -1;
    177     }
    178 
    179     @Override
    180     public void close() throws IOException {
    181         if (mEventReceiver != null) {
    182             mEventReceiver.cancel();
    183         }
    184     }
    185 
    186     /**
    187      * Constructor for the result parser
    188      *
    189      * @param listener {@link ITestInvocationListener} where to report the results
    190      * @param streaming if True, a socket receiver will be open to receive results.
    191      * @param context a {@link IInvocationContext} information about the invocation
    192      */
    193     public SubprocessTestResultsParser(
    194             ITestInvocationListener listener, boolean streaming, IInvocationContext context)
    195             throws IOException {
    196         this(listener, context);
    197         if (streaming) {
    198             mEventReceiver = new EventReceiverThread();
    199             mEventReceiver.start();
    200         }
    201     }
    202 
    203     /**
    204      * Constructor for the result parser
    205      *
    206      * @param listener {@link ITestInvocationListener} where to report the results
    207      * @param context a {@link IInvocationContext} information about the invocation
    208      */
    209     public SubprocessTestResultsParser(
    210             ITestInvocationListener listener, IInvocationContext context) {
    211         mListener = listener;
    212         mContext = context;
    213         StringBuilder sb = new StringBuilder();
    214         sb.append(StatusKeys.INVOCATION_FAILED).append("|");
    215         sb.append(StatusKeys.TEST_ASSUMPTION_FAILURE).append("|");
    216         sb.append(StatusKeys.TEST_ENDED).append("|");
    217         sb.append(StatusKeys.TEST_FAILED).append("|");
    218         sb.append(StatusKeys.TEST_IGNORED).append("|");
    219         sb.append(StatusKeys.TEST_STARTED).append("|");
    220         sb.append(StatusKeys.TEST_RUN_ENDED).append("|");
    221         sb.append(StatusKeys.TEST_RUN_FAILED).append("|");
    222         sb.append(StatusKeys.TEST_RUN_STARTED).append("|");
    223         sb.append(StatusKeys.TEST_MODULE_STARTED).append("|");
    224         sb.append(StatusKeys.TEST_MODULE_ENDED).append("|");
    225         sb.append(StatusKeys.TEST_LOG).append("|");
    226         sb.append(StatusKeys.LOG_ASSOCIATION).append("|");
    227         sb.append(StatusKeys.INVOCATION_STARTED);
    228         String patt = String.format("(.*)(%s)( )(.*)", sb.toString());
    229         mPattern = Pattern.compile(patt);
    230 
    231         // Create Handler map for each event
    232         mHandlerMap = new HashMap<String, EventHandler>();
    233         mHandlerMap.put(StatusKeys.INVOCATION_FAILED, new InvocationFailedEventHandler());
    234         mHandlerMap.put(StatusKeys.TEST_ASSUMPTION_FAILURE,
    235                 new TestAssumptionFailureEventHandler());
    236         mHandlerMap.put(StatusKeys.TEST_ENDED, new TestEndedEventHandler());
    237         mHandlerMap.put(StatusKeys.TEST_FAILED, new TestFailedEventHandler());
    238         mHandlerMap.put(StatusKeys.TEST_IGNORED, new TestIgnoredEventHandler());
    239         mHandlerMap.put(StatusKeys.TEST_STARTED, new TestStartedEventHandler());
    240         mHandlerMap.put(StatusKeys.TEST_RUN_ENDED, new TestRunEndedEventHandler());
    241         mHandlerMap.put(StatusKeys.TEST_RUN_FAILED, new TestRunFailedEventHandler());
    242         mHandlerMap.put(StatusKeys.TEST_RUN_STARTED, new TestRunStartedEventHandler());
    243         mHandlerMap.put(StatusKeys.TEST_MODULE_STARTED, new TestModuleStartedEventHandler());
    244         mHandlerMap.put(StatusKeys.TEST_MODULE_ENDED, new TestModuleEndedEventHandler());
    245         mHandlerMap.put(StatusKeys.TEST_LOG, new TestLogEventHandler());
    246         mHandlerMap.put(StatusKeys.LOG_ASSOCIATION, new LogAssociationEventHandler());
    247         mHandlerMap.put(StatusKeys.INVOCATION_STARTED, new InvocationStartedEventHandler());
    248     }
    249 
    250     public void parseFile(File file) {
    251         BufferedReader reader = null;
    252         try {
    253             reader = new BufferedReader(new FileReader(file));
    254         } catch (FileNotFoundException e) {
    255             CLog.e(e);
    256             throw new RuntimeException(e);
    257         }
    258         ArrayList<String> listString = new ArrayList<String>();
    259         String line = null;
    260         try {
    261             while ((line = reader.readLine()) != null) {
    262                 listString.add(line);
    263             }
    264             reader.close();
    265         } catch (IOException e) {
    266             CLog.e(e);
    267             throw new RuntimeException(e);
    268         }
    269         processNewLines(listString.toArray(new String[listString.size()]));
    270     }
    271 
    272     /**
    273      * call parse on each line of the array to extract the events if any.
    274      */
    275     public void processNewLines(String[] lines) {
    276         for (String line : lines) {
    277             try {
    278                 parse(line);
    279             } catch (JSONException e) {
    280                 CLog.e("Exception while parsing");
    281                 CLog.e(e);
    282                 throw new RuntimeException(e);
    283             }
    284         }
    285     }
    286 
    287     /**
    288      * Parse a line, if it matches one of the events, handle it.
    289      */
    290     private void parse(String line) throws JSONException {
    291         Matcher matcher = mPattern.matcher(line);
    292         if (matcher.find()) {
    293             EventHandler handler = mHandlerMap.get(matcher.group(2));
    294             if (handler != null) {
    295                 handler.handleEvent(matcher.group(4));
    296             } else {
    297                 CLog.w("No handler found matching: %s", matcher.group(2));
    298             }
    299         }
    300     }
    301 
    302     private void checkCurrentTestId(String className, String testName) {
    303         if (mCurrentTest == null) {
    304             mCurrentTest = new TestDescription(className, testName);
    305             CLog.w("Calling a test event without having called testStarted.");
    306         }
    307     }
    308 
    309     /**
    310      * Interface for event handling
    311      */
    312     interface EventHandler {
    313         public void handleEvent(String eventJson) throws JSONException;
    314     }
    315 
    316     private class TestRunStartedEventHandler implements EventHandler {
    317         @Override
    318         public void handleEvent(String eventJson) throws JSONException {
    319             TestRunStartedEventInfo rsi = new TestRunStartedEventInfo(new JSONObject(eventJson));
    320             mListener.testRunStarted(rsi.mRunName, rsi.mTestCount);
    321         }
    322     }
    323 
    324     private class TestRunFailedEventHandler implements EventHandler {
    325         @Override
    326         public void handleEvent(String eventJson) throws JSONException {
    327             TestRunFailedEventInfo rfi = new TestRunFailedEventInfo(new JSONObject(eventJson));
    328             mListener.testRunFailed(rfi.mReason);
    329         }
    330     }
    331 
    332     private class TestRunEndedEventHandler implements EventHandler {
    333         @Override
    334         public void handleEvent(String eventJson) throws JSONException {
    335             try {
    336                 TestRunEndedEventInfo rei = new TestRunEndedEventInfo(new JSONObject(eventJson));
    337                 // TODO: Parse directly as proto.
    338                 mListener.testRunEnded(
    339                         rei.mTime, TfMetricProtoUtil.upgradeConvert(rei.mRunMetrics));
    340             } finally {
    341                 mCurrentTest = null;
    342             }
    343         }
    344     }
    345 
    346     private class InvocationFailedEventHandler implements EventHandler {
    347         @Override
    348         public void handleEvent(String eventJson) throws JSONException {
    349             InvocationFailedEventInfo ifi =
    350                     new InvocationFailedEventInfo(new JSONObject(eventJson));
    351             mListener.invocationFailed(ifi.mCause);
    352         }
    353     }
    354 
    355     private class TestStartedEventHandler implements EventHandler {
    356         @Override
    357         public void handleEvent(String eventJson) throws JSONException {
    358             TestStartedEventInfo bti = new TestStartedEventInfo(new JSONObject(eventJson));
    359             mCurrentTest = new TestDescription(bti.mClassName, bti.mTestName);
    360             if (bti.mStartTime != null) {
    361                 mListener.testStarted(mCurrentTest, bti.mStartTime);
    362             } else {
    363                 mListener.testStarted(mCurrentTest);
    364             }
    365         }
    366     }
    367 
    368     private class TestFailedEventHandler implements EventHandler {
    369         @Override
    370         public void handleEvent(String eventJson) throws JSONException {
    371             FailedTestEventInfo fti = new FailedTestEventInfo(new JSONObject(eventJson));
    372             checkCurrentTestId(fti.mClassName, fti.mTestName);
    373             mListener.testFailed(mCurrentTest, fti.mTrace);
    374         }
    375     }
    376 
    377     private class TestEndedEventHandler implements EventHandler {
    378         @Override
    379         public void handleEvent(String eventJson) throws JSONException {
    380             try {
    381                 TestEndedEventInfo tei = new TestEndedEventInfo(new JSONObject(eventJson));
    382                 checkCurrentTestId(tei.mClassName, tei.mTestName);
    383                 if (tei.mEndTime != null) {
    384                     mListener.testEnded(
    385                             mCurrentTest,
    386                             tei.mEndTime,
    387                             TfMetricProtoUtil.upgradeConvert(tei.mRunMetrics));
    388                 } else {
    389                     mListener.testEnded(
    390                             mCurrentTest, TfMetricProtoUtil.upgradeConvert(tei.mRunMetrics));
    391                 }
    392             } finally {
    393                 mCurrentTest = null;
    394             }
    395         }
    396     }
    397 
    398     private class TestIgnoredEventHandler implements EventHandler {
    399         @Override
    400         public void handleEvent(String eventJson) throws JSONException {
    401             BaseTestEventInfo baseTestIgnored = new BaseTestEventInfo(new JSONObject(eventJson));
    402             checkCurrentTestId(baseTestIgnored.mClassName, baseTestIgnored.mTestName);
    403             mListener.testIgnored(mCurrentTest);
    404         }
    405     }
    406 
    407     private class TestAssumptionFailureEventHandler implements EventHandler {
    408         @Override
    409         public void handleEvent(String eventJson) throws JSONException {
    410             FailedTestEventInfo FailedAssumption =
    411                     new FailedTestEventInfo(new JSONObject(eventJson));
    412             checkCurrentTestId(FailedAssumption.mClassName, FailedAssumption.mTestName);
    413             mListener.testAssumptionFailure(mCurrentTest, FailedAssumption.mTrace);
    414         }
    415     }
    416 
    417     private class TestModuleStartedEventHandler implements EventHandler {
    418         @Override
    419         public void handleEvent(String eventJson) throws JSONException {
    420             TestModuleStartedEventInfo module =
    421                     new TestModuleStartedEventInfo(new JSONObject(eventJson));
    422             mCurrentModuleContext = module.mModuleContext;
    423             mListener.testModuleStarted(module.mModuleContext);
    424         }
    425     }
    426 
    427     private class TestModuleEndedEventHandler implements EventHandler {
    428         @Override
    429         public void handleEvent(String eventJson) throws JSONException {
    430             if (mCurrentModuleContext == null) {
    431                 CLog.w("Calling testModuleEnded when testModuleStarted was not called.");
    432             }
    433             mListener.testModuleEnded();
    434             mCurrentModuleContext = null;
    435         }
    436     }
    437 
    438     private class TestLogEventHandler implements EventHandler {
    439         @Override
    440         public void handleEvent(String eventJson) throws JSONException {
    441             TestLogEventInfo logInfo = new TestLogEventInfo(new JSONObject(eventJson));
    442             String name = String.format("subprocess-%s", logInfo.mDataName);
    443             try {
    444                 InputStreamSource data = new FileInputStreamSource(logInfo.mDataFile);
    445                 mListener.testLog(name, logInfo.mLogType, data);
    446             } finally {
    447                 FileUtil.deleteFile(logInfo.mDataFile);
    448             }
    449         }
    450     }
    451 
    452     private class LogAssociationEventHandler implements EventHandler {
    453         @Override
    454         public void handleEvent(String eventJson) throws JSONException {
    455             LogAssociationEventInfo assosInfo =
    456                     new LogAssociationEventInfo(new JSONObject(eventJson));
    457             if (mListener instanceof ILogSaverListener) {
    458                 ((ILogSaverListener) mListener)
    459                         .logAssociation(assosInfo.mDataName, assosInfo.mLoggedFile);
    460             }
    461         }
    462     }
    463 
    464     private class InvocationStartedEventHandler implements EventHandler {
    465         @Override
    466         public void handleEvent(String eventJson) throws JSONException {
    467             InvocationStartedEventInfo eventStart =
    468                     new InvocationStartedEventInfo(new JSONObject(eventJson));
    469             if (mContext.getTestTag() == null || "stub".equals(mContext.getTestTag())) {
    470                 mContext.setTestTag(eventStart.mTestTag);
    471             }
    472             mStartTime = eventStart.mStartTime;
    473         }
    474     }
    475 
    476     /**
    477      * Returns the start time associated with the invocation start event from the subprocess
    478      * invocation.
    479      */
    480     public Long getStartTime() {
    481         return mStartTime;
    482     }
    483 
    484     /** Returns the test that is currently in progress. */
    485     public TestDescription getCurrentTest() {
    486         return mCurrentTest;
    487     }
    488 }
    489