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

tqchen pushed a commit to branch jenkins-cleanup-trusted-main
in repository https://gitbox.apache.org/repos/asf/tvm.git

commit 6e02972f256ca9f6f998ed64e5384038ab549a9e
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Oct 15 11:39:52 2026 +0000

    [CI] Retain script and bot coverage with a minimal inline fixture
---
 tests/python/ci/jenkins_loader_test.groovy |  84 --------
 tests/python/ci/sample_prs/pr10786.json    | 130 ------------
 tests/python/ci/test_ci.py                 |  55 ++++-
 tests/python/ci/test_jenkins_helpers.py    | 322 -----------------------------
 tests/python/ci/test_jenkins_loader.py     |  37 ----
 tests/python/ci/test_tvmbot.py             | 106 ++++++++--
 tests/python/ci/test_utils.py              |  80 -------
 7 files changed, 144 insertions(+), 670 deletions(-)

diff --git a/tests/python/ci/jenkins_loader_test.groovy 
b/tests/python/ci/jenkins_loader_test.groovy
deleted file mode 100644
index 1b2fe16e2e..0000000000
--- a/tests/python/ci/jenkins_loader_test.groovy
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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 org.codehaus.groovy.control.CompilerConfiguration
-
-class LoaderPipeline extends Script {
-  Object run() {}
-  Object evaluate(String source) {
-    state.evaluated << source
-    if (state.evaluationFailure) throw state.evaluationFailure
-    return 'loaded helper'
-  }
-  def readTrusted(String path) {
-    assert path == 'ci/jenkins/generated/common.groovy'
-    if (state.readFailure) throw state.readFailure
-    return 'selected revision helper'
-  }
-  def node(String label, Closure body) {
-    throw new AssertionError('loader allocated a node')
-  }
-  def sh(Map options) {
-    throw new AssertionError('loader ran a shell command')
-  }
-
-}
-
-def loader = new GroovyClassLoader(getClass().classLoader)
-def abort = loader.parseClass('package hudson; class AbortException extends 
IOException { AbortException(String message) { super(message) } }')
-def config = new CompilerConfiguration(scriptBaseClass: 'LoaderPipeline')
-def helper = 'ci/jenkins/generated/common.groovy'
-def modified = { abort.newInstance("‘${helper}’ has been modified in an 
untrusted revision".toString()) }
-def missing = { new FileNotFoundException(helper) }
-def cases = [
-  [name: 'unchanged PR'],
-  [name: 'trusted named branch', branch: true],
-  [name: 'modified PR', readFailure: modified()],
-  [name: 'deleted PR lightweight', readFailure: missing()],
-  [name: 'deleted PR heavyweight', readFailure: 
abort.newInstance("/workspace/trusted/${helper} not found".toString())],
-  [name: 'release target PR', readFailure: modified(), target: 'release'],
-  [name: 'missing named branch', branch: true, readFailure: missing()],
-  [name: 'rejected named branch', branch: true, readFailure: modified()],
-  [name: 'unrelated abort', readFailure: abort.newInstance('controller 
offline')],
-  [name: 'unrelated missing file', readFailure: new 
FileNotFoundException('other/file')],
-  [name: 'read network failure', readFailure: new IOException('network')],
-  [name: 'interrupted read', readFailure: new 
InterruptedException('cancelled')],
-  [name: 'selected evaluation error', evaluationFailure: new 
IllegalStateException('bad helper')],
-]
-int count = 0
-args.each { filename ->
-  def fullSource = new File(filename).text
-  def source = fullSource.substring(0, fullSource.indexOf('\nci.start()'))
-  cases.each { scenario ->
-    def state = [nodes: 0, fetches: 0, evaluated: []] + scenario
-    def binding = new Binding([state: state, env: [CHANGE_ID: scenario.branch 
? null : '123', CHANGE_TARGET: scenario.target ?: 'main']])
-    Throwable failure
-    try {
-      new GroovyShell(loader, binding, config).evaluate(source)
-    } catch (Throwable caught) {
-      failure = caught
-    }
-    def expected = scenario.readFailure ?: scenario.evaluationFailure
-    assert failure.is(expected): "${filename}: ${scenario.name}: ${failure} != 
${expected}"
-    assert state.nodes == 0: scenario.name
-    assert state.fetches == 0: scenario.name
-    def shouldEvaluate = !scenario.readFailure
-    assert state.evaluated == (shouldEvaluate ? ['selected revision helper'] : 
[]): scenario.name
-    count++
-  }
-}
-println("${count} native loader cases passed")
diff --git a/tests/python/ci/sample_prs/pr10786.json 
b/tests/python/ci/sample_prs/pr10786.json
deleted file mode 100644
index 5e978ffda1..0000000000
--- a/tests/python/ci/sample_prs/pr10786.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
-  "title": "[Hexagon] 2-d allocation cleanup",
-  "body":
-      "- Added device validity check in allocation. HexagonDeviceAPI should 
only be called for CPU/Hexagon types.\r\n\r\n- Check for \"global.vtcm\" scope 
instead of \"vtcm\".  The ccope of N-d allocations produced by `LowerVtcmAlloc` 
should be `\"global.vtcm\"`.  The previous check allowed unsupported scope such 
as `\"local.vtcm\"`.\r\n\r\n- Remove `vtcmallocs` entry after calling 
free.\n\n\nThanks for contributing to TVM!   Please refer to guideline 
https://tvm.apache.org/docs/contribu [...]
-  "state": "OPEN",
-  "author": {
-    "login": "abc"
-  },
-  "comments": {
-    "pageInfo": {
-      "hasPreviousPage": false
-    },
-    "nodes": []
-  },
-  "authorCommits": {
-    "nodes": [
-      {
-        "commit": {
-          "authors": {
-            "nodes": [
-              {
-                "name": "Eric Lunderberg",
-                "email": "[email protected]"
-              },
-              {
-                "name": "Adam Straw",
-                "email": "[email protected]"
-              }
-            ]
-          }
-        }
-      }
-    ]
-  },
-  "commits": {
-    "nodes": [
-      {
-        "commit": {
-          "oid": "6f04bcf57d07f915a98fd91178f04d9e92a09fcd",
-          "statusCheckRollup": {
-            "contexts": {
-              "pageInfo": {
-                "hasNextPage": false
-              },
-              "nodes": [
-                {
-                  "name": "MacOS",
-                  "checkSuite": {
-                    "workflowRun": {
-                      "workflow": {
-                        "name": "CI"
-                      }
-                    }
-                  },
-                  "status": "COMPLETED",
-                  "conclusion": "SUCCESS",
-                  "url": "https://github.com/apache/tvm/runs/5694945392";
-                },
-                {
-                  "name": "cc-reviewers",
-                  "checkSuite": {
-                    "workflowRun": {
-                      "workflow": {
-                        "name": "PR"
-                      }
-                    }
-                  },
-                  "status": "COMPLETED",
-                  "conclusion": "SUCCESS",
-                  "url": "https://github.com/apache/tvm/runs/5694945029";
-                },
-                {
-                  "name": "tag-teams",
-                  "checkSuite": {
-                    "workflowRun": {
-                      "workflow": {
-                        "name": "Teams"
-                      }
-                    }
-                  },
-                  "status": "COMPLETED",
-                  "conclusion": "SUCCESS",
-                  "url": "https://github.com/apache/tvm/runs/5694945030";
-                },
-                {
-                  "name": "Windows",
-                  "checkSuite": {
-                    "workflowRun": {
-                      "workflow": {
-                        "name": "CI"
-                      }
-                    }
-                  },
-                  "status": "COMPLETED",
-                  "conclusion": "SUCCESS",
-                  "url": "https://github.com/apache/tvm/runs/5694945524";
-                },
-                {
-                  "state": "SUCCESS",
-                  "context": "tvm-ci/pr-head",
-                  "targetUrl": 
"https://ci.tlcpack.ai/job/tvm/job/PR-10786/1/display/redirect";
-                }
-              ]
-            }
-          }
-        }
-      }
-    ]
-  },
-  "reviewDecision": "APPROVED",
-  "reviews": {
-    "pageInfo": {
-      "hasPreviousPage": false
-    },
-    "nodes": [
-      {
-        "body": "@tvm-bot merge",
-        "updatedAt": "2022-03-25T22:13:50Z",
-        "authorCanPushToRepository": true,
-        "commit": {
-          "oid": "6f04bcf57d07f915a98fd91178f04d9e92a09fcd"
-        },
-        "author": {
-          "login": "kparzysz-quic"
-        },
-        "state": "APPROVED"
-      }
-    ]
-  }
-}
diff --git a/tests/python/ci/test_ci.py b/tests/python/ci/test_ci.py
index 7144fee3d1..4c146dd974 100644
--- a/tests/python/ci/test_ci.py
+++ b/tests/python/ci/test_ci.py
@@ -18,12 +18,65 @@
 """Test various CI scripts and GitHub Actions workflows"""
 
 import json
+import os
 import subprocess
 from pathlib import Path
+from typing import Any
 
 import pytest
 
-from .test_utils import JENKINS_SCRIPT_ROOT, TempGit, run_script
+JENKINS_SCRIPT_ROOT = Path(__file__).resolve().parents[3] / "ci" / "jenkins" / 
"scripts"
+
+
+class TempGit:
+    """
+    A wrapper to run commands in a directory (specifically for use in CI tests)
+    """
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+        # Jenkins git is too old and doesn't have 'git init --initial-branch',
+        # so init and checkout need to be separate steps
+        self.run("init", stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+        self.run("checkout", "-b", "main", stderr=subprocess.PIPE)
+        self.run("remote", "add", "origin", 
"https://github.com/apache/tvm.git";)
+
+    def run(self, *args, **kwargs):
+        """
+        Run a git command based on *args
+        """
+        proc = subprocess.run(
+            ["git"] + list(args), encoding="utf-8", cwd=self.cwd, check=False, 
**kwargs
+        )
+        if proc.returncode != 0:
+            raise RuntimeError(f"git command failed: '{args}'")
+
+        return proc
+
+
+def run_script(command: list[Any], check: bool = True, **kwargs):
+    """
+    Wrapper to run a script and print its output if there was an error
+    """
+    command = [str(c) for c in command]
+    kwargs_to_send = {
+        "stdout": subprocess.PIPE,
+        "stderr": subprocess.PIPE,
+        "encoding": "utf-8",
+    }
+    env = kwargs.pop("env", None)
+    if env is not None:
+        kwargs_to_send["env"] = {**os.environ, **env}
+    kwargs_to_send.update(kwargs)
+    proc = subprocess.run(
+        command,
+        check=False,
+        **kwargs_to_send,
+    )
+    if check and proc.returncode != 0:
+        raise RuntimeError(f"Process 
failed:\nstdout:\n{proc.stdout}\n\nstderr:\n{proc.stderr}")
+
+    return proc
 
 
 @pytest.fixture
diff --git a/tests/python/ci/test_jenkins_helpers.py 
b/tests/python/ci/test_jenkins_helpers.py
deleted file mode 100644
index ae28ea97f7..0000000000
--- a/tests/python/ci/test_jenkins_helpers.py
+++ /dev/null
@@ -1,322 +0,0 @@
-# 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.
-"""Host-side Jenkins commands preserve retries, paths and build 
configuration."""
-
-import importlib.util
-import json
-import os
-import shutil
-import subprocess
-import sys
-
-import pytest
-
-from .test_utils import JENKINS_SCRIPT_ROOT, REPO_ROOT, TempGit
-
-
-def load_s3(script_root=JENKINS_SCRIPT_ROOT):
-    spec = importlib.util.spec_from_file_location("jenkins_s3", script_root / 
"s3.py")
-    module = importlib.util.module_from_spec(spec)
-    spec.loader.exec_module(module)
-    return module
-
-
-def executable(path, body):
-    path.write_text("#!/usr/bin/env python3\n" + body)
-    path.chmod(0o755)
-
-
[email protected]("failures", [2, 3])
-def test_s3_transfer_retries(tmp_path, monkeypatch, failures):
-    script_root = JENKINS_SCRIPT_ROOT
-    # Run the real retry.sh with fake AWS and sleep, without network or delays.
-    executable(
-        tmp_path / "aws",
-        """
-import json, os, pathlib, sys
-log = pathlib.Path(os.environ["AWS_LOG"])
-with log.open("a") as out:
-    out.write(json.dumps(sys.argv[1:]) + "\\n")
-attempt = len(log.read_text().splitlines())
-print("download: s3://bucket/file to file name")
-sys.exit(1 if attempt <= int(os.environ["FAILURES"]) else 0)
-""",
-    )
-    executable(
-        tmp_path / "sleep",
-        """
-import os, pathlib, sys
-with pathlib.Path(os.environ["SLEEP_LOG"]).open("a") as out:
-    out.write(sys.argv[1] + "\\n")
-""",
-    )
-    monkeypatch.setenv("PATH", f"{tmp_path}:{os.environ['PATH']}")
-    monkeypatch.setenv("FAILURES", str(failures))
-    monkeypatch.setenv("SLEEP_LOG", str(tmp_path / "sleeps"))
-    monkeypatch.syspath_prepend(str(script_root))
-    destination = "file name"
-    for transfer in range(2):
-        log = tmp_path / f"aws-{transfer}"
-        monkeypatch.setenv("AWS_LOG", str(log))
-        s3 = load_s3(script_root)
-        if failures == 3:
-            with pytest.raises(subprocess.CalledProcessError):
-                s3.s3("s3://bucket/file", destination, False)
-        else:
-            assert s3.parse_output_files(s3.s3("s3://bucket/file", 
destination, False))
-        commands = [json.loads(line) for line in log.read_text().splitlines()]
-        assert commands == [["s3", "cp", "--no-progress", "s3://bucket/file", 
destination]] * 3
-    delays = (tmp_path / "sleeps").read_text().splitlines()
-    assert len(delays) == 4
-    assert all(30 <= int(delay) <= 200 for delay in delays)
-
-
-def test_build_environment_and_target(tmp_path):
-    # Run the entry point in a minimal checkout, capturing the tool boundary.
-    script = tmp_path / "tests/scripts/task_build.py"
-    script.parent.mkdir(parents=True)
-    shutil.copy(REPO_ROOT / "tests/scripts/task_build.py", script)
-    (tmp_path / "build").mkdir()
-    ffi_path = tmp_path / "3rdparty/tvm-ffi"
-    ffi_path.mkdir(parents=True)
-    ffi_repo = TempGit(ffi_path)
-    ffi_repo.run("config", "user.name", "CI Test")
-    ffi_repo.run("config", "user.email", "[email protected]")
-    ffi_repo.run("commit", "--allow-empty", "-m", "FFI fixture")
-    ffi_commit = ffi_repo.run("rev-parse", "HEAD", 
stdout=subprocess.PIPE).stdout.strip()
-    ffi_marker = tmp_path / "python/tvm_ffi/.source-commit"
-    ffi_marker.parent.mkdir(parents=True)
-    ffi_marker.write_text(f"{ffi_commit} {sys.implementation.cache_tag}\n")
-    executable(
-        tmp_path / "cmake",
-        """
-import json, os, pathlib, sys
-with pathlib.Path(os.environ["COMMAND_LOG"]).open("a") as out:
-    entry = [sys.argv[1:], os.getcwd(), os.environ["SCCACHE_BUCKET"], 
os.environ["CC"]]
-    out.write(json.dumps(entry) + "\\n")
-""",
-    )
-    executable(tmp_path / "sccache", "pass\n")
-    log = tmp_path / "commands"
-    env = dict(
-        os.environ,
-        PATH=f"{tmp_path}:{os.environ['PATH']}",
-        COMMAND_LOG=str(log),
-        AWS_ACCESS_KEY_ID="test",
-        CI_NUM_EXECUTORS="2",
-        VERBOSE="false",
-    )
-    subprocess.run(
-        [
-            sys.executable,
-            script,
-            "--debug",
-            "--cmake-target",
-            "cpptest",
-            "--sccache-bucket",
-            "test-cache",
-        ],
-        cwd=tmp_path,
-        env=env,
-        check=True,
-    )
-    configure, build = [json.loads(line) for line in 
log.read_text().splitlines()]
-    assert configure == [
-        ["-GNinja", "-DCMAKE_BUILD_TYPE=Debug", ".."],
-        str(tmp_path / "build"),
-        "test-cache",
-        "/opt/sccache/cc",
-    ]
-    assert build[0][:4] == ["--build", ".", "--", "cpptest"]
-    assert build[0][4].startswith("-j") and int(build[0][4][2:]) >= 1
-    assert len(build[0]) == 5
-    assert build[1:] == configure[1:]
-
-
[email protected]
-def artifact_cli(tmp_path):
-    script_root = tmp_path / "ci/jenkins/scripts"
-    script_root.mkdir(parents=True)
-    for name in ("s3.py", "retry.sh"):
-        shutil.copy(JENKINS_SCRIPT_ROOT / name, script_root / name)
-    registry = {
-        "wheels": ["build/dist/*.whl", "build/dist/a.whl", 
"./build/dist/a.whl"],
-        "extra": ["build/exact.so", "build/dir", "build/missing.so", 
"build/no-match/*.so"],
-    }
-    (script_root.parent / "artifacts.py").write_text("ARTIFACTS = " + 
repr(registry))
-    for name in (
-        "build/dist/b space.whl",
-        "build/dist/a.whl",
-        "build/exact.so",
-        "build/dir/nested",
-    ):
-        path = tmp_path / name
-        path.parent.mkdir(parents=True, exist_ok=True)
-        path.write_text("artifact")
-    binary_dir = tmp_path / "bin"
-    binary_dir.mkdir()
-    executable(
-        binary_dir / "aws",
-        """
-import json, os, pathlib, sys
-with pathlib.Path(os.environ["AWS_LOG"]).open("a") as out:
-    out.write(json.dumps(sys.argv[1:]) + "\\n")
-if sys.argv[-1] == ".":
-    pathlib.Path("downloaded file").write_text("downloaded")
-    print("download: s3://bucket/prefix/downloaded file to downloaded file")
-""",
-    )
-    log = tmp_path / "commands"
-    env = dict(os.environ, PATH=f"{binary_dir}:{os.environ['PATH']}", 
AWS_LOG=str(log))
-
-    def run(*options, action="upload"):
-        log.unlink(missing_ok=True)
-        result = subprocess.run(
-            [
-                sys.executable,
-                script_root / "s3.py",
-                "--action",
-                action,
-                "--bucket",
-                "bucket",
-                "--prefix",
-                "/prefix/",
-                *options,
-            ],
-            cwd=tmp_path,
-            env=env,
-            text=True,
-            capture_output=True,
-        )
-        commands = (
-            [json.loads(line) for line in log.read_text().splitlines()] if 
log.exists() else []
-        )
-        return result, commands
-
-    return run
-
-
-def test_artifact_upload_aliases_and_globs(artifact_cli):
-    expected = [
-        ["s3", "cp", "--no-progress", "--recursive", "build/dir", 
"s3://bucket/prefix/build/dir"],
-        ["s3", "cp", "--no-progress", "build/dist/a.whl", 
"s3://bucket/prefix/build/dist/a.whl"],
-        [
-            "s3",
-            "cp",
-            "--no-progress",
-            "build/dist/b space.whl",
-            "s3://bucket/prefix/build/dist/b space.whl",
-        ],
-        ["s3", "cp", "--no-progress", "build/exact.so", 
"s3://bucket/prefix/build/exact.so"],
-    ]
-    for flags in (
-        ["--artifacts", "wheels", "extra"],
-        ["--artifacts", "extra", "--artifacts", "wheels"],
-        ["--bundle", "wheels", "--bundle", "extra"],
-        ["--bundle", "extra", "wheels"],
-    ):
-        result, commands = artifact_cli(*flags)
-        assert result.returncode == 0, result.stderr
-        assert commands == expected
-        assert "No artifacts matched pattern: build/no-match/*.so" in 
result.stderr
-        assert "The path doesn't exist: build/missing.so" in result.stderr
-
-
-def test_artifact_paths_alias_and_unmatched_pattern(artifact_cli):
-    for flag in ("--paths", "--items"):
-        result, commands = artifact_cli(flag, "build/dist/*.whl", 
"./build/dist/a.whl")
-        assert result.returncode == 0, result.stderr
-        assert [command[-2] for command in commands] == [
-            "build/dist/a.whl",
-            "build/dist/b space.whl",
-        ]
-        result, commands = artifact_cli(flag, "absent/*.whl")
-        assert result.returncode == 0
-        assert commands == []
-        assert "No artifacts matched pattern: absent/*.whl" in result.stderr
-
-
[email protected]("artifacts_flag", ["--artifacts", "--bundle"])
[email protected]("paths_flag", ["--paths", "--items"])
-def test_artifact_flags_are_mutually_exclusive(artifact_cli, artifacts_flag, 
paths_flag):
-    result, commands = artifact_cli(artifacts_flag, "wheels", paths_flag, 
"build/exact.so")
-    assert result.returncode == 2
-    assert "not allowed with argument" in result.stderr
-    assert commands == []
-
-
-def test_artifact_download_copies_whole_prefix(artifact_cli, tmp_path):
-    result, commands = artifact_cli(action="download")
-    assert result.returncode == 0, result.stderr
-    assert commands == [["s3", "cp", "--no-progress", "--recursive", 
"s3://bucket/prefix", "."]]
-    downloaded = tmp_path / "downloaded file"
-    assert downloaded.read_text() == "downloaded"
-    assert downloaded.stat().st_mode & 0o111
-
-
[email protected]("absolute", [False, True])
-def test_legacy_artifact_literal_paths(artifact_cli, tmp_path, absolute):
-    outside = tmp_path.parent / "outside.so"
-    outside.write_text("artifact")
-    path = str(outside) if absolute else "../outside.so"
-    result, commands = artifact_cli("--items", path)
-    assert result.returncode == 0, result.stderr
-    assert commands == [["s3", "cp", "--no-progress", path, 
"s3://bucket/prefix/" + path]]
-
-
-def test_recursive_artifact_overlap(artifact_cli):
-    result, commands = artifact_cli("--paths", "build/dir", "build/dir/*", 
"build/dir/nested")
-    assert result.returncode == 0, result.stderr
-    assert commands == [
-        ["s3", "cp", "--no-progress", "--recursive", "build/dir", 
"s3://bucket/prefix/build/dir"]
-    ]
-
-
-def test_artifact_empty_directory_and_unknown_name(artifact_cli, tmp_path):
-    (tmp_path / "empty").mkdir()
-    result, commands = artifact_cli("--paths", "empty")
-    assert result.returncode != 0
-    assert "Cannot upload empty folder" in result.stderr
-    assert commands == []
-    result, commands = artifact_cli("--artifacts", "unknown")
-    assert result.returncode != 0
-    assert "Unknown artifact" in result.stderr
-    assert commands == []
-
-
-def test_current_pipeline_artifact_options(artifact_cli, tmp_path):
-    # c14bc80 passes repeated --bundle names; the new Jenkinsfiles use 
--artifacts.
-    registry = load_s3().load_artifacts()
-    (tmp_path / "ci/jenkins/artifacts.py").write_text("ARTIFACTS = " + 
repr(registry))
-    for entries in registry.values():
-        for item in entries:
-            path = tmp_path / item
-            path.parent.mkdir(parents=True, exist_ok=True)
-            path.write_text("artifact")
-    for names in (
-        ["tvm_lib", "cpptest"],
-        ["tvm_lib", "tvm_lib_gpu_extra", "cpptest"],
-        ["tvm_runtime"],
-    ):
-        old = [option for name in names for option in ("--bundle", name)]
-        old_result, old_commands = artifact_cli(*old)
-        new_result, new_commands = artifact_cli("--artifacts", *names)
-        assert old_result.returncode == new_result.returncode == 0
-        assert old_commands == new_commands
-        assert [command[-2] for command in new_commands] == sorted(
-            {item for name in names for item in registry[name]}
-        )
diff --git a/tests/python/ci/test_jenkins_loader.py 
b/tests/python/ci/test_jenkins_loader.py
deleted file mode 100644
index 8d73c97642..0000000000
--- a/tests/python/ci/test_jenkins_loader.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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.
-"""Exercise the native helper trust boundary without workspace fallback."""
-
-import shutil
-import subprocess
-from pathlib import Path
-
-import pytest
-
-from .test_utils import REPO_ROOT
-
-PIPELINES = sorted((REPO_ROOT / 
"ci/jenkins/generated").glob("*_jenkinsfile.groovy"))
-
-
-def test_native_loader_trust_boundary():
-    groovy = shutil.which("groovy")
-    if groovy is None:
-        pytest.skip("Groovy is required for the executable pipeline loader 
checks")
-    subprocess.run(
-        [groovy, str(Path(__file__).with_name("jenkins_loader_test.groovy")), 
*PIPELINES],
-        check=True,
-    )
diff --git a/tests/python/ci/test_tvmbot.py b/tests/python/ci/test_tvmbot.py
index 89ca2a1562..1e361d9438 100644
--- a/tests/python/ci/test_tvmbot.py
+++ b/tests/python/ci/test_tvmbot.py
@@ -14,11 +14,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# ruff: noqa: E501
 """
 Test the @tvm-bot merge code
 """
 
+import copy
 import json
 import os
 from pathlib import Path
@@ -26,20 +26,95 @@ from typing import Any
 
 import pytest
 
-from .test_utils import GITHUB_SCRIPT_ROOT, TempGit, run_script
-
-SUCCESS_EXPECTED_OUTPUT = """
-Dry run, would have merged with url=pulls/10786/merge and data={
-  "commit_title": "[Hexagon] 2-d allocation cleanup (#10786)",
-  "commit_message": "- Added device validity check in allocation. 
HexagonDeviceAPI should only be called for CPU/Hexagon types.\\n\\n- Check for 
\\"global.vtcm\\" scope instead of \\"vtcm\\".  The ccope of N-d allocations 
produced by `LowerVtcmAlloc` should be `\\"global.vtcm\\"`.  The previous check 
allowed unsupported scope such as `\\"local.vtcm\\"`.\\n\\n- Remove 
`vtcmallocs` entry after calling free.\\n\\nPreviously, the vtcm allocation map 
kept dangling pointers to `HexagonBuffer`  [...]
-  "sha": "6f04bcf57d07f915a98fd91178f04d9e92a09fcd",
-  "merge_method": "squash"
+from .test_ci import TempGit, run_script
+
+GITHUB_SCRIPT_ROOT = Path(__file__).resolve().parents[3] / "ci" / "github"
+
+# Only the fields consumed by the bot and the mutations below are needed.
+SAMPLE_PR = {
+    "title": "Example change",
+    "body": (
+        "Example description.\r\n\r\ncc @example-user\n\n"
+        "Co-authored-by: Example Existing <[email protected]>"
+    ),
+    "state": "OPEN",
+    "author": {"login": "abc"},
+    "authorCommits": {
+        "nodes": [
+            {
+                "commit": {
+                    "authors": {
+                        "nodes": [
+                            {"name": "Example Author", "email": 
"[email protected]"},
+                            {"name": "Example Existing", "email": 
"[email protected]"},
+                        ]
+                    }
+                }
+            }
+        ],
+    },
+    "commits": {
+        "nodes": [
+            {
+                "commit": {
+                    "oid": "abc123",
+                    "statusCheckRollup": {
+                        "contexts": {
+                            "nodes": [
+                                {
+                                    "context": "tvm-ci/pr-head",
+                                    "state": "SUCCESS",
+                                    "targetUrl": "https://example.com/build/1";,
+                                },
+                                {
+                                    "name": "Example check",
+                                    "conclusion": "SUCCESS",
+                                    "checkSuite": {"workflowRun": {"workflow": 
{"name": "CI"}}},
+                                    "url": "https://example.com/check/1";,
+                                },
+                                {
+                                    "name": "Example automation",
+                                    "conclusion": "FAILURE",
+                                    "checkSuite": {
+                                        "workflowRun": {
+                                            "databaseId": 1,
+                                            "workflow": {"name": "Example 
automation"},
+                                        }
+                                    },
+                                    "url": "https://example.com/check/2";,
+                                },
+                            ]
+                        }
+                    },
+                }
+            }
+        ],
+    },
+    "reviews": {
+        "nodes": [
+            {
+                "body": "Example review",
+                "authorCanPushToRepository": True,
+                "commit": {"oid": "abc123"},
+                "state": "APPROVED",
+            }
+        ]
+    },
 }
-""".strip()
+
+SUCCESS_EXPECTED_OUTPUT = (
+    "Dry run, would have merged with url=pulls/1/merge and data={\n"
+    '  "commit_title": "Example change (#1)",\n'
+    '  "commit_message": "Example description.\\n\\ncc example-user\\n\\n'
+    'Co-authored-by: Example Existing <[email protected]>",\n'
+    '  "sha": "abc123",\n'
+    '  "merge_method": "squash"\n'
+    "}"
+)
 
 
 class _TvmBotTest:
-    NUMBER = 10786
+    NUMBER = 1
 
     def preprocess_data(self, data: dict[str, Any]):
         """
@@ -57,14 +132,13 @@ class _TvmBotTest:
         Run the tvm-bot script using the data from preprocess_data
         """
         mergebot_script = GITHUB_SCRIPT_ROOT / "github_tvmbot.py"
-        test_json_dir = Path(__file__).resolve().parent / "sample_prs"
-        with open(test_json_dir / f"pr{self.NUMBER}.json") as f:
-            test_data = json.load(f)
+        test_data = copy.deepcopy(SAMPLE_PR)
 
         # Update testing data with replacements / additions
         test_data = self.preprocess_data(test_data)
 
         git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
+        git.run("remote", "set-url", "origin", 
"https://github.com/example/project.git";)
 
         comment = {
             "body": self.COMMENT,
@@ -232,11 +306,11 @@ class TestCoAuthors(_TvmBotTest):
 
     COMMENT = "@tvm-bot merge"
     USER = "abc"
-    EXPECTED = "Co-authored-by: Some One <[email protected]>"
+    EXPECTED = "Co-authored-by: Example Coauthor <[email protected]>"
 
     def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
         data["authorCommits"]["nodes"][0]["commit"]["authors"]["nodes"].append(
-            {"name": "Some One", "email": "[email protected]"}
+            {"name": "Example Coauthor", "email": "[email protected]"}
         )
         return data
 
diff --git a/tests/python/ci/test_utils.py b/tests/python/ci/test_utils.py
deleted file mode 100644
index 946e72fa6b..0000000000
--- a/tests/python/ci/test_utils.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# 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: RUF005
-"""
-Constants used in various CI tests
-"""
-
-import os
-import pathlib
-import subprocess
-from typing import Any
-
-REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent.parent.parent
-GITHUB_SCRIPT_ROOT = REPO_ROOT / "ci" / "github"
-JENKINS_SCRIPT_ROOT = REPO_ROOT / "ci" / "jenkins" / "scripts"
-
-
-class TempGit:
-    """
-    A wrapper to run commands in a directory (specifically for use in CI tests)
-    """
-
-    def __init__(self, cwd):
-        self.cwd = cwd
-        # Jenkins git is too old and doesn't have 'git init --initial-branch',
-        # so init and checkout need to be separate steps
-        self.run("init", stderr=subprocess.PIPE, stdout=subprocess.PIPE)
-        self.run("checkout", "-b", "main", stderr=subprocess.PIPE)
-        self.run("remote", "add", "origin", 
"https://github.com/apache/tvm.git";)
-
-    def run(self, *args, **kwargs):
-        """
-        Run a git command based on *args
-        """
-        proc = subprocess.run(
-            ["git"] + list(args), encoding="utf-8", cwd=self.cwd, check=False, 
**kwargs
-        )
-        if proc.returncode != 0:
-            raise RuntimeError(f"git command failed: '{args}'")
-
-        return proc
-
-
-def run_script(command: list[Any], check: bool = True, **kwargs):
-    """
-    Wrapper to run a script and print its output if there was an error
-    """
-    command = [str(c) for c in command]
-    kwargs_to_send = {
-        "stdout": subprocess.PIPE,
-        "stderr": subprocess.PIPE,
-        "encoding": "utf-8",
-    }
-    env = kwargs.pop("env", None)
-    if env is not None:
-        kwargs_to_send["env"] = {**os.environ, **env}
-    kwargs_to_send.update(kwargs)
-    proc = subprocess.run(
-        command,
-        check=False,
-        **kwargs_to_send,
-    )
-    if check and proc.returncode != 0:
-        raise RuntimeError(f"Process 
failed:\nstdout:\n{proc.stdout}\n\nstderr:\n{proc.stderr}")
-
-    return proc

Reply via email to