Skip to content

Commit 16a8276

Browse files
committed
make the proper test structure according to the standards; add tests for docker implementation of the scheduler
1 parent 331090f commit 16a8276

File tree

4 files changed

+182
-0
lines changed

4 files changed

+182
-0
lines changed

scrapyd_k8s/tests/unit/k8s_scheduler/__init__.py

Whitespace-only changes.

scrapyd_k8s/tests/unit/launcher/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import pytest
2+
from unittest.mock import MagicMock, patch
3+
import time
4+
from scrapyd_k8s.launcher.docker import Docker
5+
6+
@pytest.fixture
7+
def config_with_max_proc():
8+
config = MagicMock()
9+
config.scrapyd.return_value.get.return_value = '2' # max_proc set to 2
10+
return config
11+
12+
@pytest.fixture
13+
def config_without_max_proc():
14+
config = MagicMock()
15+
config.scrapyd.return_value.get.return_value = None # max_proc not set
16+
return config
17+
18+
@pytest.fixture
19+
def mock_docker_client():
20+
with patch('scrapyd_k8s.launcher.docker.docker') as mock_docker_module:
21+
mock_client = MagicMock()
22+
mock_docker_module.from_env.return_value = mock_client
23+
yield mock_client
24+
25+
@pytest.fixture
26+
def docker_launcher_with_max_proc(config_with_max_proc, mock_docker_client):
27+
return Docker(config_with_max_proc)
28+
29+
@pytest.fixture
30+
def docker_launcher_without_max_proc(config_without_max_proc, mock_docker_client):
31+
return Docker(config_without_max_proc)
32+
33+
def test_docker_init_with_max_proc(config_with_max_proc, mock_docker_client):
34+
docker_launcher = Docker(config_with_max_proc)
35+
assert docker_launcher.max_proc == 2
36+
assert docker_launcher._thread is not None
37+
assert docker_launcher._thread.is_alive()
38+
39+
def test_docker_init_without_max_proc(config_without_max_proc, mock_docker_client):
40+
docker_launcher = Docker(config_without_max_proc)
41+
assert docker_launcher.max_proc is None
42+
assert docker_launcher._thread is None
43+
44+
def test_schedule_with_capacity(docker_launcher_with_max_proc, mock_docker_client):
45+
# Mock methods
46+
docker_launcher_with_max_proc.get_running_jobs_count = MagicMock(return_value=1)
47+
docker_launcher_with_max_proc.start_pending_containers = MagicMock()
48+
49+
# Mock container creation
50+
mock_container = MagicMock()
51+
mock_docker_client.containers.create.return_value = mock_container
52+
53+
# Prepare parameters for schedule
54+
project = MagicMock()
55+
project.id.return_value = 'test_project'
56+
project.repository.return_value = 'test_repo'
57+
project.resources.return_value = {}
58+
version = 'v1'
59+
spider = 'test_spider'
60+
job_id = 'job_123'
61+
settings = {}
62+
args = {}
63+
64+
# Call schedule
65+
docker_launcher_with_max_proc.schedule(project, version, spider, job_id, settings, args)
66+
67+
# Verify that container is created
68+
mock_docker_client.containers.create.assert_called_once()
69+
70+
# Since running jobs count is less than max_proc, start_pending_containers should be called
71+
docker_launcher_with_max_proc.start_pending_containers.assert_called_once()
72+
73+
def test_schedule_no_capacity(docker_launcher_with_max_proc, mock_docker_client):
74+
# Mock methods
75+
docker_launcher_with_max_proc.get_running_jobs_count = MagicMock(return_value=2) # At max_proc
76+
docker_launcher_with_max_proc.start_pending_containers = MagicMock()
77+
78+
# Mock container creation
79+
mock_container = MagicMock()
80+
mock_docker_client.containers.create.return_value = mock_container
81+
82+
# Prepare parameters for schedule
83+
project = MagicMock()
84+
project.id.return_value = 'test_project'
85+
project.repository.return_value = 'test_repo'
86+
project.resources.return_value = {}
87+
version = 'v1'
88+
spider = 'test_spider'
89+
job_id = 'job_456'
90+
settings = {}
91+
args = {}
92+
93+
# Patch the logger to check log outputs
94+
with patch('scrapyd_k8s.launcher.docker.logger') as mock_logger:
95+
# Call schedule
96+
docker_launcher_with_max_proc.schedule(project, version, spider, job_id, settings, args)
97+
98+
# Verify that container is created
99+
mock_docker_client.containers.create.assert_called_once()
100+
101+
# start_pending_containers should not be called since we're at capacity
102+
docker_launcher_with_max_proc.start_pending_containers.assert_not_called()
103+
104+
# Verify that container.start() is not called immediately
105+
mock_container.start.assert_not_called()
106+
107+
# Check that the correct log message was output
108+
mock_logger.info.assert_called_with(f"Job {job_id} is pending due to max_proc limit.")
109+
110+
def test_schedule_no_max_proc(docker_launcher_without_max_proc, mock_docker_client):
111+
# Mock container creation
112+
mock_container = MagicMock()
113+
mock_docker_client.containers.create.return_value = mock_container
114+
115+
# Prepare parameters for schedule
116+
project = MagicMock()
117+
project.id.return_value = 'test_project'
118+
project.repository.return_value = 'test_repo'
119+
project.resources.return_value = {}
120+
version = 'v1'
121+
spider = 'test_spider'
122+
job_id = 'job_789'
123+
settings = {}
124+
args = {}
125+
126+
# Call schedule
127+
docker_launcher_without_max_proc.schedule(project, version, spider, job_id, settings, args)
128+
129+
# Verify that container is created
130+
mock_docker_client.containers.create.assert_called_once()
131+
132+
# Since max_proc is not set, container.start() should be called immediately
133+
mock_container.start.assert_called_once()
134+
135+
def test_get_running_jobs_count(docker_launcher_with_max_proc, mock_docker_client):
136+
# Mock the list of running containers
137+
mock_container_list = [MagicMock(), MagicMock()]
138+
mock_docker_client.containers.list.return_value = mock_container_list
139+
140+
count = docker_launcher_with_max_proc.get_running_jobs_count()
141+
142+
# Verify that the count matches the number of mock containers
143+
assert count == 2
144+
mock_docker_client.containers.list.assert_called_with(
145+
filters={'label': docker_launcher_with_max_proc.LABEL_PROJECT, 'status': 'running'})
146+
147+
def test_start_pending_containers(docker_launcher_with_max_proc, mock_docker_client):
148+
# Mock the get_running_jobs_count method
149+
docker_launcher_with_max_proc.get_running_jobs_count = MagicMock(return_value=1)
150+
151+
# Mock pending containers
152+
mock_pending_container = MagicMock()
153+
mock_pending_container.name = 'pending_container'
154+
mock_docker_client.containers.list.return_value = [mock_pending_container]
155+
156+
# Patch logger to check log outputs
157+
with patch('scrapyd_k8s.launcher.docker.logger') as mock_logger:
158+
# Call start_pending_containers
159+
docker_launcher_with_max_proc.start_pending_containers()
160+
161+
# Verify that the pending container's start method was called
162+
mock_pending_container.start.assert_called_once()
163+
164+
# Verify that the correct log message was output
165+
mock_logger.info.assert_called_with(
166+
f"Started pending container {mock_pending_container.name}. Total running jobs now: 2"
167+
)
168+
169+
def test_background_task_starts_pending_containers(config_with_max_proc, mock_docker_client):
170+
# Mock start_pending_containers before initializing the Docker class
171+
with patch.object(Docker, 'start_pending_containers', autospec=True) as mock_start_pending:
172+
# Initialize Docker instance
173+
docker_launcher = Docker(config_with_max_proc)
174+
175+
# Wait for slightly more than check_interval to ensure the background task runs
176+
time.sleep(5.1) # Wait for the background thread to execute
177+
178+
# Verify that start_pending_containers was called by the background thread
179+
assert mock_start_pending.call_count > 0
180+
181+
# Clean up by shutting down the background thread
182+
docker_launcher.shutdown()

0 commit comments

Comments
 (0)