Merge lp:~allenap/maas/remove-unused-testing-things into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5649
Proposed branch: lp:~allenap/maas/remove-unused-testing-things
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 450 lines (+20/-285)
4 files modified
src/maasserver/testing/testcase.py (+1/-140)
src/maastesting/tests/test_factory.py (+13/-10)
src/maastesting/utils.py (+2/-128)
src/provisioningserver/utils/tests/test_fs.py (+4/-7)
To merge this branch: bzr merge lp:~allenap/maas/remove-unused-testing-things
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Review via email: mp+314210@code.launchpad.net

Commit message

Remove SeleniumTestCase and other miscellaneous unused or little-used testing bits.

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) wrote :

LGTM!

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

I'm holding on this for a few days because I may end up using run_isolated elsewhere.

Revision history for this message
Gavin Panella (allenap) wrote :

I didn't end up using run_isolated where I thought I might.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/testing/testcase.py'
2--- src/maasserver/testing/testcase.py 2017-01-05 16:18:56 +0000
3+++ src/maasserver/testing/testcase.py 2017-01-06 10:04:40 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
6+# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Custom test-case classes."""
10@@ -8,24 +8,15 @@
11 'MAASLegacyTransactionServerTestCase',
12 'MAASServerTestCase',
13 'MAASTransactionServerTestCase',
14- 'SeleniumTestCase',
15 'SerializationFailureTestCase',
16- 'TestWithoutCrochetMixin',
17 'UniqueViolationTestCase',
18 ]
19
20 from itertools import count
21-import socketserver
22 import sys
23 import threading
24-from unittest import SkipTest
25-from unittest.mock import Mock
26 import warnings
27-import wsgiref
28
29-import crochet
30-import django
31-from django.core.urlresolvers import reverse
32 from django.db import (
33 close_old_connections,
34 connection,
35@@ -36,7 +27,6 @@
36 IntegrityError,
37 OperationalError,
38 )
39-from fixtures import Fixture
40 from maasserver.fields import register_mac_type
41 from maasserver.testing.factory import factory
42 from maasserver.testing.fixtures import (
43@@ -54,9 +44,7 @@
44 DjangoTestCase,
45 DjangoTransactionTestCase,
46 )
47-from maastesting.fixtures import DisplayFixture
48 from maastesting.testcase import MAASTestCase
49-from maastesting.utils import run_isolated
50
51
52 class MAASRegionTestCaseBase(PostCommitHooksTestMixin):
53@@ -215,133 +203,6 @@
54 self.setUpFixtures()
55
56
57-# Django supports Selenium tests only since version 1.4.
58-django_supports_selenium = (django.VERSION >= (1, 4))
59-
60-if django_supports_selenium:
61- from django.test import LiveServerTestCase
62- from selenium.webdriver.firefox.webdriver import WebDriver
63-else:
64- LiveServerTestCase = object # noqa
65-
66-
67-class LogSilencerFixture(Fixture):
68-
69- old_handle_error = wsgiref.handlers.BaseHandler.handle_error
70- old_log_exception = wsgiref.handlers.BaseHandler.log_exception
71-
72- def setUp(self):
73- super(LogSilencerFixture, self).setUp()
74- self.silence_loggers()
75- self.addCleanup(self.unsilence_loggers)
76-
77- def silence_loggers(self):
78- # Silence logging of errors to avoid the
79- # "IOError: [Errno 32] Broken pipe" error.
80- socketserver.BaseServer.handle_error = Mock()
81- wsgiref.handlers.BaseHandler.log_exception = Mock()
82-
83- def unsilence_loggers(self):
84- """Restore original handle_error/log_exception methods."""
85- socketserver.BaseServer.handle_error = self.old_handle_error
86- wsgiref.handlers.BaseHandler.log_exception = self.old_log_exception
87-
88-
89-class SeleniumTestCase(
90- DjangoTransactionTestCase, LiveServerTestCase,
91- PostCommitHooksTestMixin):
92- """Selenium-enabled test case.
93-
94- Two users are pre-created: "user" for a regular user account, or "admin"
95- for an administrator account. Both have the password "test". You can log
96- in as either using `log_in`.
97- """
98-
99- # Load the selenium test fixture.
100- fixtures = ['src/maastesting/protractor/fixture.yaml']
101-
102- @classmethod
103- def setUpClass(cls):
104- if not django_supports_selenium:
105- return
106- cls.display = DisplayFixture()
107- cls.display.__enter__()
108-
109- cls.silencer = LogSilencerFixture()
110- cls.silencer.__enter__()
111-
112- cls.selenium = WebDriver()
113- super(SeleniumTestCase, cls).setUpClass()
114-
115- def setUp(self):
116- if not django_supports_selenium:
117- raise SkipTest(
118- "Live tests only enabled if Django.version >=1.4.")
119- super(SeleniumTestCase, self).setUp()
120-
121- @classmethod
122- def tearDownClass(cls):
123- if not django_supports_selenium:
124- return
125- cls.selenium.quit()
126- cls.display.__exit__(None, None, None)
127- cls.silencer.__exit__(None, None, None)
128- super(SeleniumTestCase, cls).tearDownClass()
129-
130- def log_in(self, user='user', password='test'):
131- """Log in as the given user. Defaults to non-admin user."""
132- self.get_page('login')
133- username_input = self.selenium.find_element_by_id("id_username")
134- username_input.send_keys(user)
135- password_input = self.selenium.find_element_by_id("id_password")
136- password_input.send_keys(password)
137- self.selenium.find_element_by_xpath('//input[@value="Login"]').click()
138-
139- def get_page(self, *reverse_args, **reverse_kwargs):
140- """GET a page. Arguments are passed on to `reverse`."""
141- path = reverse(*reverse_args, **reverse_kwargs)
142- return self.selenium.get("%s%s" % (self.live_server_url, path))
143-
144-
145-class TestWithoutCrochetMixin:
146- """Ensure that Crochet's event-loop is not running.
147-
148- Crochet's event-loop cannot easily be resurrected, so this runs each
149- test in a new subprocess. There we can stop Crochet without worrying
150- about how to get it going again.
151-
152- Use this where tests must, for example, patch out global state
153- during testing, where those patches coincide with things that
154- Crochet expects to use too, ``time.sleep`` for example.
155- """
156-
157- _dead_thread = threading.Thread()
158- _dead_thread.start()
159- _dead_thread.join()
160-
161- def __call__(self, result=None):
162- if result is None:
163- result = self.defaultTestResult()
164- # nose.proxy.ResultProxy.assertMyTest() is weird, and makes
165- # things break, so we neutralise it here.
166- result.assertMyTest = lambda test: None
167- # Finally, run the test in a subprocess.
168- up = super(TestWithoutCrochetMixin, self.__class__)
169- run_isolated(up, self, result)
170-
171- run = __call__
172-
173- def setUp(self):
174- super(TestWithoutCrochetMixin, self).setUp()
175- # Ensure that Crochet's event-loop has shutdown. The following
176- # runs in the child process started by run_isolated() so we
177- # don't need to repair the damage we do.
178- if crochet._watchdog.is_alive():
179- crochet._watchdog._canary = self._dead_thread
180- crochet._watchdog.join() # Wait for the watchdog to stop.
181- self.assertFalse(crochet.reactor.running)
182-
183-
184 class SerializationFailureTestCase(
185 MAASTransactionServerTestCase, PostCommitHooksTestMixin):
186
187
188=== modified file 'src/maastesting/tests/test_factory.py'
189--- src/maastesting/tests/test_factory.py 2016-08-09 15:56:46 +0000
190+++ src/maastesting/tests/test_factory.py 2017-01-06 10:04:40 +0000
191@@ -1,4 +1,4 @@
192-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
193+# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
194 # GNU Affero General Public License version 3 (see the file LICENSE).
195
196 """Test the factory where appropriate. Don't overdo this."""
197@@ -8,11 +8,11 @@
198 from datetime import datetime
199 from itertools import count
200 import os.path
201-import random
202 from random import randint
203 import subprocess
204 from unittest.mock import sentinel
205
206+from maastesting import factory as factory_module
207 from maastesting.factory import (
208 factory,
209 TooManyRandomRetries,
210@@ -23,7 +23,6 @@
211 MockCalledOnceWith,
212 )
213 from maastesting.testcase import MAASTestCase
214-from maastesting.utils import FakeRandInt
215 from netaddr import (
216 IPAddress,
217 IPNetwork,
218@@ -59,17 +58,21 @@
219 # Artificially limit randint to a very narrow range, to guarantee
220 # some repetition in its output, and virtually guarantee that we test
221 # both outcomes of the flip-a-coin call in make_vlan_tag.
222- self.patch(random, 'randint', FakeRandInt(random.randint, 0, 1))
223- outcomes = {factory.make_vlan_tag() for _ in range(1000)}
224- self.assertEqual({1}, outcomes)
225+ random = self.patch(factory_module, "random")
226+ random.randint.side_effect = [1, 2]
227+ outcomes = {factory.make_vlan_tag(), factory.make_vlan_tag()}
228+ self.assertEqual({1, 2}, outcomes)
229
230 def test_make_vlan_tag_includes_None_if_allow_none(self):
231- self.patch(random, 'randint', FakeRandInt(random.randint, 0, 1))
232+ random = self.patch(factory_module, "random")
233+ random.choice.side_effect = [True, False, False]
234+ random.randint.side_effect = [1, 2]
235 self.assertEqual(
236- {None, 1},
237+ {None, 1, 2},
238 {
239- factory.make_vlan_tag(allow_none=True)
240- for _ in range(1000)
241+ factory.make_vlan_tag(allow_none=True),
242+ factory.make_vlan_tag(allow_none=True),
243+ factory.make_vlan_tag(allow_none=True),
244 })
245
246 def test_make_ipv4_address(self):
247
248=== modified file 'src/maastesting/utils.py'
249--- src/maastesting/utils.py 2015-12-01 18:12:59 +0000
250+++ src/maastesting/utils.py 2017-01-06 10:04:40 +0000
251@@ -1,32 +1,17 @@
252-# Copyright 2012-2015 Canonical Ltd. This software is licensed under the
253+# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
254 # GNU Affero General Public License version 3 (see the file LICENSE).
255
256 """Testing utilities."""
257
258 __all__ = [
259 "age_file",
260- "content_from_file",
261 "extract_word_list",
262- "get_write_time",
263- "FakeRandInt",
264- "preexec_fn",
265- "run_isolated",
266 "sample_binary_data",
267- ]
268+]
269
270 import codecs
271 import os
272 import re
273-import signal
274-from sys import (
275- stderr,
276- stdout,
277-)
278-from traceback import print_exc
279-
280-import subunit
281-from testtools.content import Content
282-from testtools.content_type import UTF8_TEXT
283
284
285 def age_file(path, seconds):
286@@ -37,26 +22,6 @@
287 os.utime(path, (atime, mtime - seconds))
288
289
290-def get_write_time(path):
291- """Return last modification time of file at `path`."""
292- return os.stat(path).st_mtime
293-
294-
295-def content_from_file(path):
296- """Alternative to testtools' version.
297-
298- This keeps an open file-handle, so it can obtain the log even when the
299- file has been unlinked.
300- """
301- fd = open(path, "rb")
302-
303- def iterate():
304- fd.seek(0)
305- return iter(fd)
306-
307- return Content(UTF8_TEXT, iterate)
308-
309-
310 def extract_word_list(string):
311 """Return a list of words from a string.
312
313@@ -66,75 +31,6 @@
314 return re.findall("[^,;\s]+", string)
315
316
317-def preexec_fn():
318- # Revert Python's handling of SIGPIPE. See
319- # http://bugs.python.org/issue1652 for more info.
320- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
321-
322-
323-class BytesToStdout:
324- """File-like object to forward bytes to a text-mode `stdout`.
325-
326- Bytes are decoded as ASCII and unrecognised characters are replaced.
327- """
328-
329- def write(self, data):
330- string = data.decode("ascii", "replace")
331- stdout.write(string)
332-
333-
334-def run_isolated(cls, self, result):
335- """Run a test suite or case in a subprocess.
336-
337- This is derived from ``subunit.run_isolated``. Subunit's version
338- clobbers stdout by dup'ing the subunit's stream over the top, which
339- prevents effective debugging at the terminal. This variant does not
340- suffer from the same issue.
341- """
342- c2pread, c2pwrite = os.pipe()
343- pid = os.fork()
344- if pid == 0:
345- # Child: runs test and writes subunit to c2pwrite.
346- try:
347- os.close(c2pread)
348- stream = os.fdopen(c2pwrite, 'wb')
349- sender = subunit.TestProtocolClient(stream)
350- cls.run(self, sender)
351- stream.flush()
352- stdout.flush()
353- stderr.flush()
354- except:
355- # Print error and exit hard.
356- try:
357- print_exc(file=stderr)
358- stderr.flush()
359- finally:
360- os._exit(2)
361- finally:
362- # Exit hard.
363- os._exit(0)
364- else:
365- # TestProtocolServer, by default, will write non-subunit content to
366- # stdout as *bytes*. In Python 3 it assumes that stdout has a `buffer`
367- # attribute which can accept bytes. However, nose buffers test output,
368- # and replaces sys.stdout with only a StringIO instance.
369- try:
370- stdout.write(b"")
371- except TypeError:
372- try:
373- output = stdout.buffer
374- except AttributeError:
375- output = BytesToStdout()
376- else:
377- output = stdout
378- # Parent: receives subunit from c2pread.
379- os.close(c2pwrite)
380- stream = os.fdopen(c2pread, 'rb')
381- receiver = subunit.TestProtocolServer(result, output)
382- receiver.readFrom(stream)
383- os.waitpid(pid, 0)
384-
385-
386 # Some horrible binary data that could never, ever, under any encoding
387 # known to man(1) survive mis-interpretation as text.
388 #
389@@ -146,25 +42,3 @@
390 # (1) Provided, of course, that man know only about ASCII and
391 # UTF.
392 sample_binary_data = codecs.BOM64_LE + codecs.BOM64_BE + b'\x00\xff\x00'
393-
394-
395-class FakeRandInt:
396- """Fake `randint` with forced limitations on its range.
397-
398- This lets you set a forced minimum, and/or a forced maximum, on the range
399- of any call. For example, if you pass `forced_maximum=3`, then a call
400- will never return more than 3. If you don't set a maximum, or if the
401- call's maximum argument is less than the forced maximum, then the call's
402- maximum will be respected.
403- """
404- def __init__(self, real_randint, forced_minimum=None, forced_maximum=None):
405- self.real_randint = real_randint
406- self.minimum = forced_minimum
407- self.maximum = forced_maximum
408-
409- def __call__(self, minimum, maximum):
410- if self.minimum is not None:
411- minimum = max(minimum, self.minimum)
412- if self.maximum is not None:
413- maximum = min(maximum, self.maximum)
414- return self.real_randint(minimum, maximum)
415
416=== modified file 'src/provisioningserver/utils/tests/test_fs.py'
417--- src/provisioningserver/utils/tests/test_fs.py 2016-12-14 08:43:09 +0000
418+++ src/provisioningserver/utils/tests/test_fs.py 2017-01-06 10:04:40 +0000
419@@ -1,4 +1,4 @@
420-# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
421+# Copyright 2014-2017 Canonical Ltd. This software is licensed under the
422 # GNU Affero General Public License version 3 (see the file LICENSE).
423
424 """Tests for filesystem-related utilities."""
425@@ -31,10 +31,7 @@
426 MockNotCalled,
427 )
428 from maastesting.testcase import MAASTestCase
429-from maastesting.utils import (
430- age_file,
431- get_write_time,
432-)
433+from maastesting.utils import age_file
434 import provisioningserver.config
435 from provisioningserver.utils.fs import (
436 atomic_copy,
437@@ -217,11 +214,11 @@
438 contents = factory.make_bytes()
439 dest = self.make_file(contents=contents)
440 age_file(dest, 100)
441- original_write_time = get_write_time(dest)
442+ original_write_time = os.stat(dest).st_mtime
443 loader = self.make_file(contents=contents)
444 atomic_copy(loader, dest)
445 self.assertThat(dest, FileContains(contents))
446- self.assertEqual(original_write_time, get_write_time(dest))
447+ self.assertEqual(original_write_time, os.stat(dest).st_mtime)
448
449 def test__sweeps_aside_dot_new_if_any(self):
450 contents = factory.make_bytes()