chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
"""Infrastructure and tests for CI scripts"""
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"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/contribute/ for useful information and tips. After the pull request is submitted, please request code reviews from [Reviewers](https://github.com/apache/incubator-tvm/blob/master/CONTRIBUTORS.md#reviewers) by @ them in the pull request thread.\n\n\nPreviously, the vtcm allocation map kept dangling pointers to `HexagonBuffer` objects after they had been freed.\r\n\r\n- Rename N-d alloc and free packed functions. Since most of the similar device functions use snake case, renaming `*.AllocND` to `*.alloc_nd` and `*.FreeND` to `*.free_nd`.\n\n\ncc @someone\n\r\n\r\nCo-authored-by: Adam Straw <astraw@octoml.ai>\n\n\nThanks for contributing to TVM! Please refer to guideline https://tvm.apache.org/docs/contribute/ for useful information and tips. After the pull request is submitted, please request code reviews from [Reviewers](https://github.com/apache/incubator-tvm/blob/master/CONTRIBUTORS.md#reviewers) by @ them in the pull request thread.\n\n",
|
||||
"state": "OPEN",
|
||||
"author": {
|
||||
"login": "abc"
|
||||
},
|
||||
"comments": {
|
||||
"pageInfo": {
|
||||
"hasPreviousPage": false
|
||||
},
|
||||
"nodes": []
|
||||
},
|
||||
"authorCommits": {
|
||||
"nodes": [
|
||||
{
|
||||
"commit": {
|
||||
"authors": {
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Eric Lunderberg",
|
||||
"email": "elunderberg@octoml.ai"
|
||||
},
|
||||
{
|
||||
"name": "Adam Straw",
|
||||
"email": "astraw@octoml.ai"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
# 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
|
||||
"""Test various CI scripts and GitHub Actions workflows"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import tvm.testing
|
||||
|
||||
from .test_utils import JENKINS_SCRIPT_ROOT, TempGit, run_script
|
||||
|
||||
|
||||
def parameterize_named(**kwargs):
|
||||
keys = next(iter(kwargs.values())).keys()
|
||||
return pytest.mark.parametrize(
|
||||
",".join(keys), [tuple(d.values()) for d in kwargs.values()], ids=kwargs.keys()
|
||||
)
|
||||
|
||||
|
||||
@parameterize_named(
|
||||
dont_skip_main=dict(
|
||||
commands=[],
|
||||
should_skip=False,
|
||||
pr_title="[skip ci] test",
|
||||
why="ci should not be skipped on main",
|
||||
),
|
||||
dont_skip_main_with_commit=dict(
|
||||
commands=[
|
||||
["commit", "--allow-empty", "--message", "[skip ci] commit 1"],
|
||||
],
|
||||
should_skip=False,
|
||||
pr_title="[skip ci] test",
|
||||
why="ci should not be skipped on main",
|
||||
),
|
||||
skip_on_new_branch=dict(
|
||||
commands=[
|
||||
["checkout", "-b", "some_new_branch"],
|
||||
["commit", "--allow-empty", "--message", "[skip ci] commit 1"],
|
||||
],
|
||||
should_skip=True,
|
||||
pr_title="[skip ci] test",
|
||||
why="ci should be skipped on a branch with [skip ci] in the last commit",
|
||||
),
|
||||
no_skip_in_pr_title=dict(
|
||||
commands=[
|
||||
["checkout", "-b", "some_new_branch"],
|
||||
["commit", "--allow-empty", "--message", "[skip ci] commit 1"],
|
||||
],
|
||||
should_skip=False,
|
||||
pr_title="[no skip ci] test",
|
||||
why="ci should not be skipped on a branch with "
|
||||
"[skip ci] in the last commit but not the PR title",
|
||||
),
|
||||
skip_in_pr_title=dict(
|
||||
commands=[
|
||||
["checkout", "-b", "some_new_branch"],
|
||||
["commit", "--allow-empty", "--message", "[skip ci] commit 1"],
|
||||
["commit", "--allow-empty", "--message", "commit 2"],
|
||||
],
|
||||
should_skip=True,
|
||||
pr_title="[skip ci] test",
|
||||
why="ci should be skipped with [skip ci] in the PR title",
|
||||
),
|
||||
skip_in_pr_title_many_commits=dict(
|
||||
commands=[
|
||||
["checkout", "-b", "some_new_branch"],
|
||||
["commit", "--allow-empty", "--message", "commit 1"],
|
||||
["commit", "--allow-empty", "--message", "commit 2"],
|
||||
["commit", "--allow-empty", "--message", "commit 3"],
|
||||
["commit", "--allow-empty", "--message", "commit 4"],
|
||||
],
|
||||
should_skip=True,
|
||||
pr_title="[skip ci] test",
|
||||
why="ci should be skipped with [skip ci] in the PR title",
|
||||
),
|
||||
skip_anywhere_in_title=dict(
|
||||
commands=[
|
||||
["checkout", "-b", "some_new_branch"],
|
||||
],
|
||||
should_skip=True,
|
||||
pr_title="[something][skip ci] test",
|
||||
why="skip ci tag should work anywhere in title",
|
||||
),
|
||||
)
|
||||
def test_skip_ci(tmpdir_factory, commands, should_skip, pr_title, why):
|
||||
"""
|
||||
Test that CI is skipped when it should be
|
||||
"""
|
||||
skip_ci_script = JENKINS_SCRIPT_ROOT / "git_skip_ci.py"
|
||||
|
||||
git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
|
||||
|
||||
git.run("config", "user.name", "ci")
|
||||
git.run("config", "user.email", "email@example.com")
|
||||
git.run("commit", "--allow-empty", "--message", "base commit")
|
||||
for command in commands:
|
||||
git.run(*command)
|
||||
pr_number = "1234"
|
||||
proc = run_script(
|
||||
[skip_ci_script, "--pr", pr_number, "--pr-title", pr_title],
|
||||
cwd=git.cwd,
|
||||
check=False,
|
||||
)
|
||||
expected = 0 if should_skip else 1
|
||||
if proc.returncode != expected:
|
||||
raise RuntimeError(
|
||||
f"Unexpected return code {proc.returncode} "
|
||||
f"(expected {expected}) in {why}:\n{proc.stdout}"
|
||||
)
|
||||
|
||||
|
||||
@parameterize_named(
|
||||
no_file=dict(files=[], should_skip=True),
|
||||
readme=dict(files=["README.md"], should_skip=True),
|
||||
c_file=dict(files=["test.c"], should_skip=False),
|
||||
c_and_readme=dict(files=["test.c", "README.md"], should_skip=False),
|
||||
src_file_and_readme=dict(
|
||||
files=["src/autotvm/feature_visitor.cc", "README.md"], should_skip=False
|
||||
),
|
||||
yaml_and_readme=dict(files=[".asf.yaml", "docs/README.md"], should_skip=True),
|
||||
)
|
||||
def test_skip_globs(tmpdir_factory, files, should_skip):
|
||||
"""
|
||||
Test that CI is skipped if only certain files are edited
|
||||
"""
|
||||
script = JENKINS_SCRIPT_ROOT / "git_skip_ci_globs.py"
|
||||
|
||||
git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
|
||||
|
||||
proc = run_script(
|
||||
[
|
||||
script,
|
||||
"--files",
|
||||
",".join(files),
|
||||
],
|
||||
check=False,
|
||||
cwd=git.cwd,
|
||||
)
|
||||
|
||||
if should_skip:
|
||||
assert proc.returncode == 0
|
||||
else:
|
||||
assert proc.returncode == 1
|
||||
|
||||
|
||||
def assert_in(needle: str, haystack: str):
|
||||
"""
|
||||
Check that 'needle' is in 'haystack'
|
||||
"""
|
||||
if needle not in haystack:
|
||||
raise AssertionError(f"item not found:\n{needle}\nin:\n{haystack}")
|
||||
|
||||
|
||||
@tvm.testing.skip_if_wheel_test
|
||||
@parameterize_named(
|
||||
same_tags=dict(
|
||||
tlcpackstaging_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T00:00:00.123456Z",
|
||||
"name": "123-123-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
tlcpack_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T00:00:00.123456Z",
|
||||
"name": "123-123-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
expected="Tag names were the same, no update needed",
|
||||
expected_images=[],
|
||||
),
|
||||
staging_update=dict(
|
||||
tlcpackstaging_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T01:00:00.123456Z",
|
||||
"name": "234-234-abc-staging",
|
||||
},
|
||||
{
|
||||
"last_updated": "2022-06-01T00:00:00.123456Z",
|
||||
"name": "456-456-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
tlcpack_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T00:00:00.123456Z",
|
||||
"name": "123-123-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
expected="Using tlcpackstaging tag on tlcpack",
|
||||
expected_images=[
|
||||
"ci_arm: tlcpack/ci-arm:456-456-abc",
|
||||
],
|
||||
),
|
||||
tlcpack_update=dict(
|
||||
tlcpackstaging_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T00:00:00.123456Z",
|
||||
"name": "123-123-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
tlcpack_body={
|
||||
"results": [
|
||||
{
|
||||
"last_updated": "2022-06-01T00:01:00.123456Z",
|
||||
"name": "234-234-abc",
|
||||
},
|
||||
]
|
||||
},
|
||||
expected="Found newer image, using: tlcpack",
|
||||
expected_images=[
|
||||
"ci_arm: tlcpack/ci-arm:234-234-abc",
|
||||
],
|
||||
),
|
||||
)
|
||||
def test_open_docker_update_pr(
|
||||
tmpdir_factory, tlcpackstaging_body, tlcpack_body, expected, expected_images
|
||||
):
|
||||
"""Test workflow to open a PR to update Docker images"""
|
||||
tag_script = JENKINS_SCRIPT_ROOT / "open_docker_update_pr.py"
|
||||
|
||||
git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
|
||||
git.run("config", "user.name", "ci")
|
||||
git.run("config", "user.email", "email@example.com")
|
||||
images = [
|
||||
"ci_arm",
|
||||
"ci_cortexm",
|
||||
"ci_cpu",
|
||||
"ci_gpu",
|
||||
"ci_minimal",
|
||||
"ci_riscv",
|
||||
"ci_wasm",
|
||||
]
|
||||
|
||||
docker_data = {}
|
||||
for image in images:
|
||||
docker_data[f"repositories/tlcpackstaging/{image}/tags"] = tlcpackstaging_body
|
||||
docker_data[f"repositories/tlcpack/{image.replace('_', '-')}/tags"] = tlcpack_body
|
||||
|
||||
proc = run_script(
|
||||
[
|
||||
tag_script,
|
||||
"--dry-run",
|
||||
"--testing-docker-data",
|
||||
json.dumps(docker_data),
|
||||
],
|
||||
cwd=git.cwd,
|
||||
env={"GITHUB_TOKEN": "1234"},
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
for line in expected_images:
|
||||
if line not in proc.stdout:
|
||||
raise RuntimeError(f"Missing line {line} in output:\n{proc.stdout}")
|
||||
|
||||
assert_in(expected, proc.stdout)
|
||||
|
||||
|
||||
@parameterize_named(
|
||||
use_tlcpack=dict(
|
||||
images=["ci_arm", "ci_cpu"],
|
||||
expected={
|
||||
"ci_arm": "tlcpack/ci-arm:abc-abc-123",
|
||||
"ci_cpu": "tlcpack/ci-cpu:abc-abc-234",
|
||||
},
|
||||
),
|
||||
use_staging=dict(
|
||||
images=["ci_arm2"],
|
||||
expected={
|
||||
"ci_arm2": "tlcpackstaging/ci_arm2:abc-abc-123",
|
||||
},
|
||||
),
|
||||
)
|
||||
def test_determine_docker_images(tmpdir_factory, images, expected):
|
||||
"""Test script to decide whether to use tlcpack or tlcpackstaging for images"""
|
||||
script = JENKINS_SCRIPT_ROOT / "determine_docker_images.py"
|
||||
|
||||
git_dir = tmpdir_factory.mktemp("tmp_git_dir")
|
||||
|
||||
docker_data = {
|
||||
"repositories/tlcpack/ci-arm/tags/abc-abc-123": {},
|
||||
"repositories/tlcpack/ci-cpu/tags/abc-abc-234": {},
|
||||
}
|
||||
|
||||
images_data = {
|
||||
"ci_arm": "tlcpack/ci-arm:abc-abc-123",
|
||||
"ci_cpu": "tlcpack/ci-cpu:abc-abc-234",
|
||||
"ci_arm2": "tlcpack/ci-arm2:abc-abc-123",
|
||||
}
|
||||
|
||||
run_script(
|
||||
[
|
||||
script,
|
||||
"--testing-docker-data",
|
||||
json.dumps(docker_data),
|
||||
"--testing-images-data",
|
||||
json.dumps(images_data),
|
||||
"--base-dir",
|
||||
git_dir,
|
||||
]
|
||||
+ images,
|
||||
cwd=git_dir,
|
||||
)
|
||||
|
||||
for expected_filename, expected_image in expected.items():
|
||||
with open(Path(git_dir) / expected_filename) as f:
|
||||
actual_image = f.read()
|
||||
|
||||
assert actual_image == expected_image
|
||||
|
||||
|
||||
@parameterize_named(
|
||||
invalid_name=dict(
|
||||
changed_files=[],
|
||||
name="abc",
|
||||
check="Image abc is not using new naming scheme",
|
||||
expected_code=1,
|
||||
),
|
||||
no_hash=dict(
|
||||
changed_files=[], name="123-123-abc", check="No extant hash found", expected_code=1
|
||||
),
|
||||
no_changes=dict(
|
||||
changed_files=[["test.txt"]],
|
||||
name=None,
|
||||
check="Did not find changes, no rebuild necessary",
|
||||
expected_code=0,
|
||||
),
|
||||
docker_changes=dict(
|
||||
changed_files=[["test.txt"], ["docker/test.txt"]],
|
||||
name=None,
|
||||
check="Found docker changes",
|
||||
expected_code=2,
|
||||
),
|
||||
)
|
||||
def test_should_rebuild_docker(tmpdir_factory, changed_files, name, check, expected_code):
|
||||
"""
|
||||
Check that the Docker images are built when necessary
|
||||
"""
|
||||
tag_script = JENKINS_SCRIPT_ROOT / "should_rebuild_docker.py"
|
||||
|
||||
git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
|
||||
git.run("config", "user.name", "ci")
|
||||
git.run("config", "user.email", "email@example.com")
|
||||
|
||||
git_path = Path(git.cwd)
|
||||
for i, commits in enumerate(changed_files):
|
||||
for filename in commits:
|
||||
path = git_path / filename
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
path.touch()
|
||||
git.run("add", filename)
|
||||
|
||||
git.run("commit", "-m", f"message {i}")
|
||||
|
||||
if name is None:
|
||||
ref = "HEAD"
|
||||
if len(changed_files) > 1:
|
||||
ref = f"HEAD~{len(changed_files) - 1}"
|
||||
proc = git.run("rev-parse", ref, stdout=subprocess.PIPE)
|
||||
last_hash = proc.stdout.strip()
|
||||
name = f"123-123-{last_hash}"
|
||||
|
||||
docker_data = {
|
||||
"repositories/tlcpack": {
|
||||
"results": [
|
||||
{
|
||||
"name": "ci-something",
|
||||
},
|
||||
{
|
||||
"name": "something-else",
|
||||
},
|
||||
],
|
||||
},
|
||||
"repositories/tlcpack/ci-something/tags": {
|
||||
"results": [{"name": name}, {"name": name + "old"}],
|
||||
},
|
||||
}
|
||||
|
||||
proc = run_script(
|
||||
[
|
||||
tag_script,
|
||||
"--testing-docker-data",
|
||||
json.dumps(docker_data),
|
||||
],
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=git.cwd,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert_in(check, proc.stdout)
|
||||
assert proc.returncode == expected_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tvm.testing.main()
|
||||
@@ -0,0 +1,281 @@
|
||||
# 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
|
||||
"""
|
||||
Test the @tvm-bot merge code
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tvm
|
||||
|
||||
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` objects after they had been freed.\\n\\n- Rename N-d alloc and free packed functions. Since most of the similar device functions use snake case, renaming `*.AllocND` to `*.alloc_nd` and `*.FreeND` to `*.free_nd`.\\n\\n\\ncc someone\\n\\n\\nCo-authored-by: Adam Straw <astraw@octoml.ai>",
|
||||
"sha": "6f04bcf57d07f915a98fd91178f04d9e92a09fcd",
|
||||
"merge_method": "squash"
|
||||
}
|
||||
""".strip()
|
||||
|
||||
|
||||
class _TvmBotTest:
|
||||
NUMBER = 10786
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]):
|
||||
"""
|
||||
Used to pre-process PR data before running the test. Override as
|
||||
necessary to edit data for specific test cases.
|
||||
"""
|
||||
return data
|
||||
|
||||
@tvm.testing.skip_if_wheel_test
|
||||
def test(self, tmpdir_factory):
|
||||
"""
|
||||
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)
|
||||
|
||||
# Update testing data with replacements / additions
|
||||
test_data = self.preprocess_data(test_data)
|
||||
|
||||
git = TempGit(tmpdir_factory.mktemp("tmp_git_dir"))
|
||||
|
||||
comment = {
|
||||
"body": self.COMMENT,
|
||||
"id": 123,
|
||||
"user": {
|
||||
"login": self.USER,
|
||||
},
|
||||
}
|
||||
allowed_users = [{"login": "abc"}, {"login": "other-abc"}]
|
||||
|
||||
proc = run_script(
|
||||
[
|
||||
mergebot_script,
|
||||
"--pr",
|
||||
self.NUMBER,
|
||||
"--dry-run",
|
||||
"--run-url",
|
||||
"https://example.com",
|
||||
"--testing-pr-json",
|
||||
json.dumps(test_data),
|
||||
"--testing-collaborators-json",
|
||||
json.dumps(allowed_users),
|
||||
"--testing-mentionable-users-json",
|
||||
json.dumps(allowed_users),
|
||||
"--trigger-comment-json",
|
||||
json.dumps(comment),
|
||||
],
|
||||
env={
|
||||
"TVM_BOT_JENKINS_TOKEN": "123",
|
||||
"GH_ACTIONS_TOKEN": "123",
|
||||
},
|
||||
cwd=git.cwd,
|
||||
)
|
||||
|
||||
if self.EXPECTED not in proc.stderr:
|
||||
raise RuntimeError(f"{proc.stderr}\ndid not contain\n{self.EXPECTED}")
|
||||
|
||||
|
||||
class TestNoRequest(_TvmBotTest):
|
||||
"""
|
||||
A PR for which the mergebot runs but no merge is requested
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot do something else"
|
||||
USER = "abc"
|
||||
EXPECTED = "Command 'do something else' did not match anything"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]):
|
||||
data["reviews"]["nodes"][0]["body"] = "nothing"
|
||||
return data
|
||||
|
||||
|
||||
class TestSuccessfulMerge(_TvmBotTest):
|
||||
"""
|
||||
Everything is fine so this PR will merge
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = SUCCESS_EXPECTED_OUTPUT
|
||||
|
||||
|
||||
class TestBadCI(_TvmBotTest):
|
||||
"""
|
||||
A PR which failed CI and cannot merge
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Cannot merge, these CI jobs are not successful on"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]):
|
||||
# Mark the Jenkins build as failed
|
||||
contexts = data["commits"]["nodes"][0]["commit"]["statusCheckRollup"]["contexts"]["nodes"]
|
||||
for context in contexts:
|
||||
if "context" in context and context["context"] == "tvm-ci/pr-head":
|
||||
context["state"] = "FAILED"
|
||||
return data
|
||||
|
||||
|
||||
class TestOldReview(_TvmBotTest):
|
||||
"""
|
||||
A PR with passing CI and approving reviews on an old commit so it cannot merge
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Cannot merge, did not find any approving reviews"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
data["reviews"]["nodes"][0]["commit"]["oid"] = "abc12345"
|
||||
return data
|
||||
|
||||
|
||||
class TestMissingJob(_TvmBotTest):
|
||||
"""
|
||||
PR missing an expected CI job and cannot merge
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Cannot merge, missing expected jobs"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
contexts = data["commits"]["nodes"][0]["commit"]["statusCheckRollup"]["contexts"]["nodes"]
|
||||
for context in contexts:
|
||||
if "context" in context and context["context"] == "tvm-ci/pr-head":
|
||||
context["context"] = "something"
|
||||
return data
|
||||
|
||||
|
||||
class TestInvalidAuthor(_TvmBotTest):
|
||||
"""
|
||||
Merge requester is not a committer and cannot merge
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "not-abc"
|
||||
EXPECTED = "Failed auth check 'collaborators', quitting"
|
||||
|
||||
|
||||
class TestUnauthorizedComment(_TvmBotTest):
|
||||
"""
|
||||
Check that a merge comment not from a CONTRIBUTOR is rejected
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "not-abc2"
|
||||
EXPECTED = "Failed auth check 'collaborators'"
|
||||
|
||||
|
||||
class TestNoReview(_TvmBotTest):
|
||||
"""
|
||||
Check that a merge request without any reviews is rejected
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Cannot merge, did not find any approving reviews from users with write access"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
data["reviews"]["nodes"] = []
|
||||
return data
|
||||
|
||||
|
||||
class TestChangesRequested(_TvmBotTest):
|
||||
"""
|
||||
Check that a merge request with a 'Changes Requested' review is rejected
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Cannot merge, found [this review]"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
data["reviews"]["nodes"][0]["state"] = "CHANGES_REQUESTED"
|
||||
data["reviews"]["nodes"][0]["url"] = "http://example.com"
|
||||
return data
|
||||
|
||||
|
||||
class TestCoAuthors(_TvmBotTest):
|
||||
"""
|
||||
Check that a merge request with co-authors generates the correct commit message
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Co-authored-by: Some One <someone@email.com>"
|
||||
|
||||
def preprocess_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
data["authorCommits"]["nodes"][0]["commit"]["authors"]["nodes"].append(
|
||||
{"name": "Some One", "email": "someone@email.com"}
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class TestRerunCI(_TvmBotTest):
|
||||
"""
|
||||
Start a new CI job
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot rerun"
|
||||
USER = "abc"
|
||||
EXPECTED = "Rerunning ci with"
|
||||
|
||||
|
||||
class TestRerunPermissions(_TvmBotTest):
|
||||
"""
|
||||
Start a new CI job as an unauthorized user
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot rerun"
|
||||
USER = "someone"
|
||||
EXPECTED = "Failed auth check 'mentionable_users', quitting"
|
||||
|
||||
|
||||
class TestRerunNonAuthor(_TvmBotTest):
|
||||
"""
|
||||
Start a new CI job as a mentionable user
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot rerun"
|
||||
USER = "other-abc"
|
||||
EXPECTED = "Passed auth check 'mentionable_users', continuing"
|
||||
|
||||
|
||||
class TestIgnoreJobs(_TvmBotTest):
|
||||
"""
|
||||
Ignore GitHub Actions jobs that don't start with CI /
|
||||
"""
|
||||
|
||||
COMMENT = "@tvm-bot merge"
|
||||
USER = "abc"
|
||||
EXPECTED = "Dry run, would have merged"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tvm.testing.main()
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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" / "scripts" / "github"
|
||||
JENKINS_SCRIPT_ROOT = REPO_ROOT / "ci" / "scripts" / "jenkins"
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user