You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by da...@apache.org on 2022/12/29 17:53:48 UTC

[beam] branch master updated: Shard python precommit (#24204)

This is an automated email from the ASF dual-hosted git repository.

damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 598324a8847 Shard python precommit (#24204)
598324a8847 is described below

commit 598324a8847e8bd4ea00c9526777e9e6e51229af
Author: Danny McCormick <da...@google.com>
AuthorDate: Thu Dec 29 12:53:40 2022 -0500

    Shard python precommit (#24204)
    
    * WIP: Shard python precommit
    
    * Naming
    
    * Formatting
    
    * Allow empty test suites
    
    * Fix no posargs case
    
    * Fix paths
    
    * Split out integration tests
    
    * Do common tasks in IT suite
    
    * Fix name base
    
    * More splitting
    
    * Fix quotes
    
    * try ignore-glob
    
    * Add groovy files per subfolder
    
    * name typo
    
    * Filter dataframes tests
    
    * Logging
    
    * Switch to correct directory syntax
    
    * Merge master + fix paths
    
    * Consolidate back to reasonable set
    
    * Add jobs to README
    
    * Try full quotes
    
    * Add in missing tests
    
    * Move arg lines down
---
 .test-infra/jenkins/README.md                      |  5 ++
 .test-infra/jenkins/job_PreCommit_Python.groovy    |  3 +
 ...Python.groovy => job_PreCommit_PythonIT.groovy} |  4 +-
 ...roovy => job_PreCommit_Python_Dataframe.groovy} |  5 +-
 ...groovy => job_PreCommit_Python_Examples.groovy} |  5 +-
 ....groovy => job_PreCommit_Python_Runners.groovy} |  5 +-
 ...oovy => job_PreCommit_Python_Transforms.groovy} |  5 +-
 build.gradle.kts                                   |  4 ++
 sdks/python/scripts/run_pytest.sh                  |  7 +--
 sdks/python/test-suites/tox/common.gradle          | 12 ++--
 sdks/python/test-suites/tox/py37/build.gradle      |  6 +-
 sdks/python/test-suites/tox/py38/build.gradle      | 71 ++++++++++++----------
 sdks/python/tox.ini                                |  8 ++-
 13 files changed, 86 insertions(+), 54 deletions(-)

diff --git a/.test-infra/jenkins/README.md b/.test-infra/jenkins/README.md
index e53dae86c45..8bc4b6f9e25 100644
--- a/.test-infra/jenkins/README.md
+++ b/.test-infra/jenkins/README.md
@@ -44,6 +44,11 @@ Beam Jenkins overview page: [link](https://ci-beam.apache.org/)
 | beam_PreCommit_Portable_Python | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Portable_Python_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Portable_Python_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Portable_Python_Phrase/) | `Run Portable_Python PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Portable_Python_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Portable_Python_Cron) |
 | beam_PreCommit_PythonLint | [commit](https://ci-beam.apache.org/job/beam_PreCommit_PythonLint_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_PythonLint_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_PythonLint_Phrase/) | `Run PythonLint PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_PythonLint_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_PythonLint_Cron) |
 | beam_PreCommit_Python | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Phrase/) | `Run Python PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Cron) |
+| beam_PreCommit_Python_Integration | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Integration_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Integration_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Integration_Phrase/) | `Run Python_Integration PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Integration_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Integration_Cron) |
+| beam_PreCommit_Python_Dataframe | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Dataframe_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Dataframe_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Dataframe_Phrase/) | `Run Python_TODO PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Dataframe_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Dataframe_Cron) |
+| beam_PreCommit_Python_Examples | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Examples_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Examples_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Examples_Phrase/) | `Run Python_TODO PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Examples_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Examples_Cron) |
+| beam_PreCommit_Python_Runners | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Runners_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Runners_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Runners_Phrase/) | `Run Python_TODO PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Runners_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Runners_Cron) |
+| beam_PreCommit_Python_Transforms | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_Transforms_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_Transforms_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_Transforms_Phrase/) | `Run Python_TODO PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_Transforms_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_Transforms_Cron) |
 | beam_PreCommit_PythonDocker | [commit](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocker_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocker_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocker_Phrase/) | `Run PythonDocker PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocker_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocker_Cron/) |
 | beam_PreCommit_PythonDocs| [commit](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocs_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocs_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocs_Phrase/) | `Run PythonDocs PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocs_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_PythonDocs_Cron/) |
 | beam_PreCommit_Python_PVR_Flink | [commit](https://ci-beam.apache.org/job/beam_PreCommit_Python_PVR_Flink_Commit/), [cron](https://ci-beam.apache.org/job/beam_PreCommit_Python_PVR_Flink_Cron/), [phrase](https://ci-beam.apache.org/job/beam_PreCommit_Python_PVR_Flink_Phrase/) | `Run Python_PVR_Flink PreCommit` | [![Build Status](https://ci-beam.apache.org/job/beam_PreCommit_Python_PVR_Flink_Cron/badge/icon)](https://ci-beam.apache.org/job/beam_PreCommit_Python_PVR_Flink_Cron) |
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python.groovy
index 0b5270f96ee..289a7c846b3 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_Python.groovy
@@ -22,6 +22,9 @@ PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
     nameBase: 'Python',
     gradleTask: ':pythonPreCommit',
+    gradleSwitches: [
+      '-Pposargs=\"apache_beam/*.py apache_beam/coders apache_beam/internal apache_beam/io apache_beam/metrics apache_beam/ml apache_beam/options apache_beam/portability apache_beam/testing apache_beam/tools apache_beam/typehints apache_beam/utils\"' // All other tests are covered by different jobs.
+    ],
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_PythonIT.groovy
similarity index 93%
copy from .test-infra/jenkins/job_PreCommit_Python.groovy
copy to .test-infra/jenkins/job_PreCommit_PythonIT.groovy
index 0b5270f96ee..3d563748d0b 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_PythonIT.groovy
@@ -20,8 +20,8 @@ import PrecommitJobBuilder
 
 PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
-    nameBase: 'Python',
-    gradleTask: ':pythonPreCommit',
+    nameBase: 'Python_Integration',
+    gradleTask: ':pythonPreCommitIT',
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python_Dataframe.groovy
similarity index 91%
copy from .test-infra/jenkins/job_PreCommit_Python.groovy
copy to .test-infra/jenkins/job_PreCommit_Python_Dataframe.groovy
index 0b5270f96ee..986faa71788 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_Python_Dataframe.groovy
@@ -20,8 +20,11 @@ import PrecommitJobBuilder
 
 PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
-    nameBase: 'Python',
+    nameBase: 'Python_Dataframe',
     gradleTask: ':pythonPreCommit',
+    gradleSwitches: [
+      '-Pposargs=apache_beam/dataframe/'
+    ],
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python_Examples.groovy
similarity index 92%
copy from .test-infra/jenkins/job_PreCommit_Python.groovy
copy to .test-infra/jenkins/job_PreCommit_Python_Examples.groovy
index 0b5270f96ee..12e43e155fa 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_Python_Examples.groovy
@@ -20,8 +20,11 @@ import PrecommitJobBuilder
 
 PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
-    nameBase: 'Python',
+    nameBase: 'Python_Examples',
     gradleTask: ':pythonPreCommit',
+    gradleSwitches: [
+      '-Pposargs=apache_beam/examples/'
+    ],
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python_Runners.groovy
similarity index 92%
copy from .test-infra/jenkins/job_PreCommit_Python.groovy
copy to .test-infra/jenkins/job_PreCommit_Python_Runners.groovy
index 0b5270f96ee..a9ca1da0d2e 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_Python_Runners.groovy
@@ -20,8 +20,11 @@ import PrecommitJobBuilder
 
 PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
-    nameBase: 'Python',
+    nameBase: 'Python_Runners',
     gradleTask: ':pythonPreCommit',
+    gradleSwitches: [
+      '-Pposargs=apache_beam/runners/'
+    ],
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python_Transforms.groovy
similarity index 91%
copy from .test-infra/jenkins/job_PreCommit_Python.groovy
copy to .test-infra/jenkins/job_PreCommit_Python_Transforms.groovy
index 0b5270f96ee..1264fffa07b 100644
--- a/.test-infra/jenkins/job_PreCommit_Python.groovy
+++ b/.test-infra/jenkins/job_PreCommit_Python_Transforms.groovy
@@ -20,8 +20,11 @@ import PrecommitJobBuilder
 
 PrecommitJobBuilder builder = new PrecommitJobBuilder(
     scope: this,
-    nameBase: 'Python',
+    nameBase: 'Python_Transforms',
     gradleTask: ':pythonPreCommit',
+    gradleSwitches: [
+      '-Pposargs=apache_beam/transforms/'
+    ],
     timeoutMins: 180,
     triggerPathPatterns: [
       '^model/.*$',
diff --git a/build.gradle.kts b/build.gradle.kts
index 74fb5c7d666..f489908b76d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -420,6 +420,10 @@ tasks.register("pythonPreCommit") {
   dependsOn(":sdks:python:test-suites:tox:py38:preCommitPy38")
   dependsOn(":sdks:python:test-suites:tox:py39:preCommitPy39")
   dependsOn(":sdks:python:test-suites:tox:py310:preCommitPy310")
+}
+
+tasks.register("pythonPreCommitIT") {
+  dependsOn(":sdks:python:test-suites:tox:pycommon:preCommitPyCommon")
   dependsOn(":sdks:python:test-suites:dataflow:preCommitIT")
   dependsOn(":sdks:python:test-suites:dataflow:preCommitIT_V2")
 }
diff --git a/sdks/python/scripts/run_pytest.sh b/sdks/python/scripts/run_pytest.sh
index 6c7d5c46e78..3df342aaf91 100755
--- a/sdks/python/scripts/run_pytest.sh
+++ b/sdks/python/scripts/run_pytest.sh
@@ -43,12 +43,7 @@ pytest -o junit_suite_name=${envname}_no_xdist \
   --junitxml=pytest_${envname}_no_xdist.xml -m 'no_xdist' ${pytest_args} --pyargs ${posargs}
 status2=$?
 
-# Exit with error if no tests were run in either suite (status code 5).
-if [[ $status1 == 5 && $status2 == 5 ]]; then
-  exit $status1
-fi
-
-# Exit with error if one of the statuses has an error that's not 5.
+# Exit with error if one of the statuses has an error that's not 5 (no tests run).
 if [[ $status1 != 0 && $status1 != 5 ]]; then
   exit $status1
 fi
diff --git a/sdks/python/test-suites/tox/common.gradle b/sdks/python/test-suites/tox/common.gradle
index 61802ac9c45..5f5a1012f56 100644
--- a/sdks/python/test-suites/tox/common.gradle
+++ b/sdks/python/test-suites/tox/common.gradle
@@ -18,19 +18,21 @@
 
 def pythonVersionSuffix = project.ext.pythonVersion.replace('.', '')
 
-toxTask "testPython${pythonVersionSuffix}", "py${pythonVersionSuffix}"
+def posargs = project.findProperty("posargs") ?: ""
+
+toxTask "testPython${pythonVersionSuffix}", "py${pythonVersionSuffix}", "${posargs}"
 test.dependsOn "testPython${pythonVersionSuffix}"
 
-toxTask "testPy${pythonVersionSuffix}Cloud", "py${pythonVersionSuffix}-cloud"
+toxTask "testPy${pythonVersionSuffix}Cloud", "py${pythonVersionSuffix}-cloud", "${posargs}"
 test.dependsOn "testPy${pythonVersionSuffix}Cloud"
 
-toxTask "testPy${pythonVersionSuffix}Dask", "py${pythonVersionSuffix}-dask"
+toxTask "testPy${pythonVersionSuffix}Dask", "py${pythonVersionSuffix}-dask", "${posargs}"
 test.dependsOn "testPy${pythonVersionSuffix}Dask"
 
-toxTask "testPy${pythonVersionSuffix}Cython", "py${pythonVersionSuffix}-cython"
+toxTask "testPy${pythonVersionSuffix}Cython", "py${pythonVersionSuffix}-cython", "${posargs}"
 test.dependsOn "testPy${pythonVersionSuffix}Cython"
 
-toxTask "testPy38CloudCoverage", "py38-cloudcoverage"
+toxTask "testPy38CloudCoverage", "py38-cloudcoverage", "${posargs}"
 test.dependsOn "testPy38CloudCoverage"
 
 project.tasks.register("preCommitPy${pythonVersionSuffix}") {
diff --git a/sdks/python/test-suites/tox/py37/build.gradle b/sdks/python/test-suites/tox/py37/build.gradle
index 2ea0e46ca5b..744ca675062 100644
--- a/sdks/python/test-suites/tox/py37/build.gradle
+++ b/sdks/python/test-suites/tox/py37/build.gradle
@@ -26,13 +26,15 @@ applyPythonNature()
 // Required to setup a Python 3 virtualenv and task names.
 pythonVersion = '3.7'
 
+def posargs = project.findProperty("posargs") ?: ""
+
 task lint {}
 check.dependsOn lint
 
-toxTask "lintPy37", "py37-lint"
+toxTask "lintPy37", "py37-lint", "${posargs}"
 lint.dependsOn lintPy37
 
-toxTask "mypyPy37", "py37-mypy"
+toxTask "mypyPy37", "py37-mypy", "${posargs}"
 lint.dependsOn mypyPy37
 
 apply from: "../common.gradle"
diff --git a/sdks/python/test-suites/tox/py38/build.gradle b/sdks/python/test-suites/tox/py38/build.gradle
index f51eec00da0..9a28a4c96dc 100644
--- a/sdks/python/test-suites/tox/py38/build.gradle
+++ b/sdks/python/test-suites/tox/py38/build.gradle
@@ -34,79 +34,86 @@ apply from: "../common.gradle"
 // TODO(https://github.com/apache/beam/issues/20051): Remove this once tox uses isolated builds.
 testPy38Cython.mustRunAfter testPython38, testPy38CloudCoverage
 
+// TODO(BEAM-12000): Move tasks that aren't specific to 3.8 to Py 3.9.
+def posargs = project.findProperty("posargs") ?: ""
+def runDataframesTests = (posargs == "" || posargs.contains("dataframes"))
+logger.info('Running dataframes tests: ' + runDataframesTests)
+
 // Create a test task for each major version of pyarrow
-// TODO(BEAM-12000): Move these to Py 3.9.
-toxTask "testPy38pyarrow-0", "py38-pyarrow-0"
+toxTask "testPy38pyarrow-0", "py38-pyarrow-0", "${posargs}"
 test.dependsOn "testPy38pyarrow-0"
 preCommitPy38.dependsOn "testPy38pyarrow-0"
 
-toxTask "testPy38pyarrow-1", "py38-pyarrow-1"
+toxTask "testPy38pyarrow-1", "py38-pyarrow-1", "${posargs}"
 test.dependsOn "testPy38pyarrow-1"
 preCommitPy38.dependsOn "testPy38pyarrow-1"
 
-toxTask "testPy38pyarrow-2", "py38-pyarrow-2"
+toxTask "testPy38pyarrow-2", "py38-pyarrow-2", "${posargs}"
 test.dependsOn "testPy38pyarrow-2"
 preCommitPy38.dependsOn "testPy38pyarrow-2"
 
-toxTask "testPy38pyarrow-3", "py38-pyarrow-3"
+toxTask "testPy38pyarrow-3", "py38-pyarrow-3", "${posargs}"
 test.dependsOn "testPy38pyarrow-3"
 preCommitPy38.dependsOn "testPy38pyarrow-3"
 
-toxTask "testPy38pyarrow-4", "py38-pyarrow-4"
+toxTask "testPy38pyarrow-4", "py38-pyarrow-4", "${posargs}"
 test.dependsOn "testPy38pyarrow-4"
 preCommitPy38.dependsOn "testPy38pyarrow-4"
 
-toxTask "testPy38pyarrow-5", "py38-pyarrow-5"
+toxTask "testPy38pyarrow-5", "py38-pyarrow-5", "${posargs}"
 test.dependsOn "testPy38pyarrow-5"
 preCommitPy38.dependsOn "testPy38pyarrow-5"
 
-toxTask "testPy38pyarrow-6", "py38-pyarrow-6"
+toxTask "testPy38pyarrow-6", "py38-pyarrow-6", "${posargs}"
 test.dependsOn "testPy38pyarrow-6"
 preCommitPy38.dependsOn "testPy38pyarrow-6"
 
-toxTask "testPy38pyarrow-7", "py38-pyarrow-7"
+toxTask "testPy38pyarrow-7", "py38-pyarrow-7", "${posargs}"
 test.dependsOn "testPy38pyarrow-7"
 preCommitPy38.dependsOn "testPy38pyarrow-7"
 
-toxTask "testPy38pyarrow-8", "py38-pyarrow-8"
+toxTask "testPy38pyarrow-8", "py38-pyarrow-8", "${posargs}"
 test.dependsOn "testPy38pyarrow-8"
 preCommitPy38.dependsOn "testPy38pyarrow-8"
 
-toxTask "testPy38pyarrow-9", "py38-pyarrow-9"
+toxTask "testPy38pyarrow-9", "py38-pyarrow-9", "${posargs}"
 test.dependsOn "testPy38pyarrow-9"
 preCommitPy38.dependsOn "testPy38pyarrow-9"
 
-// Create a test task for each minor version of pandas
-toxTask "testPy38pandas-11", "py38-pandas-11"
-test.dependsOn "testPy38pandas-11"
-preCommitPy38.dependsOn "testPy38pandas-11"
+// Dataframes tests won't get auto-filtered by path since they explicitly only run on the dataframes directory in tox.ini
+if (runDataframesTests) {
+  // Create a test task for each minor version of pandas
+  toxTask "testPy38pandas-11", "py38-pandas-11", "${posargs}"
+  test.dependsOn "testPy38pandas-11"
+  preCommitPy38.dependsOn "testPy38pandas-11"
 
-toxTask "testPy38pandas-12", "py38-pandas-12"
-test.dependsOn "testPy38pandas-12"
-preCommitPy38.dependsOn "testPy38pandas-12"
+  toxTask "testPy38pandas-12", "py38-pandas-12", "${posargs}"
+  test.dependsOn "testPy38pandas-12"
+  preCommitPy38.dependsOn "testPy38pandas-12"
 
-toxTask "testPy38pandas-13", "py38-pandas-13"
-test.dependsOn "testPy38pandas-13"
-preCommitPy38.dependsOn "testPy38pandas-13"
+  toxTask "testPy38pandas-13", "py38-pandas-13", "${posargs}"
+  test.dependsOn "testPy38pandas-13"
+  preCommitPy38.dependsOn "testPy38pandas-13"
 
-toxTask "testPy38pandas-14", "py38-pandas-14"
-test.dependsOn "testPy38pandas-14"
-preCommitPy38.dependsOn "testPy38pandas-14"
+  toxTask "testPy38pandas-14", "py38-pandas-14", "${posargs}"
+  test.dependsOn "testPy38pandas-14"
+  preCommitPy38.dependsOn "testPy38pandas-14"
 
-toxTask "testPy38pandas-15", "py38-pandas-15"
-test.dependsOn "testPy38pandas-15"
-preCommitPy38.dependsOn "testPy38pandas-15"
+  toxTask "testPy38pandas-15", "py38-pandas-15"
+  test.dependsOn "testPy38pandas-15"
+  preCommitPy38.dependsOn "testPy38pandas-15"
+}
 
 // Create a test task for each minor version of pytorch
-toxTask "testPy38pytorch-19", "py38-pytorch-19"
+toxTask "testPy38pytorch-19", "py38-pytorch-19", "${posargs}"
 test.dependsOn "testPy38pytorch-19"
 preCommitPy38.dependsOn "testPy38pytorch-19"
 
-toxTask "testPy38pytorch-110", "py38-pytorch-110"
+toxTask "testPy38pytorch-110", "py38-pytorch-110", "${posargs}"
 test.dependsOn "testPy38pytorch-110"
 preCommitPy38.dependsOn "testPy38pytorch-110"
 
-toxTask "whitespacelint", "whitespacelint"
+toxTask "whitespacelint", "whitespacelint", "${posargs}"
 
 task archiveFilesToLint(type: Zip) {
   archiveFileName = "files-to-whitespacelint.zip"
@@ -128,9 +135,9 @@ whitespacelint.dependsOn archiveFilesToLint, unpackFilesToLint
 unpackFilesToLint.dependsOn archiveFilesToLint
 archiveFilesToLint.dependsOn cleanPython
 
-toxTask "jest", "jest"
+toxTask "jest", "jest", "${posargs}"
 
-toxTask "eslint", "eslint"
+toxTask "eslint", "eslint", "${posargs}"
 
 task copyTsSource(type: Copy) {
   from ("$rootProject.projectDir") {
diff --git a/sdks/python/tox.ini b/sdks/python/tox.ini
index 2efa81ab203..e5aecff4888 100644
--- a/sdks/python/tox.ini
+++ b/sdks/python/tox.ini
@@ -296,8 +296,9 @@ commands =
   # Log pyarrow and numpy version for debugging
   /bin/sh -c "pip freeze | grep -E '(pyarrow|numpy)'"
   # Run pytest directly rather using run_pytest.sh. It doesn't handle
-  # selecting tests with -m (BEAM-12985)
-  pytest -o junit_suite_name={envname} --junitxml=pytest_{envname}.xml -n 6 -m uses_pyarrow {posargs}
+  # selecting tests with -m (BEAM-12985).
+  # Allow exit code 5 (no tests run) so that we can run this command safely on arbitrary subdirectories.
+  /bin/sh -c 'pytest -o junit_suite_name={envname} --junitxml=pytest_{envname}.xml -n 6 -m uses_pyarrow {posargs}; ret=$?; [ $ret = 5 ] && exit 0 || exit $ret'
 
 [testenv:py{37,38,39,310}-pandas-{11,12,13,14,15}]
 deps =
@@ -323,4 +324,5 @@ commands =
   # Log torch version for debugging
   /bin/sh -c "pip freeze | grep -E torch"
   # Run all PyTorch unit tests
-  pytest -o junit_suite_name={envname} --junitxml=pytest_{envname}.xml -n 6 -m uses_pytorch {posargs}
+  # Allow exit code 5 (no tests run) so that we can run this command safely on arbitrary subdirectories.
+  /bin/sh -c 'pytest -o junit_suite_name={envname} --junitxml=pytest_{envname}.xml -n 6 -m uses_pytorch {posargs}; ret=$?; [ $ret = 5 ] && exit 0 || exit $ret'