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