You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
6.8 KiB
Python
223 lines
6.8 KiB
Python
# Copyright (C) 2022 CVAT.ai Corporation
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import packaging.version as pv
|
|
import pytest
|
|
from cvat_cli.cli import CLI
|
|
from cvat_sdk import Client, make_client
|
|
from cvat_sdk.api_client import exceptions
|
|
from cvat_sdk.core.proxies.tasks import ResourceType, Task
|
|
from PIL import Image
|
|
|
|
from sdk.util import generate_coco_json
|
|
from shared.utils.config import BASE_URL, USER_PASS
|
|
from shared.utils.helpers import generate_image_file
|
|
|
|
from .util import generate_images, run_cli
|
|
|
|
|
|
class TestCLI:
|
|
@pytest.fixture(autouse=True)
|
|
def setup(
|
|
self,
|
|
changedb, # force fixture call order to allow DB setup
|
|
fxt_stdout: io.StringIO,
|
|
tmp_path: Path,
|
|
admin_user: str,
|
|
):
|
|
self.tmp_path = tmp_path
|
|
self.stdout = fxt_stdout
|
|
self.host, self.port = BASE_URL.rsplit(":", maxsplit=1)
|
|
self.user = admin_user
|
|
self.password = USER_PASS
|
|
self.client = make_client(
|
|
host=self.host, port=self.port, credentials=(self.user, self.password)
|
|
)
|
|
self.client.config.status_check_period = 0.01
|
|
|
|
yield
|
|
|
|
@pytest.fixture
|
|
def fxt_image_file(self):
|
|
img_path = self.tmp_path / "img_0.png"
|
|
with img_path.open("wb") as f:
|
|
f.write(generate_image_file(filename=str(img_path)).getvalue())
|
|
|
|
return img_path
|
|
|
|
@pytest.fixture
|
|
def fxt_coco_file(self, fxt_image_file: Path):
|
|
img_filename = fxt_image_file
|
|
img_size = Image.open(img_filename).size
|
|
ann_filename = self.tmp_path / "coco.json"
|
|
generate_coco_json(ann_filename, img_info=(img_filename, *img_size))
|
|
|
|
yield ann_filename
|
|
|
|
@pytest.fixture
|
|
def fxt_backup_file(self, fxt_new_task: Task, fxt_coco_file: str):
|
|
backup_path = self.tmp_path / "backup.zip"
|
|
|
|
fxt_new_task.import_annotations("COCO 1.0", filename=fxt_coco_file)
|
|
fxt_new_task.download_backup(str(backup_path))
|
|
|
|
yield backup_path
|
|
|
|
@pytest.fixture
|
|
def fxt_new_task(self):
|
|
files = generate_images(str(self.tmp_path), 5)
|
|
|
|
task = self.client.tasks.create_from_data(
|
|
spec={
|
|
"name": "test_task",
|
|
"labels": [{"name": "car"}, {"name": "person"}],
|
|
},
|
|
resource_type=ResourceType.LOCAL,
|
|
resources=files,
|
|
)
|
|
|
|
return task
|
|
|
|
def run_cli(self, cmd: str, *args: str, expected_code: int = 0) -> str:
|
|
run_cli(
|
|
self,
|
|
"--auth",
|
|
f"{self.user}:{self.password}",
|
|
"--server-host",
|
|
self.host,
|
|
"--server-port",
|
|
self.port,
|
|
cmd,
|
|
*args,
|
|
expected_code=expected_code,
|
|
)
|
|
return self.stdout.getvalue()
|
|
|
|
def test_can_create_task_from_local_images(self):
|
|
files = generate_images(str(self.tmp_path), 5)
|
|
|
|
stdout = self.run_cli(
|
|
"create",
|
|
"test_task",
|
|
ResourceType.LOCAL.name,
|
|
*files,
|
|
"--labels",
|
|
json.dumps([{"name": "car"}, {"name": "person"}]),
|
|
"--completion_verification_period",
|
|
"0.01",
|
|
)
|
|
|
|
task_id = int(stdout.split()[-1])
|
|
assert self.client.tasks.retrieve(task_id).size == 5
|
|
|
|
def test_can_list_tasks_in_simple_format(self, fxt_new_task: Task):
|
|
output = self.run_cli("ls")
|
|
|
|
results = output.split("\n")
|
|
assert any(str(fxt_new_task.id) in r for r in results)
|
|
|
|
def test_can_list_tasks_in_json_format(self, fxt_new_task: Task):
|
|
output = self.run_cli("ls", "--json")
|
|
|
|
results = json.loads(output)
|
|
assert any(r["id"] == fxt_new_task.id for r in results)
|
|
|
|
def test_can_delete_task(self, fxt_new_task: Task):
|
|
self.run_cli("delete", str(fxt_new_task.id))
|
|
|
|
with pytest.raises(exceptions.NotFoundException):
|
|
fxt_new_task.fetch()
|
|
|
|
def test_can_download_task_annotations(self, fxt_new_task: Task):
|
|
filename = self.tmp_path / "task_{fxt_new_task.id}-cvat.zip"
|
|
self.run_cli(
|
|
"dump",
|
|
str(fxt_new_task.id),
|
|
str(filename),
|
|
"--format",
|
|
"CVAT for images 1.1",
|
|
"--with-images",
|
|
"no",
|
|
"--completion_verification_period",
|
|
"0.01",
|
|
)
|
|
|
|
assert 0 < filename.stat().st_size
|
|
|
|
def test_can_download_task_backup(self, fxt_new_task: Task):
|
|
filename = self.tmp_path / "task_{fxt_new_task.id}-cvat.zip"
|
|
self.run_cli(
|
|
"export",
|
|
str(fxt_new_task.id),
|
|
str(filename),
|
|
"--completion_verification_period",
|
|
"0.01",
|
|
)
|
|
|
|
assert 0 < filename.stat().st_size
|
|
|
|
@pytest.mark.parametrize("quality", ("compressed", "original"))
|
|
def test_can_download_task_frames(self, fxt_new_task: Task, quality: str):
|
|
out_dir = str(self.tmp_path / "downloads")
|
|
self.run_cli(
|
|
"frames",
|
|
str(fxt_new_task.id),
|
|
"0",
|
|
"1",
|
|
"--outdir",
|
|
out_dir,
|
|
"--quality",
|
|
quality,
|
|
)
|
|
|
|
assert set(os.listdir(out_dir)) == {
|
|
"task_{}_frame_{:06d}.jpg".format(fxt_new_task.id, i) for i in range(2)
|
|
}
|
|
|
|
def test_can_upload_annotations(self, fxt_new_task: Task, fxt_coco_file: Path):
|
|
self.run_cli("upload", str(fxt_new_task.id), str(fxt_coco_file), "--format", "COCO 1.0")
|
|
|
|
def test_can_create_from_backup(self, fxt_new_task: Task, fxt_backup_file: Path):
|
|
stdout = self.run_cli("import", str(fxt_backup_file))
|
|
|
|
task_id = int(stdout.split()[-1])
|
|
assert task_id
|
|
assert task_id != fxt_new_task.id
|
|
assert self.client.tasks.retrieve(task_id).size == fxt_new_task.size
|
|
|
|
def test_can_warn_on_mismatching_server_version(self, monkeypatch, caplog):
|
|
def mocked_version(_):
|
|
return pv.Version("0")
|
|
|
|
# We don't actually run a separate process in the tests here, so it works
|
|
monkeypatch.setattr(Client, "get_server_version", mocked_version)
|
|
|
|
self.run_cli("ls")
|
|
|
|
assert "Server version '0' is not compatible with SDK version" in caplog.text
|
|
|
|
@pytest.mark.parametrize("verify", [True, False])
|
|
def test_can_control_ssl_verification_with_arg(self, monkeypatch, verify: bool):
|
|
# TODO: Very hacky implementation, improve it, if possible
|
|
class MyException(Exception):
|
|
pass
|
|
|
|
normal_init = CLI.__init__
|
|
|
|
def my_init(self, *args, **kwargs):
|
|
normal_init(self, *args, **kwargs)
|
|
raise MyException(self.client.api_client.configuration.verify_ssl)
|
|
|
|
monkeypatch.setattr(CLI, "__init__", my_init)
|
|
|
|
with pytest.raises(MyException) as capture:
|
|
self.run_cli(*(["--insecure"] if not verify else []), "ls")
|
|
|
|
assert capture.value.args[0] == verify
|