1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // This class sets up the environment for running the native tests inside an 6 // android application. It outputs (to a fifo) markers identifying the 7 // START/PASSED/CRASH of the test suite, FAILURE/SUCCESS of individual tests, 8 // etc. 9 // These markers are read by the test runner script to generate test results. 10 // It installs signal handlers to detect crashes. 11 12 #include <android/log.h> 13 #include <signal.h> 14 15 #include "base/android/base_jni_registrar.h" 16 #include "base/android/fifo_utils.h" 17 #include "base/android/jni_android.h" 18 #include "base/android/jni_string.h" 19 #include "base/android/scoped_java_ref.h" 20 #include "base/at_exit.h" 21 #include "base/base_switches.h" 22 #include "base/command_line.h" 23 #include "base/file_util.h" 24 #include "base/files/file_path.h" 25 #include "base/logging.h" 26 #include "base/strings/stringprintf.h" 27 #include "gtest/gtest.h" 28 #include "testing/android/native_test_util.h" 29 #include "testing/jni/ChromeNativeTestActivity_jni.h" 30 31 using testing::native_test_util::ArgsToArgv; 32 using testing::native_test_util::ParseArgsFromCommandLineFile; 33 using testing::native_test_util::ScopedMainEntryLogger; 34 35 // The main function of the program to be wrapped as a test apk. 36 extern int main(int argc, char** argv); 37 38 namespace { 39 40 // These two command line flags are supported for DumpRenderTree, which needs 41 // three fifos rather than a combined one: one for stderr, stdin and stdout. 42 const char kSeparateStderrFifo[] = "separate-stderr-fifo"; 43 const char kCreateStdinFifo[] = "create-stdin-fifo"; 44 45 // The test runner script writes the command line file in 46 // "/data/local/tmp". 47 static const char kCommandLineFilePath[] = 48 "/data/local/tmp/chrome-native-tests-command-line"; 49 50 const char kLogTag[] = "chromium"; 51 const char kCrashedMarker[] = "[ CRASHED ]\n"; 52 53 // The list of signals which are considered to be crashes. 54 const int kExceptionSignals[] = { 55 SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1 56 }; 57 58 struct sigaction g_old_sa[NSIG]; 59 60 // This function runs in a compromised context. It should not allocate memory. 61 void SignalHandler(int sig, siginfo_t* info, void* reserved) { 62 // Output the crash marker. 63 write(STDOUT_FILENO, kCrashedMarker, sizeof(kCrashedMarker)); 64 g_old_sa[sig].sa_sigaction(sig, info, reserved); 65 } 66 67 // TODO(nileshagrawal): now that we're using FIFO, test scripts can detect EOF. 68 // Remove the signal handlers. 69 void InstallHandlers() { 70 struct sigaction sa; 71 memset(&sa, 0, sizeof(sa)); 72 73 sa.sa_sigaction = SignalHandler; 74 sa.sa_flags = SA_SIGINFO; 75 76 for (unsigned int i = 0; kExceptionSignals[i] != -1; ++i) { 77 sigaction(kExceptionSignals[i], &sa, &g_old_sa[kExceptionSignals[i]]); 78 } 79 } 80 81 // Writes printf() style string to Android's logger where |priority| is one of 82 // the levels defined in <android/log.h>. 83 void AndroidLog(int priority, const char* format, ...) { 84 va_list args; 85 va_start(args, format); 86 __android_log_vprint(priority, kLogTag, format, args); 87 va_end(args); 88 } 89 90 // Ensures that the fifo at |path| is created by deleting whatever is at |path| 91 // prior to (re)creating the fifo, otherwise logs the error and terminates the 92 // program. 93 void EnsureCreateFIFO(const base::FilePath& path) { 94 unlink(path.value().c_str()); 95 if (base::android::CreateFIFO(path, 0666)) 96 return; 97 98 AndroidLog(ANDROID_LOG_ERROR, "Failed to create fifo %s: %s\n", 99 path.value().c_str(), strerror(errno)); 100 exit(EXIT_FAILURE); 101 } 102 103 // Ensures that |stream| is redirected to |path|, otherwise logs the error and 104 // terminates the program. 105 void EnsureRedirectStream(FILE* stream, 106 const base::FilePath& path, 107 const char* mode) { 108 if (base::android::RedirectStream(stream, path, mode)) 109 return; 110 111 AndroidLog(ANDROID_LOG_ERROR, "Failed to redirect stream to file: %s: %s\n", 112 path.value().c_str(), strerror(errno)); 113 exit(EXIT_FAILURE); 114 } 115 116 } // namespace 117 118 static void RunTests(JNIEnv* env, 119 jobject obj, 120 jstring jfiles_dir, 121 jobject app_context) { 122 base::AtExitManager exit_manager; 123 124 // Command line initialized basically, will be fully initialized later. 125 static const char* const kInitialArgv[] = { "ChromeTestActivity" }; 126 CommandLine::Init(arraysize(kInitialArgv), kInitialArgv); 127 128 // Set the application context in base. 129 base::android::ScopedJavaLocalRef<jobject> scoped_context( 130 env, env->NewLocalRef(app_context)); 131 base::android::InitApplicationContext(env, scoped_context); 132 base::android::RegisterJni(env); 133 134 std::vector<std::string> args; 135 ParseArgsFromCommandLineFile(kCommandLineFilePath, &args); 136 137 std::vector<char*> argv; 138 int argc = ArgsToArgv(args, &argv); 139 140 // Fully initialize command line with arguments. 141 CommandLine::ForCurrentProcess()->AppendArguments( 142 CommandLine(argc, &argv[0]), false); 143 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 144 145 base::FilePath files_dir( 146 base::android::ConvertJavaStringToUTF8(env, jfiles_dir)); 147 148 // A few options, such "--gtest_list_tests", will just use printf directly 149 // Always redirect stdout to a known file. 150 base::FilePath fifo_path(files_dir.Append(base::FilePath("test.fifo"))); 151 EnsureCreateFIFO(fifo_path); 152 153 base::FilePath stderr_fifo_path, stdin_fifo_path; 154 155 // DumpRenderTree needs a separate fifo for the stderr output. For all 156 // other tests, insert stderr content to the same fifo we use for stdout. 157 if (command_line.HasSwitch(kSeparateStderrFifo)) { 158 stderr_fifo_path = files_dir.Append(base::FilePath("stderr.fifo")); 159 EnsureCreateFIFO(stderr_fifo_path); 160 } 161 162 // DumpRenderTree uses stdin to receive input about which test to run. 163 if (command_line.HasSwitch(kCreateStdinFifo)) { 164 stdin_fifo_path = files_dir.Append(base::FilePath("stdin.fifo")); 165 EnsureCreateFIFO(stdin_fifo_path); 166 } 167 168 // Only redirect the streams after all fifos have been created. 169 EnsureRedirectStream(stdout, fifo_path, "w"); 170 if (!stdin_fifo_path.empty()) 171 EnsureRedirectStream(stdin, stdin_fifo_path, "r"); 172 if (!stderr_fifo_path.empty()) 173 EnsureRedirectStream(stderr, stderr_fifo_path, "w"); 174 else 175 dup2(STDOUT_FILENO, STDERR_FILENO); 176 177 if (command_line.HasSwitch(switches::kWaitForDebugger)) { 178 AndroidLog(ANDROID_LOG_VERBOSE, 179 "Native test waiting for GDB because flag %s was supplied", 180 switches::kWaitForDebugger); 181 base::debug::WaitForDebugger(24 * 60 * 60, false); 182 } 183 184 ScopedMainEntryLogger scoped_main_entry_logger; 185 main(argc, &argv[0]); 186 } 187 188 // This is called by the VM when the shared library is first loaded. 189 JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 190 // Install signal handlers to detect crashes. 191 InstallHandlers(); 192 193 base::android::InitVM(vm); 194 JNIEnv* env = base::android::AttachCurrentThread(); 195 if (!RegisterNativesImpl(env)) { 196 return -1; 197 } 198 199 return JNI_VERSION_1_4; 200 } 201