This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new cfb98e938c [CI] Simplify Jenkins pytest execution (#19947)
cfb98e938c is described below
commit cfb98e938c8d9525648c75fbebcb8944edb952fe
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Jul 5 21:59:51 2026 +0800
[CI] Simplify Jenkins pytest execution (#19947)
This PR simplifies Jenkins pytest execution around standard pytest-xdist
behavior.
- Runs each already-filtered CPU/GPU suite once with `-n auto`; the
broad suite keeps load-group scheduling because its order-sensitive
cases require it.
- Removes external sharding, wrapper/profile code, JUnit XML generation
and publication, the skipped-test XML consumer, obsolete suite naming,
and orphaned helpers.
- Retains one inert `task_clear_pytest.sh` entry point only because PR
jobs evaluate their Jenkinsfile from the trusted base branch before
checking out the PR; it performs no cleanup or reporting and can be
removed after this pipeline lands.
- Corrects stale broad-suite paths and explicit target guards, and
migrates a scalar stride test to the current `T.handle` pointer
semantics while preserving its negative lowering check.
- Prevents nested MetaSchedule/XGBoost unit tests from multiplying CPU
fanout without serializing the full suite.
- Builds only the `tvm_runtime` target for the secondary GPU
configuration and removes its unconsumed `gpu2` artifact upload.
The result reduces parallelism to one layer managed by pytest-xdist
while preserving GPU filtering and native failure visibility.
---
.github/workflows/main.yml | 2 +-
ci/jenkins/generated/arm_jenkinsfile.groovy | 11 +-
ci/jenkins/generated/cpu_jenkinsfile.groovy | 98 ++-------------
ci/jenkins/generated/docker_jenkinsfile.groovy | 11 +-
ci/jenkins/generated/gpu_jenkinsfile.groovy | 135 ++------------------
ci/jenkins/generated/wasm_jenkinsfile.groovy | 12 +-
ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 | 4 +-
ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 | 13 +-
ci/jenkins/templates/utils/Build.groovy.j2 | 7 --
ci/jenkins/templates/utils/base.groovy.j2 | 2 +-
ci/jenkins/templates/utils/macros.j2 | 30 +----
ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 | 1 -
ci/scripts/jenkins/pytest_ids.py | 43 -------
ci/scripts/jenkins/pytest_wrapper.py | 137 ---------------------
conftest.py | 54 --------
docker/install/ubuntu_install_python_package.sh | 2 -
docs/contribute/pull_request.rst | 2 +-
python/tvm/testing/plugin.py | 9 --
.../meta_schedule/test_meta_schedule_cost_model.py | 11 +-
.../test_meta_schedule_search_strategy.py | 1 +
.../test_meta_schedule_task_scheduler.py | 13 ++
.../test_s_tir_transform_lower_match_buffer.py | 2 +-
tests/python/testing/test_tvm_testing_features.py | 3 +-
tests/scripts/setup-pytest-env.sh | 98 ---------------
tests/scripts/task_clear_pytest.sh | 8 +-
tests/scripts/task_python_docs.sh | 2 +-
tests/scripts/task_python_integration.sh | 7 +-
tests/scripts/task_python_integration_gpuonly.sh | 1 -
tests/scripts/task_python_nightly.sh | 33 -----
tests/scripts/task_python_unittest.sh | 29 +++--
tests/scripts/task_python_unittest_gpuonly.sh | 11 +-
31 files changed, 89 insertions(+), 703 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a0e4cb0490..54dc54316d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -69,7 +69,7 @@ jobs:
python -m pytest -v -s
'tests/python/codegen/test_target_codegen_metal.py::test_func_with_trailing_pod_params'
- name: Minimal Metal Compile-and-Run
shell: bash -l {0}
- run: >-
+ run: |
python -m pytest -v -s
'tests/python/codegen/test_target_codegen_metal.py'
python -m pytest -v -s
'tests/python/codegen/test_target_codegen_gpu_common.py'
python -m pytest -v -s
'tests/python/codegen/test_gpu_codegen_allreduce.py::test_allreduce_sum[dims0-metal]'
diff --git a/ci/jenkins/generated/arm_jenkinsfile.groovy
b/ci/jenkins/generated/arm_jenkinsfile.groovy
index d6826878bf..3442caded7 100644
--- a/ci/jenkins/generated/arm_jenkinsfile.groovy
+++ b/ci/jenkins/generated/arm_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-06-23T03:28:14.520193
+// Generated at 2026-07-05T01:05:57.719405
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -97,7 +97,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
@@ -404,13 +404,6 @@ def prepare(node_type) {
}
}
}
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
diff --git a/ci/jenkins/generated/cpu_jenkinsfile.groovy
b/ci/jenkins/generated/cpu_jenkinsfile.groovy
index 879cd8b79f..0d3e408235 100644
--- a/ci/jenkins/generated/cpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/cpu_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-06-23T03:28:14.505504
+// Generated at 2026-07-05T01:05:57.741181
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -97,7 +97,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
@@ -404,13 +404,6 @@ def prepare(node_type) {
}
}
}
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
@@ -510,52 +503,7 @@ build()
-
-def shard_run_unittest_CPU_1_of_2(node_type) {
- echo 'Begin running on node_type ' + node_type
- if (!skip_ci && is_docs_only_build != 1) {
- node(node_type) {
- ws("workspace/exec_${env.EXECUTOR_NUMBER}/tvm/ut-python-cpu") {
- // NOTE: if exception happens, it will be caught outside
- init_git()
- docker_init(ci_cpu)
- timeout(time: max_time, unit: 'MINUTES') {
- withEnv([
- 'PLATFORM=cpu',
- 'TEST_STEP_NAME=unittest: CPU',
- 'TVM_NUM_SHARDS=2',
- 'TVM_SHARD_INDEX=0',
- "SKIP_SLOW_TESTS=${skip_slow_tests}"], {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action download
--bucket ${s3_bucket} --prefix ${s3_prefix}/cpu",
- label: 'Download artifacts from S3',
- )
-
- ci_setup(ci_cpu)
- cpp_unittest(ci_cpu)
- python_unittest(ci_cpu)
- })
- }
- // only run upload if things are successful
- try {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/unittest_CPU --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
- }
- }
- echo 'End running on node_type ' + node_type
- } else {
- Utils.markStageSkippedForConditional('unittest: CPU 1 of 2')
- }
-}
-
-def shard_run_unittest_CPU_2_of_2(node_type) {
+def run_unittest_CPU(node_type) {
echo 'Begin running on node_type ' + node_type
if (!skip_ci && is_docs_only_build != 1) {
node(node_type) {
@@ -567,35 +515,21 @@ def shard_run_unittest_CPU_2_of_2(node_type) {
withEnv([
'PLATFORM=cpu',
'TEST_STEP_NAME=unittest: CPU',
- 'TVM_NUM_SHARDS=2',
- 'TVM_SHARD_INDEX=1',
"SKIP_SLOW_TESTS=${skip_slow_tests}"], {
sh(
script: "./${jenkins_scripts_root}/s3.py --action download
--bucket ${s3_bucket} --prefix ${s3_prefix}/cpu",
label: 'Download artifacts from S3',
)
- ci_setup(ci_cpu)
cpp_unittest(ci_cpu)
python_unittest(ci_cpu)
})
}
- // only run upload if things are successful
- try {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/unittest_CPU --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
}
}
echo 'End running on node_type ' + node_type
} else {
- Utils.markStageSkippedForConditional('unittest: CPU 2 of 2')
+ Utils.markStageSkippedForConditional('unittest: CPU')
}
}
@@ -606,27 +540,9 @@ def test() {
SKIP_SLOW_TESTS = "${skip_slow_tests}"
}
parallel(
- 'unittest: CPU 1 of 2': {
- try {
- shard_run_unittest_CPU_1_of_2('CPU-SMALL-SPOT')
- } catch (Throwable ex) {
- echo 'Exception during SPOT run ' + ex.toString()
- if (is_last_build()) {
- // retry if at last build
- // mark the current stage as success
- // and try again via on demand node
- echo 'Retry on-demand given it is last build'
- currentBuild.result = 'SUCCESS'
- shard_run_unittest_CPU_1_of_2('CPU-SMALL')
- } else {
- echo 'Exit since it is not last build'
- throw ex
- }
- }
- },
- 'unittest: CPU 2 of 2': {
+ 'unittest: CPU': {
try {
- shard_run_unittest_CPU_2_of_2('CPU-SMALL-SPOT')
+ run_unittest_CPU('CPU-SMALL-SPOT')
} catch (Throwable ex) {
echo 'Exception during SPOT run ' + ex.toString()
if (is_last_build()) {
@@ -635,7 +551,7 @@ def test() {
// and try again via on demand node
echo 'Retry on-demand given it is last build'
currentBuild.result = 'SUCCESS'
- shard_run_unittest_CPU_2_of_2('CPU-SMALL')
+ run_unittest_CPU('CPU-SMALL')
} else {
echo 'Exit since it is not last build'
throw ex
diff --git a/ci/jenkins/generated/docker_jenkinsfile.groovy
b/ci/jenkins/generated/docker_jenkinsfile.groovy
index f5409e23aa..8ffe78942a 100644
--- a/ci/jenkins/generated/docker_jenkinsfile.groovy
+++ b/ci/jenkins/generated/docker_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-06-23T03:28:14.487314
+// Generated at 2026-07-05T01:05:57.759905
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -97,7 +97,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
@@ -404,13 +404,6 @@ def prepare(node_type) {
}
}
}
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
diff --git a/ci/jenkins/generated/gpu_jenkinsfile.groovy
b/ci/jenkins/generated/gpu_jenkinsfile.groovy
index 28c9a6040e..8624efbea8 100644
--- a/ci/jenkins/generated/gpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/gpu_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-06-23T03:28:14.533751
+// Generated at 2026-07-05T12:52:04.884879
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -97,7 +97,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
@@ -404,13 +404,6 @@ def prepare(node_type) {
}
}
}
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
@@ -475,11 +468,7 @@ def run_build(node_type) {
// compiler test
sh "rm -rf build"
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu_other.sh build"
- cmake_build("${ci_gpu} --no-gpu", 'build')
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/gpu2 --bundle tvm_lib",
- label: 'Upload artifacts to S3',
- )
+ sh "${docker_run} --env CI_NUM_EXECUTORS ${ci_gpu} --no-gpu
./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod
--sccache-region us-west-2 --cmake-target tvm_runtime --build-dir build"
})
}
}
@@ -515,8 +504,7 @@ def build() {
build()
-
-def shard_run_unittest_GPU_1_of_2(node_type) {
+def run_unittest_GPU(node_type) {
echo 'Begin running on node_type ' + node_type
if (!skip_ci && is_docs_only_build != 1) {
node(node_type) {
@@ -528,65 +516,12 @@ def shard_run_unittest_GPU_1_of_2(node_type) {
withEnv([
'PLATFORM=gpu',
'TEST_STEP_NAME=unittest: GPU',
- 'TVM_NUM_SHARDS=2',
- 'TVM_SHARD_INDEX=0',
"SKIP_SLOW_TESTS=${skip_slow_tests}"], {
sh(
script: "./${jenkins_scripts_root}/s3.py --action download
--bucket ${s3_bucket} --prefix ${s3_prefix}/gpu",
label: 'Download artifacts from S3',
)
- ci_setup(ci_gpu)
- sh (
- script: "${docker_run} ${ci_gpu}
./tests/scripts/task_python_unittest_gpuonly.sh",
- label: 'Run Python GPU unit tests',
- )
- sh (
- script: "${docker_run} ${ci_gpu}
./tests/scripts/task_python_integration_gpuonly.sh",
- label: 'Run Python GPU integration tests',
- )
- })
- }
- // only run upload if things are successful
- try {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/unittest_GPU --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
- }
- }
- echo 'End running on node_type ' + node_type
- } else {
- Utils.markStageSkippedForConditional('unittest: GPU 1 of 2')
- }
-}
-
-def shard_run_unittest_GPU_2_of_2(node_type) {
- echo 'Begin running on node_type ' + node_type
- if (!skip_ci && is_docs_only_build != 1) {
- node(node_type) {
- ws("workspace/exec_${env.EXECUTOR_NUMBER}/tvm/ut-python-gpu") {
- // NOTE: if exception happens, it will be caught outside
- init_git()
- docker_init(ci_gpu)
- timeout(time: max_time, unit: 'MINUTES') {
- withEnv([
- 'PLATFORM=gpu',
- 'TEST_STEP_NAME=unittest: GPU',
- 'TVM_NUM_SHARDS=2',
- 'TVM_SHARD_INDEX=1',
- "SKIP_SLOW_TESTS=${skip_slow_tests}"], {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action download
--bucket ${s3_bucket} --prefix ${s3_prefix}/gpu",
- label: 'Download artifacts from S3',
- )
-
- ci_setup(ci_gpu)
sh (
script: "${docker_run} ${ci_gpu}
./tests/scripts/task_java_unittest.sh",
label: 'Run Java unit tests',
@@ -601,29 +536,17 @@ def shard_run_unittest_GPU_2_of_2(node_type) {
)
})
}
- // only run upload if things are successful
- try {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/unittest_GPU --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
}
}
echo 'End running on node_type ' + node_type
} else {
- Utils.markStageSkippedForConditional('unittest: GPU 2 of 2')
+ Utils.markStageSkippedForConditional('unittest: GPU')
}
}
-
-def shard_run_docs_GPU_1_of_1(node_type) {
+def run_docs_GPU(node_type) {
echo 'Begin running on node_type ' + node_type
if (!skip_ci) {
node(node_type) {
@@ -635,15 +558,12 @@ def shard_run_docs_GPU_1_of_1(node_type) {
withEnv([
'PLATFORM=gpu',
'TEST_STEP_NAME=docs: GPU',
- 'TVM_NUM_SHARDS=1',
- 'TVM_SHARD_INDEX=0',
"SKIP_SLOW_TESTS=${skip_slow_tests}"], {
sh(
script: "./${jenkins_scripts_root}/s3.py --action download
--bucket ${s3_bucket} --prefix ${s3_prefix}/gpu",
label: 'Download artifacts from S3',
)
- ci_setup(ci_gpu)
sh (
script: "${docker_run} ${ci_gpu}
./tests/scripts/task_python_docs.sh",
label: 'Build docs',
@@ -659,22 +579,11 @@ def shard_run_docs_GPU_1_of_1(node_type) {
)
})
}
- // only run upload if things are successful
- try {
- sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/docs_GPU --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
}
}
echo 'End running on node_type ' + node_type
} else {
- Utils.markStageSkippedForConditional('docs: GPU 1 of 1')
+ Utils.markStageSkippedForConditional('docs: GPU')
}
}
@@ -686,27 +595,9 @@ def test() {
SKIP_SLOW_TESTS = "${skip_slow_tests}"
}
parallel(
- 'unittest: GPU 1 of 2': {
- try {
- shard_run_unittest_GPU_1_of_2('GPU-SPOT')
- } catch (Throwable ex) {
- echo 'Exception during SPOT run ' + ex.toString()
- if (is_last_build()) {
- // retry if at last build
- // mark the current stage as success
- // and try again via on demand node
- echo 'Retry on-demand given it is last build'
- currentBuild.result = 'SUCCESS'
- shard_run_unittest_GPU_1_of_2('GPU')
- } else {
- echo 'Exit since it is not last build'
- throw ex
- }
- }
- },
- 'unittest: GPU 2 of 2': {
+ 'unittest: GPU': {
try {
- shard_run_unittest_GPU_2_of_2('GPU-SPOT')
+ run_unittest_GPU('GPU-SPOT')
} catch (Throwable ex) {
echo 'Exception during SPOT run ' + ex.toString()
if (is_last_build()) {
@@ -715,16 +606,16 @@ def test() {
// and try again via on demand node
echo 'Retry on-demand given it is last build'
currentBuild.result = 'SUCCESS'
- shard_run_unittest_GPU_2_of_2('GPU')
+ run_unittest_GPU('GPU')
} else {
echo 'Exit since it is not last build'
throw ex
}
}
},
- 'docs: GPU 1 of 1': {
+ 'docs: GPU': {
try {
- shard_run_docs_GPU_1_of_1('GPU-SPOT')
+ run_docs_GPU('GPU-SPOT')
} catch (Throwable ex) {
echo 'Exception during SPOT run ' + ex.toString()
if (is_last_build()) {
@@ -733,7 +624,7 @@ def test() {
// and try again via on demand node
echo 'Retry on-demand given it is last build'
currentBuild.result = 'SUCCESS'
- shard_run_docs_GPU_1_of_1('GPU')
+ run_docs_GPU('GPU')
} else {
echo 'Exit since it is not last build'
throw ex
diff --git a/ci/jenkins/generated/wasm_jenkinsfile.groovy
b/ci/jenkins/generated/wasm_jenkinsfile.groovy
index ab64444e4a..7421fa86ca 100644
--- a/ci/jenkins/generated/wasm_jenkinsfile.groovy
+++ b/ci/jenkins/generated/wasm_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-06-23T03:28:14.550555
+// Generated at 2026-07-05T01:05:57.802104
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -97,7 +97,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
@@ -404,13 +404,6 @@ def prepare(node_type) {
}
}
}
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
@@ -471,7 +464,6 @@ def run_build(node_type) {
cmake_build(ci_wasm, 'build')
make_cpp_tests(ci_wasm, 'build')
cpp_unittest(ci_wasm)
- ci_setup(ci_wasm)
sh (
script: "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh",
label: 'Run WASM lint and tests',
diff --git a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
index d2e479d5e8..62b7c3dfea 100644
--- a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
@@ -37,16 +37,14 @@
{% set test_method_names = [] %}
-{% call(shard_index, num_shards) m.sharded_test_step(
+{% call m.test_step(
name="unittest: CPU",
ws="tvm/ut-python-cpu",
platform="cpu",
- num_shards=2,
docker_image="ci_cpu",
test_method_names=test_method_names,
) %}
{{ m.download_artifacts(tag='cpu') }}
- ci_setup(ci_cpu)
cpp_unittest(ci_cpu)
python_unittest(ci_cpu)
{% endcall %}
diff --git a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
index 7ab5256419..b05e84c7e7 100644
--- a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
@@ -32,28 +32,23 @@
// compiler test
sh "rm -rf build"
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu_other.sh build"
- cmake_build("${ci_gpu} --no-gpu", 'build')
- {{ m.upload_artifacts(tag='gpu2', bundles=["tvm_lib"]) }}
+ sh "${docker_run} --env CI_NUM_EXECUTORS ${ci_gpu} --no-gpu
./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod
--sccache-region us-west-2 --cmake-target tvm_runtime --build-dir build"
{% endcall %}
{% set test_method_names = [] %}
-{% call(shard_index, num_shards) m.sharded_test_step(
+{% call m.test_step(
name="unittest: GPU",
- num_shards=2,
ws="tvm/ut-python-gpu",
platform="gpu",
docker_image="ci_gpu",
test_method_names=test_method_names,
) %}
{{ m.download_artifacts(tag='gpu') }}
- ci_setup(ci_gpu)
- {% if shard_index == 2 or num_shards < 2 %}
sh (
script: "${docker_run} ${ci_gpu} ./tests/scripts/task_java_unittest.sh",
label: 'Run Java unit tests',
)
- {% endif %}
sh (
script: "${docker_run} ${ci_gpu}
./tests/scripts/task_python_unittest_gpuonly.sh",
label: 'Run Python GPU unit tests',
@@ -65,9 +60,8 @@
{% endcall %}
-{% call(shard_index, num_shards) m.sharded_test_step(
+{% call m.test_step(
name="docs: GPU",
- num_shards=1,
ws="tvm/docs-python-gpu",
platform="gpu",
docker_image="ci_gpu",
@@ -75,7 +69,6 @@
test_method_names=test_method_names,
) %}
{{ m.download_artifacts(tag='gpu') }}
- ci_setup(ci_gpu)
sh (
script: "${docker_run} ${ci_gpu} ./tests/scripts/task_python_docs.sh",
label: 'Build docs',
diff --git a/ci/jenkins/templates/utils/Build.groovy.j2
b/ci/jenkins/templates/utils/Build.groovy.j2
index c20a1c5c43..42bd4a991f 100644
--- a/ci/jenkins/templates/utils/Build.groovy.j2
+++ b/ci/jenkins/templates/utils/Build.groovy.j2
@@ -1,10 +1,3 @@
-def ci_setup(image) {
- sh (
- script: "${docker_run} ${image} ./tests/scripts/task_clear_pytest.sh",
- label: 'Clean up old workspace',
- )
-}
-
def python_unittest(image) {
sh (
script: "${docker_run} ${image} ./tests/scripts/task_python_unittest.sh",
diff --git a/ci/jenkins/templates/utils/base.groovy.j2
b/ci/jenkins/templates/utils/base.groovy.j2
index 813ceee886..a8fda460b7 100644
--- a/ci/jenkins/templates/utils/base.groovy.j2
+++ b/ci/jenkins/templates/utils/base.groovy.j2
@@ -82,7 +82,7 @@ properties([
upstream_revision = null
// command to start a docker container
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TVM_SHARD_INDEX
--env TVM_NUM_SHARDS --env RUN_DISPLAY_URL --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env SKIP_SLOW_TESTS
--env TEST_STEP_NAME'
docker_build = 'docker/build.sh'
// timeout in minutes
max_time = 180
diff --git a/ci/jenkins/templates/utils/macros.j2
b/ci/jenkins/templates/utils/macros.j2
index b1bd3679ac..959d91725f 100644
--- a/ci/jenkins/templates/utils/macros.j2
+++ b/ci/jenkins/templates/utils/macros.j2
@@ -19,18 +19,8 @@
"workspace/exec_${env.EXECUTOR_NUMBER}/{{ folder }}"
{%- endmacro -%}
-{% macro junit_to_s3(test_dir_name) %}
-sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/pytest-results/{{ test_dir_name }} --items
build/pytest-results",
- label: 'Upload JUnits to S3',
- )
-{% endmacro %}
-
-{% macro sharded_test_step(name, num_shards, ws, docker_image, platform,
test_method_names, condition="!skip_ci && is_docs_only_build != 1") %}
-
-{% for shard_index in range(1, num_shards + 1) %}
-{% set method_name = "shard_run_" + name.replace(":", "").replace(" ",
"-").replace("-", "_") + "_" + shard_index|string + "_of_" + num_shards|string
%}
-{% set test_dir_name = name.replace(":", "").replace(" ", "-").replace("-",
"_")|string %}
+{% macro test_step(name, ws, docker_image, platform, test_method_names,
condition="!skip_ci && is_docs_only_build != 1") %}
+{% set method_name = "run_" + name.replace(":", "").replace(" ",
"-").replace("-", "_") %}
def {{ method_name }}(node_type) {
echo 'Begin running on node_type ' + node_type
if ({{ condition }}) {
@@ -43,29 +33,19 @@ def {{ method_name }}(node_type) {
withEnv([
'PLATFORM={{ platform }}',
'TEST_STEP_NAME={{ name }}',
- 'TVM_NUM_SHARDS={{ num_shards }}',
- 'TVM_SHARD_INDEX={{ shard_index - 1 }}',
"SKIP_SLOW_TESTS=${skip_slow_tests}"], {
- {{ caller(shard_index, num_shards) | trim | indent(width=12) }}
+ {{ caller() | trim | indent(width=12) }}
})
}
- // only run upload if things are successful
- try {
- {{ junit_to_s3(test_dir_name) }}
- junit 'build/pytest-results/*.xml'
- } catch (Exception e) {
- echo 'Exception during JUnit upload: ' + e.toString()
- }
}
}
echo 'End running on node_type ' + node_type
} else {
- Utils.markStageSkippedForConditional('{{ name }} {{ shard_index }} of {{
num_shards }}')
+ Utils.markStageSkippedForConditional('{{ name }}')
}
}
-{% set _ = test_method_names.append((name + " " + shard_index|string + " of "
+ num_shards|string, method_name)) %}
+{% set _ = test_method_names.append((name, method_name)) %}
-{% endfor %}
{% endmacro %}
{% macro invoke_build(name, condition, node, docker_image, ws, platform) %}
diff --git a/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2
b/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2
index 0d119f043e..8e7d909bd1 100644
--- a/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2
@@ -32,7 +32,6 @@
cmake_build(ci_wasm, 'build')
make_cpp_tests(ci_wasm, 'build')
cpp_unittest(ci_wasm)
- ci_setup(ci_wasm)
sh (
script: "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh",
label: 'Run WASM lint and tests',
diff --git a/ci/scripts/jenkins/pytest_ids.py b/ci/scripts/jenkins/pytest_ids.py
deleted file mode 100755
index 548069e855..0000000000
--- a/ci/scripts/jenkins/pytest_ids.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python3
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-import argparse
-import io
-from contextlib import redirect_stdout
-
-import pytest
-
-
-class NodeidsCollector:
- def pytest_collection_modifyitems(self, items):
- self.nodeids = [item.nodeid for item in items]
-
-
-def main(folder):
- collector = NodeidsCollector()
- f = io.StringIO()
- with redirect_stdout(f):
- pytest.main(["-qq", "--collect-only", folder], plugins=[collector])
- for nodeid in collector.nodeids:
- print(nodeid)
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="List pytest nodeids for a
folder")
- parser.add_argument("--folder", required=True, help="test folder to
inspect")
- args = parser.parse_args()
- main(args.folder)
diff --git a/ci/scripts/jenkins/pytest_wrapper.py
b/ci/scripts/jenkins/pytest_wrapper.py
deleted file mode 100755
index a667810293..0000000000
--- a/ci/scripts/jenkins/pytest_wrapper.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env python3
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: E501
-import argparse
-import logging
-import os
-import textwrap
-import urllib.parse
-from pathlib import Path
-
-import junitparser
-from cmd_utils import init_log
-
-REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
-
-
-def lstrip(s: str, prefix: str) -> str:
- if s.startswith(prefix):
- s = s[len(prefix) :]
- return s
-
-
-def classname_to_file(classname: str) -> str:
- classname = lstrip(classname, "cython.")
- classname = lstrip(classname, "ctypes.")
- return classname.replace(".", "/") + ".py"
-
-
-def failed_test_ids() -> list[str]:
- FAILURE_TYPES = (junitparser.Failure, junitparser.Error)
- junit_dir = REPO_ROOT / "build" / "pytest-results"
- failed_node_ids = []
- for junit in junit_dir.glob("*.xml"):
- xml = junitparser.JUnitXml.fromfile(str(junit))
- for suite in xml:
- # handle suites
- for case in suite:
- if case.result is None:
- logging.warn(f"Incorrectly formatted JUnit found, result
was None on {case}")
- continue
-
- if len(case.result) > 0 and isinstance(case.result[0],
FAILURE_TYPES):
- node_id = classname_to_file(case.classname) + "::" +
case.name
- failed_node_ids.append(node_id)
-
- return list(set(failed_node_ids))
-
-
-def repro_command(build_type: str, failed_node_ids: list[str]) -> str | None:
- """
- Parse available JUnit XML files and output a command that users can run to
- reproduce CI failures locally
- """
- test_args = [f"--tests {node_id}" for node_id in failed_node_ids]
- test_args_str = " ".join(test_args)
- return f"python3 tests/scripts/ci.py {build_type} {test_args_str}"
-
-
-def make_issue_url(failed_node_ids: list[str]) -> str:
- names = [f"`{node_id}`" for node_id in failed_node_ids]
- run_url = os.getenv("RUN_DISPLAY_URL", "<insert run URL>")
- test_bullets = [f" - `{node_id}`" for node_id in failed_node_ids]
- params = {
- "labels": "test: flaky",
- "title": "[Flaky Test] " + ", ".join(names),
- "body": textwrap.dedent(
- """
- These tests were found to be flaky (intermittently failing on
`main` or failed in a PR with unrelated changes). See [the
docs](https://github.com/apache/tvm/blob/main/docs/contribute/ci.rst#handling-flaky-failures)
for details.
-
- ### Tests(s)\n
- """
- )
- + "\n".join(test_bullets)
- + f"\n\n### Jenkins Links\n\n - {run_url}",
- }
- return "https://github.com/apache/tvm/issues/new?" +
urllib.parse.urlencode(params)
-
-
-def show_failure_help(failed_suites: list[str]) -> None:
- failed_node_ids = failed_test_ids()
-
- if len(failed_node_ids) == 0:
- return
-
- build_type = os.getenv("PLATFORM")
-
- if build_type is None:
- raise RuntimeError("build type was None, cannot show command")
-
- repro = repro_command(build_type=build_type,
failed_node_ids=failed_node_ids)
- if repro is None:
- print("No test failures detected")
- return
-
- print(f"Report flaky test shortcut: {make_issue_url(failed_node_ids)}")
- print("=============================== PYTEST FAILURES
================================")
- print(
- "These pytest suites failed to execute. The results can be found in
the "
- "Jenkins 'Tests' tab or by scrolling up through the raw logs here. "
- "If there is no test listed below, the failure likely came from a
segmentation "
- "fault which you can find in the logs above.\n"
- )
- if failed_suites is not None and len(failed_suites) > 0:
- print("\n".join([f" - {suite}" for suite in failed_suites]))
- print("")
-
- print("You can reproduce these specific failures locally with this
command:\n")
- print(textwrap.indent(repro, prefix=" "))
- print("")
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Print information about a
failed pytest run")
- args, other = parser.parse_known_args()
- init_log()
-
- try:
- show_failure_help(failed_suites=other)
- except Exception as e:
- # This script shouldn't ever introduce failures since it's just there
to
- # add extra information, so ignore any errors
- logging.exception(e)
diff --git a/conftest.py b/conftest.py
index dd32937620..dd294ea548 100644
--- a/conftest.py
+++ b/conftest.py
@@ -14,8 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# ruff: noqa: E501
-import hashlib
import os
import sys
from pathlib import Path
@@ -25,58 +23,6 @@ IS_IN_CI = os.getenv("CI", "") == "true"
REPO_ROOT = Path(__file__).resolve().parent
-# These are long running tests (manually curated and extracted from CI logs)
-# that should be allocated to test shards in a round-robin fashion. These are
-# taken from the 20 (arbitrary number) of tests as from
-# https://ci.tlcpack.ai/job/tvm/job/main/2907/testReport
-_slowest_tests = []
-HARDCODED_ALLOCATIONS = {}
-for idx, test in enumerate(_slowest_tests):
- HARDCODED_ALLOCATIONS[test] = idx
-
-# These rely on running on the same node to pass successfully
-FIXED_ALLOCATION_PREFIXES = {
- "tests/python/testing/test_tvm_testing_features.py": 0,
-}
-
-
-def find_shard_index(nodeid: str, num_shards: int) -> int:
- """
- Return the index of the shard that should run this test
- """
- for prefix, target_shard_idx in FIXED_ALLOCATION_PREFIXES.items():
- if nodeid.startswith(prefix):
- if target_shard_idx >= num_shards:
- raise RuntimeError(
- f"Cannot collect sharded tests, {nodeid} has hardcoded
shard index {target_shard_idx} among only {num_shards} shards"
- )
- return target_shard_idx
-
- if nodeid in HARDCODED_ALLOCATIONS:
- hash = HARDCODED_ALLOCATIONS[nodeid]
- else:
- hash = hashlib.md5(nodeid.encode())
- hash = int(hash.hexdigest(), 16)
-
- return hash % num_shards
-
-
-def pytest_collection_modifyitems(config, items):
- if not all(k in os.environ for k in ["CI", "TVM_NUM_SHARDS",
"TVM_SHARD_INDEX"]):
- # Only apportion tests if in CI and in a job that is set up for it
- return
-
- num_shards = int(os.environ["TVM_NUM_SHARDS"])
- shard_index = int(os.environ["TVM_SHARD_INDEX"])
-
- print(f"Marking tests for shard {shard_index} of {num_shards}")
- items_copy = list(items)
- for item in items_copy:
- item_shard_index = find_shard_index(item.nodeid, num_shards=num_shards)
- if item_shard_index != shard_index:
- items.remove(item)
-
-
def pytest_sessionstart():
if IS_IN_CI:
hook_script_dir = REPO_ROOT / "tests" / "scripts" / "request_hook"
diff --git a/docker/install/ubuntu_install_python_package.sh
b/docker/install/ubuntu_install_python_package.sh
index d07f19ed3c..4445dfd83e 100755
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -32,13 +32,11 @@ uv pip install --upgrade \
Pillow==12.1.1 \
"psutil~=7.0" \
"pytest~=8.3" \
- "pytest-profiling~=1.8" \
"pytest-xdist~=3.6" \
pytest-rerunfailures==16.1 \
"requests~=2.32" \
"scipy~=1.13" \
"Jinja2~=3.1" \
- junitparser==4.0.2 \
"six~=1.17" \
"tornado~=6.4" \
"ml_dtypes~=0.5" \
diff --git a/docs/contribute/pull_request.rst b/docs/contribute/pull_request.rst
index fe960c709a..a6c41b755f 100644
--- a/docs/contribute/pull_request.rst
+++ b/docs/contribute/pull_request.rst
@@ -242,7 +242,7 @@ Necessary dependencies:
.. code:: bash
- pip install --user pytest Cython
+ pip install --user pytest pytest-xdist Cython
If you want to run all tests:
diff --git a/python/tvm/testing/plugin.py b/python/tvm/testing/plugin.py
index dfb858f1b8..2869664842 100644
--- a/python/tvm/testing/plugin.py
+++ b/python/tvm/testing/plugin.py
@@ -33,7 +33,6 @@ directory as the test scripts.
"""
import _pytest
-import pytest
def pytest_collection_modifyitems(config, items):
@@ -44,14 +43,6 @@ def pytest_collection_modifyitems(config, items):
_sort_tests(items)
-def pytest_sessionfinish(session, exitstatus):
- # Don't exit with an error if we select a subset of tests that doesn't
- # include anything
- if session.config.option.markexpr != "":
- if exitstatus == pytest.ExitCode.NO_TESTS_COLLECTED:
- session.exitstatus = pytest.ExitCode.OK
-
-
def _count_num_fixture_uses(items):
# Helper function, counts the number of tests that use each cached
# fixture. Should be called from pytest_collection_modifyitems().
diff --git a/tests/python/s_tir/meta_schedule/test_meta_schedule_cost_model.py
b/tests/python/s_tir/meta_schedule/test_meta_schedule_cost_model.py
index 1e209cacbc..97c9469ccf 100644
--- a/tests/python/s_tir/meta_schedule/test_meta_schedule_cost_model.py
+++ b/tests/python/s_tir/meta_schedule/test_meta_schedule_cost_model.py
@@ -149,7 +149,7 @@ def _dummy_result(num_samples: int = 4, max_run_sec: int =
10):
@requires_xgboost
def test_meta_schedule_xgb_model():
extractor = RandomFeatureExtractor()
- model = XGBModel(extractor=extractor, num_warmup_samples=2)
+ model = XGBModel(extractor=extractor, num_warmup_samples=2,
num_tuning_cores=1)
update_sample_count = 10
predict_sample_count = 100
model.update(
@@ -162,9 +162,10 @@ def test_meta_schedule_xgb_model():
@requires_xgboost
def test_meta_schedule_xgb_model_no_feature():
- model = XGBModel(num_warmup_samples=0)
+ model = XGBModel(num_warmup_samples=0, num_tuning_cores=1)
tune_ctx = TuneContext(
FullModule,
+ num_threads=1,
target={"kind": "llvm", "num-cores": 16},
space_generator="post-order-apply",
search_strategy="evolutionary",
@@ -177,7 +178,7 @@ def test_meta_schedule_xgb_model_no_feature():
@requires_xgboost
def test_meta_schedule_xgb_model_reload():
extractor = RandomFeatureExtractor()
- model = XGBModel(extractor=extractor, num_warmup_samples=10)
+ model = XGBModel(extractor=extractor, num_warmup_samples=10,
num_tuning_cores=1)
update_sample_count = 20
predict_sample_count = 30
model.update(
@@ -221,7 +222,7 @@ def test_meta_schedule_xgb_model_reload():
@requires_xgboost
def test_meta_schedule_xgb_model_reupdate():
extractor = RandomFeatureExtractor()
- model = XGBModel(extractor=extractor, num_warmup_samples=2)
+ model = XGBModel(extractor=extractor, num_warmup_samples=2,
num_tuning_cores=1)
update_sample_count = 60
predict_sample_count = 100
model.update(
@@ -252,7 +253,7 @@ def test_meta_schedule_xgb_model_callback_as_function():
# pylint: enable=import-outside-toplevel
extractor = RandomFeatureExtractor()
- model = XGBModel(extractor=extractor, num_warmup_samples=10)
+ model = XGBModel(extractor=extractor, num_warmup_samples=10,
num_tuning_cores=1)
update_sample_count = 20
predict_sample_count = 30
diff --git
a/tests/python/s_tir/meta_schedule/test_meta_schedule_search_strategy.py
b/tests/python/s_tir/meta_schedule/test_meta_schedule_search_strategy.py
index 002741c6bf..6451a94ec9 100644
--- a/tests/python/s_tir/meta_schedule/test_meta_schedule_search_strategy.py
+++ b/tests/python/s_tir/meta_schedule/test_meta_schedule_search_strategy.py
@@ -103,6 +103,7 @@ def test_meta_schedule_replay_func(
context = ms.TuneContext(
mod=Matmul,
+ num_threads=1,
space_generator=ms.space_generator.ScheduleFn(sch_fn=_schedule_matmul,
postprocs=[]),
search_strategy=TestClass(),
)
diff --git
a/tests/python/s_tir/meta_schedule/test_meta_schedule_task_scheduler.py
b/tests/python/s_tir/meta_schedule/test_meta_schedule_task_scheduler.py
index 61f5583c2a..6dc351761a 100644
--- a/tests/python/s_tir/meta_schedule/test_meta_schedule_task_scheduler.py
+++ b/tests/python/s_tir/meta_schedule/test_meta_schedule_task_scheduler.py
@@ -157,6 +157,7 @@ def test_meta_schedule_task_scheduler_single():
[
ms.TuneContext(
MatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -183,6 +184,7 @@ def test_meta_schedule_task_scheduler_multiple():
tasks = [
ms.TuneContext(
MatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -191,6 +193,7 @@ def test_meta_schedule_task_scheduler_multiple():
),
ms.TuneContext(
MatmulReluModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -199,6 +202,7 @@ def test_meta_schedule_task_scheduler_multiple():
),
ms.TuneContext(
BatchMatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_batch_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -255,6 +259,7 @@ def
test_meta_schedule_task_scheduler_override_next_task_id_only(): # pylint: d
tasks = [
ms.TuneContext(
MatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -263,6 +268,7 @@ def
test_meta_schedule_task_scheduler_override_next_task_id_only(): # pylint: d
),
ms.TuneContext(
MatmulReluModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -271,6 +277,7 @@ def
test_meta_schedule_task_scheduler_override_next_task_id_only(): # pylint: d
),
ms.TuneContext(
BatchMatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_batch_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -310,6 +317,7 @@ def
test_meta_schedule_task_scheduler_multiple_gradient_based():
tasks = [
ms.TuneContext(
MatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -318,6 +326,7 @@ def
test_meta_schedule_task_scheduler_multiple_gradient_based():
),
ms.TuneContext(
MatmulReluModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -326,6 +335,7 @@ def
test_meta_schedule_task_scheduler_multiple_gradient_based():
),
ms.TuneContext(
BatchMatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_batch_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
@@ -394,6 +404,7 @@ def
test_meta_schedule_task_scheduler_gradient_based_with_null_search_strategy()
tasks = [
ms.TuneContext(
MatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=NullSearchStrategy(rounds_with_empty_candidates=5),
@@ -402,6 +413,7 @@ def
test_meta_schedule_task_scheduler_gradient_based_with_null_search_strategy()
),
ms.TuneContext(
BatchMatmulModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_batch_matmul,
search_strategy=NullSearchStrategy(rounds_with_empty_candidates=0),
@@ -410,6 +422,7 @@ def
test_meta_schedule_task_scheduler_gradient_based_with_null_search_strategy()
),
ms.TuneContext(
MatmulReluModule,
+ num_threads=1,
target=tvm.target.Target("llvm"),
space_generator=_schedule_matmul,
search_strategy=ms.search_strategy.ReplayTrace(),
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
b/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
index a174c771a2..eaa78cf626 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
@@ -486,7 +486,7 @@ def fail_buffer_bind(a: T.handle) -> None:
# well-formed checker complains about redefinition of a stride variable
@T.prim_func(check_well_formed=False, s_tir=True)
-def fail_match_func_param(a: T.handle, m: T.handle, n: T.handle) -> None:
+def fail_match_func_param(a: T.handle, m: T.int32, n: T.int32) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 2):
with T.sblock():
diff --git a/tests/python/testing/test_tvm_testing_features.py
b/tests/python/testing/test_tvm_testing_features.py
index 549aedda6a..2f4c798947 100644
--- a/tests/python/testing/test_tvm_testing_features.py
+++ b/tests/python/testing/test_tvm_testing_features.py
@@ -14,10 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# ruff: noqa: F401, RUF012
+# ruff: noqa: RUF012
import os
-import sys
import pytest
diff --git a/tests/scripts/setup-pytest-env.sh
b/tests/scripts/setup-pytest-env.sh
deleted file mode 100755
index 171ddbc2d0..0000000000
--- a/tests/scripts/setup-pytest-env.sh
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# NOTE: allow unbound variable here
-set +u
-
-if [[ ! -z $CI_PYTEST_ADD_OPTIONS ]]; then
- export PYTEST_ADDOPTS="-s -vv $CI_PYTEST_ADD_OPTIONS $PYTEST_ADDOPTS"
-else
- export PYTEST_ADDOPTS="-s -vv $PYTEST_ADDOPTS"
-fi
-set -ux
-
-export TVM_PATH=`pwd`
-export PYTHONPATH="${TVM_PATH}/python"
-
-export TVM_PYTEST_RESULT_DIR="${TVM_PATH}/build/pytest-results"
-mkdir -p "${TVM_PYTEST_RESULT_DIR}"
-pytest_errors=()
-
-# This ensures that all pytest invocations that are run through run_pytest will
-# complete and errors will be reported once Bash is done executing all scripts.
-function cleanup() {
- set +x
- if [ "${#pytest_errors[@]}" -gt 0 ]; then
- echo "These pytest invocations failed, the results can be found in the
Jenkins 'Tests' tab or by scrolling up through the raw logs here."
- python3 ci/scripts/jenkins/pytest_wrapper.py "${pytest_errors[@]}"
- exit 1
- fi
- set -x
-}
-trap cleanup 0
-
-function run_pytest() {
- ffi_type="cython"
- set -e
- local test_suite_name="$1"
- shift
- extra_args=( "$@" )
- if [ -z "${ffi_type}" -o -z "${test_suite_name}" ]; then
- echo "error: run_pytest called incorrectly: run_pytest
${test_suite_name}" "${extra_args[@]}"
- echo "usage: run_pytest <TEST_SUITE_NAME> [pytest args...]"
- exit 2
- fi
-
- # Allow unbound variable here.
- set +u
- if [[ -z "${TVM_SHARD_INDEX}" ]]; then
- current_shard="no-shard"
- else
- current_shard="shard-${TVM_SHARD_INDEX}"
- fi
- set -u
-
- has_reruns=$(python3 -m pytest --help 2>&1 | grep 'reruns=' || true)
- if [ -n "$has_reruns" ]; then
- if [[ ! "${extra_args[*]}" == *"--reruns"* ]]; then
- extra_args+=('--reruns=3')
- fi
- fi
-
- suite_name="${test_suite_name}-${current_shard}-${ffi_type}"
-
- DEFAULT_PARALLELISM=1
-
- if [[ ! "${extra_args[*]}" == *" -n"* ]] && [[ ! "${extra_args[*]}" == *"
-dist"* ]]; then
- extra_args+=("-n=$DEFAULT_PARALLELISM")
- fi
-
- exit_code=0
- set +e
- python3 -m pytest \
- -o "junit_suite_name=${suite_name}" \
- "--junit-xml=${TVM_PYTEST_RESULT_DIR}/${suite_name}.xml" \
- "--junit-prefix=${ffi_type}" \
- "${extra_args[@]}" || exit_code=$?
- # Pytest will return error code -5 if no test is collected.
- if [ "$exit_code" -ne "0" ] && [ "$exit_code" -ne "5" ]; then
- pytest_errors+=("${suite_name}: $@")
- fi
- # To avoid overwriting.
- set -e
-}
diff --git a/tests/scripts/task_clear_pytest.sh
b/tests/scripts/task_clear_pytest.sh
index 0a24f62531..430425791c 100755
--- a/tests/scripts/task_clear_pytest.sh
+++ b/tests/scripts/task_clear_pytest.sh
@@ -16,9 +16,7 @@
# specific language governing permissions and limitations
# under the License.
-set -euxo pipefail
+set -euo pipefail
-# Ensure no stale pytest-results remain from a previous test run.
-pushd build
-rm -rf pytest-results
-popd
+# PR jobs evaluate their Jenkinsfile from the trusted base branch. Keep this
+# inert entry point until the Jenkinsfile update has landed on the base branch.
diff --git a/tests/scripts/task_python_docs.sh
b/tests/scripts/task_python_docs.sh
index 7c3122cd92..11b2501b7b 100755
--- a/tests/scripts/task_python_docs.sh
+++ b/tests/scripts/task_python_docs.sh
@@ -18,7 +18,7 @@
set -euxo pipefail
-source tests/scripts/setup-pytest-env.sh
+export PYTHONPATH="$(pwd)/python"
# to avoid CI CPU thread throttling.
export TVM_BIND_THREADS=0
diff --git a/tests/scripts/task_python_integration.sh
b/tests/scripts/task_python_integration.sh
index dcd7b8ff47..7dd3c9b578 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -18,18 +18,13 @@
set -euxo pipefail
-source tests/scripts/setup-pytest-env.sh
+export PYTHONPATH="$(pwd)/python"
export LD_LIBRARY_PATH="build:${LD_LIBRARY_PATH:-}"
# to avoid CI CPU thread throttling.
export TVM_BIND_THREADS=0
export TVM_NUM_THREADS=2
-# NOTE: also set by task_python_integration_gpuonly.sh.
-if [ -z "${TVM_INTEGRATION_TESTSUITE_NAME:-}" ]; then
- TVM_INTEGRATION_TESTSUITE_NAME=python-integration
-fi
-
# cleanup pycache
find . -type f -path "*.pyc" | xargs rm -f
diff --git a/tests/scripts/task_python_integration_gpuonly.sh
b/tests/scripts/task_python_integration_gpuonly.sh
index f3ea1f9856..9b825b9182 100755
--- a/tests/scripts/task_python_integration_gpuonly.sh
+++ b/tests/scripts/task_python_integration_gpuonly.sh
@@ -22,7 +22,6 @@ export
TVM_TEST_TARGETS='cuda;opencl;metal;rocm;nvptx;{"kind":"opencl","device":
# Every GPU test carries the `gpu` marker; the specific backend is gated by
skipif.
export PYTEST_ADDOPTS="-m gpu $PYTEST_ADDOPTS"
export TVM_RELAY_TEST_TARGETS="cuda"
-export TVM_INTEGRATION_TESTSUITE_NAME=python-integration-gpu
export TVM_INTEGRATION_GPU_ONLY=1
./tests/scripts/task_python_integration.sh
diff --git a/tests/scripts/task_python_nightly.sh
b/tests/scripts/task_python_nightly.sh
deleted file mode 100755
index b480563a8c..0000000000
--- a/tests/scripts/task_python_nightly.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-set -euxo pipefail
-
-source tests/scripts/setup-pytest-env.sh
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-# cleanup pycache
-find . -type f -path "*.pyc" | xargs rm -f
-
-run_pytest python-topi-nightly tests/python/topi/nightly
-
-# Tensorflow device verification and network tests on nightly
-export CI_ENV_NIGHTLY
-python3 tests/python/relax/test_frontend_tflite.py
diff --git a/tests/scripts/task_python_unittest.sh
b/tests/scripts/task_python_unittest.sh
index 7e09926cc0..0728743fb5 100755
--- a/tests/scripts/task_python_unittest.sh
+++ b/tests/scripts/task_python_unittest.sh
@@ -18,7 +18,8 @@
set -euxo pipefail
-source tests/scripts/setup-pytest-env.sh
+export PYTHONPATH="$(pwd)/python"
+export PYTEST_ADDOPTS="-s -vv ${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}"
# cleanup pycache
find . -type f -path "*.pyc" | xargs rm -f
@@ -26,23 +27,20 @@ find . -type f -path "*.pyc" | xargs rm -f
# setup tvm-ffi into python folder
uv pip install -v --target=python ./3rdparty/tvm-ffi/
-# NOTE: also set by task_python_unittest_gpuonly.sh.
-if [ -z "${TVM_UNITTEST_TESTSUITE_NAME:-}" ]; then
- TVM_UNITTEST_TESTSUITE_NAME=python-unittest
+# First run the minimal platform test. A GPU-only run can select no tests
here.
+if [ ! -d tests/python/all-platform-minimal-test ]; then
+ echo "Missing pytest target: tests/python/all-platform-minimal-test" >&2
+ exit 1
fi
+python3 -m pytest -n auto tests/python/all-platform-minimal-test || [ "$?" -eq
5 ]
-# First run minimal test on both ctypes and cython.
-run_pytest ${TVM_UNITTEST_TESTSUITE_NAME}-platform-minimal-test
tests/python/all-platform-minimal-test
-
-# Then run all unittests on both ctypes and cython.
+# Then run all unit tests.
TEST_FILES=(
- "ffi"
"arith"
"ci"
"codegen"
"driver"
"ir"
- "meta_schedule"
"runtime"
"target"
"te"
@@ -51,6 +49,7 @@ TEST_FILES=(
"s_tir/schedule"
"s_tir/dlight"
"s_tir/analysis"
+ "s_tir/meta_schedule"
"s_tir/transform"
"tirx-analysis"
"tirx-base"
@@ -62,7 +61,13 @@ TEST_FILES=(
PYTEST_TARGETS=()
for TEST_FILE in "${TEST_FILES[@]}"; do
- PYTEST_TARGETS+=("tests/python/${TEST_FILE}")
+ TEST_PATH="tests/python/${TEST_FILE}"
+ if [ ! -e "${TEST_PATH}" ]; then
+ echo "Missing pytest target: ${TEST_PATH}" >&2
+ exit 1
+ fi
+ PYTEST_TARGETS+=("${TEST_PATH}")
done
-run_pytest "${TVM_UNITTEST_TESTSUITE_NAME}" "${PYTEST_TARGETS[@]}"
+# Do not mask pytest's exit 5: an unexpectedly empty broad suite must fail CI.
+python3 -m pytest -n auto --dist=loadgroup "${PYTEST_TARGETS[@]}"
diff --git a/tests/scripts/task_python_unittest_gpuonly.sh
b/tests/scripts/task_python_unittest_gpuonly.sh
index be3ea07947..8194b23ac4 100755
--- a/tests/scripts/task_python_unittest_gpuonly.sh
+++ b/tests/scripts/task_python_unittest_gpuonly.sh
@@ -24,15 +24,18 @@ export PYTEST_ADDOPTS="-m gpu ${PYTEST_ADDOPTS:-}"
# Test most of the enabled runtimes here.
# TODO: disabled opencl tests due to segmentation fault.
export TVM_TEST_TARGETS='cuda;metal;rocm;nvptx'
-export TVM_UNITTEST_TESTSUITE_NAME=python-unittest-gpu
./tests/scripts/task_python_unittest.sh
# Kept separate to avoid increasing time needed to run CI, testing
# only minimal functionality of Vulkan runtime.
export TVM_TEST_TARGETS='{"kind":"vulkan","from_device":0}'
-export TVM_UNITTEST_TESTSUITE_NAME=python-codegen-vulkan
-source tests/scripts/setup-pytest-env.sh
+export PYTHONPATH="$(pwd)/python"
+export PYTEST_ADDOPTS="-s -vv ${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}"
-run_pytest ${TVM_UNITTEST_TESTSUITE_NAME}-1
tests/python/codegen/test_target_codegen_vulkan.py
+if [ ! -f tests/python/codegen/test_target_codegen_vulkan.py ]; then
+ echo "Missing pytest target:
tests/python/codegen/test_target_codegen_vulkan.py" >&2
+ exit 1
+fi
+python3 -m pytest -n auto tests/python/codegen/test_target_codegen_vulkan.py