Refactor CI (#26)
parent
4dc01b0441
commit
2e161b0e9f
@ -0,0 +1,94 @@
|
||||
---
|
||||
title: 'Running tests'
|
||||
linkTitle: 'Running tests'
|
||||
weight: 11
|
||||
description: 'Instructions on how to run all existence tests.'
|
||||
---
|
||||
|
||||
# E2E tests
|
||||
|
||||
**Initial steps**:
|
||||
1. Run CVAT instance:
|
||||
```
|
||||
docker-compose \
|
||||
-f docker-compose.yml \
|
||||
-f docker-compose.dev.yml \
|
||||
-f components/serverless/docker-compose.serverless.yml \
|
||||
-f tests/docker-compose.file_share.yml up -d
|
||||
```
|
||||
1. Add test user in CVAT:
|
||||
```
|
||||
docker exec -i cvat \
|
||||
/bin/bash -c \
|
||||
"echo \"from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@localhost.company', '12qwaszx')\" | python3 ~/manage.py shell"
|
||||
```
|
||||
1. Install npm dependencies:
|
||||
```
|
||||
cd tests
|
||||
npm ci
|
||||
```
|
||||
|
||||
**Running tests**
|
||||
|
||||
```
|
||||
npm run cypress:run:chrome
|
||||
npm run cypress:run:chrome:canvas3d
|
||||
```
|
||||
|
||||
# REST API tests
|
||||
|
||||
**Initial steps**
|
||||
1. Install all necessary requirements before running REST API tests:
|
||||
```
|
||||
pip install -r ./tests/rest_api/requirements.txt
|
||||
```
|
||||
|
||||
**Running tests**
|
||||
|
||||
Run all REST API tests:
|
||||
|
||||
```
|
||||
pytest ./tests/rest_api
|
||||
```
|
||||
|
||||
This command will automatically start all necessary docker containers.
|
||||
|
||||
If you want to start/stop these containers without running tests
|
||||
use special options for it:
|
||||
|
||||
```
|
||||
pytest ./tests/rest_api --start-services
|
||||
pytest ./tests/rest_api --stop-services
|
||||
```
|
||||
|
||||
If you need to rebuild your CVAT images add `--rebuild` option:
|
||||
```
|
||||
pytest ./tests/rest_api --rebuild
|
||||
```
|
||||
|
||||
# Unit tests
|
||||
|
||||
**Initial steps**
|
||||
1. Install necessary Python dependencies:
|
||||
```
|
||||
pip install -r cvat/requirements/testing.txt
|
||||
```
|
||||
1. Install npm dependencies:
|
||||
```
|
||||
npm ci
|
||||
```
|
||||
1. Run CVAT instance
|
||||
```
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
**Running tests**
|
||||
1. Python tests
|
||||
```
|
||||
python manage.py test --settings cvat.settings.testing cvat/apps utils/cli
|
||||
```
|
||||
1. JS tests
|
||||
```
|
||||
cd cvat-core
|
||||
npm run test
|
||||
```
|
||||
@ -0,0 +1,37 @@
|
||||
{
|
||||
"video": false,
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"viewportWidth": 1300,
|
||||
"viewportHeight": 960,
|
||||
"defaultCommandTimeout": 25000,
|
||||
"downloadsFolder": "cypress/fixtures",
|
||||
"env": {
|
||||
"user": "admin",
|
||||
"email": "admin@localhost.company",
|
||||
"password": "12qwaszx",
|
||||
"coverage": false
|
||||
},
|
||||
"testFiles": [
|
||||
"actions_objects2/case_108_rotated_bounding_boxes.js",
|
||||
"actions_objects2/case_10_polygon_shape_track_label_points.js",
|
||||
"actions_objects2/case_115_ellipse_shape_track_label.js",
|
||||
"actions_objects2/case_11_polylines_shape_track_label_points.js",
|
||||
"actions_objects2/case_12_points_shape_track_label.js",
|
||||
"actions_objects2/case_13_merge_split_features.js",
|
||||
"actions_objects2/case_14_appearance_features.js",
|
||||
"actions_objects2/case_15_group_features.js",
|
||||
"actions_objects2/case_16_z_order_features.js",
|
||||
"actions_objects2/case_17_lock_hide_features.js",
|
||||
"issues_prs/issue_2418_object_tag_same_labels.js",
|
||||
"issues_prs/issue_2485_navigation_empty_frames.js",
|
||||
"issues_prs/issue_2486_not_edit_object_aam.js",
|
||||
"issues_prs/issue_2487_extra_instances_canvas_grouping.js",
|
||||
"issues_prs/issue_2661_displaying_attached_files_when_creating_task.js",
|
||||
"issues_prs/issue_2753_call_HOC_component_each_render.js",
|
||||
"issues_prs/issue_2807_polyline_editing.js",
|
||||
"issues_prs/issue_2992_crop_polygon_properly.js",
|
||||
"issues_prs/pr_1370_check_UI_fail_with_object_dragging_and_go_next_frame.js",
|
||||
"issues_prs/pr_2203_error_cannot_read_property_at_saving_job.js",
|
||||
"remove_users_tasks_projects_organizations.js"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"video": false,
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"viewportWidth": 1300,
|
||||
"viewportHeight": 960,
|
||||
"defaultCommandTimeout": 25000,
|
||||
"downloadsFolder": "cypress/fixtures",
|
||||
"env": {
|
||||
"user": "admin",
|
||||
"email": "admin@localhost.company",
|
||||
"password": "12qwaszx",
|
||||
"coverage": false
|
||||
},
|
||||
"testFiles": [
|
||||
"actions_projects_models/case_104_project_export_3d.js",
|
||||
"canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js",
|
||||
"canvas3d_functionality_2/case_62_canvas3d_functionality_views_resize.js",
|
||||
"canvas3d_functionality_2/case_63_canvas3d_functionality_control_button_mouse_interaction.js",
|
||||
"canvas3d_functionality_2/case_64_canvas3d_functionality_cuboid.js",
|
||||
"remove_users_tasks_projects_organizations.js"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@ -1,306 +1,2 @@
|
||||
# Copyright (C) 2021 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
from subprocess import run, CalledProcessError
|
||||
import pytest
|
||||
import json
|
||||
import os.path as osp
|
||||
from .utils.config import ASSETS_DIR
|
||||
|
||||
CVAT_DB_DIR = osp.join(ASSETS_DIR, 'cvat_db')
|
||||
|
||||
def _run(command):
|
||||
try:
|
||||
run(command.split(), check=True) #nosec
|
||||
except CalledProcessError:
|
||||
pytest.exit(f'Command failed: {command}. Add `-s` option to see more details')
|
||||
|
||||
def restore_data_volume():
|
||||
_run(f"docker container cp {osp.join(ASSETS_DIR, 'cvat_db', 'cvat_data.tar.bz2')} cvat:cvat_data.tar.bz2")
|
||||
_run(f"docker exec -i cvat tar --strip 3 -xjf /cvat_data.tar.bz2 -C /home/django/data/")
|
||||
|
||||
def create_test_db():
|
||||
_run(f"docker container cp {osp.join(CVAT_DB_DIR, 'restore.sql')} cvat_db:restore.sql")
|
||||
_run(f"docker container cp {osp.join(CVAT_DB_DIR, 'data.json')} cvat:data.json")
|
||||
_run('docker exec cvat python manage.py loaddata /data.json')
|
||||
_run('docker exec cvat_db psql -U root -d postgres -v from=cvat -v to=test_db -f restore.sql')
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def init_test_db():
|
||||
restore_data_volume()
|
||||
create_test_db()
|
||||
|
||||
yield
|
||||
|
||||
_run('docker exec cvat_db psql -U root -d postgres -v from=test_db -v to=cvat -f restore.sql')
|
||||
_run('docker exec cvat_db dropdb test_db')
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def restore():
|
||||
_run('docker exec cvat_db psql -U root -d postgres -v from=test_db -v to=cvat -f restore.sql')
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def restore_cvat_data():
|
||||
restore_data_volume()
|
||||
|
||||
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='module')
|
||||
def users():
|
||||
with open(osp.join(ASSETS_DIR, 'users.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def organizations():
|
||||
with open(osp.join(ASSETS_DIR, 'organizations.json')) as f:
|
||||
return Container(json.load(f))
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def memberships():
|
||||
with open(osp.join(ASSETS_DIR, 'memberships.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def tasks():
|
||||
with open(osp.join(ASSETS_DIR, 'tasks.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def projects():
|
||||
with open(osp.join(ASSETS_DIR, 'projects.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def jobs():
|
||||
with open(osp.join(ASSETS_DIR, 'jobs.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def invitations():
|
||||
with open(osp.join(ASSETS_DIR, 'invitations.json')) as f:
|
||||
return Container(json.load(f)['results'], key='key')
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def annotations():
|
||||
with open(osp.join(ASSETS_DIR, 'annotations.json')) as f:
|
||||
return json.load(f)
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def cloud_storages():
|
||||
with open(osp.join(ASSETS_DIR, 'cloudstorages.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def issues():
|
||||
with open(osp.join(ASSETS_DIR, 'issues.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def users_by_name(users):
|
||||
return {user['username']: user for user in users}
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def jobs_by_org(tasks, jobs):
|
||||
data = {}
|
||||
for job in jobs:
|
||||
data.setdefault(tasks[job['task_id']]['organization'], []).append(job)
|
||||
data[''] = data.pop(None, [])
|
||||
return data
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
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='module')
|
||||
def find_users(test_db):
|
||||
def find(**kwargs):
|
||||
assert len(kwargs) > 0
|
||||
assert any(kwargs.values())
|
||||
|
||||
data = test_db
|
||||
kwargs = dict(filter(lambda a: a[1] is not None, kwargs.items()))
|
||||
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='module')
|
||||
def test_db(users, users_by_name, memberships):
|
||||
data = []
|
||||
fields = ['username', 'id', 'privilege', 'role', 'org', 'membership_id']
|
||||
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)
|
||||
|
||||
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'])
|
||||
|
||||
return data
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
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='module')
|
||||
def is_org_member(memberships):
|
||||
def check(user_id, org_id):
|
||||
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 and m['organization'] == org_id)
|
||||
return check
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def find_job_staff_user(is_job_staff):
|
||||
def find(jobs, users, is_staff):
|
||||
for job in jobs:
|
||||
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='module')
|
||||
def find_task_staff_user(is_task_staff):
|
||||
def find(tasks, users, is_staff):
|
||||
for task in tasks:
|
||||
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='module')
|
||||
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='module')
|
||||
def filter_jobs_with_shapes(annotations):
|
||||
def find(jobs):
|
||||
return list(filter(lambda j: annotations['job'][str(j['id'])]['shapes'], jobs))
|
||||
return find
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def filter_tasks_with_shapes(annotations):
|
||||
def find(tasks):
|
||||
return list(filter(lambda t: annotations['task'][str(t['id'])]['shapes'], tasks))
|
||||
return find
|
||||
from .fixtures.init import *
|
||||
from .fixtures.data import *
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# Copyright (C) 2021 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,276 @@
|
||||
# Copyright (C) 2021 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import os.path as osp
|
||||
from rest_api.utils.config import ASSETS_DIR
|
||||
|
||||
CVAT_DB_DIR = osp.join(ASSETS_DIR, 'cvat_db')
|
||||
|
||||
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(osp.join(ASSETS_DIR, 'users.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def organizations():
|
||||
with open(osp.join(ASSETS_DIR, 'organizations.json')) as f:
|
||||
return Container(json.load(f))
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def memberships():
|
||||
with open(osp.join(ASSETS_DIR, 'memberships.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def tasks():
|
||||
with open(osp.join(ASSETS_DIR, 'tasks.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def projects():
|
||||
with open(osp.join(ASSETS_DIR, 'projects.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def jobs():
|
||||
with open(osp.join(ASSETS_DIR, 'jobs.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def invitations():
|
||||
with open(osp.join(ASSETS_DIR, 'invitations.json')) as f:
|
||||
return Container(json.load(f)['results'], key='key')
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def annotations():
|
||||
with open(osp.join(ASSETS_DIR, 'annotations.json')) as f:
|
||||
return json.load(f)
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def cloud_storages():
|
||||
with open(osp.join(ASSETS_DIR, 'cloudstorages.json')) as f:
|
||||
return Container(json.load(f)['results'])
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def issues():
|
||||
with open(osp.join(ASSETS_DIR, 'issues.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):
|
||||
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 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
|
||||
assert any(kwargs.values())
|
||||
|
||||
data = test_db
|
||||
kwargs = dict(filter(lambda a: a[1] is not None, kwargs.items()))
|
||||
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']
|
||||
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)
|
||||
|
||||
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'])
|
||||
|
||||
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'] != None
|
||||
and m['organization'] == org_id)
|
||||
return find
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def is_org_member(memberships):
|
||||
def check(user_id, org_id):
|
||||
if org_id in ['', None]:
|
||||
return True
|
||||
else:
|
||||
return user_id in set(m['user']['id'] for m in memberships
|
||||
if m['user'] != None and m['organization'] == org_id)
|
||||
return check
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def find_job_staff_user(is_job_staff):
|
||||
def find(jobs, users, is_staff):
|
||||
for job in jobs:
|
||||
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):
|
||||
for task in tasks:
|
||||
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_with_shapes(annotations):
|
||||
def find(jobs):
|
||||
return list(filter(lambda j: annotations['job'][str(j['id'])]['shapes'], jobs))
|
||||
return find
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def filter_tasks_with_shapes(annotations):
|
||||
def find(tasks):
|
||||
return list(filter(lambda t: annotations['task'][str(t['id'])]['shapes'], tasks))
|
||||
return find
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def tasks_with_shapes(tasks, filter_tasks_with_shapes):
|
||||
return filter_tasks_with_shapes(tasks)
|
||||
@ -0,0 +1,177 @@
|
||||
import os.path as osp
|
||||
import re
|
||||
from http import HTTPStatus
|
||||
from subprocess import PIPE, CalledProcessError, run
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import requests
|
||||
from rest_api.utils.config import ASSETS_DIR, get_api_url
|
||||
|
||||
CVAT_ROOT_DIR = __file__[: __file__.rfind(osp.join("tests", ""))]
|
||||
CVAT_DB_DIR = osp.join(ASSETS_DIR, "cvat_db")
|
||||
PREFIX = "test"
|
||||
|
||||
CONTAINER_NAME_FILES = [
|
||||
osp.join(CVAT_ROOT_DIR, dc_file)
|
||||
for dc_file in (
|
||||
"components/analytics/docker-compose.analytics.tests.yml",
|
||||
"docker-compose.tests.yml",
|
||||
)
|
||||
]
|
||||
|
||||
DC_FILES = [
|
||||
osp.join(CVAT_ROOT_DIR, dc_file)
|
||||
for dc_file in ("docker-compose.dev.yml", "tests/rest_api/docker-compose.minio.yml")
|
||||
] + CONTAINER_NAME_FILES
|
||||
|
||||
|
||||
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)",
|
||||
)
|
||||
|
||||
|
||||
def _run(command):
|
||||
try:
|
||||
proc = run(command.split(), check=True, stdout=PIPE, stderr=PIPE) # nosec
|
||||
return proc.stdout.decode(), proc.stderr.decode()
|
||||
except CalledProcessError as exc:
|
||||
pytest.exit(
|
||||
f"Command failed: {command}.\n"
|
||||
f"Error message: {exc.stderr.decode()}.\n"
|
||||
f"Add `-s` option to see more details"
|
||||
)
|
||||
|
||||
|
||||
def docker_cp(source, target):
|
||||
_run(f"docker container cp {source} {target}")
|
||||
|
||||
|
||||
def exec_cvat(command):
|
||||
_run(f"docker exec {PREFIX}_cvat_1 {command}")
|
||||
|
||||
|
||||
def exec_cvat_db(command):
|
||||
_run(f"docker exec {PREFIX}_cvat_db_1 {command}")
|
||||
|
||||
|
||||
def restore_db():
|
||||
exec_cvat_db("psql -U root -d postgres -v from=test_db -v to=cvat -f /tmp/restore.sql")
|
||||
|
||||
|
||||
def create_compose_files():
|
||||
for filename in CONTAINER_NAME_FILES:
|
||||
with open(filename.replace(".tests.yml", ".yml"), "r") as dcf, open(filename, "w") as ndcf:
|
||||
ndcf.writelines(
|
||||
[line for line in dcf.readlines() if not re.match("^.+container_name.+$", line)]
|
||||
)
|
||||
|
||||
|
||||
def delete_compose_files():
|
||||
for filename in CONTAINER_NAME_FILES:
|
||||
if osp.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
|
||||
def wait_for_server():
|
||||
while True:
|
||||
response = requests.get(get_api_url("users/self"))
|
||||
if response.status_code == HTTPStatus.UNAUTHORIZED:
|
||||
break
|
||||
|
||||
def restore_data_volumes():
|
||||
docker_cp(osp.join(CVAT_DB_DIR, "cvat_data.tar.bz2"), f"{PREFIX}_cvat_1:/tmp/cvat_data.tar.bz2")
|
||||
exec_cvat("tar --strip 3 -xjf /tmp/cvat_data.tar.bz2 -C /home/django/data/")
|
||||
|
||||
def start_services(rebuild=False):
|
||||
running_containers = [cn for cn in _run("docker ps --format {{.Names}}")[0].split("\n") if cn]
|
||||
|
||||
if any([cn in ["cvat", "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)}"
|
||||
)
|
||||
|
||||
out = _run(f"docker-compose -p {PREFIX} -f {' -f '.join(DC_FILES)} up -d " + "--build" * rebuild)[1]
|
||||
|
||||
restore_data_volumes()
|
||||
docker_cp(osp.join(CVAT_DB_DIR, "restore.sql"), f"{PREFIX}_cvat_db_1:/tmp/restore.sql")
|
||||
docker_cp(osp.join(CVAT_DB_DIR, "data.json"), f"{PREFIX}_cvat_1:/tmp/data.json")
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def services(request):
|
||||
stop = request.config.getoption("--stop-services")
|
||||
start = request.config.getoption("--start-services")
|
||||
rebuild = request.config.getoption("--rebuild")
|
||||
cleanup = request.config.getoption("--cleanup")
|
||||
|
||||
if start and stop:
|
||||
raise Exception("--start-services and --stop-services are incompatible")
|
||||
|
||||
if cleanup:
|
||||
delete_compose_files()
|
||||
pytest.exit(f"All generated test files have been deleted", returncode=0)
|
||||
|
||||
if not all([osp.exists(f) for f in CONTAINER_NAME_FILES]):
|
||||
create_compose_files()
|
||||
|
||||
if stop:
|
||||
out = _run(f"docker-compose -p {PREFIX} -f {' -f '.join(DC_FILES)} down -v")[1]
|
||||
out = set(l.split()[1] for l in out.split("\n") if "done" in l.split())
|
||||
pytest.exit(f"All testing containers are stopped: {', '.join(out)}", returncode=0)
|
||||
|
||||
started_services = start_services(rebuild)
|
||||
wait_for_server()
|
||||
|
||||
exec_cvat("python manage.py loaddata /tmp/data.json")
|
||||
exec_cvat_db("psql -U root -d postgres -v from=cvat -v to=test_db -f /tmp/restore.sql")
|
||||
|
||||
if start:
|
||||
pytest.exit(
|
||||
f"All necessary containers have been created and started: {started_services}",
|
||||
returncode=0,
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
restore_db()
|
||||
exec_cvat_db("dropdb test_db")
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def changedb():
|
||||
restore_db()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def dontchangedb():
|
||||
restore_db()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def restore_cvat_data():
|
||||
restore_data_volumes()
|
||||
@ -0,0 +1,3 @@
|
||||
# Copyright (C) 2021 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
Loading…
Reference in New Issue