1 #include <android/log.h>
2 #include <errno.h>
3 #include <jni.h>
4 #include <pthread.h>
5 #include <Python.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <unistd.h>
9
10
throw_runtime_exception(JNIEnv * env,const char * message)11 static void throw_runtime_exception(JNIEnv *env, const char *message) {
12 (*env)->ThrowNew(
13 env,
14 (*env)->FindClass(env, "java/lang/RuntimeException"),
15 message);
16 }
17
18
19 // --- Stdio redirection ------------------------------------------------------
20
21 // Most apps won't need this, because the Python-level sys.stdout and sys.stderr
22 // are redirected to the Android logcat by Python itself. However, in the
23 // testbed it's useful to redirect the native streams as well, to debug problems
24 // in the Python startup or redirection process.
25 //
26 // Based on
27 // https://github.com/beeware/briefcase-android-gradle-template/blob/v0.3.11/%7B%7B%20cookiecutter.safe_formal_name%20%7D%7D/app/src/main/cpp/native-lib.cpp
28
29 typedef struct {
30 FILE *file;
31 int fd;
32 android_LogPriority priority;
33 char *tag;
34 int pipe[2];
35 } StreamInfo;
36
37 static StreamInfo STREAMS[] = {
38 {stdout, STDOUT_FILENO, ANDROID_LOG_INFO, "native.stdout", {-1, -1}},
39 {stderr, STDERR_FILENO, ANDROID_LOG_WARN, "native.stderr", {-1, -1}},
40 {NULL, -1, ANDROID_LOG_UNKNOWN, NULL, {-1, -1}},
41 };
42
43 // The maximum length of a log message in bytes, including the level marker and
44 // tag, is defined as LOGGER_ENTRY_MAX_PAYLOAD in
45 // platform/system/logging/liblog/include/log/log.h. As of API level 30, messages
46 // longer than this will be be truncated by logcat. This limit has already been
47 // reduced at least once in the history of Android (from 4076 to 4068 between API
48 // level 23 and 26), so leave some headroom.
49 static const int MAX_BYTES_PER_WRITE = 4000;
50
redirection_thread(void * arg)51 static void *redirection_thread(void *arg) {
52 StreamInfo *si = (StreamInfo*)arg;
53 ssize_t read_size;
54 char buf[MAX_BYTES_PER_WRITE];
55 while ((read_size = read(si->pipe[0], buf, sizeof buf - 1)) > 0) {
56 buf[read_size] = '\0'; /* add null-terminator */
57 __android_log_write(si->priority, si->tag, buf);
58 }
59 return 0;
60 }
61
redirect_stream(StreamInfo * si)62 static char *redirect_stream(StreamInfo *si) {
63 /* make the FILE unbuffered, to ensure messages are never lost */
64 if (setvbuf(si->file, 0, _IONBF, 0)) {
65 return "setvbuf";
66 }
67
68 /* create the pipe and redirect the file descriptor */
69 if (pipe(si->pipe)) {
70 return "pipe";
71 }
72 if (dup2(si->pipe[1], si->fd) == -1) {
73 return "dup2";
74 }
75
76 /* start the logging thread */
77 pthread_t thr;
78 if ((errno = pthread_create(&thr, 0, redirection_thread, si))) {
79 return "pthread_create";
80 }
81 if ((errno = pthread_detach(thr))) {
82 return "pthread_detach";
83 }
84 return 0;
85 }
86
Java_org_python_testbed_PythonTestRunner_redirectStdioToLogcat(JNIEnv * env,jobject obj)87 JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToLogcat(
88 JNIEnv *env, jobject obj
89 ) {
90 for (StreamInfo *si = STREAMS; si->file; si++) {
91 char *error_prefix;
92 if ((error_prefix = redirect_stream(si))) {
93 char error_message[1024];
94 snprintf(error_message, sizeof(error_message),
95 "%s: %s", error_prefix, strerror(errno));
96 throw_runtime_exception(env, error_message);
97 return;
98 }
99 }
100 }
101
102
103 // --- Python initialization ---------------------------------------------------
104
set_config_string(JNIEnv * env,PyConfig * config,wchar_t ** config_str,jstring value)105 static PyStatus set_config_string(
106 JNIEnv *env, PyConfig *config, wchar_t **config_str, jstring value
107 ) {
108 const char *value_utf8 = (*env)->GetStringUTFChars(env, value, NULL);
109 PyStatus status = PyConfig_SetBytesString(config, config_str, value_utf8);
110 (*env)->ReleaseStringUTFChars(env, value, value_utf8);
111 return status;
112 }
113
throw_status(JNIEnv * env,PyStatus status)114 static void throw_status(JNIEnv *env, PyStatus status) {
115 throw_runtime_exception(env, status.err_msg ? status.err_msg : "");
116 }
117
Java_org_python_testbed_PythonTestRunner_runPython(JNIEnv * env,jobject obj,jstring home,jstring runModule)118 JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython(
119 JNIEnv *env, jobject obj, jstring home, jstring runModule
120 ) {
121 PyConfig config;
122 PyStatus status;
123 PyConfig_InitIsolatedConfig(&config);
124
125 status = set_config_string(env, &config, &config.home, home);
126 if (PyStatus_Exception(status)) {
127 throw_status(env, status);
128 return 1;
129 }
130
131 status = set_config_string(env, &config, &config.run_module, runModule);
132 if (PyStatus_Exception(status)) {
133 throw_status(env, status);
134 return 1;
135 }
136
137 // Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
138 config.install_signal_handlers = 1;
139
140 status = Py_InitializeFromConfig(&config);
141 if (PyStatus_Exception(status)) {
142 throw_status(env, status);
143 return 1;
144 }
145
146 return Py_RunMain();
147 }
148