diff --git a/CHANGELOG.md b/CHANGELOG.md index b6cebc54..cfe6c3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Analytics not accessible when https is enabled () - Dataset import in an organization (, ) - Updated minimist npm package to v1.2.6 () +- Request Status Code 500 "StopIteration" when exporting dataset () ### Security - TDB diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 7cf11a32..c4b52e3d 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -184,7 +184,7 @@ class ImageListReader(IMediaReader): source_path, step=self._step, start=self._start, - stop=self._stop, + stop=self._stop - 1, dimension=self._dimension, sorting_method=self._sorting_method ) diff --git a/tests/rest_api/requirements.txt b/tests/rest_api/requirements.txt index 7a114215..34d3f348 100644 --- a/tests/rest_api/requirements.txt +++ b/tests/rest_api/requirements.txt @@ -1,3 +1,4 @@ pytest==6.2.5 requests==2.26.0 deepdiff==5.6.0 +Pillow==9.0.1 diff --git a/tests/rest_api/test_tasks.py b/tests/rest_api/test_tasks.py index 33ec63ee..9a082721 100644 --- a/tests/rest_api/test_tasks.py +++ b/tests/rest_api/test_tasks.py @@ -3,10 +3,34 @@ # SPDX-License-Identifier: MIT from http import HTTPStatus -from deepdiff import DeepDiff +from io import BytesIO +from time import sleep + import pytest +from deepdiff import DeepDiff +from PIL import Image + +from .utils.config import (get_method, patch_method, post_files_method, + post_method) + + +def generate_image_file(filename, size=(50, 50)): + f = BytesIO() + image = Image.new('RGB', size=size) + image.save(f, 'jpeg') + f.name = filename + f.seek(0) + + return f + +def generate_image_files(count): + images = [] + for i in range(count): + image = generate_image_file(f'{i}.jpeg') + images.append(image) + + return images -from .utils.config import get_method, post_method, patch_method class TestGetTasks: def _test_task_list_200(self, user, project_id, data, exclude_paths = '', **kwargs): @@ -103,6 +127,31 @@ class TestPostTasks: response = post_method(user, '/tasks', spec, **kwargs) assert response.status_code == HTTPStatus.FORBIDDEN + @staticmethod + def _wait_until_task_is_created(username, task_id): + url = f'tasks/{task_id}/status' + + while True: + response = get_method(username, url) + response_json = response.json() + if response_json['state'] == 'Finished' or response_json['state'] == 'Failed': + return response + sleep(1) + + def _test_create_task_with_images(self, username, spec, data, files): + response = post_method(username, '/tasks', spec) + assert response.status_code == HTTPStatus.CREATED + task_id = response.json()['id'] + + response = post_files_method(username, f'/tasks/{task_id}/data', data, files) + assert response.status_code == HTTPStatus.ACCEPTED + + response = self._wait_until_task_is_created(username, task_id) + response_json = response.json() + assert response_json['state'] == 'Finished' + + return task_id + def _test_users_to_create_task_in_project(self, project_id, users, is_staff, is_allow, is_project_staff, **kwargs): if is_staff: users = [user for user in users if is_project_staff(user['id'], project_id) ] @@ -144,6 +193,32 @@ class TestPostTasks: users = find_users(org=org['id'], role=role) self._test_users_to_create_task_in_project(project_id, users, is_staff, is_allow, is_project_staff, org=org['slug']) + def test_can_create_task_with_defined_start_and_stop_frames(self): + username = 'admin1' + task_spec = { + 'name': f'test {username} to create a task with defined start and stop frames', + "labels": [{ + "name": "car", + "color": "#ff00ff" + }], + } + + task_data = { + 'image_quality': 75, + 'start_frame': 2, + 'stop_frame': 5 + } + task_files = { + f'client_files[{i}]': image for i, image in enumerate(generate_image_files(7)) + } + + task_id = self._test_create_task_with_images(username, task_spec, task_data, task_files) + + # check task size + response = get_method(username, f'tasks/{task_id}') + response_json = response.json() + assert response_json['size'] == 4 + class TestGetData: _USERNAME = 'user1' @@ -214,3 +289,56 @@ class TestPatchTaskAnnotations: org_id=org, action='update') self._test_check_respone(is_allow, response, data) + +@pytest.mark.usefixtures("restore") +class TestExportDatasetTask: + @staticmethod + def _wait_until_task_is_created(username, task_id): + url = f'tasks/{task_id}/status' + + while True: + response = get_method(username, url) + response_json = response.json() + if response_json['state'] == 'Finished' or response_json['state'] == 'Failed': + return response + sleep(1) + + def _test_create_task(self, username, spec, data, files): + response = post_method(username, '/tasks', spec) + assert response.status_code == HTTPStatus.CREATED + task_id = response.json()['id'] + + response = post_files_method(username, f'/tasks/{task_id}/data', data, files) + assert response.status_code == HTTPStatus.ACCEPTED + + response = self._wait_until_task_is_created(username, task_id) + response_json = response.json() + assert response_json['state'] == 'Finished' + + return task_id + + def test_can_create_task_with_defined_start_and_stop_frames(self): + username = 'admin1' + task_spec = { + 'name': f'test {username} to create a task with defined start and stop frames', + "labels": [{ + "name": "car", + "color": "#ff00ff" + }], + } + + task_data = { + 'image_quality': 75, + 'start_frame': 2, + 'stop_frame': 5 + } + task_files = { + f'client_files[{i}]': image for i, image in enumerate(generate_image_files(7)) + } + + task_id = self._test_create_task(username, task_spec, task_data, task_files) + + # check task size + response = get_method(username, f'tasks/{task_id}') + response_json = response.json() + assert response_json['size'] == 4 diff --git a/tests/rest_api/utils/config.py b/tests/rest_api/utils/config.py index 2bcaf603..7183a7b8 100644 --- a/tests/rest_api/utils/config.py +++ b/tests/rest_api/utils/config.py @@ -36,8 +36,8 @@ def patch_method(username, endpoint, data, **kwargs): def post_method(username, endpoint, data, **kwargs): return requests.post(get_api_url(endpoint, **kwargs), json=data, auth=(username, USER_PASS)) -def post_files_method(username, endpoint, data, **kwargs): - return requests.post(get_api_url(endpoint, **kwargs), files=data, auth=(username, USER_PASS)) +def post_files_method(username, endpoint, data, files, **kwargs): + return requests.post(get_api_url(endpoint, **kwargs), data=data, files=files, auth=(username, USER_PASS)) def server_get(username, endpoint, **kwargs): return requests.get(get_server_url(endpoint, **kwargs), auth=(username, USER_PASS))