Skip to content

Commit 1f5eb87

Browse files
committed
fix: several test improvements
1 parent 2e4e0c4 commit 1f5eb87

4 files changed

Lines changed: 63 additions & 54 deletions

File tree

tests/base.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import os.path
33
import shutil
4+
import sys
45
import tempfile
56
import typing
67
import unittest
@@ -96,7 +97,7 @@ def fake_get_service(name):
9697
stack.enter_context(
9798
unittest.mock.patch(f'{module}.get_service', fake_get_service)
9899
)
99-
yield mapping
100+
yield
100101

101102

102103
class ConfigTest(unittest.TestCase):
@@ -132,15 +133,13 @@ def tearDown(self):
132133
def inject_fixtures(self, caplog):
133134
self.caplog = caplog
134135

135-
def enter_context(self, cm):
136-
"""Enter a context manager for the duration of the test.
136+
if sys.version_info < (3, 11):
137137

138-
Backport of unittest.TestCase.enterContext, which is only available on
139-
Python 3.11+ (we still support 3.10).
140-
"""
141-
value = cm.__enter__()
142-
self.addCleanup(cm.__exit__, None, None, None)
143-
return value
138+
def enterContext(self, cm):
139+
"""Backport of unittest.TestCase.enterContext for Python 3.10."""
140+
value = cm.__enter__()
141+
self.addCleanup(cm.__exit__, None, None, None)
142+
return value
144143

145144
def validate(self) -> validation.Config:
146145
config = self.config.copy()

tests/config/test_validation.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ValidationService(DumbService):
4242
class TestValidation(ConfigTest):
4343
def setUp(self):
4444
super().setUp()
45-
self.enter_context(register_services({"test": ValidationService}))
45+
self.enterContext(register_services({"test": ValidationService}))
4646
self.config = {
4747
"general": {"targets": ["my_service"]},
4848
"my_service": {"service": "test", "username": "ralph"},
@@ -83,6 +83,16 @@ def test_service_missing(self):
8383

8484
self.assertValidationError("No option 'service' in section: 'my_service'")
8585

86+
def test_service_missing_without_other_service(self):
87+
del self.config['my_service']['service']
88+
89+
self.assertValidationError(
90+
"2 validation errors found in configpath\n"
91+
"See https://bugwarrior.readthedocs.io\n\n"
92+
"[my_service] <- Field required\n"
93+
"[my_service] <- unrecognized option"
94+
)
95+
8696
def test_extra_field(self):
8797
"""Undeclared fields are forbidden."""
8898
self.config['my_service']['undeclared_field'] = 'extra'
@@ -135,8 +145,6 @@ def test_deprecated_filter_merge_requests_and_include_merge_requests(self):
135145

136146
def test_deprecated_project_name(self):
137147
"""We're just testing that deprecation doesn't break validation."""
138-
self.validate()
139-
140148
self.config['my_service']['project_name'] = 'myproject'
141149
self.validate()
142150

tests/test_command.py

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,22 @@ class SecondaryService(DumbService):
4141
ISSUE_CLASS = SecondaryIssue
4242

4343

44-
ARBITRARY_RECORD = {
45-
'title': 'Hallo',
46-
'url': 'https://example.com',
47-
'number': 10,
48-
'labels': [],
49-
}
50-
ARBITRARY_EXTRA = {'project': 'one', 'type': 'issue', 'annotations': []}
44+
def yields_one(target_specific_url=False):
45+
def issues(self):
46+
record = {
47+
'title': 'Hallo',
48+
'url': 'https://example.com',
49+
'number': 10,
50+
'labels': [],
51+
}
52+
53+
if target_specific_url:
54+
record['url'] = f'https://example.com/{self.config.target}'
5155

56+
extra = {'project': 'one', 'type': 'issue', 'annotations': []}
57+
yield self.get_issue_for_record(record, extra)
5258

53-
def yields_one(self):
54-
yield self.get_issue_for_record(ARBITRARY_RECORD, ARBITRARY_EXTRA)
59+
return issues
5560

5661

5762
def yields_none(self):
@@ -62,12 +67,6 @@ def raises(self):
6267
raise Exception('message')
6368

6469

65-
def yields_per_target(self):
66-
"""Yield one issue whose url is unique per target."""
67-
record = dict(ARBITRARY_RECORD, url=f'https://example.com/{self.config.target}')
68-
yield self.get_issue_for_record(record, ARBITRARY_EXTRA)
69-
70-
7170
def fake_service(issues, base=DumbService):
7271
"""
7372
Build a fake service class whose issues() is the given function.
@@ -106,9 +105,10 @@ def test_success(self):
106105
"""
107106
A normal `bugwarrior pull` invocation.
108107
"""
109-
self.enter_context(register_services({'test': fake_service(yields_one)}))
110-
111-
with self.caplog.at_level(logging.INFO):
108+
with (
109+
register_services({'test': fake_service(yields_one())}),
110+
self.caplog.at_level(logging.INFO),
111+
):
112112
self.runner.invoke(command.cli, args=('pull', '--debug'))
113113

114114
logs = [rec.message for rec in self.caplog.records]
@@ -121,9 +121,10 @@ def test_failure(self):
121121
"""
122122
A broken `bugwarrior pull` invocation.
123123
"""
124-
self.enter_context(register_services({'test': fake_service(raises)}))
125-
126-
with self.caplog.at_level(logging.ERROR):
124+
with (
125+
register_services({'test': fake_service(raises)}),
126+
self.caplog.at_level(logging.ERROR),
127+
):
127128
self.runner.invoke(command.cli, args=('pull', '--debug'))
128129

129130
self.assertNotEqual(self.caplog.records, [])
@@ -147,16 +148,15 @@ def test_partial_failure_survival(self):
147148
self.config['my_broken_service'] = {'service': 'secondary'}
148149
self.write_rc(self.config)
149150

150-
self.enter_context(
151+
with (
151152
register_services(
152153
{
153154
'test': fake_service(yields_none),
154155
'secondary': fake_service(raises, base=SecondaryService),
155156
}
156-
)
157-
)
158-
159-
with self.caplog.at_level(logging.INFO):
157+
),
158+
self.caplog.at_level(logging.INFO),
159+
):
160160
self.runner.invoke(command.cli, args=('pull', '--debug'))
161161

162162
logs = [rec.message for rec in self.caplog.records]
@@ -175,23 +175,23 @@ def test_partial_failure_database_integrity(self):
175175

176176
# Add a task to each service.
177177
both_working = {
178-
'test': fake_service(yields_per_target),
179-
'secondary': fake_service(yields_per_target, base=SecondaryService),
178+
'test': fake_service(yields_one(target_specific_url=True)),
179+
'secondary': fake_service(
180+
yields_one(target_specific_url=True), base=SecondaryService
181+
),
180182
}
181-
with register_services(both_working):
182-
with self.caplog.at_level(logging.DEBUG):
183-
self.runner.invoke(command.cli, args=('pull', '--debug'))
183+
with register_services(both_working), self.caplog.at_level(logging.DEBUG):
184+
self.runner.invoke(command.cli, args=('pull', '--debug'))
184185
logs = [rec.message for rec in self.caplog.records]
185186
self.assertIn('Adding 2 tasks', logs)
186187

187188
# Break the secondary service and run pull again.
188189
secondary_broken = {
189-
'test': fake_service(yields_per_target),
190+
'test': fake_service(yields_one(target_specific_url=True)),
190191
'secondary': fake_service(raises, base=SecondaryService),
191192
}
192-
with register_services(secondary_broken):
193-
with self.caplog.at_level(logging.INFO):
194-
self.runner.invoke(command.cli, args=('pull', '--debug'))
193+
with register_services(secondary_broken), self.caplog.at_level(logging.INFO):
194+
self.runner.invoke(command.cli, args=('pull', '--debug'))
195195
logs = [rec.message for rec in self.caplog.records]
196196

197197
# Make sure my_broken_service failed while my_service succeeded.
@@ -209,13 +209,15 @@ def test_locked_repository(self, file_lock):
209209
"""
210210
# The service is never collected (the lock fails first), but config
211211
# loading still resolves it.
212-
self.enter_context(register_services({'test': DumbService}))
213212
lockfile_path = pathlib.Path(self.lists_path) / 'bugwarrior.lockfile'
214213
file_lock.return_value.__enter__.side_effect = command.Timeout(
215214
str(lockfile_path)
216215
)
217216

218-
with self.caplog.at_level(logging.CRITICAL):
217+
with (
218+
register_services({'test': DumbService}),
219+
self.caplog.at_level(logging.CRITICAL),
220+
):
219221
result = self.runner.invoke(command.cli, args=('pull', '--debug'))
220222

221223
self.assertEqual(result.exit_code, 1)
@@ -231,9 +233,10 @@ def test_legacy_cli(self):
231233
232234
Also test that it logs a deprecation warning.
233235
"""
234-
self.enter_context(register_services({'test': fake_service(yields_one)}))
235-
236-
with self.caplog.at_level(logging.INFO):
236+
with (
237+
register_services({'test': fake_service(yields_one())}),
238+
self.caplog.at_level(logging.INFO),
239+
):
237240
self.runner.invoke(command.pull, args=('--debug'))
238241

239242
logs = [rec.message for rec in self.caplog.records]

tests/test_db.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ def test_handles_missing_tags(self):
6363
class TestSynchronize(ConfigTest):
6464
def setUp(self):
6565
super().setUp()
66-
# synchronize() resolves the service via get_service for UNIQUE_KEY/UDAS.
67-
self.enter_context(register_services())
66+
self.enterContext(register_services())
6867
self.bwconfig = Config(
6968
service_configs=[DumbConfig(target='my_service')],
7069
main=schema.MainSectionConfig(

0 commit comments

Comments
 (0)