forked from wikimedia/pywikibot
-
Notifications
You must be signed in to change notification settings - Fork 3
/
bot_tests.py
executable file
·323 lines (265 loc) · 11 KB
/
bot_tests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
#!/usr/bin/env python3
"""Bot tests."""
#
# (C) Pywikibot team, 2015-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations
import sys
from contextlib import suppress
import pywikibot
import pywikibot.bot
from pywikibot import i18n
from tests.aspects import (
DefaultSiteTestCase,
SiteAttributeTestCase,
TestCase,
unittest,
)
class TWNBotTestCase(TestCase):
"""Verify that i18n is available."""
@classmethod
def setUpClass(cls):
"""Verify that the translations are available."""
if not i18n.messages_available():
raise unittest.SkipTest(
f'i18n messages package {i18n._messages_package_name!r} not'
' available.')
super().setUpClass()
class TestBotTreatExit:
"""Mixin to provide handling for treat and exit."""
def _treat(self, pages, post_treat=None):
"""Get tests which are executed on each treat.
It uses pages as an iterator and compares the page given to the page
returned by pages iterator. It checks that the bot's _site and site
attributes are set to the page's site. If _treat_site is set with a
Site it compares it to that one too.
Afterwards it calls post_treat so it's possible to do additional
checks.
Site attributes are only present on Bot and SingleSitesBot, not
MultipleSitesBot.
"""
def treat(page):
self.assertEqual(page, next(self._page_iter))
if self._treat_site is None:
self.assertFalse(hasattr(self.bot, 'site'))
self.assertFalse(hasattr(self.bot, '_site'))
elif not isinstance(self.bot, pywikibot.bot.MultipleSitesBot):
self.assertIsNotNone(self.bot._site)
self.assertEqual(self.bot.site, self.bot._site)
if self._treat_site:
self.assertEqual(self.bot._site, self._treat_site)
self.assertEqual(page.site, self.bot.site)
if post_treat:
post_treat(page)
self._page_iter = iter(pages)
return treat
def _treat_page(self, pages=True, post_treat=None):
"""Adjust to CurrentPageBot signature.
It uses almost the same logic as _treat but returns a wrapper function
which itself calls the function returned by _treat.
The pages may be set to True which sill use _treat_generator as the
source for the pages.
"""
def treat_page():
treat(self.bot.current_page)
if pages is True:
pages = self._treat_generator()
treat = self._treat(pages, post_treat)
return treat_page
def _exit(self, treated, written=0, exception=None):
"""Get tests which are executed on exit."""
def exit():
exc = sys.exc_info()[0]
if exc is AssertionError:
# When an AssertionError happened we shouldn't do these
# assertions as they are invalid anyway and hide the actual
# failed assertion
return # pragma: no cover
self.assertEqual(self.bot.counter['read'], treated)
self.assertEqual(self.bot.counter['write'], written)
if exception:
self.assertIs(exc, exception)
else:
self.assertIsNone(exc)
with self.assertRaisesRegex(StopIteration, '^$'):
next(self._page_iter)
return exit
class TestDrySiteBot(TestBotTreatExit, SiteAttributeTestCase):
"""Tests for the BaseBot subclasses."""
CANT_SET_ATTRIBUTE_RE = "can't set attribute"
NOT_IN_TREAT_RE = 'Requesting the site not while in treat is not allowed.'
dry = True
sites = {
'de': {
'family': 'wikipedia',
'code': 'de'
},
'en': {
'family': 'wikipedia',
'code': 'en'
}
}
def _generator(self):
"""Generic generator."""
yield pywikibot.Page(self.de, 'Page 1')
yield pywikibot.Page(self.en, 'Page 2')
yield pywikibot.Page(self.de, 'Page 3')
yield pywikibot.Page(self.en, 'Page 4')
def test_SingleSiteBot_automatic(self):
"""Test SingleSiteBot class with no predefined site."""
self._treat_site = self.de
self.bot = pywikibot.bot.SingleSiteBot(site=None,
generator=self._generator())
self.bot.treat = self._treat([pywikibot.Page(self.de, 'Page 1'),
pywikibot.Page(self.de, 'Page 3')])
self.bot.exit = self._exit(2)
self.bot.run()
self.assertEqual(self.bot.site, self._treat_site)
def test_SingleSiteBot_specific(self):
"""Test SingleSiteBot class with predefined site."""
self._treat_site = self.en
self.bot = pywikibot.bot.SingleSiteBot(site=self.en,
generator=self._generator())
self.bot.treat = self._treat([pywikibot.Page(self.en, 'Page 2'),
pywikibot.Page(self.en, 'Page 4')])
self.bot.exit = self._exit(2)
self.bot.run()
self.assertEqual(self.bot.site, self._treat_site)
def test_MultipleSitesBot(self):
"""Test MultipleSitesBot class."""
# Assert no specific site
self._treat_site = False
self.bot = pywikibot.bot.MultipleSitesBot(generator=self._generator())
self.bot.treat = self._treat(self._generator())
self.bot.exit = self._exit(4)
self.bot.run()
def test_Bot(self):
"""Test normal Bot class."""
# Assert no specific site
self._treat_site = False
self.bot = pywikibot.bot.Bot(generator=self._generator())
self.bot.treat = self._treat(self._generator())
self.bot.exit = self._exit(4)
self.bot.run()
def test_CurrentPageBot(self):
"""Test normal Bot class."""
def post_treat(page):
self.assertIs(self.bot.current_page, page)
# Assert no specific site
self._treat_site = None
self.bot = pywikibot.bot.CurrentPageBot(generator=self._generator())
self.bot.treat_page = self._treat_page(self._generator(), post_treat)
self.bot.exit = self._exit(4)
self.bot.run()
def test_Bot_ValueError(self):
"""Test normal Bot class with a ValueError in treat."""
def post_treat(page):
if page.title() == 'Page 3':
raise ValueError('Whatever')
self._treat_site = False
self.bot = pywikibot.bot.Bot(generator=self._generator())
self.bot.treat = self._treat([pywikibot.Page(self.de, 'Page 1'),
pywikibot.Page(self.en, 'Page 2'),
pywikibot.Page(self.de, 'Page 3')],
post_treat)
self.bot.exit = self._exit(3, exception=ValueError)
with self.assertRaisesRegex(ValueError, 'Whatever'):
self.bot.run()
def test_Bot_KeyboardInterrupt(self):
"""Test normal Bot class with a KeyboardInterrupt in treat."""
def post_treat(page):
if page.title() == 'Page 3':
raise KeyboardInterrupt('Whatever')
self._treat_site = False
self.bot = pywikibot.bot.Bot(generator=self._generator())
self.bot.treat = self._treat([pywikibot.Page(self.de, 'Page 1'),
pywikibot.Page(self.en, 'Page 2'),
pywikibot.Page(self.de, 'Page 3')],
post_treat)
self.bot.exit = self._exit(3, exception=None)
self.bot.run()
# TODO: This could be written as dry tests probably by faking the important
# properties
class LiveBotTestCase(TestBotTreatExit, DefaultSiteTestCase):
"""Test bot classes which need to check the Page object live."""
def _treat_generator(self):
"""Yield the current page until it's None."""
while self._current_page:
yield self._current_page
def _missing_generator(self):
"""Yield pages and the last one does not exist."""
self._count = 0 # skip_page skips one page
self._current_page = next(self.site.allpages(total=1))
yield self._current_page
while self._current_page.exists():
self._count += 1
self._current_page = pywikibot.Page(
self.site, self._current_page.title() + 'X')
yield self._current_page
self._current_page = None
def _exit(self, treated=None, written=0, exception=None):
"""Set the number of treated pages to _count."""
def exit():
t = self._count if treated is None else treated
# Due to PEP 3135 super()._exit(...)() would raise
# RuntimeError: super(): no arguments
super(LiveBotTestCase, self)._exit(t, written, exception)()
return exit
def test_ExistingPageBot(self):
"""Test ExistingPageBot class."""
def post_treat(page):
"""Verify the page exists."""
self.assertTrue(page.exists())
self._treat_site = None
self.bot = pywikibot.bot.ExistingPageBot(
generator=self._missing_generator())
self.bot.treat_page = self._treat_page(post_treat=post_treat)
self.bot.exit = self._exit()
self.bot.run()
def test_CreatingPageBot(self):
"""Test CreatingPageBot class."""
# This doesn't verify much (e.g. it could yield the first existing
# page) but the assertion in post_treat should verify that the page
# is valid
def treat_generator():
"""Yield just one current page (the last one)."""
yield self._current_page
def post_treat(page):
"""Verify the page is missing."""
self.assertFalse(page.exists())
self._treat_site = None
self.bot = pywikibot.bot.CreatingPageBot(
generator=self._missing_generator())
self.bot.treat_page = self._treat_page(treat_generator(), post_treat)
self.bot.exit = self._exit()
self.bot.run()
class Options(pywikibot.bot.OptionHandler):
"""A derived OptionHandler class."""
available_options = {
'foo': 'bar',
'bar': 42,
'baz': False
}
class TestOptionHandler(TestCase):
"""OptionHandler test class."""
dry = True
def setUp(self):
"""Setup tests."""
self.option_handler = Options(baz=True)
super().setUp()
def test_opt_values(self):
"""Test OptionHandler."""
oh = self.option_handler
self.assertEqual(oh.opt.foo, 'bar')
self.assertEqual(oh.opt.bar, 42)
self.assertTrue(oh.opt.baz)
self.assertEqual(oh.opt.foo, oh.opt['foo'])
oh.opt.baz = 'Hey'
self.assertEqual(oh.opt.baz, 'Hey')
self.assertEqual(oh.opt['baz'], 'Hey')
self.assertNotIn('baz', oh.opt.__dict__)
if __name__ == '__main__':
with suppress(SystemExit):
unittest.main()