chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import json
|
||||
import operator
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator, Iterable
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.utils.config import ASSETS_DIR, SHARE_DIR
|
||||
|
||||
|
||||
class Container:
|
||||
def __init__(self, data, key="id"):
|
||||
self.raw_data = data
|
||||
self.map_data = {obj[key]: obj for obj in data}
|
||||
|
||||
@property
|
||||
def raw(self):
|
||||
return self.raw_data
|
||||
|
||||
@property
|
||||
def map(self):
|
||||
return self.map_data
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.raw_data)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.raw_data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, slice):
|
||||
return self.raw_data[key]
|
||||
return self.map_data[key]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def users():
|
||||
with open(ASSETS_DIR / "users.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def organizations():
|
||||
with open(ASSETS_DIR / "organizations.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def memberships():
|
||||
with open(ASSETS_DIR / "memberships.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tasks():
|
||||
with open(ASSETS_DIR / "tasks.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
def filter_assets(resources: Iterable, **kwargs):
|
||||
filtered_resources = []
|
||||
exclude_prefix = "exclude_"
|
||||
filter_operator = lambda arg, func: bool(func(arg))
|
||||
|
||||
for resource in resources:
|
||||
is_matched = True
|
||||
for key, value in kwargs.items():
|
||||
if not is_matched:
|
||||
break
|
||||
|
||||
op = operator.eq
|
||||
if key.startswith(exclude_prefix):
|
||||
key = key[len(exclude_prefix) :]
|
||||
op = operator.ne
|
||||
elif callable(value):
|
||||
op = filter_operator
|
||||
|
||||
cur_value, rest = resource, key
|
||||
while rest:
|
||||
field_and_rest = rest.split("__", maxsplit=1)
|
||||
if 2 == len(field_and_rest):
|
||||
field, rest = field_and_rest
|
||||
else:
|
||||
field, rest = field_and_rest[0], None
|
||||
cur_value = cur_value.get(field, None)
|
||||
# e.g. task has null target_storage
|
||||
# or there are mutexed project_id, task_id
|
||||
if not cur_value:
|
||||
break
|
||||
|
||||
if not (not rest and op(cur_value, value) or rest and op == operator.ne):
|
||||
is_matched = False
|
||||
|
||||
if is_matched:
|
||||
filtered_resources.append(resource)
|
||||
|
||||
return filtered_resources
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_projects(projects):
|
||||
def filter_(**kwargs):
|
||||
return filter_assets(projects, **kwargs)
|
||||
|
||||
return filter_
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_tasks(tasks):
|
||||
def filter_(**kwargs):
|
||||
return filter_assets(tasks, **kwargs)
|
||||
|
||||
return filter_
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tasks_wlc(labels, tasks): # tasks with labels count
|
||||
tasks = deepcopy(tasks)
|
||||
tasks_by_project = defaultdict(list)
|
||||
for task in tasks:
|
||||
tasks_by_project[task["project_id"]].append(task)
|
||||
task["labels"]["count"] = 0
|
||||
|
||||
for label in labels:
|
||||
task_id = label.get("task_id")
|
||||
project_id = label.get("project_id")
|
||||
if not label["parent_id"]:
|
||||
if task_id:
|
||||
tasks[task_id]["labels"]["count"] += 1
|
||||
elif project_id:
|
||||
for task in tasks_by_project[project_id]:
|
||||
task["labels"]["count"] += 1
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def projects():
|
||||
with open(ASSETS_DIR / "projects.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def projects_wlc(projects, labels): # projects with labels count
|
||||
projects = deepcopy(projects)
|
||||
for project in projects:
|
||||
project["labels"]["count"] = 0
|
||||
|
||||
for label in labels:
|
||||
project_id = label.get("project_id")
|
||||
if not label["parent_id"] and project_id:
|
||||
projects[project_id]["labels"]["count"] += 1
|
||||
|
||||
return projects
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def jobs():
|
||||
with open(ASSETS_DIR / "jobs.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def jobs_wlc(jobs, tasks_wlc): # jobs with labels count
|
||||
jobs = deepcopy(jobs)
|
||||
for job in jobs:
|
||||
tid = job["task_id"]
|
||||
job["labels"]["count"] = tasks_wlc[tid]["labels"]["count"]
|
||||
return jobs
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def invitations():
|
||||
with open(ASSETS_DIR / "invitations.json") as f:
|
||||
return Container(json.load(f)["results"], key="key")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def annotations():
|
||||
with open(ASSETS_DIR / "annotations.json") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
CloudStorageAssets = Container
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def cloud_storages() -> CloudStorageAssets:
|
||||
with open(ASSETS_DIR / "cloudstorages.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def issues():
|
||||
with open(ASSETS_DIR / "issues.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def comments():
|
||||
with open(ASSETS_DIR / "comments.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def webhooks():
|
||||
with open(ASSETS_DIR / "webhooks.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def labels():
|
||||
with open(ASSETS_DIR / "labels.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def quality_reports():
|
||||
with open(ASSETS_DIR / "quality_reports.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def quality_conflicts():
|
||||
with open(ASSETS_DIR / "quality_conflicts.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def quality_settings():
|
||||
with open(ASSETS_DIR / "quality_settings.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def consensus_settings():
|
||||
with open(ASSETS_DIR / "consensus_settings.json") as f:
|
||||
return Container(json.load(f)["results"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def users_by_name(users):
|
||||
return {user["username"]: user for user in users}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def jobs_by_org(tasks, jobs):
|
||||
# FUTURE-FIXME: should be based on organizations to include orgs without jobs too
|
||||
data = {}
|
||||
for job in jobs:
|
||||
data.setdefault(tasks[job["task_id"]]["organization"], []).append(job)
|
||||
data[""] = data.pop(None, [])
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def projects_by_org(projects):
|
||||
data = {}
|
||||
for project in projects:
|
||||
data.setdefault(project["organization"], []).append(project)
|
||||
data[""] = data.pop(None, [])
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tasks_by_org(tasks):
|
||||
data = {}
|
||||
for task in tasks:
|
||||
data.setdefault(task["organization"], []).append(task)
|
||||
data[""] = data.pop(None, [])
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def issues_by_org(tasks, jobs, issues):
|
||||
data = {}
|
||||
for issue in issues:
|
||||
data.setdefault(tasks[jobs[issue["job"]]["task_id"]]["organization"], []).append(issue)
|
||||
data[""] = data.pop(None, [])
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def assignee_id():
|
||||
def get_id(data):
|
||||
if data.get("assignee") is not None:
|
||||
return data["assignee"]["id"]
|
||||
|
||||
return get_id
|
||||
|
||||
|
||||
def ownership(func):
|
||||
def wrap(user_id, resource_id):
|
||||
if resource_id is None:
|
||||
return False
|
||||
return func(user_id, resource_id)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_project_staff(projects, assignee_id):
|
||||
@ownership
|
||||
def check(user_id, pid):
|
||||
return user_id == projects[pid]["owner"]["id"] or user_id == assignee_id(projects[pid])
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_task_staff(tasks, is_project_staff, assignee_id):
|
||||
@ownership
|
||||
def check(user_id, tid):
|
||||
return (
|
||||
user_id == tasks[tid]["owner"]["id"]
|
||||
or user_id == assignee_id(tasks[tid])
|
||||
or is_project_staff(user_id, tasks[tid]["project_id"])
|
||||
)
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_job_staff(jobs, is_task_staff, assignee_id):
|
||||
@ownership
|
||||
def check(user_id, jid):
|
||||
return user_id == assignee_id(jobs[jid]) or is_task_staff(user_id, jobs[jid]["task_id"])
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_issue_staff(issues, jobs, assignee_id):
|
||||
@ownership
|
||||
def check(user_id, issue_id):
|
||||
return (
|
||||
user_id == issues[issue_id]["owner"]["id"]
|
||||
or user_id == assignee_id(issues[issue_id])
|
||||
or user_id == assignee_id(jobs[issues[issue_id]["job"]])
|
||||
)
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_issue_admin(issues, jobs, is_task_staff):
|
||||
@ownership
|
||||
def check(user_id, issue_id):
|
||||
return is_task_staff(user_id, jobs[issues[issue_id]["job"]]["task_id"])
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def find_users(test_db):
|
||||
def find(**kwargs):
|
||||
assert len(kwargs) > 0
|
||||
|
||||
data = test_db
|
||||
for field, value in kwargs.items():
|
||||
if field.startswith("exclude_"):
|
||||
field = field.split("_", maxsplit=1)[1]
|
||||
exclude_rows = set(v["id"] for v in filter(lambda a: a[field] == value, test_db))
|
||||
data = list(filter(lambda a: a["id"] not in exclude_rows, data))
|
||||
else:
|
||||
data = list(filter(lambda a: a[field] == value, data))
|
||||
|
||||
return data
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_db(users, users_by_name, memberships):
|
||||
data = []
|
||||
fields = [
|
||||
"username",
|
||||
"id",
|
||||
"privilege",
|
||||
"role",
|
||||
"org",
|
||||
"membership_id",
|
||||
"is_superuser",
|
||||
"has_analytics_access",
|
||||
]
|
||||
|
||||
def add_row(**kwargs):
|
||||
data.append({field: kwargs.get(field) for field in fields})
|
||||
|
||||
for user in users:
|
||||
for group in user["groups"]:
|
||||
add_row(
|
||||
username=user["username"],
|
||||
id=user["id"],
|
||||
privilege=group,
|
||||
has_analytics_access=user["has_analytics_access"],
|
||||
is_superuser=user["is_superuser"],
|
||||
)
|
||||
|
||||
for membership in memberships:
|
||||
username = membership["user"]["username"]
|
||||
for group in users_by_name[username]["groups"]:
|
||||
add_row(
|
||||
username=username,
|
||||
role=membership["role"],
|
||||
privilege=group,
|
||||
id=membership["user"]["id"],
|
||||
org=membership["organization"],
|
||||
membership_id=membership["id"],
|
||||
has_analytics_access=users_by_name[username]["has_analytics_access"],
|
||||
is_superuser=users_by_name[username]["is_superuser"],
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def org_staff(memberships):
|
||||
def find(org_id):
|
||||
if org_id in ["", None]:
|
||||
return set()
|
||||
else:
|
||||
return set(
|
||||
m["user"]["id"]
|
||||
for m in memberships
|
||||
if m["role"] in ["maintainer", "owner"]
|
||||
and m["user"] is not None
|
||||
and m["organization"] == org_id
|
||||
)
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def is_org_member(memberships):
|
||||
def check(user_id, org_id, *, role=None):
|
||||
if org_id in ["", None]:
|
||||
return True
|
||||
else:
|
||||
return user_id in set(
|
||||
m["user"]["id"]
|
||||
for m in memberships
|
||||
if m["user"] is not None
|
||||
if m["organization"] == org_id
|
||||
if not role or m["role"] == role
|
||||
)
|
||||
|
||||
return check
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def find_job_staff_user(is_job_staff):
|
||||
def find(jobs, users, is_staff, wo_jobs=None):
|
||||
for job in jobs:
|
||||
if wo_jobs is not None and job["id"] in wo_jobs:
|
||||
continue
|
||||
for user in users:
|
||||
if is_staff == is_job_staff(user["id"], job["id"]):
|
||||
return user["username"], job["id"]
|
||||
return None, None
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def find_task_staff_user(is_task_staff):
|
||||
def find(tasks, users, is_staff, wo_tasks=None):
|
||||
for task in tasks:
|
||||
if wo_tasks is not None and task["id"] in wo_tasks:
|
||||
continue
|
||||
for user in users:
|
||||
if is_staff == is_task_staff(user["id"], task["id"]):
|
||||
return user["username"], task["id"]
|
||||
return None, None
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def find_issue_staff_user(is_issue_staff, is_issue_admin):
|
||||
def find(issues, users, is_staff, is_admin):
|
||||
for issue in issues:
|
||||
for user in users:
|
||||
i_admin, i_staff = (
|
||||
is_issue_admin(user["id"], issue["id"]),
|
||||
is_issue_staff(user["id"], issue["id"]),
|
||||
)
|
||||
if (is_admin is None and (i_staff or i_admin) == is_staff) or (
|
||||
is_admin == i_admin and is_staff == i_staff
|
||||
):
|
||||
return user["username"], issue["id"]
|
||||
return None, None
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_jobs(jobs):
|
||||
def filter_(**kwargs):
|
||||
return filter_assets(jobs, **kwargs)
|
||||
|
||||
return filter_
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_labels(labels):
|
||||
def filter_(**kwargs):
|
||||
return filter_assets(labels, **kwargs)
|
||||
|
||||
return filter_
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_jobs_with_shapes(annotations):
|
||||
def find(jobs):
|
||||
return list(filter(lambda j: annotations["job"].get(str(j["id"]), {}).get("shapes"), jobs))
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filter_tasks_with_shapes(annotations):
|
||||
def find(tasks):
|
||||
return list(
|
||||
filter(lambda t: annotations["task"].get(str(t["id"]), {}).get("shapes"), tasks)
|
||||
)
|
||||
|
||||
return find
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def jobs_with_shapes(jobs, filter_jobs_with_shapes):
|
||||
return filter_jobs_with_shapes(jobs)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tasks_with_shapes(tasks, filter_tasks_with_shapes):
|
||||
return filter_tasks_with_shapes(tasks)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def admin_user(users):
|
||||
for user in users:
|
||||
if user["is_superuser"] and user["is_active"]:
|
||||
return user["username"]
|
||||
raise Exception("Can't find any admin user in the test DB")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def regular_user(users):
|
||||
for user in users:
|
||||
if not user["is_superuser"] and user["is_active"]:
|
||||
return user["username"]
|
||||
raise Exception("Can't find any regular user in the test DB")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def regular_lonely_user(users):
|
||||
for user in users:
|
||||
if user["username"] == "lonely_user":
|
||||
return user["username"]
|
||||
raise Exception("Can't find the lonely user in the test DB")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def job_has_annotations(annotations) -> bool:
|
||||
def check_has_annotations(job_id: int) -> bool:
|
||||
job_annotations = annotations["job"][str(job_id)]
|
||||
return bool(
|
||||
job_annotations["tags"] or job_annotations["shapes"] or job_annotations["tracks"]
|
||||
)
|
||||
|
||||
return check_has_annotations
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def access_tokens(access_tokens_by_username):
|
||||
"Private keys are available in the 'private_key' field."
|
||||
|
||||
return sorted(
|
||||
(t for user_tokens in access_tokens_by_username.values() for t in user_tokens),
|
||||
key=lambda t: t["id"],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def raw_access_tokens_by_username():
|
||||
with open(ASSETS_DIR / "access_tokens.json") as f:
|
||||
return json.load(f)["user"]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def access_tokens_by_username(raw_access_tokens_by_username):
|
||||
"Private keys are available in the 'private_key' field."
|
||||
|
||||
private_keys = {
|
||||
3: "XQRwNl8D.N5EYCzdyWdroeVVfJylkquAmBqgt9Kw2", # nosec
|
||||
4: "waUchCLi.wWxJTdYBt6R8auMse86bwHobMomjQvEB", # nosec
|
||||
5: "2HVbBoWR.ZJqJtm3TEKEkjqZwyoL7Ig71LVvKRj79", # nosec
|
||||
7: "gIUANJCa.W4Y101GNS8wOyFcncvxMZjTEnU7dzAUF", # nosec
|
||||
}
|
||||
|
||||
data = {}
|
||||
for username, user_tokens in raw_access_tokens_by_username.items():
|
||||
if not user_tokens:
|
||||
continue
|
||||
|
||||
extended_user_tokens = []
|
||||
|
||||
for access_token in user_tokens:
|
||||
access_token = access_token.copy()
|
||||
access_token["private_key"] = private_keys[access_token["id"]]
|
||||
extended_user_tokens.append(access_token)
|
||||
|
||||
data[username] = extended_user_tokens
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def fxt_local_audio_file_path() -> Generator[Path, None, None]:
|
||||
yield SHARE_DIR / "audio" / "sample1.mp3"
|
||||
@@ -0,0 +1,707 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from enum import Enum
|
||||
from http import HTTPStatus
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, CalledProcessError, run
|
||||
from time import sleep
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from shared.utils.config import ASSETS_DIR, get_server_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CVAT_ROOT_DIR = next(dir.parent for dir in Path(__file__).parents if dir.name == "tests")
|
||||
CVAT_DB_DIR = ASSETS_DIR / "cvat_db"
|
||||
CLICKHOUSE_INIT_SCRIPT = "components/analytics/clickhouse/init.py"
|
||||
PREFIX = "test"
|
||||
|
||||
CONTAINER_NAME_FILES = ["docker-compose.tests.yml"]
|
||||
|
||||
DC_FILES = CONTAINER_NAME_FILES + [
|
||||
"docker-compose.dev.yml",
|
||||
"tests/docker-compose.file_share.yml",
|
||||
"tests/docker-compose.minio.yml",
|
||||
"tests/docker-compose.pat_settings.yml",
|
||||
"tests/docker-compose.test_servers.yml",
|
||||
]
|
||||
|
||||
|
||||
class Container(str, Enum):
|
||||
DB = "cvat_db"
|
||||
SERVER = "cvat_server"
|
||||
WORKER_ANNOTATION = "cvat_worker_annotation"
|
||||
WORKER_IMPORT = "cvat_worker_import"
|
||||
WORKER_EXPORT = "cvat_worker_export"
|
||||
WORKER_QUALITY_REPORTS = "cvat_worker_quality_reports"
|
||||
WORKER_WEBHOOKS = "cvat_worker_webhooks"
|
||||
WORKER_UTILS = "cvat_worker_utils"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def covered(cls):
|
||||
return [item.value for item in cls if item != cls.DB]
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("CVAT REST API testing options")
|
||||
group._addoption(
|
||||
"--start-services",
|
||||
action="store_true",
|
||||
help="Start all necessary CVAT containers without running tests. (default: %(default)s)",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--stop-services",
|
||||
action="store_true",
|
||||
help="Stop all testing containers without running tests. (default: %(default)s)",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--rebuild",
|
||||
action="store_true",
|
||||
help="Rebuild CVAT images and then start containers. (default: %(default)s)",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--cleanup",
|
||||
action="store_true",
|
||||
help="Delete files that was create by tests without running tests. (default: %(default)s)",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--dumpdb",
|
||||
action="store_true",
|
||||
help="Update data.json without running tests. (default: %(default)s)",
|
||||
)
|
||||
group._addoption(
|
||||
"--keep-data",
|
||||
action="store_true",
|
||||
help="Do not reset volumes and database. (default: %(default)s)",
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--no-services",
|
||||
action="store_true",
|
||||
help=("""Don't start, stop, or seed any containers — assume the user runs
|
||||
the test stack externally"""),
|
||||
)
|
||||
|
||||
group._addoption(
|
||||
"--platform",
|
||||
action="store",
|
||||
default="local",
|
||||
choices=("kube", "local"),
|
||||
help="Platform identifier - 'kube' or 'local'. (default: %(default)s)",
|
||||
)
|
||||
|
||||
|
||||
def _run(command, capture_output=True):
|
||||
_command = command.split() if isinstance(command, str) else command
|
||||
logger.debug(f"Executing a command: {_command}")
|
||||
|
||||
if capture_output:
|
||||
proc = run(_command, check=True, stdout=PIPE) # nosec
|
||||
stdout = proc.stdout.decode()
|
||||
else:
|
||||
proc = run(_command, check=True) # nosec
|
||||
stdout = ""
|
||||
|
||||
if stdout:
|
||||
logger.debug(f"Output (stdout): {stdout}")
|
||||
|
||||
return stdout
|
||||
|
||||
|
||||
def _kube_get_pod_name(label_filter):
|
||||
return _run(f"kubectl get pods -l {label_filter} -o jsonpath={{.items[0].metadata.name}}")
|
||||
|
||||
|
||||
def _kube_get_server_pod_name():
|
||||
return _kube_get_pod_name("component=server")
|
||||
|
||||
|
||||
def _kube_get_db_pod_name():
|
||||
return _kube_get_pod_name("app.kubernetes.io/name=postgresql")
|
||||
|
||||
|
||||
def _kube_get_clichouse_pod_name():
|
||||
return _kube_get_pod_name("app.kubernetes.io/name=clickhouse")
|
||||
|
||||
|
||||
def _kube_get_redis_inmem_pod_name():
|
||||
return _kube_get_pod_name("app.kubernetes.io/name=redis")
|
||||
|
||||
|
||||
def _kube_get_redis_ondisk_pod_name():
|
||||
return _kube_get_pod_name("app.kubernetes.io/name=cvat,tier=kvrocks")
|
||||
|
||||
|
||||
def docker_cp(source, target):
|
||||
_run(f"docker container cp {source} {target}")
|
||||
|
||||
|
||||
def kube_cp(source, target):
|
||||
_run(f"kubectl cp {source} {target}")
|
||||
|
||||
|
||||
def docker_exec(container, command, capture_output=True):
|
||||
return _run(f"docker exec -u root {PREFIX}_{container}_1 {command}", capture_output)
|
||||
|
||||
|
||||
def docker_exec_cvat(command: list[str] | str):
|
||||
base = f"docker exec {PREFIX}_cvat_server_1"
|
||||
_command = f"{base} {command}" if isinstance(command, str) else base.split() + command
|
||||
return _run(_command)
|
||||
|
||||
|
||||
def kube_exec_cvat(command: list[str] | str):
|
||||
pod_name = _kube_get_server_pod_name()
|
||||
base = f"kubectl exec {pod_name} --"
|
||||
_command = f"{base} {command}" if isinstance(command, str) else base.split() + command
|
||||
return _run(_command)
|
||||
|
||||
|
||||
def container_exec_cvat(request: pytest.FixtureRequest, command: list[str] | str):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
return docker_exec_cvat(command)
|
||||
elif platform == "kube":
|
||||
return kube_exec_cvat(command)
|
||||
else:
|
||||
assert False, "unknown platform"
|
||||
|
||||
|
||||
def kube_exec_cvat_db(command):
|
||||
pod_name = _kube_get_db_pod_name()
|
||||
_run(["kubectl", "exec", pod_name, "--"] + command)
|
||||
|
||||
|
||||
def docker_exec_clickhouse_db(command):
|
||||
_run(["docker", "exec", f"{PREFIX}_cvat_clickhouse_1"] + command)
|
||||
|
||||
|
||||
def kube_exec_clickhouse_db(command):
|
||||
pod_name = _kube_get_clichouse_pod_name()
|
||||
_run(["kubectl", "exec", pod_name, "--"] + command)
|
||||
|
||||
|
||||
def docker_exec_redis_inmem(command):
|
||||
return _run(["docker", "exec", f"{PREFIX}_cvat_redis_inmem_1"] + command)
|
||||
|
||||
|
||||
def kube_exec_redis_inmem(command):
|
||||
pod_name = _kube_get_redis_inmem_pod_name()
|
||||
return _run(["kubectl", "exec", pod_name, "--"] + command)
|
||||
|
||||
|
||||
def docker_exec_redis_ondisk(command):
|
||||
_run(["docker", "exec", f"{PREFIX}_cvat_redis_ondisk_1"] + command)
|
||||
|
||||
|
||||
def kube_exec_redis_ondisk(command):
|
||||
pod_name = _kube_get_redis_ondisk_pod_name()
|
||||
_run(["kubectl", "exec", pod_name, "--"] + command)
|
||||
|
||||
|
||||
def docker_restore_db():
|
||||
docker_exec(
|
||||
Container.DB, "psql -U root -d postgres -v from=test_db -v to=cvat -f /tmp/restore.sql"
|
||||
)
|
||||
|
||||
|
||||
def kube_restore_db():
|
||||
kube_exec_cvat_db(
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"PGPASSWORD=cvat_postgresql_postgres psql -U postgres -d postgres -v from=test_db -v to=cvat -f /tmp/restore.sql",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def docker_restore_clickhouse_db():
|
||||
docker_exec_cvat(
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
f'python "{CLICKHOUSE_INIT_SCRIPT}" --clear',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def kube_restore_clickhouse_db():
|
||||
kube_exec_cvat(
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
f'python "{CLICKHOUSE_INIT_SCRIPT}" --clear',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _get_redis_inmem_keys_to_keep():
|
||||
return (
|
||||
"rq:worker:",
|
||||
"rq:workers",
|
||||
"rq:scheduler_instance:",
|
||||
"rq:queues:",
|
||||
"cvat:applied_migrations",
|
||||
"cvat:applied_migration:",
|
||||
)
|
||||
|
||||
|
||||
def docker_restore_redis_inmem():
|
||||
docker_exec_redis_inmem(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'redis-cli -e --scan --pattern "*" |'
|
||||
'grep -v "' + r"\|".join(_get_redis_inmem_keys_to_keep()) + '" |'
|
||||
"xargs -r redis-cli -e del",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def kube_restore_redis_inmem():
|
||||
kube_exec_redis_inmem(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'export REDISCLI_AUTH="${REDIS_PASSWORD}" && '
|
||||
'redis-cli -e --scan --pattern "*" | '
|
||||
'grep -v "' + r"\|".join(_get_redis_inmem_keys_to_keep()) + '" | '
|
||||
"xargs -r redis-cli -e del",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def docker_restore_redis_ondisk():
|
||||
docker_exec_redis_ondisk(["redis-cli", "-e", "-p", "6666", "flushall"])
|
||||
|
||||
|
||||
def kube_restore_redis_ondisk():
|
||||
kube_exec_redis_ondisk(
|
||||
["sh", "-c", 'REDISCLI_AUTH="${CVAT_REDIS_ONDISK_PASSWORD}" redis-cli -e -p 6666 flushall']
|
||||
)
|
||||
|
||||
|
||||
def running_containers():
|
||||
return [cn for cn in _run("docker ps --format {{.Names}}").split("\n") if cn]
|
||||
|
||||
|
||||
def dump_db():
|
||||
if "test_cvat_server_1" not in running_containers():
|
||||
pytest.exit("CVAT is not running")
|
||||
try:
|
||||
run( # nosec
|
||||
[
|
||||
sys.executable,
|
||||
str(Path(__file__).resolve().parents[1] / "utils" / "dump_test_db.py"),
|
||||
"--output",
|
||||
str(CVAT_DB_DIR / "data.json"),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
except CalledProcessError:
|
||||
pytest.exit("Database dump failed.\n")
|
||||
|
||||
|
||||
def create_compose_files(container_name_files):
|
||||
for filename in container_name_files:
|
||||
with (
|
||||
open(filename.with_name(filename.name.replace(".tests", "")), "r") as dcf,
|
||||
open(filename, "w") as ndcf,
|
||||
):
|
||||
dc_config = yaml.safe_load(dcf)
|
||||
|
||||
for service_name, service_config in dc_config["services"].items():
|
||||
service_config.pop("container_name", None)
|
||||
if service_name in (Container.SERVER, Container.WORKER_UTILS):
|
||||
service_env = service_config["environment"]
|
||||
service_env["DJANGO_SETTINGS_MODULE"] = "cvat.settings.testing_rest"
|
||||
|
||||
if service_name in Container.covered():
|
||||
service_env = service_config["environment"]
|
||||
service_env["COVERAGE_PROCESS_START"] = ".coveragerc"
|
||||
service_config["volumes"].append(
|
||||
"./tests/python/.coveragerc:/home/django/.coveragerc"
|
||||
)
|
||||
|
||||
yaml.dump(dc_config, ndcf)
|
||||
|
||||
|
||||
def delete_compose_files(container_name_files):
|
||||
for filename in container_name_files:
|
||||
filename.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def wait_for_services(num_secs: int = 300) -> None:
|
||||
for i in range(num_secs):
|
||||
logger.debug(f"waiting for the server to load ... ({i})")
|
||||
|
||||
try:
|
||||
response = requests.get(get_server_url("api/server/health/", format="json"))
|
||||
|
||||
statuses = response.json()
|
||||
logger.debug(f"server status: \n{statuses}")
|
||||
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
logger.debug("the server has finished loading!")
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"an error occurred during the server status checking: {e}")
|
||||
|
||||
sleep(1)
|
||||
|
||||
raise Exception(
|
||||
f"Failed to reach the server during {num_secs} seconds. Please check the configuration."
|
||||
)
|
||||
|
||||
|
||||
def docker_restore_data_volumes():
|
||||
docker_cp(
|
||||
CVAT_DB_DIR / "cvat_data.tar.bz2",
|
||||
f"{PREFIX}_cvat_server_1:/tmp/cvat_data.tar.bz2",
|
||||
)
|
||||
docker_exec_cvat("tar --strip 3 -xjf /tmp/cvat_data.tar.bz2 -C /home/django/data/")
|
||||
|
||||
|
||||
def kube_restore_data_volumes():
|
||||
pod_name = _kube_get_server_pod_name()
|
||||
kube_cp(
|
||||
CVAT_DB_DIR / "cvat_data.tar.bz2",
|
||||
f"{pod_name}:/tmp/cvat_data.tar.bz2",
|
||||
)
|
||||
kube_exec_cvat("tar --strip 3 -xjf /tmp/cvat_data.tar.bz2 -C /home/django/data/")
|
||||
|
||||
|
||||
def get_server_image_tag():
|
||||
return f"cvat/server:{os.environ.get('CVAT_VERSION', 'dev')}"
|
||||
|
||||
|
||||
def docker_compose(dc_files, cvat_root_dir):
|
||||
return [
|
||||
"docker",
|
||||
"compose",
|
||||
f"--project-name={PREFIX}",
|
||||
# use compatibility mode to have fixed names for containers (with underscores)
|
||||
# https://github.com/docker/compose#about-update-and-backward-compatibility
|
||||
"--compatibility",
|
||||
f"--env-file={cvat_root_dir / 'tests/python/webhook_receiver/.env'}",
|
||||
*(f"--file={f}" for f in dc_files),
|
||||
]
|
||||
|
||||
|
||||
def start_services(dc_files, rebuild=False, cvat_root_dir=CVAT_ROOT_DIR):
|
||||
if any([cn in ["cvat_server", "cvat_db"] for cn in running_containers()]):
|
||||
pytest.exit(
|
||||
"It's looks like you already have running cvat containers. Stop them and try again. "
|
||||
f"List of running containers: {', '.join(running_containers())}"
|
||||
)
|
||||
|
||||
_run(
|
||||
docker_compose(dc_files, cvat_root_dir) + ["up", "-d", *["--build"] * rebuild],
|
||||
capture_output=False,
|
||||
)
|
||||
|
||||
|
||||
def stop_services(dc_files, cvat_root_dir=CVAT_ROOT_DIR):
|
||||
run(docker_compose(dc_files, cvat_root_dir) + ["down", "-v"], capture_output=False)
|
||||
|
||||
|
||||
def session_start(
|
||||
session,
|
||||
cvat_root_dir=CVAT_ROOT_DIR,
|
||||
cvat_db_dir=CVAT_DB_DIR,
|
||||
extra_dc_files=None,
|
||||
waiting_time=300,
|
||||
):
|
||||
stop = session.config.getoption("--stop-services")
|
||||
start = session.config.getoption("--start-services")
|
||||
rebuild = session.config.getoption("--rebuild")
|
||||
keep_data = session.config.getoption("--keep-data")
|
||||
cleanup = session.config.getoption("--cleanup")
|
||||
dumpdb = session.config.getoption("--dumpdb")
|
||||
|
||||
if session.config.getoption("--collect-only"):
|
||||
if any((stop, start, rebuild, cleanup, dumpdb)):
|
||||
raise Exception("""--collect-only is not compatible with any of the other options:
|
||||
--stop-services --start-services --rebuild --cleanup --dumpdb""")
|
||||
return # don't need to start the services to collect tests
|
||||
|
||||
if session.config.getoption("--no-services"):
|
||||
# User manages the stack and the server externally — pytest does
|
||||
# nothing on session start. Per-test restore fixtures still docker-exec
|
||||
# into the user's containers (which must be named test_<service>_1).
|
||||
return
|
||||
|
||||
platform = session.config.getoption("--platform")
|
||||
|
||||
if platform == "kube" and any((stop, start, rebuild, cleanup, dumpdb)):
|
||||
raise Exception("""--platform=kube is not compatible with any of the other options
|
||||
--stop-services --start-services --rebuild --cleanup --dumpdb""")
|
||||
|
||||
if platform == "local":
|
||||
local_start(
|
||||
start,
|
||||
stop,
|
||||
dumpdb,
|
||||
cleanup,
|
||||
rebuild,
|
||||
keep_data,
|
||||
cvat_root_dir,
|
||||
cvat_db_dir,
|
||||
extra_dc_files,
|
||||
waiting_time,
|
||||
)
|
||||
|
||||
elif platform == "kube":
|
||||
kube_start(cvat_db_dir, keep_data)
|
||||
|
||||
|
||||
def local_start(
|
||||
start,
|
||||
stop,
|
||||
dumpdb,
|
||||
cleanup,
|
||||
rebuild,
|
||||
keep_data,
|
||||
cvat_root_dir,
|
||||
cvat_db_dir,
|
||||
extra_dc_files,
|
||||
waiting_time,
|
||||
):
|
||||
if start and stop:
|
||||
raise Exception("--start-services and --stop-services are incompatible")
|
||||
|
||||
if dumpdb:
|
||||
dump_db()
|
||||
pytest.exit("data.json has been updated", returncode=0)
|
||||
|
||||
dc_files = [cvat_root_dir / f for f in DC_FILES]
|
||||
if extra_dc_files is not None:
|
||||
dc_files += extra_dc_files
|
||||
|
||||
container_name_files = [cvat_root_dir / f for f in CONTAINER_NAME_FILES]
|
||||
|
||||
if cleanup:
|
||||
delete_compose_files(container_name_files)
|
||||
pytest.exit("All generated test files have been deleted", returncode=0)
|
||||
|
||||
if not all([f.exists() for f in container_name_files]) or rebuild:
|
||||
delete_compose_files(container_name_files)
|
||||
create_compose_files(container_name_files)
|
||||
|
||||
if stop:
|
||||
stop_services(dc_files, cvat_root_dir)
|
||||
pytest.exit("All testing containers are stopped", returncode=0)
|
||||
|
||||
start_services(dc_files, rebuild, cvat_root_dir)
|
||||
|
||||
if not keep_data:
|
||||
docker_restore_data_volumes()
|
||||
docker_cp(cvat_db_dir / "restore.sql", f"{PREFIX}_cvat_db_1:/tmp/restore.sql")
|
||||
docker_cp(cvat_db_dir / "data.json", f"{PREFIX}_cvat_server_1:/tmp/data.json")
|
||||
|
||||
wait_for_services(waiting_time)
|
||||
|
||||
docker_exec_cvat(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"./manage.py flush --no-input && ./manage.py loaddata_sorted /tmp/data.json",
|
||||
]
|
||||
)
|
||||
docker_exec(
|
||||
Container.DB, "psql -U root -d postgres -v from=cvat -v to=test_db -f /tmp/restore.sql"
|
||||
)
|
||||
|
||||
if start:
|
||||
pytest.exit("All necessary containers have been created and started.", returncode=0)
|
||||
|
||||
|
||||
def kube_start(cvat_db_dir, keep_data):
|
||||
if keep_data:
|
||||
wait_for_services()
|
||||
return
|
||||
|
||||
kube_restore_data_volumes()
|
||||
server_pod_name = _kube_get_server_pod_name()
|
||||
db_pod_name = _kube_get_db_pod_name()
|
||||
kube_cp(cvat_db_dir / "restore.sql", f"{db_pod_name}:/tmp/restore.sql")
|
||||
kube_cp(cvat_db_dir / "data.json", f"{server_pod_name}:/tmp/data.json")
|
||||
|
||||
wait_for_services()
|
||||
|
||||
kube_exec_cvat(
|
||||
["sh", "-c", "./manage.py flush --no-input && ./manage.py loaddata_sorted /tmp/data.json"]
|
||||
)
|
||||
|
||||
kube_exec_cvat_db(
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"PGPASSWORD=cvat_postgresql_postgres psql -U postgres -d postgres -v from=cvat -v to=test_db -f /tmp/restore.sql",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def pytest_sessionstart(session: pytest.Session) -> None:
|
||||
session_start(session)
|
||||
|
||||
|
||||
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
|
||||
session_finish(session)
|
||||
|
||||
|
||||
def session_finish(session):
|
||||
if session.config.getoption("--collect-only"):
|
||||
return
|
||||
|
||||
platform = session.config.getoption("--platform")
|
||||
|
||||
if platform == "local":
|
||||
if os.environ.get("COVERAGE_PROCESS_START"):
|
||||
collect_code_coverage_from_containers()
|
||||
|
||||
|
||||
def collect_code_coverage_from_containers():
|
||||
for container in Container.covered():
|
||||
process_command = "python3"
|
||||
|
||||
# find process with code coverage
|
||||
pid = docker_exec(container, f"pidof {process_command} -o 1")
|
||||
|
||||
# stop process with code coverage
|
||||
docker_exec(container, f"kill -15 {pid}")
|
||||
sleep(3)
|
||||
|
||||
# get code coverage report
|
||||
docker_exec(container, "coverage combine", capture_output=False)
|
||||
docker_exec(container, "coverage json", capture_output=False)
|
||||
docker_cp(
|
||||
f"{PREFIX}_{container}_1:home/django/coverage.json",
|
||||
f"coverage_{container}.json",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_db_per_function(request):
|
||||
# Note that autouse fixtures are executed first within their scope, so be aware of the order
|
||||
# Pre-test DB setups (eg. with class-declared autouse setup() method) may be cleaned.
|
||||
# https://docs.pytest.org/en/stable/reference/fixtures.html#autouse-fixtures-are-executed-first-within-their-scope
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_db()
|
||||
else:
|
||||
kube_restore_db()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_db_per_class(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_db()
|
||||
else:
|
||||
kube_restore_db()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_cvat_data_per_function(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_data_volumes()
|
||||
else:
|
||||
kube_restore_data_volumes()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_cvat_data_per_class(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_data_volumes()
|
||||
else:
|
||||
kube_restore_data_volumes()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_clickhouse_db_per_function(request):
|
||||
# Note that autouse fixtures are executed first within their scope, so be aware of the order
|
||||
# Pre-test DB setups (eg. with class-declared autouse setup() method) may be cleaned.
|
||||
# https://docs.pytest.org/en/stable/reference/fixtures.html#autouse-fixtures-are-executed-first-within-their-scope
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_clickhouse_db()
|
||||
else:
|
||||
kube_restore_clickhouse_db()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_clickhouse_db_per_class(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_clickhouse_db()
|
||||
else:
|
||||
kube_restore_clickhouse_db()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_redis_inmem_per_function(request):
|
||||
# Note that autouse fixtures are executed first within their scope, so be aware of the order
|
||||
# Pre-test DB setups (eg. with class-declared autouse setup() method) may be cleaned.
|
||||
# https://docs.pytest.org/en/stable/reference/fixtures.html#autouse-fixtures-are-executed-first-within-their-scope
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_redis_inmem()
|
||||
else:
|
||||
kube_restore_redis_inmem()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_redis_inmem_per_class(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_redis_inmem()
|
||||
else:
|
||||
kube_restore_redis_inmem()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_redis_ondisk_per_function(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_redis_ondisk()
|
||||
else:
|
||||
kube_restore_redis_ondisk()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_redis_ondisk_per_class(request):
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_redis_ondisk()
|
||||
else:
|
||||
kube_restore_redis_ondisk()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def restore_redis_ondisk_after_class(request):
|
||||
yield
|
||||
|
||||
platform = request.config.getoption("--platform")
|
||||
if platform == "local":
|
||||
docker_restore_redis_ondisk()
|
||||
else:
|
||||
kube_restore_redis_ondisk()
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from functools import partial
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
import pytest
|
||||
|
||||
import shared.utils.s3 as s3
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_uploaded_s3_file(request: pytest.FixtureRequest):
|
||||
def upload_file(s3_client: s3.S3Client, *, path: PurePosixPath | str, data: bytes):
|
||||
s3_client.create_file(
|
||||
data=data,
|
||||
filename=str(path),
|
||||
)
|
||||
request.addfinalizer(
|
||||
partial(
|
||||
s3_client.remove_file,
|
||||
filename=str(path),
|
||||
)
|
||||
)
|
||||
|
||||
return upload_file
|
||||
@@ -0,0 +1,39 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import io
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_stdout(capsys):
|
||||
class IoProxy(io.IOBase):
|
||||
def __init__(self, capsys):
|
||||
self.capsys = capsys
|
||||
|
||||
def getvalue(self) -> str:
|
||||
capture = self.capsys.readouterr()
|
||||
return capture.out
|
||||
|
||||
yield IoProxy(capsys)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_logger():
|
||||
logger_stream = io.StringIO()
|
||||
logger = logging.Logger("test", level=logging.INFO)
|
||||
logger.propagate = False
|
||||
logger.addHandler(logging.StreamHandler(logger_stream))
|
||||
yield logger, logger_stream
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_test_name(request: pytest.FixtureRequest):
|
||||
name = request.node.name
|
||||
if request.fixturename and request.fixturename != "fxt_test_name":
|
||||
name += f"[{request.fixturename}]"
|
||||
|
||||
yield name
|
||||
Reference in New Issue
Block a user