423 lines
12 KiB
Python
423 lines
12 KiB
Python
# 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()
|