Skip to content

Commit e4ac857

Browse files
committed
Ensure subtest parameters are always serializable for pytest-xdist
Prevents DumpError when test parameters are sent between test executors but cannot be serialised (primarily a problem for out mock objects).
1 parent cf5d49d commit e4ac857

File tree

12 files changed

+111
-94
lines changed

12 files changed

+111
-94
lines changed

tests/bot/exts/backend/sync/test_base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ async def test_sync_message_edited(self):
3939
(helpers.MockMessage(), ResponseCodeError(mock.MagicMock()), True),
4040
)
4141

42-
for message, side_effect, should_edit in subtests:
43-
with self.subTest(message=message, side_effect=side_effect, should_edit=should_edit):
42+
for i, (message, side_effect, should_edit) in enumerate(subtests):
43+
with self.subTest(test_case=i, has_message=message is not None, should_edit=should_edit):
4444
TestSyncer._sync.side_effect = side_effect
4545
ctx = helpers.MockContext()
4646
ctx.send.return_value = message
@@ -58,8 +58,8 @@ async def test_sync_message_sent(self):
5858
(helpers.MockContext(), helpers.MockMessage()),
5959
)
6060

61-
for ctx, message in subtests:
62-
with self.subTest(ctx=ctx, message=message):
61+
for i, (ctx, _message) in enumerate(subtests):
62+
with self.subTest(test_case=i, has_ctx=ctx is not None):
6363
await TestSyncer.sync(self.guild, ctx)
6464

6565
if ctx is not None:

tests/bot/exts/backend/sync/test_cog.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ class SyncCogTests(SyncCogTestCase):
6565
@unittest.mock.patch("bot.exts.backend.sync._cog.create_task", new_callable=unittest.mock.MagicMock)
6666
async def test_sync_cog_sync_on_load(self, mock_create_task: unittest.mock.MagicMock):
6767
"""Sync function should be synced on cog load only if guild is found."""
68-
for guild in (helpers.MockGuild(), None):
69-
with self.subTest(guild=guild):
68+
for i, guild in enumerate((helpers.MockGuild(), None)):
69+
with self.subTest(test_case=i, has_guild=guild is not None):
7070
mock_create_task.reset_mock()
7171
self.bot.reset_mock()
7272
self.RoleSyncer.reset_mock()
@@ -126,8 +126,8 @@ async def patch_user_helper(self, side_effect: BaseException) -> None:
126126

127127
async def test_sync_cog_patch_user(self):
128128
"""A PATCH request should be sent and 404 errors ignored."""
129-
for side_effect in (None, self.response_error(404)):
130-
with self.subTest(side_effect=side_effect):
129+
for i, side_effect in enumerate((None, self.response_error(404))):
130+
with self.subTest(test_case=i, has_error=side_effect is not None):
131131
await self.patch_user_helper(side_effect)
132132

133133
async def test_sync_cog_patch_user_non_404(self):
@@ -207,7 +207,7 @@ async def test_sync_cog_on_guild_role_update(self):
207207

208208
for should_put, attributes in subtests:
209209
for attribute in attributes:
210-
with self.subTest(should_put=should_put, changed_attribute=attribute):
210+
with self.subTest(should_put=should_put, attribute=attribute):
211211
self.bot.api_client.put.reset_mock()
212212

213213
after_role_data = role_data.copy()
@@ -372,8 +372,8 @@ async def on_member_join_helper(self, side_effect: Exception) -> dict:
372372

373373
async def test_sync_cog_on_member_join(self):
374374
"""Should PUT user's data or POST it if the user doesn't exist."""
375-
for side_effect in (None, self.response_error(404)):
376-
with self.subTest(side_effect=side_effect):
375+
for i, side_effect in enumerate((None, self.response_error(404))):
376+
with self.subTest(test_case=i, has_error=side_effect is not None):
377377
self.bot.api_client.post.reset_mock()
378378
data = await self.on_member_join_helper(side_effect)
379379

@@ -422,6 +422,6 @@ async def test_commands_require_admin(self):
422422
self.cog.sync_users_command,
423423
)
424424

425-
for cmd in cmds:
426-
with self.subTest(cmd=cmd):
425+
for i, cmd in enumerate(cmds):
426+
with self.subTest(test_case=i):
427427
await self.assertHasPermissionsCheck(cmd, {"administrator": True})

tests/bot/exts/backend/test_error_handler.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ async def test_error_handler_command_invoke_error(self):
135135
}
136136
)
137137

138-
for case in test_cases:
139-
with self.subTest(args=case["args"], expect_mock_call=case["expect_mock_call"]):
138+
for i, case in enumerate(test_cases):
139+
mock_type = "send" if case["expect_mock_call"] == "send" else "mock_function"
140+
with self.subTest(test_case=i, expect_mock_call=mock_type):
140141
self.ctx.send.reset_mock()
141142
self.assertIsNone(await self.cog.on_command_error(*case["args"]))
142143
if case["expect_mock_call"] == "send":
@@ -161,8 +162,8 @@ async def test_error_handler_conversion_error(self):
161162
}
162163
)
163164

164-
for case in cases:
165-
with self.subTest(**case):
165+
for i, case in enumerate(cases):
166+
with self.subTest(test_case=i):
166167
self.assertIsNone(await self.cog.on_command_error(self.ctx, case["error"]))
167168
case["mock_function_to_call"].assert_awaited_once_with(self.ctx, case["error"].original)
168169

@@ -173,8 +174,8 @@ async def test_error_handler_unexpected_errors(self):
173174
errors.ExtensionError(name="foo"),
174175
)
175176

176-
for err in errs:
177-
with self.subTest(error=err):
177+
for i, err in enumerate(errs):
178+
with self.subTest(test_case=i):
178179
self.cog.handle_unexpected_error.reset_mock()
179180
self.assertIsNone(await self.cog.on_command_error(self.ctx, err))
180181
self.cog.handle_unexpected_error.assert_awaited_once_with(self.ctx, err)
@@ -251,8 +252,8 @@ async def test_try_silence_silence_arguments(self):
251252
(MockTextChannel(), True)
252253
)
253254

254-
for channel, kick in test_cases:
255-
with self.subTest(kick=kick, channel=channel):
255+
for i, (channel, kick) in enumerate(test_cases):
256+
with self.subTest(test_case=i, kick=kick):
256257
self.ctx.reset_mock()
257258
self.ctx.invoked_with = "shh"
258259

@@ -291,8 +292,8 @@ async def test_try_silence_unsilence(self):
291292
("unshh", MockTextChannel())
292293
)
293294

294-
for invoke, channel in test_cases:
295-
with self.subTest(message=invoke, channel=channel):
295+
for i, (invoke, channel) in enumerate(test_cases):
296+
with self.subTest(test_case=i, message=invoke, has_channel=channel is not None):
296297
self.bot.get_command.side_effect = (self.silence.silence, self.silence.unsilence)
297298
self.ctx.reset_mock()
298299

@@ -386,33 +387,39 @@ async def test_handle_input_error_handler_errors(self):
386387
"""Should handle each error probably."""
387388
test_cases = (
388389
{
390+
"error_type": "MissingRequiredArgument",
389391
"error": errors.MissingRequiredArgument(MagicMock()),
390392
"call_prepared": True
391393
},
392394
{
395+
"error_type": "TooManyArguments",
393396
"error": errors.TooManyArguments(),
394397
"call_prepared": True
395398
},
396399
{
400+
"error_type": "BadArgument",
397401
"error": errors.BadArgument(),
398402
"call_prepared": True
399403
},
400404
{
405+
"error_type": "BadUnionArgument",
401406
"error": errors.BadUnionArgument(MagicMock(), MagicMock(), MagicMock()),
402407
"call_prepared": True
403408
},
404409
{
410+
"error_type": "ArgumentParsingError",
405411
"error": errors.ArgumentParsingError(),
406412
"call_prepared": False
407413
},
408414
{
415+
"error_type": "UserInputError",
409416
"error": errors.UserInputError(),
410417
"call_prepared": True
411418
}
412419
)
413420

414421
for case in test_cases:
415-
with self.subTest(error=case["error"], call_prepared=case["call_prepared"]):
422+
with self.subTest(error_type=case["error_type"], call_prepared=case["call_prepared"]):
416423
self.ctx.reset_mock()
417424
self.cog.send_error_with_help = AsyncMock()
418425
self.assertIsNone(await self.cog.handle_user_input_error(self.ctx, case["error"]))
@@ -426,33 +433,39 @@ async def test_handle_check_failure_errors(self):
426433
"""Should await `ctx.send` when error is check failure."""
427434
test_cases = (
428435
{
436+
"error_type": "BotMissingPermissions",
429437
"error": errors.BotMissingPermissions(MagicMock()),
430438
"call_ctx_send": True
431439
},
432440
{
441+
"error_type": "BotMissingRole",
433442
"error": errors.BotMissingRole(MagicMock()),
434443
"call_ctx_send": True
435444
},
436445
{
446+
"error_type": "BotMissingAnyRole",
437447
"error": errors.BotMissingAnyRole(MagicMock()),
438448
"call_ctx_send": True
439449
},
440450
{
451+
"error_type": "NoPrivateMessage",
441452
"error": errors.NoPrivateMessage(),
442453
"call_ctx_send": True
443454
},
444455
{
456+
"error_type": "InWhitelistCheckFailure",
445457
"error": InWhitelistCheckFailure(1234),
446458
"call_ctx_send": True
447459
},
448460
{
461+
"error_type": "ResponseCodeError",
449462
"error": ResponseCodeError(MagicMock()),
450463
"call_ctx_send": False
451464
}
452465
)
453466

454467
for case in test_cases:
455-
with self.subTest(error=case["error"], call_ctx_send=case["call_ctx_send"]):
468+
with self.subTest(error_type=case["error_type"], call_ctx_send=case["call_ctx_send"]):
456469
self.ctx.reset_mock()
457470
await self.cog.handle_check_failure(self.ctx, case["error"])
458471
if case["call_ctx_send"]:
@@ -465,25 +478,29 @@ async def test_handle_api_error(self, log_mock):
465478
"""Should `ctx.send` on HTTP error codes, and log at correct level."""
466479
test_cases = (
467480
{
481+
"status": 400,
468482
"error": ResponseCodeError(AsyncMock(status=400)),
469483
"log_level": "error"
470484
},
471485
{
486+
"status": 404,
472487
"error": ResponseCodeError(AsyncMock(status=404)),
473488
"log_level": "debug"
474489
},
475490
{
491+
"status": 550,
476492
"error": ResponseCodeError(AsyncMock(status=550)),
477493
"log_level": "warning"
478494
},
479495
{
496+
"status": 1000,
480497
"error": ResponseCodeError(AsyncMock(status=1000)),
481498
"log_level": "warning"
482499
}
483500
)
484501

485502
for case in test_cases:
486-
with self.subTest(error=case["error"], log_level=case["log_level"]):
503+
with self.subTest(status=case["status"], log_level=case["log_level"]):
487504
self.ctx.reset_mock()
488505
log_mock.reset_mock()
489506
await self.cog.handle_api_error(self.ctx, case["error"])
@@ -500,7 +517,7 @@ async def test_handle_api_error(self, log_mock):
500517
async def test_handle_unexpected_error(self, log_mock, new_scope_mock):
501518
"""Should `ctx.send` this error, error log this and sent to Sentry."""
502519
for case in (None, MockGuild()):
503-
with self.subTest(guild=case):
520+
with self.subTest(has_guild=case is not None):
504521
self.ctx.reset_mock()
505522
log_mock.reset_mock()
506523
new_scope_mock.reset_mock()

tests/bot/exts/filtering/test_settings_entries.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ def test_filtering_dms_when_necessary(self):
144144
(False, MockTextChannel(), True)
145145
)
146146

147-
for apply_in_dms, channel, expected in cases:
148-
with self.subTest(apply_in_dms=apply_in_dms, channel=channel):
147+
for i, (apply_in_dms, channel, expected) in enumerate(cases):
148+
with self.subTest(test_case=i, apply_in_dms=apply_in_dms):
149149
filter_dms = FilterDM(filter_dm=apply_in_dms)
150150
self.ctx.channel = channel
151151

tests/bot/exts/info/test_information.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,22 @@ async def test_user_command_helper_method_get_requests(self):
122122
},
123123
)
124124

125-
for test_value in test_values:
125+
for i, test_value in enumerate(test_values):
126126
helper_method = test_value["helper_method"]
127127
endpoint, params = test_value["expected_args"]
128128

129-
with self.subTest(method=helper_method, endpoint=endpoint, params=params):
129+
with self.subTest(test_case=i, endpoint=endpoint):
130130
await helper_method(self.member)
131131
self.bot.api_client.get.assert_called_once_with(endpoint, params=params)
132132
self.bot.api_client.get.reset_mock()
133133

134134
async def _method_subtests(self, method, test_values, default_header):
135135
"""Helper method that runs the subtests for the different helper methods."""
136-
for test_value in test_values:
136+
for i, test_value in enumerate(test_values):
137137
api_response = test_value["api response"]
138138
expected_lines = test_value["expected_lines"]
139139

140-
with self.subTest(method=method, api_response=api_response, expected_lines=expected_lines):
140+
with self.subTest(test_case=i):
141141
self.bot.api_client.get.return_value = api_response
142142

143143
expected_output = "\n".join(expected_lines)

tests/bot/exts/moderation/infraction/test_utils.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ async def test_post_user(self):
6666
}
6767
]
6868

69-
for case in test_cases:
69+
for i, case in enumerate(test_cases):
7070
user = case["user"]
7171
post_result = case["post_result"]
7272
raise_error = case["raise_error"]
7373
payload = case["payload"]
7474

75-
with self.subTest(user=user, post_result=post_result, raise_error=raise_error, payload=payload):
75+
with self.subTest(test_case=i, has_error=raise_error is not None):
7676
self.bot.api_client.post.reset_mock(side_effect=True)
7777
self.ctx.bot.api_client.post.return_value = post_result
7878

@@ -235,8 +235,8 @@ async def test_send_infraction_embed(self, send_private_embed_mock):
235235
}
236236
]
237237

238-
for case in test_cases:
239-
with self.subTest(args=case["args"], expected=case["expected_output"], send=case["send_result"]):
238+
for i, case in enumerate(test_cases):
239+
with self.subTest(test_case=i, send=case["send_result"]):
240240
send_private_embed_mock.reset_mock()
241241

242242
send_private_embed_mock.return_value = case["send_result"]
@@ -259,7 +259,7 @@ async def test_notify_pardon(self, send_private_embed_mock):
259259
test_case((self.user, "Test title", "Example content", Icons.user_update), Icons.user_update, False)
260260
]
261261

262-
for case in test_cases:
262+
for i, case in enumerate(test_cases):
263263
expected = Embed(
264264
description="Example content",
265265
colour=Colours.soft_green
@@ -268,7 +268,7 @@ async def test_notify_pardon(self, send_private_embed_mock):
268268
icon_url=case.icon
269269
)
270270

271-
with self.subTest(args=case.args, expected=expected):
271+
with self.subTest(test_case=i):
272272
send_private_embed_mock.reset_mock()
273273

274274
send_private_embed_mock.return_value = case.send_result
@@ -288,13 +288,13 @@ async def test_send_private_embed(self):
288288
test_case = namedtuple("test_case", ["expected_output", "raised_exception"])
289289
test_cases = [
290290
test_case(True, None),
291-
test_case(False, HTTPException(AsyncMock(), AsyncMock())),
292-
test_case(False, Forbidden(AsyncMock(), AsyncMock())),
293-
test_case(False, NotFound(AsyncMock(), AsyncMock()))
291+
test_case(False, HTTPException(AsyncMock(), "test error")),
292+
test_case(False, Forbidden(AsyncMock(), "test error")),
293+
test_case(False, NotFound(AsyncMock(), "test error"))
294294
]
295295

296-
for case in test_cases:
297-
with self.subTest(expected=case.expected_output, raised=case.raised_exception):
296+
for i, case in enumerate(test_cases):
297+
with self.subTest(test_case=i, expected=case.expected_output):
298298
self.user.send.reset_mock(side_effect=True)
299299
self.user.send.side_effect = case.raised_exception
300300

0 commit comments

Comments
 (0)