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 }