From 8d41ac9b9b88dee9ef8cf1acd481950221fe8b6d Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Thu, 11 Sep 2025 21:12:24 +0100 Subject: [PATCH 1/7] Update Android test-command handling --- cibuildwheel/platforms/android.py | 7 ++++--- test/test_android.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index b3f614fba..2f7e8f7b2 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -613,8 +613,8 @@ def test_wheel(state: BuildState, wheel: Path) -> None: # Parse test-command. test_args = shlex.split(test_command) - if test_args[:2] in [["python", "-c"], ["python", "-m"]]: - test_args[:3] = [test_args[1], test_args[2], "--"] + if test_args[0] == "python" and any(arg in test_args for arg in ["-c", "-m"]): + del test_args[0] elif test_args[0] in ["pytest"]: # We transform some commands into the `python -m` form, but this is deprecated. msg = ( @@ -623,7 +623,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: "If this works, all you need to do is add that to your test command." ) log.warning(msg) - test_args[:1] = ["-m", test_args[0], "--"] + test_args[:1] = ["-m", test_args[0]] else: msg = ( f"Test command {test_command!r} is not supported on Android. " @@ -642,6 +642,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: "--cwd", cwd_dir, *(["-v"] if state.options.build_verbosity > 0 else []), + "--", *test_args, env=state.build_env, ) diff --git a/test/test_android.py b/test/test_android.py index a391674e8..bd4149ecf 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -262,6 +262,11 @@ def test_test_command_good(command, expected_output, tmp_path, spam_env, capfd): "Test command './test_spam.py' is not supported on Android. " "Supported commands are 'python -m' and 'python -c'.", ), + ( + "python test_spam.py", + "Test command 'python test_spam.py' is not supported on Android. " + "Supported commands are 'python -m' and 'python -c'.", + ), # Build-time failure: unrecognized placeholder ( "pytest {project}", @@ -283,6 +288,29 @@ def test_test_command_bad(command, expected_output, tmp_path, spam_env, capfd): assert expected_output in capfd.readouterr().err +@needs_emulator +@pytest.mark.parametrize( + ("options", "expected"), + [ + ("", 0), + ("-E", 1), + ], +) +def test_test_command_python_options(options, expected, tmp_path, capfd): + project = new_c_project() + project.generate(tmp_path) + + command = 'import sys; print(f"{sys.flags.ignore_environment=}")' + cibuildwheel_run( + tmp_path, + add_env={ + **cp313_env, + "CIBW_TEST_COMMAND": f"python {options} -c '{command}'", + }, + ) + assert f"sys.flags.ignore_environment={expected}" in capfd.readouterr().out + + @needs_emulator def test_package_subdir(tmp_path, spam_env, capfd): spam_paths = list(tmp_path.iterdir()) From 5c2a8dda280ad969ebe9632a3202f24236943c85 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 15 Oct 2025 10:58:56 +0100 Subject: [PATCH 2/7] Update to Python 3.13.8 --- cibuildwheel/resources/build-platforms.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml index f773b8bac..5005e57b1 100644 --- a/cibuildwheel/resources/build-platforms.toml +++ b/cibuildwheel/resources/build-platforms.toml @@ -231,8 +231,8 @@ python_configurations = [ [android] python_configurations = [ - { identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-aarch64-linux-android.tar.gz" }, - { identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-x86_64-linux-android.tar.gz" }, + { identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.8/python-3.13.8-aarch64-linux-android.tar.gz" }, + { identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.8/python-3.13.8-x86_64-linux-android.tar.gz" }, { identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0-aarch64-linux-android.tar.gz" }, { identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0-x86_64-linux-android.tar.gz" }, ] From 4e80e19500217442a74c316e058de6d746d7515a Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 15 Oct 2025 11:36:16 +0100 Subject: [PATCH 3/7] Update documentation and tests --- README.md | 2 +- cibuildwheel/platforms/android.py | 2 +- docs/options.md | 10 ++--- test/test_android.py | 66 +++++++++++++++++++------------ 4 files changed, 47 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 5266ee697..5c4eb6219 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Usage ² [Uses cross-compilation](https://cibuildwheel.pypa.io/en/stable/faq/#windows-arm64). It is not possible to test `arm64` on this CI platform.
³ Requires a macOS runner; runs tests on the simulator for the runner's architecture.
⁴ Building for Android requires the runner to be Linux x86_64, macOS ARM64 or macOS x86_64. Testing has [additional requirements](https://cibuildwheel.pypa.io/en/stable/platforms/#android).
-⁵ The `macos-15` and `macos-latest` images are [incompatible with cibuildwheel at this time](platforms/#ios-system-requirements)
when building iOS wheels. +⁵ The `macos-15` and `macos-latest` images are [incompatible with cibuildwheel at this time](https://cibuildwheel.pypa.io/en/stable/platforms/#ios-system-requirements) when building iOS wheels.
diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index f4a310292..952e24c5d 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -631,7 +631,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: else: msg = ( f"Test command {test_command!r} is not supported on Android. " - f"Supported commands are 'python -m' and 'python -c'." + f"Command must begin with 'python' and contain '-m' or '-c'." ) raise errors.FatalError(msg) diff --git a/docs/options.md b/docs/options.md index 9f02fffec..17fe43205 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1317,7 +1317,7 @@ The available Pyodide versions are determined by the version of `pyodide-build` ### `test-command` {: #test-command env-var toml} > The command to test each built wheel -Shell command to run tests after the build. The wheel will be installed +Command to run tests after the build. The wheel will be installed automatically and available for import from the tests. If this variable is not set, your wheel will not be installed after building. @@ -1345,11 +1345,11 @@ tree. To access your test code, you have a couple of options: On all platforms other than Android and iOS, the command is run in a shell, so you can write things like `cmd1 && cmd2`. -On Android and iOS, the command is parsed by `shlex.split`, and is required to -be in one of the following forms: +On Android and iOS, the command is parsed by `shlex.split`, and must be a Python +command: -* `python -c command ...` (Android only) -* `python -m module-name ...` +* On Android, the command must must begin with `python` and contain `-m` or `-c`. +* On iOS, the command must begin with `python -m`. Platform-specific environment variables are also available:
`CIBW_TEST_COMMAND_MACOS` | `CIBW_TEST_COMMAND_WINDOWS` | `CIBW_TEST_COMMAND_LINUX` | `CIBW_TEST_COMMAND_ANDROID` | `CIBW_TEST_COMMAND_IOS` | `CIBW_TEST_COMMAND_PYODIDE` diff --git a/test/test_android.py b/test/test_android.py index bd4149ecf..4539310d7 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -32,22 +32,25 @@ allow_module_level=True, ) -# Detect CI services which have the Android SDK pre-installed. -ci_supports_build = ( - ("CIRRUS_CI" in os.environ and platform.system() == "Darwin") - or "GITHUB_ACTIONS" in os.environ - or "TF_BUILD" in os.environ # Azure Pipelines -) +# Azure Pipelines does not set the CI variable. +ci = any(key in os.environ for key in ["CI", "TF_BUILD"]) if "ANDROID_HOME" not in os.environ: msg = "ANDROID_HOME environment variable is not set" - if ci_supports_build: + + # Fail if we're on a CI service which is supposed to have the Android SDK + # pre-installed; otherwise skip the module. + if ( + ("CIRRUS_CI" in os.environ and platform.system() == "Darwin") + or "GITHUB_ACTIONS" in os.environ + or "TF_BUILD" in os.environ + ): pytest.fail(msg) else: pytest.skip(msg, allow_module_level=True) # Many CI services don't support running the Android emulator: see platforms.md. -ci_supports_emulator = "GITHUB_ACTIONS" in os.environ and platform.system() == "Linux" +supports_emulator = (not ci) or ("GITHUB_ACTIONS" in os.environ and platform.system() == "Linux") def needs_emulator(test): @@ -55,7 +58,7 @@ def needs_emulator(test): # application ID, so these tests must be run serially. test = pytest.mark.serial(test) - if ci_supports_build and not ci_supports_emulator: + if not supports_emulator: test = pytest.mark.skip("This CI platform doesn't support the emulator")(test) return test @@ -92,12 +95,20 @@ def test_android_home(tmp_path, capfd): assert "ANDROID_HOME environment variable is not set" in capfd.readouterr().err -# the first build can fail to setup - mark as flaky, and serial to make sure it runs first +# android-env.sh may need to install the NDK, and it isn't safe to do that multiple +# times in parallel. So make sure there's at least one test which gets as far as doing +# a build, which is marked as serial so it will run before the parallel tests, but isn't +# marked as needs_emulator so it will run on all CI platforms. @pytest.mark.serial -@pytest.mark.flaky(reruns=2) -def test_expected_wheels(tmp_path): +def test_expected_wheels(tmp_path, spam_env): new_c_project().generate(tmp_path) - wheels = cibuildwheel_run(tmp_path, add_env={"CIBW_PLATFORM": "android"}) + + # Build wheels for all Python versions on the current architecture. + del spam_env["CIBW_BUILD"] + if not supports_emulator: + del spam_env["CIBW_TEST_COMMAND"] + + wheels = cibuildwheel_run(tmp_path, add_env=spam_env) assert wheels == expected_wheels( "spam", "0.1.0", platform="android", machine_arch=native_arch.android_abi ) @@ -222,12 +233,20 @@ def test_spam(): print("Spam test passed") """ ) + project.files["test_empty.py"] = dedent( + """\ + def test_empty(): + pass + """ + ) + project.generate(tmp_path) return { **cp313_env, - "CIBW_TEST_SOURCES": "test_spam.py", + "CIBW_TEST_SOURCES": "test_spam.py test_empty.py", "CIBW_TEST_REQUIRES": "pytest==8.3.5", + "CIBW_TEST_COMMAND": "python -m pytest", } @@ -236,6 +255,7 @@ def test_spam(): ("command", "expected_output"), [ ("python -c 'import test_spam; test_spam.test_spam()'", "Spam test passed"), + ("python -m pytest", "=== 2 passed in "), ("python -m pytest test_spam.py", "=== 1 passed in "), ("pytest test_spam.py", "=== 1 passed in "), ], @@ -260,12 +280,12 @@ def test_test_command_good(command, expected_output, tmp_path, spam_env, capfd): ( "./test_spam.py", "Test command './test_spam.py' is not supported on Android. " - "Supported commands are 'python -m' and 'python -c'.", + "Command must begin with 'python' and contain '-m' or '-c'.", ), ( "python test_spam.py", "Test command 'python test_spam.py' is not supported on Android. " - "Supported commands are 'python -m' and 'python -c'.", + "Command must begin with 'python' and contain '-m' or '-c'.", ), # Build-time failure: unrecognized placeholder ( @@ -319,17 +339,11 @@ def test_package_subdir(tmp_path, spam_env, capfd): for path in spam_paths: path.rename(package_dir / path.name) - test_filename = "package/" + spam_env["CIBW_TEST_SOURCES"] - cibuildwheel_run( - tmp_path, - package_dir, - add_env={ - **spam_env, - "CIBW_TEST_SOURCES": test_filename, - "CIBW_TEST_COMMAND": f"python -m pytest {test_filename}", - }, + spam_env["CIBW_TEST_SOURCES"] = " ".join( + f"package/{path}" for path in spam_env["CIBW_TEST_SOURCES"].split() ) - assert "=== 1 passed in " in capfd.readouterr().out + cibuildwheel_run(tmp_path, package_dir, add_env=spam_env) + assert "=== 2 passed in " in capfd.readouterr().out @needs_emulator From 64b753912241b507c3ba1150ecd1fb7e270a259c Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Thu, 16 Oct 2025 19:30:50 +0100 Subject: [PATCH 4/7] Deal with `sysconfig.get_config_var("exec_prefix")` changing in Python 3.14, and add cross venv tests --- cibuildwheel/resources/_cross_venv.py | 18 +++++++++++------- test/_cross_venv_test_android.py | 20 ++++++++++++++++++++ test/test_android.py | 6 +++++- 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 test/_cross_venv_test_android.py diff --git a/cibuildwheel/resources/_cross_venv.py b/cibuildwheel/resources/_cross_venv.py index 84237a2eb..40dfaca5f 100644 --- a/cibuildwheel/resources/_cross_venv.py +++ b/cibuildwheel/resources/_cross_venv.py @@ -68,16 +68,20 @@ def cross_getandroidapilevel() -> int: # sysconfig ############################################################### # - # We don't change the actual sys.base_prefix and base_exec_prefix, because that - # could have unpredictable effects. Instead, we change the internal variables - # used to generate sysconfig.get_path("include"). - exec_prefix = sysconfig.get_config_var("exec_prefix") - sysconfig._BASE_PREFIX = sysconfig._BASE_EXEC_PREFIX = exec_prefix # type: ignore[attr-defined] - - # Reload the sysconfigdata file, generating its name from sys.abiflags, + # Load the sysconfigdata file, generating its name from sys.abiflags, # sys.platform, and sys.implementation._multiarch. sysconfig._init_config_vars() # type: ignore[attr-defined] + # We don't change the actual sys.base_prefix and base_exec_prefix, because that + # could have unpredictable effects. Instead, we change the sysconfig variables + # used by sysconfig.get_paths(). + vars = sysconfig.get_config_vars() + try: + host_prefix = vars["host_prefix"] # This variable was added in Python 3.14. + except KeyError: + host_prefix = vars["exec_prefix"] + vars["installed_base"] = vars["installed_platbase"] = host_prefix + # sysconfig.get_platform, which determines the wheel tag, is implemented in terms of # sys.platform, sysconfig.get_config_var("ANDROID_API_LEVEL") (see localized_vars in # android.py), and os.uname. diff --git a/test/_cross_venv_test_android.py b/test/_cross_venv_test_android.py new file mode 100644 index 000000000..809f6332c --- /dev/null +++ b/test/_cross_venv_test_android.py @@ -0,0 +1,20 @@ +import sys +import sysconfig +from pathlib import Path + +assert sys.platform == "android" +assert sysconfig.get_platform().startswith("android-") + +android_prefix = Path(f"{sys.prefix}/../python/prefix").resolve() +assert android_prefix.is_dir() + +vars = sysconfig.get_config_vars() +assert vars["INCLUDEDIR"] == f"{android_prefix}/include" +assert vars["LDVERSION"] == f"{sys.version_info[0]}.{sys.version_info[1]}{sys.abiflags}" +assert vars["INCLUDEPY"] == f"{vars['INCLUDEDIR']}/python{vars['LDVERSION']}" +assert vars["LIBDIR"] == f"{android_prefix}/lib" +assert vars["Py_ENABLE_SHARED"] == 1 + +paths = sysconfig.get_paths() +assert paths["include"] == vars["INCLUDEPY"] +assert paths["platinclude"] == vars["INCLUDEPY"] diff --git a/test/test_android.py b/test/test_android.py index 4539310d7..007ee4b71 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -101,7 +101,11 @@ def test_android_home(tmp_path, capfd): # marked as needs_emulator so it will run on all CI platforms. @pytest.mark.serial def test_expected_wheels(tmp_path, spam_env): - new_c_project().generate(tmp_path) + # Since this test covers all Python versions, check the cross venv. + test_module = "_cross_venv_test_android" + project = new_c_project(setup_py_add=f"import {test_module}") + project.files[f"{test_module}.py"] = (Path(__file__).parent / f"{test_module}.py").read_text() + project.generate(tmp_path) # Build wheels for all Python versions on the current architecture. del spam_env["CIBW_BUILD"] From 0495c1e4dbc90250734c1c6a784eed5e7604466a Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Thu, 16 Oct 2025 19:54:08 +0100 Subject: [PATCH 5/7] Allow test commands starting with `python3` --- cibuildwheel/platforms/android.py | 4 ++-- docs/options.md | 3 ++- test/test_android.py | 39 +++++++++++++------------------ 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index 952e24c5d..23d201aa2 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -617,7 +617,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: # Parse test-command. test_args = shlex.split(test_command) - if test_args[0] == "python" and any(arg in test_args for arg in ["-c", "-m"]): + if test_args[0] in ["python", "python3"] and any(arg in test_args for arg in ["-c", "-m"]): del test_args[0] elif test_args[0] in ["pytest"]: # We transform some commands into the `python -m` form, but this is deprecated. @@ -631,7 +631,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: else: msg = ( f"Test command {test_command!r} is not supported on Android. " - f"Command must begin with 'python' and contain '-m' or '-c'." + f"Command must begin with 'python' or 'python3', and contain '-m' or '-c'." ) raise errors.FatalError(msg) diff --git a/docs/options.md b/docs/options.md index 17fe43205..60102636d 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1348,7 +1348,8 @@ On all platforms other than Android and iOS, the command is run in a shell, so y On Android and iOS, the command is parsed by `shlex.split`, and must be a Python command: -* On Android, the command must must begin with `python` and contain `-m` or `-c`. +* On Android, the command must must begin with `python` or `python3`, and contain `-m` + or `-c`. * On iOS, the command must begin with `python -m`. Platform-specific environment variables are also available:
diff --git a/test/test_android.py b/test/test_android.py index 007ee4b71..fe7072e3a 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -258,7 +258,7 @@ def test_empty(): @pytest.mark.parametrize( ("command", "expected_output"), [ - ("python -c 'import test_spam; test_spam.test_spam()'", "Spam test passed"), + ("python3 -c 'import test_spam; test_spam.test_spam()'", "Spam test passed"), ("python -m pytest", "=== 2 passed in "), ("python -m pytest test_spam.py", "=== 1 passed in "), ("pytest test_spam.py", "=== 1 passed in "), @@ -276,32 +276,25 @@ def test_test_command_good(command, expected_output, tmp_path, spam_env, capfd): ) in stderr +BAD_FORMAT_ERROR = ( + "Test command '{}' is not supported on Android. " + "Command must begin with 'python' or 'python3', and contain '-m' or '-c'." +) +BAD_PLACEHOLDER_ERROR = ( + "Test command '{}' with a '{{project}}' or '{{package}}' placeholder " + "is not supported on Android" +) + + @needs_emulator @pytest.mark.parametrize( ("command", "expected_output"), [ - # Build-time failure: unrecognized command - ( - "./test_spam.py", - "Test command './test_spam.py' is not supported on Android. " - "Command must begin with 'python' and contain '-m' or '-c'.", - ), - ( - "python test_spam.py", - "Test command 'python test_spam.py' is not supported on Android. " - "Command must begin with 'python' and contain '-m' or '-c'.", - ), - # Build-time failure: unrecognized placeholder - ( - "pytest {project}", - "Test command 'pytest {project}' with a '{project}' or '{package}' " - "placeholder is not supported on Android", - ), - ( - "pytest {package}", - "Test command 'pytest {package}' with a '{project}' or '{package}' " - "placeholder is not supported on Android", - ), + # Build-time failure + ("./test_spam.py", BAD_FORMAT_ERROR.format("./test_spam.py")), + ("python test_spam.py", BAD_FORMAT_ERROR.format("python test_spam.py")), + ("pytest {project}", BAD_PLACEHOLDER_ERROR.format("pytest {project}")), + ("pytest {package}", BAD_PLACEHOLDER_ERROR.format("pytest {package}")), # Runtime failure ("pytest test_ham.py", "not found: test_ham.py"), ], From 22e8fbd2e4d07192ba4efa196692fe389c8447fb Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Sun, 19 Oct 2025 12:12:05 +0100 Subject: [PATCH 6/7] test-command cleanups --- cibuildwheel/platforms/android.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index 23d201aa2..611c453a5 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -618,6 +618,9 @@ def test_wheel(state: BuildState, wheel: Path) -> None: # Parse test-command. test_args = shlex.split(test_command) if test_args[0] in ["python", "python3"] and any(arg in test_args for arg in ["-c", "-m"]): + # Forward the args to the CPython testbed script. We require '-c' or '-m' + # to be in the command, because without those flags, the testbed script + # will prepend '-m test', which will run Python's own test suite. del test_args[0] elif test_args[0] in ["pytest"]: # We transform some commands into the `python -m` form, but this is deprecated. @@ -627,7 +630,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: "If this works, all you need to do is add that to your test command." ) log.warning(msg) - test_args[:1] = ["-m", test_args[0]] + test_args.insert(0, "-m") else: msg = ( f"Test command {test_command!r} is not supported on Android. " From bad797de50fb7cd3259059c682aaf49834ab9601 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 19 Oct 2025 11:12:38 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cibuildwheel/platforms/android.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index 611c453a5..dca2f5117 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -619,7 +619,7 @@ def test_wheel(state: BuildState, wheel: Path) -> None: test_args = shlex.split(test_command) if test_args[0] in ["python", "python3"] and any(arg in test_args for arg in ["-c", "-m"]): # Forward the args to the CPython testbed script. We require '-c' or '-m' - # to be in the command, because without those flags, the testbed script + # to be in the command, because without those flags, the testbed script # will prepend '-m test', which will run Python's own test suite. del test_args[0] elif test_args[0] in ["pytest"]: