-
Notifications
You must be signed in to change notification settings - Fork 262
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added unit test setup for worker (#402)
* Added unit test setup for worker * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed coverage upload * Fixed flake8 issue and corrected the comment file path * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changed comment input * Updated workflow file * Updated workflow file * Updated workflow file * Added markdown report * Added updated tox.ini * Hardcoded file name in workflow * Replaced commenting with summary * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added sticky comment * Removed other comment * Added build on merge to main also --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kirtiman Mishra <[email protected]>
- Loading branch information
1 parent
135c95c
commit a120b83
Showing
10 changed files
with
705 additions
and
271 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
name: Run tox tests | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
types: [opened, synchronize, reopened, ready_for_review] | ||
branches: [main] | ||
|
||
jobs: | ||
test: | ||
if: github.event.pull_request.draft == false | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.9' | ||
|
||
- name: Cache tox environments | ||
uses: actions/cache@v3 | ||
with: | ||
path: .tox/ | ||
key: ${{ runner.os }}-tox-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }} | ||
restore-keys: | | ||
${{ runner.os }}-tox- | ||
- name: Install tox | ||
run: pip install tox | ||
|
||
- name: Run tox | ||
id: tox | ||
run: | | ||
tox | ||
- name: Render the report to the PR | ||
uses: marocchino/sticky-pull-request-comment@v2 | ||
with: | ||
header: worker-test-report | ||
recreate: true | ||
path: worker-report.md | ||
|
||
- name: Output reports to the job summary when tests fail | ||
shell: bash | ||
run: | | ||
if [ -f "worker-report.md" ]; then | ||
echo "<details><summary>Worker Test Report</summary>" >> $GITHUB_STEP_SUMMARY | ||
echo "" >> $GITHUB_STEP_SUMMARY | ||
cat "worker-report.md" >> $GITHUB_STEP_SUMMARY | ||
echo "" >> $GITHUB_STEP_SUMMARY | ||
echo "</details>" >> $GITHUB_STEP_SUMMARY | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[tox] | ||
env_list = py{39,310,311}, worker | ||
|
||
# [testenv] | ||
# skip_install = true | ||
|
||
[testenv:worker] | ||
changedir = worker | ||
setenv = | ||
PDM_IGNORE_SAVED_PYTHON="1" | ||
deps = pdm | ||
allowlist_externals= | ||
sh | ||
commands_pre = | ||
pdm sync --dev | ||
sh -c '[ -f cloud_requirements.txt ] && pip install -r cloud_requirements.txt || echo "cloud_requirements.txt not found"' | ||
commands = | ||
pytest -v --md-report-verbose=1 --md-report --md-report-flavor gfm --md-report-output ../worker-report.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import logging | ||
import os | ||
from unittest.mock import MagicMock | ||
|
||
import pytest | ||
from docker.errors import ImageNotFound | ||
from unstract.worker.constants import Env | ||
|
||
from .docker import Client, DockerContainer | ||
|
||
DOCKER_MODULE = "unstract.worker.clients.docker" | ||
|
||
|
||
@pytest.fixture | ||
def docker_container(): | ||
container = MagicMock() | ||
return DockerContainer(container) | ||
|
||
|
||
@pytest.fixture | ||
def docker_client(): | ||
image_name = "test-image" | ||
image_tag = "latest" | ||
logger = logging.getLogger("test-logger") | ||
return Client(image_name, image_tag, logger) | ||
|
||
|
||
def test_logs(docker_container, mocker): | ||
"""Test the logs method to ensure it yields log lines.""" | ||
mock_container = mocker.patch.object(docker_container, "container") | ||
mock_container.logs.return_value = [b"log line 1", b"log line 2"] | ||
|
||
logs = list(docker_container.logs(follow=True)) | ||
assert logs == ["log line 1", "log line 2"] | ||
|
||
|
||
def test_cleanup(docker_container, mocker): | ||
"""Test the cleanup method to ensure it removes the container.""" | ||
mock_container = mocker.patch.object(docker_container, "container") | ||
mocker.patch(f"{DOCKER_MODULE}.Utils.remove_container_on_exit", return_value=True) | ||
|
||
docker_container.cleanup() | ||
mock_container.remove.assert_called_once_with(force=True) | ||
|
||
|
||
def test_cleanup_skip(docker_container, mocker): | ||
"""Test the cleanup method to ensure it doesn't remove the container.""" | ||
mock_container = mocker.patch.object(docker_container, "container") | ||
mocker.patch(f"{DOCKER_MODULE}.Utils.remove_container_on_exit", return_value=False) | ||
|
||
docker_container.cleanup() | ||
mock_container.remove.assert_not_called() | ||
|
||
|
||
def test_client_init(mocker): | ||
"""Test the Client initialization.""" | ||
mock_from_env = mocker.patch(f"{DOCKER_MODULE}.DockerClient.from_env") | ||
client_instance = Client("test-image", "latest", logging.getLogger("test-logger")) | ||
|
||
mock_from_env.assert_called_once() | ||
assert client_instance.client is not None | ||
|
||
|
||
def test_get_image_exists(docker_client, mocker): | ||
"""Test the __image_exists method.""" | ||
# Mock the client object | ||
mock_client = mocker.patch.object(docker_client, "client") | ||
# Create a mock for the 'images' attribute | ||
mock_images = mocker.MagicMock() | ||
# Attach the mock to the client object | ||
mock_client.images = mock_images | ||
# Patch the 'get' method of the 'images' attribute | ||
mock_images.get.side_effect = ImageNotFound("Image not found") | ||
|
||
assert not docker_client._Client__image_exists("test-image:latest") | ||
mock_images.get.assert_called_once_with("test-image:latest") | ||
|
||
|
||
def test_get_image(docker_client, mocker): | ||
"""Test the get_image method.""" | ||
# Patch the client object to control its behavior | ||
mock_client = mocker.patch.object(docker_client, "client") | ||
# Patch the images attribute of the client to control its behavior | ||
mock_images = mocker.MagicMock() | ||
mock_client.images = mock_images | ||
|
||
# Case 1: Image exists | ||
mock_images.get.side_effect = MagicMock() # Mock that image exists | ||
assert docker_client.get_image() == "test-image:latest" | ||
mock_images.get.assert_called_once_with("test-image:latest") # Ensure get is called | ||
|
||
# Case 2: Image does not exist | ||
mock_images.get.side_effect = ImageNotFound( | ||
"Image not found" | ||
) # Mock that image doesn't exist | ||
mock_pull = mocker.patch.object( | ||
docker_client.client.api, "pull" | ||
) # Patch pull method | ||
mock_pull.return_value = iter([{"status": "pulling"}]) # Simulate pull process | ||
assert docker_client.get_image() == "test-image:latest" | ||
mock_pull.assert_called_once_with( | ||
repository="test-image", | ||
tag="latest", | ||
stream=True, | ||
decode=True, | ||
) | ||
|
||
|
||
def test_get_container_run_config(docker_client, mocker): | ||
"""Test the get_container_run_config method.""" | ||
os.environ[Env.WORKFLOW_DATA_DIR] = "/source" | ||
command = ["echo", "hello"] | ||
organization_id = "org123" | ||
workflow_id = "wf123" | ||
execution_id = "ex123" | ||
|
||
mocker.patch.object(docker_client, "_Client__image_exists", return_value=True) | ||
mocker_normalize = mocker.patch( | ||
f"{DOCKER_MODULE}.ContainerClientHelper.normalize_container_name", | ||
return_value="test-image", | ||
) | ||
config = docker_client.get_container_run_config( | ||
command, | ||
organization_id, | ||
workflow_id, | ||
execution_id, | ||
envs={"KEY": "VALUE"}, | ||
auto_remove=True, | ||
) | ||
|
||
mocker_normalize.assert_called_once_with("test-image") | ||
assert config["name"] == "test-image" | ||
assert config["image"] == "test-image:latest" | ||
assert config["command"] == ["echo", "hello"] | ||
assert config["environment"] == {"KEY": "VALUE"} | ||
assert config["mounts"] == [ | ||
{ | ||
"type": "bind", | ||
"source": f"/source/{organization_id}/{workflow_id}/{execution_id}", | ||
"target": "/data", | ||
} | ||
] | ||
|
||
|
||
def test_get_container_run_config_without_mount(docker_client, mocker): | ||
"""Test the get_container_run_config method.""" | ||
os.environ[Env.WORKFLOW_DATA_DIR] = "/source" | ||
command = ["echo", "hello"] | ||
|
||
mocker.patch.object(docker_client, "_Client__image_exists", return_value=True) | ||
mocker_normalize = mocker.patch( | ||
f"{DOCKER_MODULE}.ContainerClientHelper.normalize_container_name", | ||
return_value="test-image", | ||
) | ||
config = docker_client.get_container_run_config( | ||
command, | ||
None, | ||
None, | ||
None, | ||
auto_remove=True, | ||
) | ||
|
||
mocker_normalize.assert_called_once_with("test-image") | ||
assert config["name"] == "test-image" | ||
assert config["image"] == "test-image:latest" | ||
assert config["command"] == ["echo", "hello"] | ||
assert config["environment"] == {} | ||
assert config["mounts"] == [] | ||
|
||
|
||
def test_run_container(docker_client, mocker): | ||
"""Test the run_container method.""" | ||
# Patch the client object to control its behavior | ||
mock_client = mocker.patch.object(docker_client, "client") | ||
|
||
config = { | ||
"name": "test-image", | ||
"image": "test-image:latest", | ||
"command": ["echo", "hello"], | ||
"detach": True, | ||
"stream": True, | ||
"auto_remove": True, | ||
"environment": {"KEY": "VALUE"}, | ||
"stderr": True, | ||
"stdout": True, | ||
"network": "", | ||
"mounts": [], | ||
} | ||
|
||
assert isinstance(docker_client.run_container(config), DockerContainer) | ||
mock_client.containers.run.assert_called_once_with(**config) | ||
|
||
|
||
if __name__ == "__main__": | ||
pytest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters