mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.14] gh-137242: Allow Android testbed to take all Python command-line options (GH-138805) (#139637)
Co-authored-by: Malcolm Smith <smith@chaquo.com>
This commit is contained in:
parent
bb212a1a8b
commit
f776254080
7 changed files with 152 additions and 129 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -393,7 +393,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Build and test
|
- name: Build and test
|
||||||
run: ./Android/android.py ci ${{ matrix.arch }}-linux-android
|
run: ./Android/android.py ci --fast-ci ${{ matrix.arch }}-linux-android
|
||||||
|
|
||||||
build-wasi:
|
build-wasi:
|
||||||
name: 'WASI'
|
name: 'WASI'
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
|
@ -552,27 +553,33 @@ async def gradle_task(context):
|
||||||
task_prefix = "connected"
|
task_prefix = "connected"
|
||||||
env["ANDROID_SERIAL"] = context.connected
|
env["ANDROID_SERIAL"] = context.connected
|
||||||
|
|
||||||
if context.command:
|
if context.ci_mode:
|
||||||
mode = "-c"
|
context.args[0:0] = [
|
||||||
module = context.command
|
# See _add_ci_python_opts in libregrtest/main.py.
|
||||||
else:
|
"-W", "error", "-bb", "-E",
|
||||||
mode = "-m"
|
|
||||||
module = context.module or "test"
|
# Randomization is disabled because order-dependent failures are
|
||||||
|
# much less likely to pass on a rerun in single-process mode.
|
||||||
|
"-m", "test",
|
||||||
|
f"--{context.ci_mode}-ci", "--single-process", "--no-randomize"
|
||||||
|
]
|
||||||
|
|
||||||
|
if not any(arg in context.args for arg in ["-c", "-m"]):
|
||||||
|
context.args[0:0] = ["-m", "test"]
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
gradlew, "--console", "plain", f"{task_prefix}DebugAndroidTest",
|
gradlew, "--console", "plain", f"{task_prefix}DebugAndroidTest",
|
||||||
] + [
|
] + [
|
||||||
# Build-time properties
|
f"-P{name}={value}"
|
||||||
f"-Ppython.{name}={value}"
|
|
||||||
for name, value in [
|
for name, value in [
|
||||||
("sitePackages", context.site_packages), ("cwd", context.cwd)
|
("python.sitePackages", context.site_packages),
|
||||||
] if value
|
("python.cwd", context.cwd),
|
||||||
] + [
|
(
|
||||||
# Runtime properties
|
"android.testInstrumentationRunnerArguments.pythonArgs",
|
||||||
f"-Pandroid.testInstrumentationRunnerArguments.python{name}={value}"
|
json.dumps(context.args),
|
||||||
for name, value in [
|
),
|
||||||
("Mode", mode), ("Module", module), ("Args", join_command(context.args))
|
]
|
||||||
] if value
|
if value
|
||||||
]
|
]
|
||||||
if context.verbose >= 2:
|
if context.verbose >= 2:
|
||||||
args.append("--info")
|
args.append("--info")
|
||||||
|
|
@ -740,15 +747,14 @@ def ci(context):
|
||||||
else:
|
else:
|
||||||
with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
|
with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
|
||||||
print("::group::Tests")
|
print("::group::Tests")
|
||||||
|
|
||||||
# Prove the package is self-contained by using it to run the tests.
|
# Prove the package is self-contained by using it to run the tests.
|
||||||
shutil.unpack_archive(package_path, temp_dir)
|
shutil.unpack_archive(package_path, temp_dir)
|
||||||
|
launcher_args = [
|
||||||
# Randomization is disabled because order-dependent failures are
|
"--managed", "maxVersion", "-v", f"--{context.ci_mode}-ci"
|
||||||
# much less likely to pass on a rerun in single-process mode.
|
]
|
||||||
launcher_args = ["--managed", "maxVersion", "-v"]
|
|
||||||
test_args = ["--fast-ci", "--single-process", "--no-randomize"]
|
|
||||||
run(
|
run(
|
||||||
["./android.py", "test", *launcher_args, "--", *test_args],
|
["./android.py", "test", *launcher_args],
|
||||||
cwd=temp_dir
|
cwd=temp_dir
|
||||||
)
|
)
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
|
|
@ -831,18 +837,11 @@ def add_parser(*args, **kwargs):
|
||||||
test.add_argument(
|
test.add_argument(
|
||||||
"--cwd", metavar="DIR", type=abspath,
|
"--cwd", metavar="DIR", type=abspath,
|
||||||
help="Directory to copy as the app's working directory.")
|
help="Directory to copy as the app's working directory.")
|
||||||
|
|
||||||
mode_group = test.add_mutually_exclusive_group()
|
|
||||||
mode_group.add_argument(
|
|
||||||
"-c", dest="command", help="Execute the given Python code.")
|
|
||||||
mode_group.add_argument(
|
|
||||||
"-m", dest="module", help="Execute the module with the given name.")
|
|
||||||
test.epilog = (
|
|
||||||
"If neither -c nor -m are passed, the default is '-m test', which will "
|
|
||||||
"run Python's own test suite.")
|
|
||||||
test.add_argument(
|
test.add_argument(
|
||||||
"args", nargs="*", help=f"Arguments to add to sys.argv. "
|
"args", nargs="*", help=f"Python command-line arguments. "
|
||||||
f"Separate them from {SCRIPT_NAME}'s own arguments with `--`.")
|
f"Separate them from {SCRIPT_NAME}'s own arguments with `--`. "
|
||||||
|
f"If neither -c nor -m are included, `-m test` will be prepended, "
|
||||||
|
f"which will run Python's own test suite.")
|
||||||
|
|
||||||
# Package arguments.
|
# Package arguments.
|
||||||
for subcommand in [package, ci]:
|
for subcommand in [package, ci]:
|
||||||
|
|
@ -850,6 +849,16 @@ def add_parser(*args, **kwargs):
|
||||||
"-g", action="store_true", default=False, dest="debug",
|
"-g", action="store_true", default=False, dest="debug",
|
||||||
help="Include debug information in package")
|
help="Include debug information in package")
|
||||||
|
|
||||||
|
# CI arguments
|
||||||
|
for subcommand in [test, ci]:
|
||||||
|
group = subcommand.add_mutually_exclusive_group(required=subcommand is ci)
|
||||||
|
group.add_argument(
|
||||||
|
"--fast-ci", action="store_const", dest="ci_mode", const="fast",
|
||||||
|
help="Add test arguments for GitHub Actions")
|
||||||
|
group.add_argument(
|
||||||
|
"--slow-ci", action="store_const", dest="ci_mode", const="slow",
|
||||||
|
help="Add test arguments for buildbots")
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class PythonSuite {
|
||||||
val status = PythonTestRunner(
|
val status = PythonTestRunner(
|
||||||
InstrumentationRegistry.getInstrumentation().targetContext
|
InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
).run(
|
).run(
|
||||||
InstrumentationRegistry.getArguments()
|
InstrumentationRegistry.getArguments().getString("pythonArgs")!!,
|
||||||
)
|
)
|
||||||
assertEquals(0, status)
|
assertEquals(0, status)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -15,6 +16,13 @@ static void throw_runtime_exception(JNIEnv *env, const char *message) {
|
||||||
message);
|
message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void throw_errno(JNIEnv *env, const char *error_prefix) {
|
||||||
|
char error_message[1024];
|
||||||
|
snprintf(error_message, sizeof(error_message),
|
||||||
|
"%s: %s", error_prefix, strerror(errno));
|
||||||
|
throw_runtime_exception(env, error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Stdio redirection ------------------------------------------------------
|
// --- Stdio redirection ------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -95,10 +103,7 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
|
||||||
for (StreamInfo *si = STREAMS; si->file; si++) {
|
for (StreamInfo *si = STREAMS; si->file; si++) {
|
||||||
char *error_prefix;
|
char *error_prefix;
|
||||||
if ((error_prefix = redirect_stream(si))) {
|
if ((error_prefix = redirect_stream(si))) {
|
||||||
char error_message[1024];
|
throw_errno(env, error_prefix);
|
||||||
snprintf(error_message, sizeof(error_message),
|
|
||||||
"%s: %s", error_prefix, strerror(errno));
|
|
||||||
throw_runtime_exception(env, error_message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,13 +112,38 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
|
||||||
|
|
||||||
// --- Python initialization ---------------------------------------------------
|
// --- Python initialization ---------------------------------------------------
|
||||||
|
|
||||||
static PyStatus set_config_string(
|
static char *init_signals() {
|
||||||
JNIEnv *env, PyConfig *config, wchar_t **config_str, jstring value
|
// Some tests use SIGUSR1, but that's blocked by default in an Android app in
|
||||||
) {
|
// order to make it available to `sigwait` in the Signal Catcher thread.
|
||||||
const char *value_utf8 = (*env)->GetStringUTFChars(env, value, NULL);
|
// (https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:art/runtime/signal_catcher.cc).
|
||||||
PyStatus status = PyConfig_SetBytesString(config, config_str, value_utf8);
|
// That thread's functionality is only useful for debugging the JVM, so disabling
|
||||||
(*env)->ReleaseStringUTFChars(env, value, value_utf8);
|
// it should not weaken the tests.
|
||||||
return status;
|
//
|
||||||
|
// There's no safe way of stopping the thread completely (#123982), but simply
|
||||||
|
// unblocking SIGUSR1 is enough to fix most tests.
|
||||||
|
//
|
||||||
|
// However, in tests that generate multiple different signals in quick
|
||||||
|
// succession, it's possible for SIGUSR1 to arrive while the main thread is busy
|
||||||
|
// running the C-level handler for a different signal. In that case, the SIGUSR1
|
||||||
|
// may be sent to the Signal Catcher thread instead, which will generate a log
|
||||||
|
// message containing the text "reacting to signal".
|
||||||
|
//
|
||||||
|
// Such tests may need to be changed in one of the following ways:
|
||||||
|
// * Use a signal other than SIGUSR1 (e.g. test_stress_delivery_simultaneous in
|
||||||
|
// test_signal.py).
|
||||||
|
// * Send the signal to a specific thread rather than the whole process (e.g.
|
||||||
|
// test_signals in test_threadsignals.py.
|
||||||
|
sigset_t set;
|
||||||
|
if (sigemptyset(&set)) {
|
||||||
|
return "sigemptyset";
|
||||||
|
}
|
||||||
|
if (sigaddset(&set, SIGUSR1)) {
|
||||||
|
return "sigaddset";
|
||||||
|
}
|
||||||
|
if ((errno = pthread_sigmask(SIG_UNBLOCK, &set, NULL))) {
|
||||||
|
return "pthread_sigmask";
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void throw_status(JNIEnv *env, PyStatus status) {
|
static void throw_status(JNIEnv *env, PyStatus status) {
|
||||||
|
|
@ -121,27 +151,47 @@ static void throw_status(JNIEnv *env, PyStatus status) {
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython(
|
JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython(
|
||||||
JNIEnv *env, jobject obj, jstring home, jstring runModule
|
JNIEnv *env, jobject obj, jstring home, jarray args
|
||||||
) {
|
) {
|
||||||
|
const char *home_utf8 = (*env)->GetStringUTFChars(env, home, NULL);
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
snprintf(cwd, sizeof(cwd), "%s/%s", home_utf8, "cwd");
|
||||||
|
if (chdir(cwd)) {
|
||||||
|
throw_errno(env, "chdir");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *error_prefix;
|
||||||
|
if ((error_prefix = init_signals())) {
|
||||||
|
throw_errno(env, error_prefix);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PyConfig config;
|
PyConfig config;
|
||||||
PyStatus status;
|
PyStatus status;
|
||||||
PyConfig_InitIsolatedConfig(&config);
|
PyConfig_InitPythonConfig(&config);
|
||||||
|
|
||||||
status = set_config_string(env, &config, &config.home, home);
|
jsize argc = (*env)->GetArrayLength(env, args);
|
||||||
if (PyStatus_Exception(status)) {
|
const char *argv[argc + 1];
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
jobject arg = (*env)->GetObjectArrayElement(env, args, i);
|
||||||
|
argv[i] = (*env)->GetStringUTFChars(env, arg, NULL);
|
||||||
|
}
|
||||||
|
argv[argc] = NULL;
|
||||||
|
|
||||||
|
// PyConfig_SetBytesArgv "must be called before other methods, since the
|
||||||
|
// preinitialization configuration depends on command line arguments"
|
||||||
|
if (PyStatus_Exception(status = PyConfig_SetBytesArgv(&config, argc, (char**)argv))) {
|
||||||
throw_status(env, status);
|
throw_status(env, status);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = set_config_string(env, &config, &config.run_module, runModule);
|
status = PyConfig_SetBytesString(&config, &config.home, home_utf8);
|
||||||
if (PyStatus_Exception(status)) {
|
if (PyStatus_Exception(status)) {
|
||||||
throw_status(env, status);
|
throw_status(env, status);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
|
|
||||||
config.install_signal_handlers = 1;
|
|
||||||
|
|
||||||
status = Py_InitializeFromConfig(&config);
|
status = Py_InitializeFromConfig(&config);
|
||||||
if (PyStatus_Exception(status)) {
|
if (PyStatus_Exception(status)) {
|
||||||
throw_status(env, status);
|
throw_status(env, status);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.os.*
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.*
|
import androidx.appcompat.app.*
|
||||||
|
import org.json.JSONArray
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,30 +16,25 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
val status = PythonTestRunner(this).run("-m", "test", "-W -uall")
|
val status = PythonTestRunner(this).run("""["-m", "test", "-W", "-uall"]""")
|
||||||
findViewById<TextView>(R.id.tvHello).text = "Exit status $status"
|
findViewById<TextView>(R.id.tvHello).text = "Exit status $status"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PythonTestRunner(val context: Context) {
|
class PythonTestRunner(val context: Context) {
|
||||||
fun run(instrumentationArgs: Bundle) = run(
|
|
||||||
instrumentationArgs.getString("pythonMode")!!,
|
|
||||||
instrumentationArgs.getString("pythonModule")!!,
|
|
||||||
instrumentationArgs.getString("pythonArgs") ?: "",
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Run Python.
|
/** Run Python.
|
||||||
*
|
*
|
||||||
* @param mode Either "-c" or "-m".
|
* @param args Python command-line, encoded as JSON.
|
||||||
* @param module Python statements for "-c" mode, or a module name for
|
|
||||||
* "-m" mode.
|
|
||||||
* @param args Arguments to add to sys.argv. Will be parsed by `shlex.split`.
|
|
||||||
* @return The Python exit status: zero on success, nonzero on failure. */
|
* @return The Python exit status: zero on success, nonzero on failure. */
|
||||||
fun run(mode: String, module: String, args: String) : Int {
|
fun run(args: String) : Int {
|
||||||
Os.setenv("PYTHON_MODE", mode, true)
|
// We leave argument 0 as an empty string, which is a placeholder for the
|
||||||
Os.setenv("PYTHON_MODULE", module, true)
|
// executable name in embedded mode.
|
||||||
Os.setenv("PYTHON_ARGS", args, true)
|
val argsJsonArray = JSONArray(args)
|
||||||
|
val argsStringArray = Array<String>(argsJsonArray.length() + 1) { it -> ""}
|
||||||
|
for (i in 0..<argsJsonArray.length()) {
|
||||||
|
argsStringArray[i + 1] = argsJsonArray.getString(i)
|
||||||
|
}
|
||||||
|
|
||||||
// Python needs this variable to help it find the temporary directory,
|
// Python needs this variable to help it find the temporary directory,
|
||||||
// but Android only sets it on API level 33 and later.
|
// but Android only sets it on API level 33 and later.
|
||||||
|
|
@ -47,10 +43,7 @@ class PythonTestRunner(val context: Context) {
|
||||||
val pythonHome = extractAssets()
|
val pythonHome = extractAssets()
|
||||||
System.loadLibrary("main_activity")
|
System.loadLibrary("main_activity")
|
||||||
redirectStdioToLogcat()
|
redirectStdioToLogcat()
|
||||||
|
return runPython(pythonHome.toString(), argsStringArray)
|
||||||
// The main module is in src/main/python. We don't simply call it
|
|
||||||
// "main", as that could clash with third-party test code.
|
|
||||||
return runPython(pythonHome.toString(), "android_testbed_main")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractAssets() : File {
|
private fun extractAssets() : File {
|
||||||
|
|
@ -59,6 +52,13 @@ class PythonTestRunner(val context: Context) {
|
||||||
throw RuntimeException("Failed to delete $pythonHome")
|
throw RuntimeException("Failed to delete $pythonHome")
|
||||||
}
|
}
|
||||||
extractAssetDir("python", context.filesDir)
|
extractAssetDir("python", context.filesDir)
|
||||||
|
|
||||||
|
// Empty directories are lost in the asset packing/unpacking process.
|
||||||
|
val cwd = File(pythonHome, "cwd")
|
||||||
|
if (!cwd.exists()) {
|
||||||
|
cwd.mkdir()
|
||||||
|
}
|
||||||
|
|
||||||
return pythonHome
|
return pythonHome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,5 +88,5 @@ class PythonTestRunner(val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun redirectStdioToLogcat()
|
private external fun redirectStdioToLogcat()
|
||||||
private external fun runPython(home: String, runModule: String) : Int
|
private external fun runPython(home: String, args: Array<String>) : Int
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import os
|
|
||||||
import runpy
|
|
||||||
import shlex
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Some tests use SIGUSR1, but that's blocked by default in an Android app in
|
|
||||||
# order to make it available to `sigwait` in the Signal Catcher thread.
|
|
||||||
# (https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:art/runtime/signal_catcher.cc).
|
|
||||||
# That thread's functionality is only useful for debugging the JVM, so disabling
|
|
||||||
# it should not weaken the tests.
|
|
||||||
#
|
|
||||||
# There's no safe way of stopping the thread completely (#123982), but simply
|
|
||||||
# unblocking SIGUSR1 is enough to fix most tests.
|
|
||||||
#
|
|
||||||
# However, in tests that generate multiple different signals in quick
|
|
||||||
# succession, it's possible for SIGUSR1 to arrive while the main thread is busy
|
|
||||||
# running the C-level handler for a different signal. In that case, the SIGUSR1
|
|
||||||
# may be sent to the Signal Catcher thread instead, which will generate a log
|
|
||||||
# message containing the text "reacting to signal".
|
|
||||||
#
|
|
||||||
# Such tests may need to be changed in one of the following ways:
|
|
||||||
# * Use a signal other than SIGUSR1 (e.g. test_stress_delivery_simultaneous in
|
|
||||||
# test_signal.py).
|
|
||||||
# * Send the signal to a specific thread rather than the whole process (e.g.
|
|
||||||
# test_signals in test_threadsignals.py.
|
|
||||||
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
|
|
||||||
|
|
||||||
mode = os.environ["PYTHON_MODE"]
|
|
||||||
module = os.environ["PYTHON_MODULE"]
|
|
||||||
sys.argv[1:] = shlex.split(os.environ["PYTHON_ARGS"])
|
|
||||||
|
|
||||||
cwd = f"{sys.prefix}/cwd"
|
|
||||||
if not os.path.exists(cwd):
|
|
||||||
# Empty directories are lost in the asset packing/unpacking process.
|
|
||||||
os.mkdir(cwd)
|
|
||||||
os.chdir(cwd)
|
|
||||||
|
|
||||||
if mode == "-c":
|
|
||||||
# In -c mode, sys.path starts with an empty string, which means whatever the current
|
|
||||||
# working directory is at the moment of each import.
|
|
||||||
sys.path.insert(0, "")
|
|
||||||
exec(module, {})
|
|
||||||
elif mode == "-m":
|
|
||||||
sys.path.insert(0, os.getcwd())
|
|
||||||
runpy.run_module(module, run_name="__main__", alter_sys=True)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"unknown mode: {mode}")
|
|
||||||
|
|
@ -646,15 +646,23 @@ def _add_cross_compile_opts(self, regrtest_opts):
|
||||||
return (environ, keep_environ)
|
return (environ, keep_environ)
|
||||||
|
|
||||||
def _add_ci_python_opts(self, python_opts, keep_environ):
|
def _add_ci_python_opts(self, python_opts, keep_environ):
|
||||||
# --fast-ci and --slow-ci add options to Python:
|
# --fast-ci and --slow-ci add options to Python.
|
||||||
# "-u -W default -bb -E"
|
#
|
||||||
|
# Some platforms run tests in embedded mode and cannot change options
|
||||||
|
# after startup, so if this function changes, consider also updating:
|
||||||
|
# * gradle_task in Android/android.py
|
||||||
|
|
||||||
# Unbuffered stdout and stderr
|
# Unbuffered stdout and stderr. This isn't helpful on Android, because
|
||||||
if not sys.stdout.write_through:
|
# it would cause lines to be split into multiple log messages.
|
||||||
|
if not sys.stdout.write_through and sys.platform != "android":
|
||||||
python_opts.append('-u')
|
python_opts.append('-u')
|
||||||
|
|
||||||
# Add warnings filter 'error'
|
# Add warnings filter 'error', unless the user specified a different
|
||||||
if 'default' not in sys.warnoptions:
|
# filter. Ignore BytesWarning since it's controlled by '-b' below.
|
||||||
|
if not [
|
||||||
|
opt for opt in sys.warnoptions
|
||||||
|
if not opt.endswith("::BytesWarning")
|
||||||
|
]:
|
||||||
python_opts.extend(('-W', 'error'))
|
python_opts.extend(('-W', 'error'))
|
||||||
|
|
||||||
# Error on bytes/str comparison
|
# Error on bytes/str comparison
|
||||||
|
|
@ -673,8 +681,12 @@ def _execute_python(self, cmd, environ):
|
||||||
|
|
||||||
cmd_text = shlex.join(cmd)
|
cmd_text = shlex.join(cmd)
|
||||||
try:
|
try:
|
||||||
print(f"+ {cmd_text}", flush=True)
|
# Android and iOS run tests in embedded mode. To update their
|
||||||
|
# Python options, see the comment in _add_ci_python_opts.
|
||||||
|
if not cmd[0]:
|
||||||
|
raise ValueError("No Python executable is present")
|
||||||
|
|
||||||
|
print(f"+ {cmd_text}", flush=True)
|
||||||
if hasattr(os, 'execv') and not MS_WINDOWS:
|
if hasattr(os, 'execv') and not MS_WINDOWS:
|
||||||
os.execv(cmd[0], cmd)
|
os.execv(cmd[0], cmd)
|
||||||
# On success, execv() do no return.
|
# On success, execv() do no return.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue