• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.textclassifier.testing;
18 
19 import android.system.ErrnoException;
20 import android.system.Os;
21 import android.util.Log;
22 
23 import androidx.test.internal.runner.listener.InstrumentationRunListener;
24 
25 import org.junit.runner.Result;
26 
27 /**
28  * To make native coverage measurement possible.
29  */
30 public class TextClassifierInstrumentationListener extends InstrumentationRunListener {
31     private static final String LOG_TAG = "androidtc";
32     // Signal used to trigger a dump of Clang coverage information.
33     // See {@code maybeDumpNativeCoverage} below.
34     private static final int COVERAGE_SIGNAL = 37;
35 
36     @Override
testRunFinished(Result result)37     public void testRunFinished(Result result) throws Exception {
38         maybeDumpNativeCoverage();
39         super.testRunFinished(result);
40     }
41 
42     /**
43      * If this test process is instrumented for native coverage, then trigger a dump
44      * of the coverage data and wait until either we detect the dumping has finished or 60 seconds,
45      * whichever is shorter.
46      *
47      * Background: Coverage builds install a signal handler for signal 37 which flushes coverage
48      * data to disk, which may take a few seconds.  Tests running as an app process will get
49      * killed with SIGKILL once the app code exits, even if the coverage handler is still running.
50      *
51      * Method: If a handler is installed for signal 37, then assume this is a coverage run and
52      * send signal 37.  The handler is non-reentrant and so signal 37 will then be blocked until
53      * the handler completes. So after we send the signal, we loop checking the blocked status
54      * for signal 37 until we hit the 60 second deadline.  If the signal is blocked then sleep for
55      * 2 seconds, and if it becomes unblocked then the handler exitted so we can return early.
56      * If the signal is not blocked at the start of the loop then most likely the handler has
57      * not yet been invoked.  This should almost never happen as it should get blocked on delivery
58      * when we call {@code Os.kill()}, so sleep for a shorter duration (100ms) and try again.  There
59      * is a race condition here where the handler is delayed but then runs for less than 100ms and
60      * gets missed, in which case this method will loop with 100ms sleeps until the deadline.
61      *
62      * In the case where the handler runs for more than 60 seconds, the test process will be allowed
63      * to exit so coverage information may be incomplete.
64      *
65      * There is no API for determining signal dispositions, so this method uses the
66      * {@link SignalMaskInfo} class to read the data from /proc.  If there is an error parsing
67      * the /proc data then this method will also loop until the 60s deadline passes.
68      */
maybeDumpNativeCoverage()69     private void maybeDumpNativeCoverage() {
70         SignalMaskInfo siginfo = new SignalMaskInfo();
71         if (!siginfo.isValid()) {
72             Log.e(LOG_TAG, "Invalid signal info");
73             return;
74         }
75 
76         if (!siginfo.isCaught(COVERAGE_SIGNAL)) {
77             // Process is not instrumented for coverage
78             Log.i(LOG_TAG, "Not dumping coverage, no handler installed");
79             return;
80         }
81 
82         Log.i(LOG_TAG,
83                 String.format("Sending coverage dump signal %d to pid %d uid %d", COVERAGE_SIGNAL,
84                         Os.getpid(), Os.getuid()));
85         try {
86             Os.kill(Os.getpid(), COVERAGE_SIGNAL);
87         } catch (ErrnoException e) {
88             Log.e(LOG_TAG, "Unable to send coverage signal", e);
89             return;
90         }
91 
92         long start = System.currentTimeMillis();
93         long deadline = start + 60 * 1000L;
94         while (System.currentTimeMillis() < deadline) {
95             siginfo.refresh();
96             try {
97                 if (siginfo.isValid() && siginfo.isBlocked(COVERAGE_SIGNAL)) {
98                     // Signal is currently blocked so assume a handler is running
99                     Thread.sleep(2000L);
100                     siginfo.refresh();
101                     if (siginfo.isValid() && !siginfo.isBlocked(COVERAGE_SIGNAL)) {
102                         // Coverage handler exited while we were asleep
103                         Log.i(LOG_TAG,
104                                 String.format("Coverage dump detected finished after %dms",
105                                         System.currentTimeMillis() - start));
106                         break;
107                     }
108                 } else {
109                     // Coverage signal handler not yet started or invalid siginfo
110                     Thread.sleep(100L);
111                 }
112             } catch (InterruptedException e) {
113                 // ignored
114             }
115         }
116     }
117 }