Skip to content

Commit fa2b2c1

Browse files
chore: update 'wait_for_sidekiq' function & use it more
DRAFT: actually working on not using `wait_for_sidekiq` Make the `timeout` argument in `wait_for_sidekiq` be approximately the seconds desired. Previously it was an iteration count so depending on what the `step` argument was set to the time before the function would time-out would vary. Simpler for users to treat `timeout` as the (approximate) number of seconds before `wait_for_sidekiq` will timeout. Return new `WaitSidekiq` object which contains statistics from the `wait_for_sidekiq` run. Use `wait_for_sidekiq` in our fixtures defined in `conftest.py` after they delete resources. This will help increase the odds that we don't have stray resources left-around when the next test starts. On my local system the run-time of py_func_v4 went from 7m50s to 7m59s.
1 parent d65ce36 commit fa2b2c1

File tree

4 files changed

+186
-37
lines changed

4 files changed

+186
-37
lines changed

tests/functional/api/test_merge_requests.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_merge_request_merge(project):
101101

102102

103103
def test_merge_request_should_remove_source_branch(
104-
project, merge_request, wait_for_sidekiq
104+
project, merge_request, ensure_deleted
105105
) -> None:
106106
"""Test to ensure
107107
https://github.com/python-gitlab/python-gitlab/issues/1120 is fixed.
@@ -113,9 +113,6 @@ def test_merge_request_should_remove_source_branch(
113113

114114
mr.merge(should_remove_source_branch=True)
115115

116-
result = wait_for_sidekiq(timeout=60)
117-
assert result is True, "sidekiq process should have terminated but did not"
118-
119116
# Wait until it is merged
120117
mr_iid = mr.iid
121118
for _ in range(60):
@@ -124,24 +121,16 @@ def test_merge_request_should_remove_source_branch(
124121
break
125122
time.sleep(0.5)
126123
assert mr.merged_at is not None
127-
time.sleep(0.5)
128-
result = wait_for_sidekiq(timeout=60)
129-
assert result is True, "sidekiq process should have terminated but did not"
130-
131-
# Ensure we can NOT get the MR branch
132-
with pytest.raises(gitlab.exceptions.GitlabGetError):
133-
result = project.branches.get(source_branch)
134-
# Help to debug in case the expected exception doesn't happen.
135-
import pprint
136124

137-
print("mr:", pprint.pformat(mr))
138-
print("mr.merged_at:", pprint.pformat(mr.merged_at))
139-
print("result:", pprint.pformat(result))
125+
# Ensure the MR branch is deleted
126+
ensure_deleted(
127+
pg_object=project.branches,
128+
object_id=source_branch,
129+
description="Project branch",
130+
)
140131

141132

142-
def test_merge_request_large_commit_message(
143-
project, merge_request, wait_for_sidekiq
144-
) -> None:
133+
def test_merge_request_large_commit_message(project, merge_request) -> None:
145134
"""Test to ensure https://github.com/python-gitlab/python-gitlab/issues/1452
146135
is fixed.
147136
Bug reported that very long 'merge_commit_message' in mr.merge() would
@@ -156,9 +145,6 @@ def test_merge_request_large_commit_message(
156145

157146
mr.merge(merge_commit_message=merge_commit_message)
158147

159-
result = wait_for_sidekiq(timeout=60)
160-
assert result is True, "sidekiq process should have terminated but did not"
161-
162148
# Wait until it is merged
163149
mr_iid = mr.iid
164150
for _ in range(60):
@@ -182,7 +168,7 @@ def test_merge_request_merge_ref(merge_request) -> None:
182168

183169

184170
def test_merge_request_merge_ref_should_fail(
185-
project, merge_request, wait_for_sidekiq
171+
project, merge_request, ensure_raises_exception
186172
) -> None:
187173
source_branch = "merge_ref_test2"
188174
mr = merge_request(source_branch=source_branch)
@@ -196,9 +182,14 @@ def test_merge_request_merge_ref_should_fail(
196182
"commit_message": "Another commit in main branch",
197183
}
198184
)
199-
result = wait_for_sidekiq(timeout=60)
200-
assert result is True, "sidekiq process should have terminated but did not"
201185

186+
ensure_raises_exception(
187+
func=mr.merge_ref,
188+
exception=gitlab.exceptions.GitlabGetError,
189+
args=[],
190+
kwargs={},
191+
description="mr.merge_ref()",
192+
)
202193
# Check for non-existing merge_ref for MR with conflicts
203194
with pytest.raises(gitlab.exceptions.GitlabGetError):
204195
response = mr.merge_ref()

tests/functional/api/test_projects.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,5 @@ def test_project_groups_list(gl, group):
329329
groups = project.groups.list()
330330
group_ids = set([x.id for x in groups])
331331
assert set((group.id, group2.id)) == group_ids
332+
# NOTE: `group` will automatically be deleted by the fixture, but not `group2`
333+
group2.delete()

tests/functional/api/test_users.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_block_user(gl, user):
3636
assert user in users
3737

3838

39-
def test_delete_user(gl, wait_for_sidekiq):
39+
def test_delete_user(gl, ensure_deleted):
4040
new_user = gl.users.create(
4141
{
4242
"email": "delete-user@test.com",
@@ -45,11 +45,11 @@ def test_delete_user(gl, wait_for_sidekiq):
4545
"password": "delete-user-pass",
4646
}
4747
)
48+
new_user_id = new_user.id
4849

4950
new_user.delete()
50-
result = wait_for_sidekiq(timeout=60)
51-
assert result is True, "sidekiq process should have terminated but did not"
5251

52+
ensure_deleted(pg_object=gl.users, object_id=new_user_id, description="User")
5353
assert new_user.id not in [user.id for user in gl.users.list()]
5454

5555

tests/functional/conftest.py

Lines changed: 165 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import dataclasses
12
import tempfile
23
import time
34
import uuid
45
from pathlib import Path
56
from subprocess import check_output
7+
from typing import Any, Dict, List
68

79
import pytest
810

@@ -30,6 +32,34 @@ def reset_gitlab(gl):
3032
if user.username != "root":
3133
user.delete(hard_delete=True)
3234

35+
sleep_interval = 0.1
36+
timeout = 60
37+
max_iterations = int(timeout / sleep_interval)
38+
39+
# Ensure everything is deleted.
40+
start_time = time.perf_counter()
41+
42+
def ensure_list_empty(pg_object, description: str) -> None:
43+
for _ in range(max_iterations):
44+
if pg_object.list() == []:
45+
break
46+
time.sleep(sleep_interval)
47+
assert (
48+
pg_object.list() == []
49+
), f"Did not delete all {description}. Elapsed_time: {time.perf_counter() - start_time}"
50+
51+
ensure_list_empty(pg_object=gl.projects, description="projects")
52+
ensure_list_empty(pg_object=gl.groups, description="groups")
53+
ensure_list_empty(pg_object=gl.variables, description="variables")
54+
55+
for _ in range(max_iterations):
56+
if len(gl.users.list()) <= 1:
57+
break
58+
time.sleep(sleep_interval)
59+
assert (
60+
len(gl.users.list()) <= 1
61+
), f"elapsed_time: {time.perf_counter() - start_time}"
62+
3363

3464
def set_token(container, fixture_dir):
3565
set_token_rb = fixture_dir / "set_token.rb"
@@ -105,25 +135,59 @@ def _check(container):
105135
return _check
106136

107137

138+
@dataclasses.dataclass
139+
class WaitSidekiq:
140+
iterations: int
141+
step: float
142+
elapsed_time: float
143+
success: bool
144+
145+
108146
@pytest.fixture
109-
def wait_for_sidekiq(gl):
147+
def wait_for_sidekiq(gl: gitlab.Gitlab):
148+
"""
149+
Return a helper function to wait until there are no busy sidekiq processes.
150+
151+
Use this with asserts for slow tasks (group/project/user creation/deletion).
152+
"""
153+
return _wait_for_sidekiq(gl=gl)
154+
155+
156+
def _wait_for_sidekiq(gl: gitlab.Gitlab):
110157
"""
111158
Return a helper function to wait until there are no busy sidekiq processes.
112159
113160
Use this with asserts for slow tasks (group/project/user creation/deletion).
114161
"""
115162

116-
def _wait(timeout=30, step=0.5):
117-
for _ in range(timeout):
163+
def _wait(
164+
timeout: int = 60,
165+
step: float = 0.1,
166+
) -> WaitSidekiq:
167+
# timeout is approximately the timeout in seconds
168+
max_iterations = int(timeout / step)
169+
start_time = time.perf_counter()
170+
success = False
171+
for count in range(max_iterations):
172+
print(f"_wait: count: {count}")
118173
time.sleep(step)
119174
busy = False
120175
processes = gl.sidekiq.process_metrics()["processes"]
121176
for process in processes:
177+
print(f"_wait: process['busy']: {process['busy']}")
122178
if process["busy"]:
123179
busy = True
124180
if not busy:
125-
return True
126-
return False
181+
success = True
182+
break
183+
result = WaitSidekiq(
184+
iterations=count,
185+
step=step,
186+
elapsed_time=time.perf_counter() - start_time,
187+
success=success,
188+
)
189+
print(f"_wait: {result}")
190+
return result
127191

128192
return _wait
129193

@@ -194,6 +258,73 @@ def gitlab_runner(gl):
194258
check_output(docker_exec + unregister).decode()
195259

196260

261+
@pytest.fixture
262+
def ensure_raises_exception():
263+
"""
264+
Return a helper function to wait until specified exception is raised
265+
266+
Use this with asserts for slow tasks (group/project/user creation/deletion).
267+
"""
268+
return _ensure_raises_exception_wrapped()
269+
270+
271+
def _ensure_raises_exception(
272+
*,
273+
func,
274+
exception: Exception,
275+
args: List[Any],
276+
kwargs: Dict[str, Any],
277+
description: str,
278+
) -> None:
279+
"""Ensure the exception is specified when calling `func`. If no exception is raided
280+
after timeout period, fail the test"""
281+
sleep_interval = 0.1
282+
timeout = 60
283+
max_iterations = int(timeout / sleep_interval)
284+
285+
for _ in range(max_iterations):
286+
try:
287+
func(*args, **kwargs)
288+
except exception:
289+
return
290+
time.sleep(sleep_interval)
291+
pytest.fail(f"{description} did not raise exception {exception}")
292+
293+
294+
def _ensure_raises_exception_wrapped():
295+
return _ensure_raises_exception
296+
297+
298+
@pytest.fixture
299+
def ensure_deleted():
300+
"""
301+
Return a helper function to wait until specified object is deleted
302+
303+
Use this with asserts for slow tasks (group/project/user creation/deletion).
304+
"""
305+
return _ensure_deleted_wrapped()
306+
307+
308+
def _ensure_deleted(*, pg_object, object_id: int, description: str) -> None:
309+
"""Ensure the object specified can not be retrieved. If object still exists after
310+
timeout period, fail the test"""
311+
sleep_interval = 0.1
312+
timeout = 60
313+
max_iterations = int(timeout / sleep_interval)
314+
315+
for _ in range(max_iterations):
316+
try:
317+
pg_object.get(object_id)
318+
except gitlab.exceptions.GitlabGetError:
319+
return
320+
time.sleep(sleep_interval)
321+
pytest.fail(f"{description} {object_id} was not deleted")
322+
323+
324+
def _ensure_deleted_wrapped():
325+
return _ensure_deleted
326+
327+
197328
@pytest.fixture(scope="module")
198329
def group(gl):
199330
"""Group fixture for group API resource tests."""
@@ -203,6 +334,7 @@ def group(gl):
203334
"path": f"group-{_id}",
204335
}
205336
group = gl.groups.create(data)
337+
group_id = group.id
206338

207339
yield group
208340

@@ -211,6 +343,8 @@ def group(gl):
211343
except gitlab.exceptions.GitlabDeleteError as e:
212344
print(f"Group already deleted: {e}")
213345

346+
_ensure_deleted(pg_object=gl.groups, object_id=group_id, description="Group")
347+
214348

215349
@pytest.fixture(scope="module")
216350
def project(gl):
@@ -219,6 +353,7 @@ def project(gl):
219353
name = f"test-project-{_id}"
220354

221355
project = gl.projects.create(name=name)
356+
project_id = project.id
222357

223358
yield project
224359

@@ -227,6 +362,8 @@ def project(gl):
227362
except gitlab.exceptions.GitlabDeleteError as e:
228363
print(f"Project already deleted: {e}")
229364

365+
_ensure_deleted(pg_object=gl.projects, object_id=project_id, description="Project")
366+
230367

231368
@pytest.fixture(scope="function")
232369
def merge_request(project, wait_for_sidekiq):
@@ -249,8 +386,10 @@ def _merge_request(*, source_branch: str):
249386
# NOTE(jlvillal): Sometimes the CI would give a "500 Internal Server
250387
# Error". Hoping that waiting until all other processes are done will
251388
# help with that.
252-
result = wait_for_sidekiq(timeout=60)
253-
assert result is True, "sidekiq process should have terminated but did not"
389+
result = wait_for_sidekiq()
390+
assert (
391+
result.success is True
392+
), f"sidekiq process should have terminated but did not: {result}"
254393

255394
project.refresh() # Gets us the current default branch
256395
project.branches.create(
@@ -274,8 +413,10 @@ def _merge_request(*, source_branch: str):
274413
"remove_source_branch": True,
275414
}
276415
)
277-
result = wait_for_sidekiq(timeout=60)
278-
assert result is True, "sidekiq process should have terminated but did not"
416+
result = wait_for_sidekiq()
417+
assert (
418+
result.success is True
419+
), f"sidekiq process should have terminated but did not: {result}"
279420

280421
mr_iid = mr.iid
281422
for _ in range(60):
@@ -298,6 +439,18 @@ def _merge_request(*, source_branch: str):
298439
# Ignore if branch was already deleted
299440
pass
300441

442+
for mr_iid, source_branch in to_delete:
443+
_ensure_deleted(
444+
pg_object=project.mergerequests,
445+
object_id=mr_iid,
446+
description="Project mergerequest",
447+
)
448+
_ensure_deleted(
449+
pg_object=project.branches,
450+
object_id=source_branch,
451+
description="Project branch",
452+
)
453+
301454

302455
@pytest.fixture(scope="module")
303456
def project_file(project):
@@ -342,6 +495,7 @@ def user(gl):
342495
password = "fakepassword"
343496

344497
user = gl.users.create(email=email, username=username, name=name, password=password)
498+
user_id = user.id
345499

346500
yield user
347501

@@ -350,6 +504,8 @@ def user(gl):
350504
except gitlab.exceptions.GitlabDeleteError as e:
351505
print(f"User already deleted: {e}")
352506

507+
_ensure_deleted(pg_object=gl.users, object_id=user_id, description="User")
508+
353509

354510
@pytest.fixture(scope="module")
355511
def issue(project):

0 commit comments

Comments
 (0)