Merge lp:~facundo/magicicada-client/full-closed-venv into lp:magicicada-client

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 1447
Merged at revision: 1447
Proposed branch: lp:~facundo/magicicada-client/full-closed-venv
Merge into: lp:magicicada-client
Diff against target: 4651 lines (+2923/-353)
62 files modified
Makefile (+1/-1)
contrib/devtools/__init__.py (+1/-0)
contrib/devtools/compat.py (+49/-0)
contrib/devtools/errors.py (+35/-0)
contrib/devtools/handlers.py (+97/-0)
contrib/devtools/reactors/__init__.py (+1/-0)
contrib/devtools/reactors/gi.py (+53/-0)
contrib/devtools/runners/__init__.py (+305/-0)
contrib/devtools/runners/txrunner.py (+133/-0)
contrib/devtools/services/__init__.py (+66/-0)
contrib/devtools/services/dbus.py (+119/-0)
contrib/devtools/testcases/__init__.py (+187/-0)
contrib/devtools/testcases/dbus.py (+138/-0)
contrib/devtools/testcases/txsocketserver.py (+358/-0)
contrib/devtools/testing/__init__.py (+1/-0)
contrib/devtools/testing/txcheck.py (+381/-0)
contrib/devtools/testing/txwebserver.py (+125/-0)
contrib/devtools/utils.py (+181/-0)
contrib/dirspec/__init__.py (+16/-0)
contrib/dirspec/basedir.py (+159/-0)
contrib/dirspec/utils.py (+188/-0)
contrib/u1trial (+41/-0)
dependencies-devel.txt (+0/-3)
dependencies.txt (+5/-8)
magicicadaclient/platform/tests/filesystem_notifications/__init__.py (+3/-2)
magicicadaclient/platform/tests/filesystem_notifications/common.py (+12/-13)
magicicadaclient/platform/tests/filesystem_notifications/test_darwin.py (+10/-9)
magicicadaclient/platform/tests/filesystem_notifications/test_filesystem_notifications.py (+2/-1)
magicicadaclient/platform/tests/filesystem_notifications/test_fsevents_daemon.py (+10/-15)
magicicadaclient/platform/tests/ipc/test_linux.py (+2/-4)
magicicadaclient/platform/tests/ipc/test_perspective_broker.py (+3/-8)
magicicadaclient/platform/tests/os_helper/test_darwin.py (+3/-7)
magicicadaclient/platform/tests/os_helper/test_linux.py (+3/-6)
magicicadaclient/platform/tests/session/test_common.py (+2/-1)
magicicadaclient/platform/tests/session/test_linux.py (+7/-13)
magicicadaclient/platform/tests/test_tools.py (+4/-6)
magicicadaclient/syncdaemon/tests/test_action_queue.py (+14/-14)
magicicadaclient/syncdaemon/tests/test_eq_inotify.py (+11/-15)
magicicadaclient/syncdaemon/tests/test_eventqueue.py (+2/-6)
magicicadaclient/syncdaemon/tests/test_fileshelf.py (+26/-31)
magicicadaclient/syncdaemon/tests/test_fsm.py (+36/-33)
magicicadaclient/syncdaemon/tests/test_hashqueue.py (+16/-16)
magicicadaclient/syncdaemon/tests/test_interaction_interfaces.py (+11/-15)
magicicadaclient/syncdaemon/tests/test_localrescan.py (+15/-15)
magicicadaclient/syncdaemon/tests/test_logger.py (+5/-7)
magicicadaclient/syncdaemon/tests/test_main.py (+5/-5)
magicicadaclient/syncdaemon/tests/test_offloadqueue.py (+3/-2)
magicicadaclient/syncdaemon/tests/test_pathlockingtree.py (+2/-5)
magicicadaclient/syncdaemon/tests/test_sync.py (+13/-9)
magicicadaclient/syncdaemon/tests/test_tritcask.py (+11/-13)
magicicadaclient/syncdaemon/tests/test_vm.py (+11/-9)
magicicadaclient/testing/testcase.py (+1/-1)
magicicadaclient/utils/__init__.py (+3/-4)
magicicadaclient/utils/tests/test_common.py (+4/-4)
magicicadaclient/utils/tests/test_ipc.py (+5/-5)
magicicadaclient/utils/tests/test_tcpactivation.py (+2/-5)
magicicadaclient/utils/tests/test_translation.py (+1/-1)
magicicadaclient/utils/tests/test_txsecrets.py (+2/-2)
requirements-devel.txt (+2/-0)
requirements.txt (+6/-1)
run-tests (+1/-1)
setup.py (+14/-37)
To merge this branch: bzr merge lp:~facundo/magicicada-client/full-closed-venv
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+347341@code.launchpad.net

Commit message

Went full venv.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

20:43 < nessita> revisé tu branch, todo ok, pero no entiendo cómo anda "from devtools import..." porque no veo que nadie meta contrib en el pythonpath
20:44 < nessita> (y de elegir, preferiría que los imports sean from contrib.devtools ...)
20:45 < Facu> nessita, hola! no sé, todo lo que puse en contrib se importó solo, así que ahí no toqué nada
20:46 < Facu>| digo, ese mecanismo ya estaba de antes
20:48 < nessita> Facu, lo puedo landear así pero me da cosa que no veo quien carajo lo mete en el pythonpath
21:02 < Facu> nessita, +1 a "sacar eso", pero se podría hacer después; en cualquier caso, tenemos cosas más "urgentes" para hacer, yo ni le pondría prioridad
21:03 < nessita> okis

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2018-04-14 23:34:20 +0000
3+++ Makefile 2018-06-03 23:09:20 +0000
4@@ -43,7 +43,7 @@
5 cat dependencies-devel.txt | xargs apt-get install -y --no-install-recommends
6
7 venv:
8- virtualenv --system-site-packages $(ENV)
9+ virtualenv $(ENV)
10 $(ENV)/bin/pip install -r requirements.txt -r requirements-devel.txt
11
12 lint:
13
14=== added directory 'contrib/devtools'
15=== added file 'contrib/devtools/__init__.py'
16--- contrib/devtools/__init__.py 1970-01-01 00:00:00 +0000
17+++ contrib/devtools/__init__.py 2018-06-03 23:09:20 +0000
18@@ -0,0 +1,1 @@
19+"""Testing utilities for Ubuntu One client code."""
20
21=== added file 'contrib/devtools/compat.py'
22--- contrib/devtools/compat.py 1970-01-01 00:00:00 +0000
23+++ contrib/devtools/compat.py 2018-06-03 23:09:20 +0000
24@@ -0,0 +1,49 @@
25+# -*- coding: utf-8 -*-
26+#
27+# Copyright 2012 Canonical Ltd.
28+#
29+# This program is free software: you can redistribute it and/or modify it
30+# under the terms of the GNU General Public License version 3, as published
31+# by the Free Software Foundation.
32+#
33+# This program is distributed in the hope that it will be useful, but
34+# WITHOUT ANY WARRANTY; without even the implied warranties of
35+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
36+# PURPOSE. See the GNU General Public License for more details.
37+#
38+# You should have received a copy of the GNU General Public License along
39+# with this program. If not, see <http://www.gnu.org/licenses/>.
40+#
41+# In addition, as a special exception, the copyright holders give
42+# permission to link the code of portions of this program with the
43+# OpenSSL library under certain conditions as described in each
44+# individual source file, and distribute linked combinations
45+# including the two.
46+# You must obey the GNU General Public License in all respects
47+# for all of the code used other than OpenSSL. If you modify
48+# file(s) with this exception, you may extend this exception to your
49+# version of the file(s), but you are not obligated to do so. If you
50+# do not wish to do so, delete this exception statement from your
51+# version. If you delete this exception statement from all source
52+# files in the program, then also delete it here.
53+"""Python 2 and 3 compatibility."""
54+
55+from __future__ import unicode_literals
56+
57+# The following approach was outlined in Lennart Regebro's
58+# "Porting to Python 3" book.
59+# http://python3porting.com/noconv.html#more-bytes-strings-and-unicode
60+
61+import sys
62+
63+# Disable redefined builtin, invalid name warning
64+# pylint: disable=W0622,C0103
65+
66+if sys.version_info < (3,):
67+ text_type = unicode
68+ binary_type = str
69+ basestring = basestring
70+else:
71+ text_type = str
72+ binary_type = bytes
73+ basestring = str
74
75=== added file 'contrib/devtools/errors.py'
76--- contrib/devtools/errors.py 1970-01-01 00:00:00 +0000
77+++ contrib/devtools/errors.py 2018-06-03 23:09:20 +0000
78@@ -0,0 +1,35 @@
79+# Copyright 2012 Canonical Ltd.
80+#
81+# This program is free software: you can redistribute it and/or modify it
82+# under the terms of the GNU General Public License version 3, as published
83+# by the Free Software Foundation.
84+#
85+# This program is distributed in the hope that it will be useful, but
86+# WITHOUT ANY WARRANTY; without even the implied warranties of
87+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
88+# PURPOSE. See the GNU General Public License for more details.
89+#
90+# You should have received a copy of the GNU General Public License along
91+# with this program. If not, see <http://www.gnu.org/licenses/>.
92+#
93+# In addition, as a special exception, the copyright holders give
94+# permission to link the code of portions of this program with the
95+# OpenSSL library under certain conditions as described in each
96+# individual source file, and distribute linked combinations
97+# including the two.
98+# You must obey the GNU General Public License in all respects
99+# for all of the code used other than OpenSSL. If you modify
100+# file(s) with this exception, you may extend this exception to your
101+# version of the file(s), but you are not obligated to do so. If you
102+# do not wish to do so, delete this exception statement from your
103+# version. If you delete this exception statement from all source
104+# files in the program, then also delete it here.
105+"""Custom error types for Ubuntu One developer tools."""
106+
107+
108+class TestError(Exception):
109+ """An error occurred in attempting to load or start the tests."""
110+
111+
112+class UsageError(Exception):
113+ """An error occurred in parsing the command line arguments."""
114
115=== added file 'contrib/devtools/handlers.py'
116--- contrib/devtools/handlers.py 1970-01-01 00:00:00 +0000
117+++ contrib/devtools/handlers.py 2018-06-03 23:09:20 +0000
118@@ -0,0 +1,97 @@
119+# -*- coding: utf-8 -*-
120+
121+# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
122+# Author: Facundo Batista <facundo@canonical.com>
123+#
124+# Copyright 2009-2012 Canonical Ltd.
125+#
126+# This program is free software: you can redistribute it and/or modify it
127+# under the terms of the GNU General Public License version 3, as published
128+# by the Free Software Foundation.
129+#
130+# This program is distributed in the hope that it will be useful, but
131+# WITHOUT ANY WARRANTY; without even the implied warranties of
132+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
133+# PURPOSE. See the GNU General Public License for more details.
134+#
135+# You should have received a copy of the GNU General Public License along
136+# with this program. If not, see <http://www.gnu.org/licenses/>.
137+#
138+# In addition, as a special exception, the copyright holders give
139+# permission to link the code of portions of this program with the
140+# OpenSSL library under certain conditions as described in each
141+# individual source file, and distribute linked combinations
142+# including the two.
143+# You must obey the GNU General Public License in all respects
144+# for all of the code used other than OpenSSL. If you modify
145+# file(s) with this exception, you may extend this exception to your
146+# version of the file(s), but you are not obligated to do so. If you
147+# do not wish to do so, delete this exception statement from your
148+# version. If you delete this exception statement from all source
149+# files in the program, then also delete it here.
150+"""Set of helpers handlers."""
151+
152+from __future__ import print_function
153+
154+import logging
155+
156+
157+class MementoHandler(logging.Handler):
158+ """ A handler class which store logging records in a list """
159+
160+ def __init__(self, *args, **kwargs):
161+ """ Create the instance, and add a records attribute. """
162+ logging.Handler.__init__(self, *args, **kwargs)
163+ self.records = []
164+ self.debug = False
165+
166+ def emit(self, record):
167+ """ Just add the record to self.records. """
168+ self.format(record)
169+ self.records.append(record)
170+
171+ def dump_contents(self, msgs):
172+ """Dumps the contents of the MementoHandler."""
173+ if self.debug:
174+ print("Expecting:")
175+ for msg in msgs:
176+ print("\t", msg)
177+ print("MementoHandler contents:")
178+ for rec in self.records:
179+ print("\t", rec.exc_info)
180+ print("\t", logging.getLevelName(rec.levelno))
181+ print("\t\t", rec.message)
182+ print("\t\t", rec.exc_text)
183+
184+ def check(self, level, *msgs):
185+ """Verifies that the msgs are logged in the specified level"""
186+ for rec in self.records:
187+ if rec.levelno == level and all(m in rec.message for m in msgs):
188+ return rec
189+ self.dump_contents(msgs)
190+ return False
191+
192+ def check_debug(self, *msgs):
193+ """Shortcut for checking in DEBUG."""
194+ return self.check(logging.DEBUG, *msgs)
195+
196+ def check_info(self, *msgs):
197+ """Shortcut for checking in INFO."""
198+ return self.check(logging.INFO, *msgs)
199+
200+ def check_warning(self, *msgs):
201+ """Shortcut for checking in WARNING."""
202+ return self.check(logging.WARNING, *msgs)
203+
204+ def check_error(self, *msgs):
205+ """Shortcut for checking in ERROR."""
206+ return self.check(logging.ERROR, *msgs)
207+
208+ def check_exception(self, exception_info, *msgs):
209+ """Shortcut for checking exceptions."""
210+ for rec in self.records:
211+ if rec.levelno == logging.ERROR and \
212+ all(m in rec.exc_text + rec.message for m in msgs) and \
213+ exception_info in rec.exc_info:
214+ return True
215+ return False
216
217=== added directory 'contrib/devtools/reactors'
218=== added file 'contrib/devtools/reactors/__init__.py'
219--- contrib/devtools/reactors/__init__.py 1970-01-01 00:00:00 +0000
220+++ contrib/devtools/reactors/__init__.py 2018-06-03 23:09:20 +0000
221@@ -0,0 +1,1 @@
222+"""Twisted reactors for testing."""
223
224=== added file 'contrib/devtools/reactors/gi.py'
225--- contrib/devtools/reactors/gi.py 1970-01-01 00:00:00 +0000
226+++ contrib/devtools/reactors/gi.py 2018-06-03 23:09:20 +0000
227@@ -0,0 +1,53 @@
228+# Copyright 2009-2012 Canonical Ltd.
229+#
230+# This program is free software: you can redistribute it and/or modify it
231+# under the terms of the GNU General Public License version 3, as published
232+# by the Free Software Foundation.
233+#
234+# This program is distributed in the hope that it will be useful, but
235+# WITHOUT ANY WARRANTY; without even the implied warranties of
236+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
237+# PURPOSE. See the GNU General Public License for more details.
238+#
239+# You should have received a copy of the GNU General Public License along
240+# with this program. If not, see <http://www.gnu.org/licenses/>.
241+#
242+# In addition, as a special exception, the copyright holders give
243+# permission to link the code of portions of this program with the
244+# OpenSSL library under certain conditions as described in each
245+# individual source file, and distribute linked combinations
246+# including the two.
247+# You must obey the GNU General Public License in all respects
248+# for all of the code used other than OpenSSL. If you modify
249+# file(s) with this exception, you may extend this exception to your
250+# version of the file(s), but you are not obligated to do so. If you
251+# do not wish to do so, delete this exception statement from your
252+# version. If you delete this exception statement from all source
253+# files in the program, then also delete it here.
254+"""The introspection based main loop integration reactor for testing."""
255+
256+REACTOR_URL = 'http://twistedmatrix.com/trac/ticket/4558'
257+
258+
259+def load_reactor(reactor_name=None):
260+ """Load the reactor module and return it."""
261+ return __import__(reactor_name, None, None, [''])
262+
263+
264+def install(options=None):
265+ """Install the reactor and parse any options we might need."""
266+ reactor = None
267+ if options is not None and options['gui']:
268+ try:
269+ reactor = load_reactor('twisted.internet.gtk3reactor')
270+ except ImportError:
271+ print('Falling back to gtk2reactor module.')
272+ reactor = load_reactor('twisted.internet.gtk2reactor')
273+ else:
274+ try:
275+ reactor = load_reactor('twisted.internet.gireactor')
276+ except ImportError:
277+ print('Falling back to glib2reactor module.')
278+ reactor = load_reactor('twisted.internet.glib2reactor')
279+
280+ reactor.install()
281
282=== added directory 'contrib/devtools/runners'
283=== added file 'contrib/devtools/runners/__init__.py'
284--- contrib/devtools/runners/__init__.py 1970-01-01 00:00:00 +0000
285+++ contrib/devtools/runners/__init__.py 2018-06-03 23:09:20 +0000
286@@ -0,0 +1,305 @@
287+# Copyright 2009-2012 Canonical Ltd.
288+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
289+#
290+# This program is free software: you can redistribute it and/or modify it
291+# under the terms of the GNU General Public License version 3, as published
292+# by the Free Software Foundation.
293+#
294+# This program is distributed in the hope that it will be useful, but
295+# WITHOUT ANY WARRANTY; without even the implied warranties of
296+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
297+# PURPOSE. See the GNU General Public License for more details.
298+#
299+# You should have received a copy of the GNU General Public License along
300+# with this program. If not, see <http://www.gnu.org/licenses/>.
301+#
302+# In addition, as a special exception, the copyright holders give
303+# permission to link the code of portions of this program with the
304+# OpenSSL library under certain conditions as described in each
305+# individual source file, and distribute linked combinations
306+# including the two.
307+# You must obey the GNU General Public License in all respects
308+# for all of the code used other than OpenSSL. If you modify
309+# file(s) with this exception, you may extend this exception to your
310+# version of the file(s), but you are not obligated to do so. If you
311+# do not wish to do so, delete this exception statement from your
312+# version. If you delete this exception statement from all source
313+# files in the program, then also delete it here.
314+"""The base test runner object."""
315+
316+from __future__ import print_function, unicode_literals
317+
318+import coverage
319+import gc
320+import inspect
321+import os
322+import re
323+import sys
324+import unittest
325+
326+from devtools.errors import TestError, UsageError
327+from devtools.testing.txcheck import TXCheckSuite
328+from devtools.utils import OptionParser
329+from devtools.compat import text_type
330+
331+__all__ = ['BaseTestOptions', 'BaseTestRunner', 'main']
332+
333+
334+def _is_in_ignored_path(testcase, paths):
335+ """Return if the testcase is in one of the ignored paths."""
336+ for ignored_path in paths:
337+ if testcase.startswith(ignored_path):
338+ return True
339+ return False
340+
341+
342+class BaseTestRunner(object):
343+ """The base test runner type. Does not actually run tests."""
344+
345+ def __init__(self, options=None, *args, **kwargs):
346+ super(BaseTestRunner, self).__init__(*args, **kwargs)
347+
348+ # set $HOME to the _trial_temp dir, to avoid breaking user files
349+ trial_temp_dir = os.environ.get('TRIAL_TEMP_DIR', os.getcwd())
350+ homedir = os.path.join(trial_temp_dir, options['temp-directory'])
351+ os.environ['HOME'] = homedir
352+
353+ # setup $XDG_*_HOME variables and create the directories
354+ xdg_cache = os.path.join(homedir, 'xdg_cache')
355+ xdg_config = os.path.join(homedir, 'xdg_config')
356+ xdg_data = os.path.join(homedir, 'xdg_data')
357+ os.environ['XDG_CACHE_HOME'] = xdg_cache
358+ os.environ['XDG_CONFIG_HOME'] = xdg_config
359+ os.environ['XDG_DATA_HOME'] = xdg_data
360+
361+ if not os.path.exists(xdg_cache):
362+ os.makedirs(xdg_cache)
363+ if not os.path.exists(xdg_config):
364+ os.makedirs(xdg_config)
365+ if not os.path.exists(xdg_data):
366+ os.makedirs(xdg_data)
367+
368+ # setup the ROOTDIR env var
369+ os.environ['ROOTDIR'] = os.getcwd()
370+
371+ # Need an attribute for tempdir so we can use it later
372+ self.tempdir = homedir
373+ self.working_dir = os.path.join(self.tempdir, 'trial')
374+
375+ self.source_files = []
376+ self.required_services = []
377+
378+ def _load_unittest(self, relpath):
379+ """Load unit tests from a Python module with the given 'relpath'."""
380+ assert relpath.endswith(".py"), (
381+ "%s does not appear to be a Python module" % relpath)
382+ if not os.path.basename(relpath).startswith('test_'):
383+ return
384+ modpath = relpath.replace(os.path.sep, ".")[:-3]
385+ module = __import__(modpath, None, None, [""])
386+
387+ # If the module specifies required_services, make sure we get them
388+ members = [x[1] for x in inspect.getmembers(module, inspect.isclass)]
389+ for member_type in members:
390+ if hasattr(member_type, 'required_services'):
391+ member = member_type()
392+ for service in member.required_services():
393+ if service not in self.required_services:
394+ self.required_services.append(service)
395+ del member
396+ gc.collect()
397+
398+ # If the module has a 'suite' or 'test_suite' function, use that
399+ # to load the tests.
400+ if hasattr(module, "suite"):
401+ return module.suite()
402+ elif hasattr(module, "test_suite"):
403+ return module.test_suite()
404+ else:
405+ return unittest.defaultTestLoader.loadTestsFromModule(module)
406+
407+ def _collect_tests(self, path, test_pattern, ignored_modules,
408+ ignored_paths):
409+ """Return the set of unittests."""
410+ suite = TXCheckSuite()
411+ if test_pattern:
412+ pattern = re.compile('.*%s.*' % test_pattern)
413+ else:
414+ pattern = None
415+
416+ # Disable this lint warning as we need to access _tests in the
417+ # test suites, to collect the tests
418+ # pylint: disable=W0212
419+ if path:
420+ try:
421+ module_suite = self._load_unittest(path)
422+ if pattern:
423+ for inner_suite in module_suite._tests:
424+ for test in inner_suite._tests:
425+ if pattern.match(test.id()):
426+ suite.addTest(test)
427+ else:
428+ suite.addTests(module_suite)
429+ return suite
430+ except AssertionError:
431+ pass
432+ else:
433+ raise TestError('Path should be defined.')
434+
435+ # We don't use the dirs variable, so ignore the warning
436+ # pylint: disable=W0612
437+ for root, dirs, files in os.walk(path):
438+ for test in files:
439+ filepath = os.path.join(root, test)
440+ if test.endswith(".py") and test not in ignored_modules and \
441+ not _is_in_ignored_path(filepath, ignored_paths):
442+ self.source_files.append(filepath)
443+ if test.startswith("test_"):
444+ module_suite = self._load_unittest(filepath)
445+ if pattern:
446+ for inner_suite in module_suite._tests:
447+ for test in inner_suite._tests:
448+ if pattern.match(test.id()):
449+ suite.addTest(test)
450+ else:
451+ suite.addTests(module_suite)
452+ return suite
453+
454+ def get_suite(self, config):
455+ """Get the test suite to use."""
456+ suite = unittest.TestSuite()
457+ for path in config['tests']:
458+ suite.addTest(self._collect_tests(path, config['test'],
459+ config['ignore-modules'],
460+ config['ignore-paths']))
461+ if config['loop']:
462+ old_suite = suite
463+ suite = unittest.TestSuite()
464+ for _ in range(config['loop']):
465+ suite.addTest(old_suite)
466+
467+ return suite
468+
469+ def run_tests(self, suite):
470+ """Run the test suite."""
471+ return False
472+
473+
474+class BaseTestOptions(OptionParser):
475+ """Base options for our test runner."""
476+
477+ optFlags = [['coverage', 'c', 'Generate a coverage report for the tests.'],
478+ ['gui', None, 'Use the GUI mode of some runners.'],
479+ ['help', 'h', ''],
480+ ['help-runners', None, 'List information about test runners.'],
481+ ]
482+
483+ optParameters = [['test', 't', None, None],
484+ ['loop', None, 1, None],
485+ ['ignore-modules', 'i', '', None],
486+ ['ignore-paths', 'p', '', None],
487+ ['runner', None, 'txrunner', None],
488+ ['temp-directory', None, b'_trial_temp', None],
489+ ]
490+
491+ def __init__(self, *args, **kwargs):
492+ super(BaseTestOptions, self).__init__(*args, **kwargs)
493+
494+ def opt_help_runners(self):
495+ """List the runners which are supported."""
496+ sys.exit(0)
497+
498+ def opt_ignore_modules(self, option):
499+ """Comma-separate list of test modules to ignore,
500+ e.g: test_gtk.py, test_account.py
501+ """
502+ self['ignore-modules'] = list(map(text_type.strip, option.split(',')))
503+
504+ def opt_ignore_paths(self, option):
505+ """Comma-separated list of relative paths to ignore,
506+ e.g: tests/platform/windows, tests/platform/macosx
507+ """
508+ self['ignore-paths'] = list(map(text_type.strip, option.split(',')))
509+
510+ def opt_loop(self, option):
511+ """Loop tests the specified number of times."""
512+ try:
513+ self['loop'] = int(option)
514+ except ValueError:
515+ raise UsageError('A positive integer value must be specified.')
516+
517+ def opt_temp_directory(self, option):
518+ """Path to use as a working directory for tests.
519+ [default: _trial_temp]
520+ """
521+ self['temp-directory'] = option
522+
523+ def opt_test(self, option):
524+ """Run specific tests, e.g: className.methodName"""
525+ self['test'] = option
526+
527+ # We use some camelcase names for trial compatibility here.
528+ def parseArgs(self, *args):
529+ """Handle the extra arguments."""
530+ if isinstance(self.tests, set):
531+ self['tests'].update(args)
532+ elif isinstance(self.tests, list):
533+ self['tests'].extend(args)
534+
535+
536+def _get_runner_options(runner_name):
537+ """Return the test runner module, and its options object."""
538+ module_name = 'devtools.runners.%s' % runner_name
539+ runner = __import__(module_name, None, None, [''])
540+ options = None
541+ if getattr(runner, 'TestOptions', None) is not None:
542+ options = runner.TestOptions()
543+ if options is None:
544+ options = BaseTestOptions()
545+ return (runner, options)
546+
547+
548+def main():
549+ """Do the deed."""
550+ if len(sys.argv) == 1:
551+ sys.argv.append('--help')
552+
553+ try:
554+ pos = sys.argv.index('--runner')
555+ runner_name = sys.argv.pop(pos + 1)
556+ sys.argv.pop(pos)
557+ except ValueError:
558+ runner_name = 'txrunner'
559+ finally:
560+ runner, options = _get_runner_options(runner_name)
561+ options.parseOptions()
562+
563+ test_runner = runner.TestRunner(options=options)
564+ suite = test_runner.get_suite(options)
565+
566+ if options['coverage']:
567+ coverage.erase()
568+ coverage.start()
569+
570+ running_services = []
571+
572+ succeeded = False
573+ try:
574+ # Start any required services
575+ for service_obj in test_runner.required_services:
576+ service = service_obj()
577+ service.start_service(tempdir=test_runner.tempdir)
578+ running_services.append(service)
579+
580+ succeeded = test_runner.run_tests(suite)
581+ finally:
582+ # Stop all the running services
583+ for service in running_services:
584+ service.stop_service()
585+
586+ if options['coverage']:
587+ coverage.stop()
588+ coverage.report(test_runner.source_files, ignore_errors=True,
589+ show_missing=False)
590+
591+ sys.exit(not succeeded)
592
593=== added file 'contrib/devtools/runners/txrunner.py'
594--- contrib/devtools/runners/txrunner.py 1970-01-01 00:00:00 +0000
595+++ contrib/devtools/runners/txrunner.py 2018-06-03 23:09:20 +0000
596@@ -0,0 +1,133 @@
597+# Copyright 2009-2012 Canonical Ltd.
598+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
599+#
600+# This program is free software: you can redistribute it and/or modify it
601+# under the terms of the GNU General Public License version 3, as published
602+# by the Free Software Foundation.
603+#
604+# This program is distributed in the hope that it will be useful, but
605+# WITHOUT ANY WARRANTY; without even the implied warranties of
606+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
607+# PURPOSE. See the GNU General Public License for more details.
608+#
609+# You should have received a copy of the GNU General Public License along
610+# with this program. If not, see <http://www.gnu.org/licenses/>.
611+#
612+# In addition, as a special exception, the copyright holders give
613+# permission to link the code of portions of this program with the
614+# OpenSSL library under certain conditions as described in each
615+# individual source file, and distribute linked combinations
616+# including the two.
617+# You must obey the GNU General Public License in all respects
618+# for all of the code used other than OpenSSL. If you modify
619+# file(s) with this exception, you may extend this exception to your
620+# version of the file(s), but you are not obligated to do so. If you
621+# do not wish to do so, delete this exception statement from your
622+# version. If you delete this exception statement from all source
623+# files in the program, then also delete it here.
624+"""The twisted test runner and options."""
625+
626+from __future__ import print_function, unicode_literals
627+
628+import sys
629+
630+from twisted.scripts import trial
631+from twisted.trial.runner import TrialRunner
632+
633+from devtools.errors import TestError
634+from devtools.runners import BaseTestOptions, BaseTestRunner
635+
636+__all__ = ['TestRunner', 'TestOptions']
637+
638+
639+class TestRunner(BaseTestRunner, TrialRunner):
640+ """The twisted test runner implementation."""
641+
642+ def __init__(self, options=None):
643+ # Handle running trial in debug or dry-run mode
644+ self.config = options
645+
646+ try:
647+ reactor_name = ('devtools.reactors.%s' % (self.config['reactor'],))
648+ reactor = __import__(reactor_name, None, None, [''])
649+ except ImportError:
650+ raise TestError('The specified reactor is not supported.')
651+ else:
652+ try:
653+ reactor.install(options=self.config)
654+ except ImportError:
655+ raise TestError(
656+ 'The Python package providing the requested reactor is '
657+ 'not installed. You can find it here: %s' %
658+ reactor.REACTOR_URL)
659+
660+ mode = None
661+ if self.config['debug']:
662+ mode = TrialRunner.DEBUG
663+ if self.config['dry-run']:
664+ mode = TrialRunner.DRY_RUN
665+
666+ # Hook up to the parent test runner
667+ super(TestRunner, self).__init__(
668+ options=options,
669+ reporterFactory=self.config['reporter'],
670+ mode=mode,
671+ profile=self.config['profile'],
672+ logfile=self.config['logfile'],
673+ tracebackFormat=self.config['tbformat'],
674+ realTimeErrors=self.config['rterrors'],
675+ uncleanWarnings=self.config['unclean-warnings'],
676+ forceGarbageCollection=self.config['force-gc'])
677+ # Named for trial compatibility.
678+ # pylint: disable=C0103
679+ self.workingDirectory = self.working_dir
680+ # pylint: enable=C0103
681+
682+ def run_tests(self, suite):
683+ """Run the twisted test suite."""
684+ if self.config['until-failure']:
685+ result = self.runUntilFailure(suite)
686+ else:
687+ result = self.run(suite)
688+ return result.wasSuccessful()
689+
690+
691+def _get_default_reactor():
692+ """Return the platform-dependent default reactor to use."""
693+ default_reactor = 'gi'
694+ if sys.platform in ['darwin', 'win32']:
695+ default_reactor = 'twisted'
696+ return default_reactor
697+
698+
699+class TestOptions(trial.Options, BaseTestOptions):
700+ """Class for twisted options handling."""
701+
702+ optFlags = [["help-reactors", None],
703+ ]
704+
705+ optParameters = [["reactor", "r", _get_default_reactor()],
706+ ]
707+
708+ def __init__(self, *args, **kwargs):
709+ super(TestOptions, self).__init__(*args, **kwargs)
710+ self['rterrors'] = True
711+
712+ def opt_coverage(self, option):
713+ """Handle special flags."""
714+ self['coverage'] = True
715+ opt_c = opt_coverage
716+
717+ def opt_help_reactors(self):
718+ """Help on available reactors for use with tests"""
719+ synopsis = ('')
720+ print(synopsis)
721+ print('Need to get list of reactors and print them here.\n')
722+ sys.exit(0)
723+
724+ def opt_reactor(self, option):
725+ """Which reactor to use (see --help-reactors for a list
726+ of possibilities)
727+ """
728+ self['reactor'] = option
729+ opt_r = opt_reactor
730
731=== added directory 'contrib/devtools/services'
732=== added file 'contrib/devtools/services/__init__.py'
733--- contrib/devtools/services/__init__.py 1970-01-01 00:00:00 +0000
734+++ contrib/devtools/services/__init__.py 2018-06-03 23:09:20 +0000
735@@ -0,0 +1,66 @@
736+# Copyright 2011-2012 Canonical Ltd.
737+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
738+#
739+# This program is free software: you can redistribute it and/or modify it
740+# under the terms of the GNU General Public License version 3, as published
741+# by the Free Software Foundation.
742+#
743+# This program is distributed in the hope that it will be useful, but
744+# WITHOUT ANY WARRANTY; without even the implied warranties of
745+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
746+# PURPOSE. See the GNU General Public License for more details.
747+#
748+# You should have received a copy of the GNU General Public License along
749+# with this program. If not, see <http://www.gnu.org/licenses/>.
750+#
751+# In addition, as a special exception, the copyright holders give
752+# permission to link the code of portions of this program with the
753+# OpenSSL library under certain conditions as described in each
754+# individual source file, and distribute linked combinations
755+# including the two.
756+# You must obey the GNU General Public License in all respects
757+# for all of the code used other than OpenSSL. If you modify
758+# file(s) with this exception, you may extend this exception to your
759+# version of the file(s), but you are not obligated to do so. If you
760+# do not wish to do so, delete this exception statement from your
761+# version. If you delete this exception statement from all source
762+# files in the program, then also delete it here.
763+"""Service runners for testing."""
764+
765+import os
766+import socket
767+
768+from dirspec.basedir import load_data_paths
769+
770+
771+def find_config_file(in_config_file):
772+ """Find the first appropriate conf to use."""
773+ # In case we're running from within the source tree
774+ path = os.path.abspath(os.path.join(os.path.dirname(__file__),
775+ os.path.pardir, os.path.pardir,
776+ os.path.pardir,
777+ "data", in_config_file))
778+ if not os.path.exists(path):
779+ # Use the installed file in $pkgdatadir as source
780+ for path in load_data_paths("ubuntuone-dev-tools", in_config_file):
781+ if os.path.exists(path):
782+ break
783+
784+ # Check to make sure we didn't just fall out of the loop
785+ if not os.path.exists(path):
786+ raise IOError('Could not locate suitable %s' % in_config_file)
787+ return path
788+
789+
790+def get_arbitrary_port():
791+ """
792+ Find an unused port, and return it.
793+
794+ There might be a small race condition here, but we aren't
795+ worried about it.
796+ """
797+ sock = socket.socket()
798+ sock.bind(('localhost', 0))
799+ _, port = sock.getsockname()
800+ sock.close()
801+ return port
802
803=== added file 'contrib/devtools/services/dbus.py'
804--- contrib/devtools/services/dbus.py 1970-01-01 00:00:00 +0000
805+++ contrib/devtools/services/dbus.py 2018-06-03 23:09:20 +0000
806@@ -0,0 +1,119 @@
807+# Copyright 2009-2012 Canonical Ltd.
808+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
809+#
810+# This program is free software: you can redistribute it and/or modify it
811+# under the terms of the GNU General Public License version 3, as published
812+# by the Free Software Foundation.
813+#
814+# This program is distributed in the hope that it will be useful, but
815+# WITHOUT ANY WARRANTY; without even the implied warranties of
816+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
817+# PURPOSE. See the GNU General Public License for more details.
818+#
819+# You should have received a copy of the GNU General Public License along
820+# with this program. If not, see <http://www.gnu.org/licenses/>.
821+#
822+# In addition, as a special exception, the copyright holders give
823+# permission to link the code of portions of this program with the
824+# OpenSSL library under certain conditions as described in each
825+# individual source file, and distribute linked combinations
826+# including the two.
827+# You must obey the GNU General Public License in all respects
828+# for all of the code used other than OpenSSL. If you modify
829+# file(s) with this exception, you may extend this exception to your
830+# version of the file(s), but you are not obligated to do so. If you
831+# do not wish to do so, delete this exception statement from your
832+# version. If you delete this exception statement from all source
833+# files in the program, then also delete it here.
834+"""Utilities for finding and running a dbus session bus for testing."""
835+
836+from __future__ import unicode_literals
837+
838+import os
839+import signal
840+import subprocess
841+
842+from distutils.spawn import find_executable
843+
844+# pylint: disable=F0401,E0611
845+try:
846+ from urllib.parse import quote
847+except ImportError:
848+ from urllib import quote
849+# pylint: enable=F0401,E0611
850+
851+from devtools.services import find_config_file
852+DBUS_CONFIG_FILE = 'dbus-session.conf.in'
853+
854+
855+class DBusLaunchError(Exception):
856+ """Error while launching dbus-daemon"""
857+ pass
858+
859+
860+class NotFoundError(Exception):
861+ """Not found error"""
862+ pass
863+
864+
865+class DBusRunner(object):
866+ """Class for running dbus-daemon with a private session."""
867+
868+ def __init__(self):
869+ self.dbus_address = None
870+ self.dbus_pid = None
871+ self.running = False
872+ self.config_file = None
873+
874+ def _generate_config_file(self, tempdir=None):
875+ """Find the first appropriate dbus-session.conf to use."""
876+ # load the config file
877+ path = find_config_file(DBUS_CONFIG_FILE)
878+ # replace config settings
879+ self.config_file = os.path.join(tempdir, 'dbus-session.conf')
880+ dbus_address = 'unix:tmpdir=%s' % quote(tempdir)
881+ with open(path) as in_file:
882+ content = in_file.read()
883+ with open(self.config_file, 'w') as out_file:
884+ out_file.write(content.replace('@ADDRESS@', dbus_address))
885+
886+ def start_service(self, tempdir=None):
887+ """Start our own session bus daemon for testing."""
888+ dbus = find_executable("dbus-daemon")
889+ if not dbus:
890+ raise NotFoundError("dbus-daemon was not found.")
891+
892+ self._generate_config_file(tempdir)
893+
894+ dbus_args = ["--fork",
895+ "--config-file=" + self.config_file,
896+ "--print-address=1",
897+ "--print-pid=2"]
898+ sp = subprocess.Popen([dbus] + dbus_args,
899+ bufsize=4096, stdout=subprocess.PIPE,
900+ stderr=subprocess.PIPE)
901+
902+ # Call wait here as under the qt4 reactor we get an error about
903+ # interrupted system call if we don't.
904+ sp.wait()
905+ self.dbus_address = b"".join(sp.stdout.readlines()).strip()
906+ self.dbus_pid = int(b"".join(sp.stderr.readlines()).strip())
907+
908+ if self.dbus_address != "":
909+ os.environ["DBUS_SESSION_BUS_ADDRESS"] = \
910+ self.dbus_address.decode("utf8")
911+ else:
912+ os.kill(self.dbus_pid, signal.SIGKILL)
913+ raise DBusLaunchError("There was a problem launching dbus-daemon.")
914+ self.running = True
915+
916+ def stop_service(self):
917+ """Stop our DBus session bus daemon."""
918+ try:
919+ del os.environ["DBUS_SESSION_BUS_ADDRESS"]
920+ except KeyError:
921+ pass
922+ os.kill(self.dbus_pid, signal.SIGKILL)
923+ self.running = False
924+ os.unlink(self.config_file)
925+ self.config_file = None
926
927=== added directory 'contrib/devtools/testcases'
928=== added file 'contrib/devtools/testcases/__init__.py'
929--- contrib/devtools/testcases/__init__.py 1970-01-01 00:00:00 +0000
930+++ contrib/devtools/testcases/__init__.py 2018-06-03 23:09:20 +0000
931@@ -0,0 +1,187 @@
932+# -*- coding: utf-8 -*-
933+#
934+# Copyright 2009-2012 Canonical Ltd.
935+#
936+# This program is free software: you can redistribute it and/or modify it
937+# under the terms of the GNU General Public License version 3, as published
938+# by the Free Software Foundation.
939+#
940+# This program is distributed in the hope that it will be useful, but
941+# WITHOUT ANY WARRANTY; without even the implied warranties of
942+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
943+# PURPOSE. See the GNU General Public License for more details.
944+#
945+# You should have received a copy of the GNU General Public License along
946+# with this program. If not, see <http://www.gnu.org/licenses/>.
947+#
948+# In addition, as a special exception, the copyright holders give
949+# permission to link the code of portions of this program with the
950+# OpenSSL library under certain conditions as described in each
951+# individual source file, and distribute linked combinations
952+# including the two.
953+# You must obey the GNU General Public License in all respects
954+# for all of the code used other than OpenSSL. If you modify
955+# file(s) with this exception, you may extend this exception to your
956+# version of the file(s), but you are not obligated to do so. If you
957+# do not wish to do so, delete this exception statement from your
958+# version. If you delete this exception statement from all source
959+# files in the program, then also delete it here.
960+"""Base tests cases and test utilities."""
961+
962+from __future__ import with_statement
963+
964+import contextlib
965+import os
966+import shutil
967+import sys
968+
969+from functools import wraps
970+
971+from twisted.trial.unittest import TestCase, SkipTest
972+
973+
974+@contextlib.contextmanager
975+def environ(env_var, new_value):
976+ """context manager to replace/add an environ value"""
977+ old_value = os.environ.get(env_var, None)
978+ os.environ[env_var] = new_value
979+ yield
980+ if old_value is None:
981+ os.environ.pop(env_var)
982+ else:
983+ os.environ[env_var] = old_value
984+
985+
986+def _id(obj):
987+ """Return the obj calling the funct."""
988+ return obj
989+
990+
991+# pylint: disable=C0103
992+def skipTest(reason):
993+ """Unconditionally skip a test."""
994+
995+ def decorator(test_item):
996+ """Decorate the test so that it is skipped."""
997+ if not (isinstance(test_item, type) and
998+ issubclass(test_item, TestCase)):
999+
1000+ @wraps(test_item)
1001+ def skip_wrapper(*args, **kwargs):
1002+ """Skip a test method raising an exception."""
1003+ raise SkipTest(reason)
1004+ test_item = skip_wrapper
1005+
1006+ # tell twisted.trial.unittest to skip the test, pylint will complain
1007+ # since it thinks we are redefining a name out of the scope
1008+ # pylint: disable=W0621,W0612
1009+ test_item.skip = reason
1010+ # pylint: enable=W0621,W0612
1011+ # because the item was skipped, we will make sure that no
1012+ # services are started for it
1013+ if hasattr(test_item, "required_services"):
1014+ # pylint: disable=W0612
1015+ test_item.required_services = lambda *args, **kwargs: []
1016+ # pylint: enable=W0612
1017+ return test_item
1018+ return decorator
1019+
1020+
1021+def skipIf(condition, reason):
1022+ """Skip a test if the condition is true."""
1023+ if condition:
1024+ return skipTest(reason)
1025+ return _id
1026+
1027+
1028+def skipIfOS(current_os, reason):
1029+ """Skip test for a particular os or lists of them."""
1030+ if os:
1031+ if sys.platform in current_os or sys.platform == current_os:
1032+ return skipTest(reason)
1033+ return _id
1034+ return _id
1035+
1036+
1037+def skipIfNotOS(current_os, reason):
1038+ """Skip test we are not in a particular os."""
1039+ if os:
1040+ if sys.platform not in current_os or \
1041+ sys.platform != current_os:
1042+ return skipTest(reason)
1043+ return _id
1044+ return _id
1045+
1046+
1047+def skipIfJenkins(current_os, reason):
1048+ """Skip test for a particular os or lists of them
1049+ when running on Jenkins."""
1050+ if os.getenv("JENKINS", False) and (sys.platform in current_os or
1051+ sys.platform == current_os):
1052+ return skipTest(reason)
1053+ return _id
1054+
1055+
1056+# pylint: enable=C0103
1057+
1058+
1059+class BaseTestCase(TestCase):
1060+ """Base TestCase with helper methods to handle temp dir.
1061+
1062+ This class provides:
1063+ mktemp(name): helper to create temporary dirs
1064+ rmtree(path): support read-only shares
1065+ makedirs(path): support read-only shares
1066+
1067+ """
1068+
1069+ def required_services(self):
1070+ """Return the list of required services for DBusTestCase."""
1071+ return []
1072+
1073+ def mktemp(self, name='temp'):
1074+ """Customized mktemp that accepts an optional name argument."""
1075+ tempdir = os.path.join(self.tmpdir, name)
1076+ if os.path.exists(tempdir):
1077+ self.rmtree(tempdir)
1078+ self.makedirs(tempdir)
1079+ return tempdir
1080+
1081+ @property
1082+ def tmpdir(self):
1083+ """Default tmpdir: module/class/test_method."""
1084+ # check if we already generated the root path
1085+ root_dir = getattr(self, '__root', None)
1086+ if root_dir:
1087+ return root_dir
1088+ max_filename = 32 # some platforms limit lengths of filenames
1089+ base = os.path.join(self.__class__.__module__[:max_filename],
1090+ self.__class__.__name__[:max_filename],
1091+ self._testMethodName[:max_filename])
1092+ # use _trial_temp dir, it should be os.gwtcwd()
1093+ # define the root temp dir of the testcase, pylint: disable=W0201
1094+ self.__root = os.path.join(os.getcwd(), base)
1095+ return self.__root
1096+
1097+ def rmtree(self, path):
1098+ """Custom rmtree that handle ro parent(s) and childs."""
1099+ if not os.path.exists(path):
1100+ return
1101+ # change perms to rw, so we can delete the temp dir
1102+ if path != getattr(self, '__root', None):
1103+ os.chmod(os.path.dirname(path), 0o755)
1104+ if not os.access(path, os.W_OK):
1105+ os.chmod(path, 0o755)
1106+ # pylint: disable=W0612
1107+ for dirpath, dirs, files in os.walk(path):
1108+ for dirname in dirs:
1109+ if not os.access(os.path.join(dirpath, dirname), os.W_OK):
1110+ os.chmod(os.path.join(dirpath, dirname), 0o777)
1111+ shutil.rmtree(path)
1112+
1113+ def makedirs(self, path):
1114+ """Custom makedirs that handle ro parent."""
1115+ parent = os.path.dirname(path)
1116+ if os.path.exists(parent):
1117+ os.chmod(parent, 0o755)
1118+ os.makedirs(path)
1119
1120=== added file 'contrib/devtools/testcases/dbus.py'
1121--- contrib/devtools/testcases/dbus.py 1970-01-01 00:00:00 +0000
1122+++ contrib/devtools/testcases/dbus.py 2018-06-03 23:09:20 +0000
1123@@ -0,0 +1,138 @@
1124+# -*- coding: utf-8 -*-
1125+# Copyright 2009-2012 Canonical Ltd.
1126+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
1127+#
1128+# This program is free software: you can redistribute it and/or modify it
1129+# under the terms of the GNU General Public License version 3, as published
1130+# by the Free Software Foundation.
1131+#
1132+# This program is distributed in the hope that it will be useful, but
1133+# WITHOUT ANY WARRANTY; without even the implied warranties of
1134+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1135+# PURPOSE. See the GNU General Public License for more details.
1136+#
1137+# You should have received a copy of the GNU General Public License along
1138+# with this program. If not, see <http://www.gnu.org/licenses/>.
1139+#
1140+# In addition, as a special exception, the copyright holders give
1141+# permission to link the code of portions of this program with the
1142+# OpenSSL library under certain conditions as described in each
1143+# individual source file, and distribute linked combinations
1144+# including the two.
1145+# You must obey the GNU General Public License in all respects
1146+# for all of the code used other than OpenSSL. If you modify
1147+# file(s) with this exception, you may extend this exception to your
1148+# version of the file(s), but you are not obligated to do so. If you
1149+# do not wish to do so, delete this exception statement from your
1150+# version. If you delete this exception statement from all source
1151+# files in the program, then also delete it here.
1152+"""Base dbus tests cases and test utilities."""
1153+
1154+from __future__ import absolute_import, with_statement
1155+
1156+import os
1157+
1158+from twisted.internet import defer
1159+
1160+from devtools.testcases import BaseTestCase, skipIf
1161+
1162+# DBusRunner for DBusTestCase using tests
1163+from devtools.services.dbus import DBusRunner
1164+
1165+
1166+# pylint: disable=F0401,C0103,W0406,E0611
1167+try:
1168+ import dbus
1169+except ImportError as e:
1170+ dbus = None
1171+
1172+try:
1173+ import dbus.service as service
1174+except ImportError:
1175+ service = None
1176+
1177+try:
1178+ from dbus.mainloop.glib import DBusGMainLoop
1179+except ImportError:
1180+ DBusGMainLoop = None
1181+
1182+# pylint: enable=F0401,C0103,W0406,E0611
1183+
1184+
1185+class InvalidSessionBus(Exception):
1186+ """Error when we are connected to the wrong session bus in tests."""
1187+
1188+
1189+class FakeDBusInterface(object):
1190+ """A fake DBusInterface..."""
1191+
1192+ def shutdown(self, with_restart=False):
1193+ """...that only knows how to go away"""
1194+
1195+
1196+@skipIf(dbus is None or service is None or DBusGMainLoop is None,
1197+ "The test requires dbus.")
1198+class DBusTestCase(BaseTestCase):
1199+ """Test the DBus event handling."""
1200+
1201+ def required_services(self):
1202+ """Return the list of required services for DBusTestCase."""
1203+ services = super(DBusTestCase, self).required_services()
1204+ services.extend([DBusRunner])
1205+ return services
1206+
1207+ @defer.inlineCallbacks
1208+ def setUp(self):
1209+ """Setup the infrastructure fo the test (dbus service)."""
1210+ # Class 'BaseTestCase' has no 'setUp' member
1211+ # pylint: disable=E1101
1212+ # dbus modules will be imported by the decorator
1213+ # pylint: disable=E0602
1214+ yield super(DBusTestCase, self).setUp()
1215+
1216+ # We need to ensure DBUS_SESSION_BUS_ADDRESS is private here
1217+ # pylint: disable=F0401,E0611
1218+ try:
1219+ from urllib.parse import unquote
1220+ except ImportError:
1221+ from urllib import unquote
1222+ # pylint: enable=F0401,E0611
1223+
1224+ bus_address = os.environ.get('DBUS_SESSION_BUS_ADDRESS', None)
1225+ if os.path.dirname(unquote(bus_address.split(',')[0].split('=')[1])) \
1226+ != os.path.dirname(os.getcwd()):
1227+ raise InvalidSessionBus('DBUS_SESSION_BUS_ADDRESS is wrong.')
1228+
1229+ # Set up the main loop and bus connection
1230+ self.loop = DBusGMainLoop(set_as_default=True)
1231+
1232+ # NOTE: The address_or_type value must remain explicitly as
1233+ # str instead of anything from devtools.compat. dbus
1234+ # expects this to be str regardless of version.
1235+ self.bus = dbus.bus.BusConnection(address_or_type=str(bus_address),
1236+ mainloop=self.loop)
1237+
1238+ # Monkeypatch the dbus.SessionBus/SystemBus methods, to ensure we
1239+ # always point at our own private bus instance.
1240+ self.patch(dbus, 'SessionBus', lambda: self.bus)
1241+ self.patch(dbus, 'SystemBus', lambda: self.bus)
1242+
1243+ # Check that we are on the correct bus for real
1244+# Disable this for now, because our tests are extremely broken :(
1245+# bus_names = self.bus.list_names()
1246+# if len(bus_names) > 2:
1247+# raise InvalidSessionBus('Too many bus connections: %s (%r)' %
1248+# (len(bus_names), bus_names))
1249+
1250+ # monkeypatch busName.__del__ to avoid errors on gc
1251+ # we take care of releasing the name in shutdown
1252+ service.BusName.__del__ = lambda _: None
1253+ yield self.bus.set_exit_on_disconnect(False)
1254+ self.signal_receivers = set()
1255+
1256+ @defer.inlineCallbacks
1257+ def tearDown(self):
1258+ """Cleanup the test."""
1259+ yield self.bus.flush()
1260+ yield self.bus.close()
1261+ yield super(DBusTestCase, self).tearDown()
1262
1263=== added file 'contrib/devtools/testcases/txsocketserver.py'
1264--- contrib/devtools/testcases/txsocketserver.py 1970-01-01 00:00:00 +0000
1265+++ contrib/devtools/testcases/txsocketserver.py 2018-06-03 23:09:20 +0000
1266@@ -0,0 +1,358 @@
1267+# -*- coding: utf-8 -*-
1268+# Copyright 2012 Canonical Ltd.
1269+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
1270+#
1271+# This program is free software: you can redistribute it and/or modify it
1272+# under the terms of the GNU General Public License version 3, as published
1273+# by the Free Software Foundation.
1274+#
1275+# This program is distributed in the hope that it will be useful, but
1276+# WITHOUT ANY WARRANTY; without even the implied warranties of
1277+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1278+# PURPOSE. See the GNU General Public License for more details.
1279+#
1280+# You should have received a copy of the GNU General Public License along
1281+# with this program. If not, see <http://www.gnu.org/licenses/>.
1282+#
1283+# In addition, as a special exception, the copyright holders give
1284+# permission to link the code of portions of this program with the
1285+# OpenSSL library under certain conditions as described in each
1286+# individual source file, and distribute linked combinations
1287+# including the two.
1288+# You must obey the GNU General Public License in all respects
1289+# for all of the code used other than OpenSSL. If you modify
1290+# file(s) with this exception, you may extend this exception to your
1291+# version of the file(s), but you are not obligated to do so. If you
1292+# do not wish to do so, delete this exception statement from your
1293+# version. If you delete this exception statement from all source
1294+# files in the program, then also delete it here.
1295+
1296+"""Base test case for twisted servers."""
1297+
1298+import os
1299+import shutil
1300+import tempfile
1301+
1302+from twisted.internet import defer, endpoints, protocol
1303+from twisted.spread import pb
1304+
1305+from devtools.testcases import BaseTestCase
1306+
1307+# no init method + twisted common warnings
1308+# pylint: disable=W0232, C0103, E1101
1309+
1310+
1311+def server_protocol_factory(cls):
1312+ """Factory to create tidy protocols."""
1313+
1314+ if cls is None:
1315+ cls = protocol.Protocol
1316+
1317+ class ServerTidyProtocol(cls):
1318+ """A tidy protocol."""
1319+
1320+ def connectionLost(self, *args):
1321+ """Lost the connection."""
1322+ cls.connectionLost(self, *args)
1323+ # lets tell everyone
1324+ # pylint: disable=W0212
1325+ if (self.factory._disconnecting and
1326+ self.factory.testserver_on_connection_lost is not None and
1327+ not self.factory.testserver_on_connection_lost.called):
1328+ self.factory.testserver_on_connection_lost.callback(self)
1329+ # pylint: enable=W0212
1330+
1331+ return ServerTidyProtocol
1332+
1333+
1334+def server_factory_factory(cls):
1335+ """Factory that creates special types of factories for tests."""
1336+
1337+ if cls is None:
1338+ cls = protocol.ServerFactory
1339+
1340+ class TidyServerFactory(cls):
1341+ """A tidy factory."""
1342+
1343+ testserver_on_connection_lost = None
1344+
1345+ def buildProtocol(self, addr):
1346+ prot = cls.buildProtocol(self, addr)
1347+ self.testserver_on_connection_lost = defer.Deferred()
1348+ return prot
1349+
1350+ return TidyServerFactory
1351+
1352+
1353+def client_protocol_factory(cls):
1354+ """Factory to create tidy protocols."""
1355+
1356+ if cls is None:
1357+ cls = protocol.Protocol
1358+
1359+ class ClientTidyProtocol(cls):
1360+ """A tidy protocol."""
1361+
1362+ def connectionLost(self, *a):
1363+ """Connection list."""
1364+ cls.connectionLost(self, *a)
1365+ # pylint: disable=W0212
1366+ if (self.factory._disconnecting and
1367+ self.factory.testserver_on_connection_lost is not None and
1368+ not self.factory.testserver_on_connection_lost.called):
1369+ self.factory.testserver_on_connection_lost.callback(self)
1370+ # pylint: enable=W0212
1371+
1372+ return ClientTidyProtocol
1373+
1374+
1375+class TidySocketServer(object):
1376+ """Ensure that twisted servers are correctly managed in tests.
1377+
1378+ Closing a twisted server is a complicated matter. In order to do so you
1379+ have to ensure that three different deferreds are fired:
1380+
1381+ 1. The server must stop listening.
1382+ 2. The client connection must disconnect.
1383+ 3. The server connection must disconnect.
1384+
1385+ This class allows to create a server and a client that will ensure that
1386+ the reactor is left clean by following the pattern described at
1387+ http://mumak.net/stuff/twisted-disconnect.html
1388+ """
1389+ def __init__(self):
1390+ """Create a new instance."""
1391+ self.listener = None
1392+ self.server_factory = None
1393+
1394+ self.connector = None
1395+ self.client_factory = None
1396+
1397+ def get_server_endpoint(self):
1398+ """Return the server endpoint description."""
1399+ raise NotImplementedError('To be implemented by child classes.')
1400+
1401+ def get_client_endpoint(self):
1402+ """Return the client endpoint description."""
1403+ raise NotImplementedError('To be implemented by child classes.')
1404+
1405+ @defer.inlineCallbacks
1406+ def listen_server(self, server_class, *args, **kwargs):
1407+ """Start a server in a random port."""
1408+ from twisted.internet import reactor
1409+ tidy_class = server_factory_factory(server_class)
1410+ self.server_factory = tidy_class(*args, **kwargs)
1411+ self.server_factory._disconnecting = False
1412+ self.server_factory.protocol = server_protocol_factory(
1413+ self.server_factory.protocol)
1414+ endpoint = endpoints.serverFromString(reactor,
1415+ self.get_server_endpoint())
1416+ self.listener = yield endpoint.listen(self.server_factory)
1417+ defer.returnValue(self.server_factory)
1418+
1419+ @defer.inlineCallbacks
1420+ def connect_client(self, client_class, *args, **kwargs):
1421+ """Conect a client to a given server."""
1422+ from twisted.internet import reactor
1423+
1424+ if self.server_factory is None:
1425+ raise ValueError('Server Factory was not provided.')
1426+ if self.listener is None:
1427+ raise ValueError('%s has not started listening.',
1428+ self.server_factory)
1429+
1430+ self.client_factory = client_class(*args, **kwargs)
1431+ self.client_factory._disconnecting = False
1432+ self.client_factory.protocol = client_protocol_factory(
1433+ self.client_factory.protocol)
1434+ self.client_factory.testserver_on_connection_lost = defer.Deferred()
1435+ endpoint = endpoints.clientFromString(reactor,
1436+ self.get_client_endpoint())
1437+ self.connector = yield endpoint.connect(self.client_factory)
1438+ defer.returnValue(self.client_factory)
1439+
1440+ def clean_up(self):
1441+ """Action to be performed for clean up."""
1442+ if self.server_factory is None or self.listener is None:
1443+ # nothing to clean
1444+ return defer.succeed(None)
1445+
1446+ if self.listener and self.connector:
1447+ # clean client and server
1448+ self.server_factory._disconnecting = True
1449+ self.client_factory._disconnecting = True
1450+ d = defer.maybeDeferred(self.listener.stopListening)
1451+ self.connector.transport.loseConnection()
1452+ if self.server_factory.testserver_on_connection_lost:
1453+ return defer.gatherResults(
1454+ [d,
1455+ self.client_factory.testserver_on_connection_lost,
1456+ self.server_factory.testserver_on_connection_lost])
1457+ else:
1458+ return defer.gatherResults(
1459+ [d,
1460+ self.client_factory.testserver_on_connection_lost])
1461+ if self.listener:
1462+ # just clean the server since there is no client
1463+ # pylint: disable=W0201
1464+ self.server_factory._disconnecting = True
1465+ return defer.maybeDeferred(self.listener.stopListening)
1466+ # pylint: enable=W0201
1467+
1468+
1469+class TidyTCPServer(TidySocketServer):
1470+ """A tidy tcp domain sockets server."""
1471+
1472+ client_endpoint_pattern = 'tcp:host=127.0.0.1:port=%s'
1473+ server_endpoint_pattern = 'tcp:0:interface=127.0.0.1'
1474+
1475+ def get_server_endpoint(self):
1476+ """Return the server endpoint description."""
1477+ return self.server_endpoint_pattern
1478+
1479+ def get_client_endpoint(self):
1480+ """Return the client endpoint description."""
1481+ if self.server_factory is None:
1482+ raise ValueError('Server Factory was not provided.')
1483+ if self.listener is None:
1484+ raise ValueError('%s has not started listening.',
1485+ self.server_factory)
1486+ return self.client_endpoint_pattern % self.listener.getHost().port
1487+
1488+
1489+class TidyUnixServer(TidySocketServer):
1490+ """A tidy unix domain sockets server."""
1491+
1492+ client_endpoint_pattern = 'unix:path=%s'
1493+ server_endpoint_pattern = 'unix:%s'
1494+
1495+ def __init__(self):
1496+ """Create a new instance."""
1497+ super(TidyUnixServer, self).__init__()
1498+ self.temp_dir = tempfile.mkdtemp()
1499+ self.path = os.path.join(self.temp_dir, 'tidy_unix_server')
1500+
1501+ def get_server_endpoint(self):
1502+ """Return the server endpoint description."""
1503+ return self.server_endpoint_pattern % self.path
1504+
1505+ def get_client_endpoint(self):
1506+ """Return the client endpoint description."""
1507+ return self.client_endpoint_pattern % self.path
1508+
1509+ def clean_up(self):
1510+ """Action to be performed for clean up."""
1511+ result = super(TidyUnixServer, self).clean_up()
1512+ # remove the dir once we are disconnected
1513+ result.addCallback(lambda _: shutil.rmtree(self.temp_dir))
1514+ return result
1515+
1516+
1517+class ServerTestCase(BaseTestCase):
1518+ """Base test case for tidy servers."""
1519+
1520+ @defer.inlineCallbacks
1521+ def setUp(self):
1522+ """Set the diff tests."""
1523+ yield super(ServerTestCase, self).setUp()
1524+
1525+ try:
1526+ self.server_runner = self.get_server()
1527+ except NotImplementedError:
1528+ self.server_runner = None
1529+
1530+ self.server_factory = None
1531+ self.client_factory = None
1532+ self.server_disconnected = None
1533+ self.client_connected = None
1534+ self.client_disconnected = None
1535+ self.listener = None
1536+ self.connector = None
1537+ self.addCleanup(self.tear_down_server_client)
1538+
1539+ def get_server(self):
1540+ """Return the server to be used to run the tests."""
1541+ raise NotImplementedError('To be implemented by child classes.')
1542+
1543+ @defer.inlineCallbacks
1544+ def listen_server(self, server_class, *args, **kwargs):
1545+ """Listen a server.
1546+
1547+ The method takes the server class and the arguments that should be
1548+ passed to the server constructor.
1549+ """
1550+ self.server_factory = yield self.server_runner.listen_server(
1551+ server_class, *args, **kwargs)
1552+ self.server_disconnected = \
1553+ self.server_factory.testserver_on_connection_lost
1554+ self.listener = self.server_runner.listener
1555+
1556+ @defer.inlineCallbacks
1557+ def connect_client(self, client_class, *args, **kwargs):
1558+ """Connect the client.
1559+
1560+ The method takes the client factory class and the arguments that
1561+ should be passed to the client constructor.
1562+ """
1563+ self.client_factory = yield self.server_runner.connect_client(
1564+ client_class, *args, **kwargs)
1565+ self.client_disconnected = \
1566+ self.client_factory.testserver_on_connection_lost
1567+ self.connector = self.server_runner.connector
1568+
1569+ def tear_down_server_client(self):
1570+ """Clean the server and client."""
1571+ if self.server_runner:
1572+ return self.server_runner.clean_up()
1573+
1574+
1575+class TCPServerTestCase(ServerTestCase):
1576+ """Test that uses a single twisted server."""
1577+
1578+ def get_server(self):
1579+ """Return the server to be used to run the tests."""
1580+ return TidyTCPServer()
1581+
1582+
1583+class UnixServerTestCase(ServerTestCase):
1584+ """Test that uses a single twisted server."""
1585+
1586+ def get_server(self):
1587+ """Return the server to be used to run the tests."""
1588+ return TidyUnixServer()
1589+
1590+
1591+class PbServerTestCase(ServerTestCase):
1592+ """Test a pb server."""
1593+
1594+ def get_server(self):
1595+ """Return the server to be used to run the tests."""
1596+ raise NotImplementedError('To be implemented by child classes.')
1597+
1598+ @defer.inlineCallbacks
1599+ def listen_server(self, *args, **kwargs):
1600+ """Listen a pb server."""
1601+ yield super(PbServerTestCase, self).listen_server(pb.PBServerFactory,
1602+ *args, **kwargs)
1603+
1604+ @defer.inlineCallbacks
1605+ def connect_client(self, *args, **kwargs):
1606+ """Connect a pb client."""
1607+ yield super(PbServerTestCase, self).connect_client(pb.PBClientFactory,
1608+ *args, **kwargs)
1609+
1610+
1611+class TCPPbServerTestCase(PbServerTestCase):
1612+ """Test a pb server over TCP."""
1613+
1614+ def get_server(self):
1615+ """Return the server to be used to run the tests."""
1616+ return TidyTCPServer()
1617+
1618+
1619+class UnixPbServerTestCase(PbServerTestCase):
1620+ """Test a pb server over Unix domain sockets."""
1621+
1622+ def get_server(self):
1623+ """Return the server to be used to run the tests."""
1624+ return TidyUnixServer()
1625
1626=== added directory 'contrib/devtools/testing'
1627=== added file 'contrib/devtools/testing/__init__.py'
1628--- contrib/devtools/testing/__init__.py 1970-01-01 00:00:00 +0000
1629+++ contrib/devtools/testing/__init__.py 2018-06-03 23:09:20 +0000
1630@@ -0,0 +1,1 @@
1631+"""Testing helpers."""
1632
1633=== added file 'contrib/devtools/testing/txcheck.py'
1634--- contrib/devtools/testing/txcheck.py 1970-01-01 00:00:00 +0000
1635+++ contrib/devtools/testing/txcheck.py 2018-06-03 23:09:20 +0000
1636@@ -0,0 +1,381 @@
1637+# -*- coding: utf-8 -*-
1638+
1639+# Author: Tim Cole <tim.cole@canonical.com>
1640+#
1641+# Copyright 2011-2012 Canonical Ltd.
1642+#
1643+# This program is free software: you can redistribute it and/or modify it
1644+# under the terms of the GNU General Public License version 3, as published
1645+# by the Free Software Foundation.
1646+#
1647+# This program is distributed in the hope that it will be useful, but
1648+# WITHOUT ANY WARRANTY; without even the implied warranties of
1649+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1650+# PURPOSE. See the GNU General Public License for more details.
1651+#
1652+# You should have received a copy of the GNU General Public License along
1653+# with this program. If not, see <http://www.gnu.org/licenses/>.
1654+#
1655+# In addition, as a special exception, the copyright holders give
1656+# permission to link the code of portions of this program with the
1657+# OpenSSL library under certain conditions as described in each
1658+# individual source file, and distribute linked combinations
1659+# including the two.
1660+# You must obey the GNU General Public License in all respects
1661+# for all of the code used other than OpenSSL. If you modify
1662+# file(s) with this exception, you may extend this exception to your
1663+# version of the file(s), but you are not obligated to do so. If you
1664+# do not wish to do so, delete this exception statement from your
1665+# version. If you delete this exception statement from all source
1666+# files in the program, then also delete it here.
1667+"""Utilities for performing correctness checks."""
1668+
1669+import sys
1670+import ast
1671+from inspect import getsource
1672+from textwrap import dedent
1673+from itertools import takewhile
1674+from unittest import TestCase, TestSuite, TestResult
1675+
1676+from twisted.trial.unittest import TestCase as TwistedTestCase
1677+
1678+
1679+def type_to_name(type_obj):
1680+ """Return a name for a type."""
1681+ package_name = getattr(type_obj, '__module__', None)
1682+ if package_name:
1683+ return "%s.%s" % (package_name, type_obj.__name__)
1684+ else:
1685+ return type_obj.__name__
1686+
1687+
1688+class Problem(AssertionError):
1689+ """An object representing a problem in a method."""
1690+
1691+ def __init__(self, method, test_class, ancestor_class):
1692+ """Initialize an instance."""
1693+ super(Problem, self).__init__()
1694+ self.method = method
1695+ self.test_class = test_class
1696+ self.ancestor_class = ancestor_class
1697+
1698+ def __eq__(self, other):
1699+ """Test equality."""
1700+ return type(self) == type(other) and self.__dict__ == other.__dict__
1701+
1702+ def __ne__(self, other):
1703+ """Test inequality."""
1704+ return not (self == other)
1705+
1706+ def __hash__(self):
1707+ """Return hash."""
1708+ member_hash = 0
1709+ for (key, value) in self.__dict__.items():
1710+ member_hash ^= hash(key) ^ hash(value)
1711+ return hash(type(self)) ^ member_hash
1712+
1713+ def __str__(self):
1714+ """Return a friendlier representation."""
1715+ if self.ancestor_class != self.test_class:
1716+ method_string = ("%s in ancestor method %s.%s" %
1717+ (type_to_name(self.test_class),
1718+ type_to_name(self.ancestor_class),
1719+ self.method))
1720+ else:
1721+ method_string = ("%s.%s" %
1722+ (type_to_name(self.test_class), self.method))
1723+ return ("%s for %s" % (type(self).__name__, method_string))
1724+
1725+ def __repr__(self):
1726+ """Return representation string."""
1727+ return "<%s %r>" % (type(self), self.__dict__)
1728+
1729+
1730+class MethodShadowed(Problem):
1731+ """Problem when trial's run method is shadowed."""
1732+
1733+
1734+class SuperResultDiscarded(Problem):
1735+ """Problem when callback chains are broken."""
1736+
1737+
1738+class SuperNotCalled(Problem):
1739+ """Problem when super isn't called."""
1740+
1741+
1742+class MissingInlineCallbacks(Problem):
1743+ """Problem when the inlineCallbacks decorator is missing."""
1744+
1745+
1746+class MissingReturnValue(Problem):
1747+ """Problem when there's no return value."""
1748+
1749+
1750+def match_type(expected_type):
1751+ """Return predicate matching nodes of given type."""
1752+ return lambda node: isinstance(node, expected_type)
1753+
1754+
1755+def match_equal(expected_value):
1756+ """Return predicate matching nodes equaling the given value."""
1757+ return lambda node: expected_value == node
1758+
1759+
1760+def match_in(expected_values):
1761+ """Return predicate matching node if in collection of expected values."""
1762+ return lambda node: node in expected_values
1763+
1764+
1765+def match_not_none():
1766+ """Returns a predicate matching nodes which are not None."""
1767+ return lambda node: node is not None
1768+
1769+
1770+def match_any(*subtests):
1771+ """Return short-circuiting predicate matching any given subpredicate."""
1772+ if len(subtests) == 1:
1773+ return subtests[0]
1774+ else:
1775+
1776+ def test(node):
1777+ """Try each subtest until we find one that passes."""
1778+ for subtest in subtests:
1779+ if subtest(node):
1780+ return True
1781+ return False
1782+
1783+ return test
1784+
1785+
1786+def match_all(*subtests):
1787+ """Return short-circuiting predicate matching all given subpredicates."""
1788+ if len(subtests) == 1:
1789+ return subtests[0]
1790+ else:
1791+
1792+ def test(node):
1793+ """Try each subtest until we find one that fails."""
1794+ for subtest in subtests:
1795+ if not subtest(node):
1796+ return False
1797+ return True
1798+
1799+ return test
1800+
1801+
1802+def match_attr(attr_name, *tests):
1803+ """Return predicate matching subpredicates against an attribute value."""
1804+ return lambda node: match_all(*tests)(getattr(node, attr_name))
1805+
1806+
1807+def match_path(initial_test, *components):
1808+ """Return predicate which recurses into the tree via given attributes."""
1809+ components = list(components)
1810+ components.reverse()
1811+
1812+ def test(node):
1813+ return True
1814+
1815+ for component in components:
1816+ attr_name = component[0]
1817+ subtests = component[1:]
1818+ test = match_attr(attr_name, match_all(match_all(*subtests), test))
1819+ return match_all(initial_test, test)
1820+
1821+
1822+def match_child(*tests):
1823+ """Return predicate which tests any child."""
1824+ subtest = match_all(*tests)
1825+
1826+ def test(node):
1827+ """Try each child until we find one that matches."""
1828+ for child in ast.iter_child_nodes(node):
1829+ if subtest(child):
1830+ return True
1831+ return False
1832+
1833+ return test
1834+
1835+
1836+def match_descendant(subtest, prune):
1837+ """Return predicate which tests a node and any descendants."""
1838+
1839+ def test(node):
1840+ """Recursively (breadth-first) search for a matching node."""
1841+ for child in ast.iter_child_nodes(node):
1842+ if prune(child):
1843+ continue
1844+ if subtest(child) or test(child):
1845+ return True
1846+ return False
1847+
1848+ return test
1849+
1850+
1851+def matches(node, *tests):
1852+ """Convenience function to try predicates on a node."""
1853+ return match_all(*tests)(node)
1854+
1855+
1856+def any_matches(nodes, *tests):
1857+ """Convenience function to try predicates on any of a sequence of nodes."""
1858+ test = match_all(*tests)
1859+ for node in nodes:
1860+ if test(node):
1861+ return True
1862+ return False
1863+
1864+
1865+def iter_matching_child_nodes(node, *tests):
1866+ """Yields every matching child node."""
1867+ test = match_all(*tests)
1868+ for child in ast.iter_child_nodes(node):
1869+ if test(child):
1870+ yield child
1871+
1872+
1873+SETUP_FUNCTION_NAMES = ('setUp', 'tearDown')
1874+SETUP_FUNCTION = match_path(match_type(ast.FunctionDef),
1875+ ('name', match_in(SETUP_FUNCTION_NAMES)))
1876+
1877+SUPER = match_path(match_type(ast.Call),
1878+ ('func', match_type(ast.Attribute)),
1879+ ('value', match_type(ast.Call)),
1880+ ('func', match_type(ast.Name)),
1881+ ('id', match_equal("super")))
1882+
1883+BARE_SUPER = match_path(match_type(ast.Expr),
1884+ ('value', SUPER))
1885+
1886+YIELD = match_type(ast.Yield)
1887+
1888+INLINE_CALLBACKS_DECORATOR = \
1889+ match_any(match_path(match_type(ast.Attribute),
1890+ ('attr', match_equal('inlineCallbacks'))),
1891+ match_path(match_type(ast.Name),
1892+ ('id', match_equal('inlineCallbacks'))))
1893+
1894+RETURN_VALUE = \
1895+ match_path(match_type(ast.Return),
1896+ ('value', match_not_none()))
1897+
1898+DEFS = match_any(match_type(ast.ClassDef),
1899+ match_type(ast.FunctionDef))
1900+
1901+
1902+def find_problems(class_to_check):
1903+ """Check twisted test setup in a given test class."""
1904+ mro = class_to_check.__mro__
1905+ if TwistedTestCase not in mro:
1906+ return set()
1907+
1908+ problems = set()
1909+
1910+ ancestry = takewhile(lambda c: c != TwistedTestCase, mro)
1911+ for ancestor_class in ancestry:
1912+ if 'run' in ancestor_class.__dict__:
1913+ problem = MethodShadowed(method='run',
1914+ test_class=class_to_check,
1915+ ancestor_class=ancestor_class)
1916+ problems.add(problem)
1917+
1918+ source = dedent(getsource(ancestor_class))
1919+ tree = ast.parse(source)
1920+ # the top level of the tree is a Module
1921+ class_node = tree.body[0]
1922+
1923+ # Check setUp/tearDown
1924+ for def_node in iter_matching_child_nodes(class_node, SETUP_FUNCTION):
1925+ if matches(def_node, match_child(BARE_SUPER)):
1926+ # Superclass method called, but its result wasn't used
1927+ problem = SuperResultDiscarded(method=def_node.name,
1928+ test_class=class_to_check,
1929+ ancestor_class=ancestor_class)
1930+ problems.add(problem)
1931+ if not matches(def_node, match_descendant(SUPER, DEFS)):
1932+ # The call to the overridden superclass method is missing
1933+ problem = SuperNotCalled(method=def_node.name,
1934+ test_class=class_to_check,
1935+ ancestor_class=ancestor_class)
1936+ problems.add(problem)
1937+
1938+ decorators = def_node.decorator_list
1939+
1940+ if matches(def_node, match_descendant(YIELD, DEFS)):
1941+ # Yield was used, making this a generator
1942+ if not any_matches(decorators, INLINE_CALLBACKS_DECORATOR):
1943+ # ...but the inlineCallbacks decorator is missing
1944+ problem = MissingInlineCallbacks(
1945+ method=def_node.name,
1946+ test_class=class_to_check,
1947+ ancestor_class=ancestor_class)
1948+ problems.add(problem)
1949+ else:
1950+ if not matches(def_node, match_descendant(RETURN_VALUE, DEFS)):
1951+ # The function fails to return a deferred
1952+ problem = MissingReturnValue(
1953+ method=def_node.name,
1954+ test_class=class_to_check,
1955+ ancestor_class=ancestor_class)
1956+ problems.add(problem)
1957+
1958+ return problems
1959+
1960+
1961+def get_test_classes(suite):
1962+ """Return all the unique test classes involved in a suite."""
1963+ classes = set()
1964+
1965+ def find_classes(suite_or_test):
1966+ """Recursively find all the test classes."""
1967+ if isinstance(suite_or_test, TestSuite):
1968+ for subtest in suite_or_test:
1969+ find_classes(subtest)
1970+ else:
1971+ classes.add(type(suite_or_test))
1972+
1973+ find_classes(suite)
1974+
1975+ return classes
1976+
1977+
1978+def make_check_testcase(tests):
1979+ """Make TestCase which checks the given twisted tests."""
1980+
1981+ class TXCheckTest(TestCase):
1982+ """Test case which checks the test classes for problems."""
1983+
1984+ def runTest(self): # pylint: disable=C0103
1985+ """Do nothing."""
1986+
1987+ def run(self, result=None):
1988+ """Check all the test classes for problems."""
1989+ if result is None:
1990+ result = TestResult()
1991+
1992+ test_classes = set()
1993+
1994+ for test_object in tests:
1995+ test_classes |= get_test_classes(test_object)
1996+
1997+ for test_class in test_classes:
1998+ problems = find_problems(test_class)
1999+ for problem in problems:
2000+ try:
2001+ raise problem
2002+ except Problem:
2003+ result.addFailure(self, sys.exc_info())
2004+
2005+ return TXCheckTest()
2006+
2007+
2008+class TXCheckSuite(TestSuite):
2009+ """Test suite which checks twisted tests."""
2010+
2011+ def __init__(self, tests=()):
2012+ """Initialize with the given tests, and add a special test."""
2013+
2014+ tests = list(tests)
2015+ tests.insert(0, make_check_testcase(self))
2016+
2017+ super(TXCheckSuite, self).__init__(tests)
2018
2019=== added file 'contrib/devtools/testing/txwebserver.py'
2020--- contrib/devtools/testing/txwebserver.py 1970-01-01 00:00:00 +0000
2021+++ contrib/devtools/testing/txwebserver.py 2018-06-03 23:09:20 +0000
2022@@ -0,0 +1,125 @@
2023+# -*- coding: utf-8 -*-
2024+# Copyright 2012 Canonical Ltd.
2025+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2026+#
2027+# This program is free software: you can redistribute it and/or modify it
2028+# under the terms of the GNU General Public License version 3, as published
2029+# by the Free Software Foundation.
2030+#
2031+# This program is distributed in the hope that it will be useful, but
2032+# WITHOUT ANY WARRANTY; without even the implied warranties of
2033+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2034+# PURPOSE. See the GNU General Public License for more details.
2035+#
2036+# You should have received a copy of the GNU General Public License along
2037+# with this program. If not, see <http://www.gnu.org/licenses/>.
2038+#
2039+# In addition, as a special exception, the copyright holders give
2040+# permission to link the code of portions of this program with the
2041+# OpenSSL library under certain conditions as described in each
2042+# individual source file, and distribute linked combinations
2043+# including the two.
2044+# You must obey the GNU General Public License in all respects
2045+# for all of the code used other than OpenSSL. If you modify
2046+# file(s) with this exception, you may extend this exception to your
2047+# version of the file(s), but you are not obligated to do so. If you
2048+# do not wish to do so, delete this exception statement from your
2049+# version. If you delete this exception statement from all source
2050+# files in the program, then also delete it here.
2051+
2052+"""A tx based web server."""
2053+
2054+from __future__ import unicode_literals
2055+
2056+from twisted.internet import defer, reactor, ssl
2057+from twisted.protocols.policies import WrappingFactory
2058+from twisted.web import server
2059+
2060+from devtools.testcases.txsocketserver import server_protocol_factory
2061+
2062+# no init method + twisted common warnings
2063+# pylint: disable=W0232, C0103, E1101
2064+
2065+
2066+class BaseWebServer(object):
2067+ """Webserver used to perform requests in tests."""
2068+
2069+ def __init__(self, root_resource, scheme):
2070+ """Create and start the instance.
2071+
2072+ The ssl_settings parameter contains a dictionary with the key and cert
2073+ that will be used to perform ssl connections. The root_resource
2074+ contains the resource with all its childre.
2075+ """
2076+ self.root = root_resource
2077+ self.scheme = scheme
2078+ self.port = None
2079+ # use an http.HTTPFactory that was modified to ensure that we have
2080+ # clean close connections
2081+ self.site = server.Site(self.root, timeout=None)
2082+ self.wrapper = WrappingFactory(self.site)
2083+ self.wrapper.testserver_on_connection_lost = defer.Deferred()
2084+ self.wrapper.protocol = server_protocol_factory(self.wrapper.protocol)
2085+ self.wrapper._disconnecting = False
2086+
2087+ def listen(self, site):
2088+ """Listen a port to allow the tests."""
2089+ raise NotImplementedError('Base abstract class.')
2090+
2091+ def get_iri(self):
2092+ """Build the iri for this mock server."""
2093+ return "{scheme}://127.0.0.1:{port}/".format(scheme=self.scheme,
2094+ port=self.get_port())
2095+
2096+ def get_port(self):
2097+ """Return the port where we are listening."""
2098+ return self.port.getHost().port
2099+
2100+ def start(self):
2101+ """Start the service."""
2102+ self.port = self.listen(self.wrapper)
2103+
2104+ def stop(self):
2105+ """Shut it down."""
2106+ if self.port:
2107+ self.wrapper._disconnecting = True
2108+ connected = self.wrapper.protocols.keys()
2109+ if connected:
2110+ for con in connected:
2111+ con.transport.loseConnection()
2112+ else:
2113+ self.wrapper.testserver_on_connection_lost = \
2114+ defer.succeed(None)
2115+ d = defer.maybeDeferred(self.port.stopListening)
2116+ return defer.gatherResults(
2117+ [d,
2118+ self.wrapper.testserver_on_connection_lost])
2119+ return defer.succeed(None)
2120+
2121+
2122+class HTTPWebServer(BaseWebServer):
2123+ """A Webserver that listens to http connections."""
2124+
2125+ def __init__(self, root_resource):
2126+ """Create a new instance."""
2127+ super(HTTPWebServer, self).__init__(root_resource, 'http')
2128+
2129+ def listen(self, site):
2130+ """Listen a port to allow the tests."""
2131+ return reactor.listenTCP(0, site)
2132+
2133+
2134+class HTTPSWebServer(BaseWebServer):
2135+ """A WebServer that listens to https connections."""
2136+
2137+ def __init__(self, root_resource, ssl_settings=None):
2138+ """Create a new instance."""
2139+ super(HTTPSWebServer, self).__init__(root_resource, 'https')
2140+ self.ssl_settings = ssl_settings
2141+
2142+ def listen(self, site):
2143+ """Listen a port to allow the tests."""
2144+ ssl_context = ssl.DefaultOpenSSLContextFactory(
2145+ self.ssl_settings['key'], self.ssl_settings['cert'])
2146+
2147+ return reactor.listenSSL(0, site, ssl_context)
2148
2149=== added file 'contrib/devtools/utils.py'
2150--- contrib/devtools/utils.py 1970-01-01 00:00:00 +0000
2151+++ contrib/devtools/utils.py 2018-06-03 23:09:20 +0000
2152@@ -0,0 +1,181 @@
2153+# Copyright 2009-2012 Canonical Ltd.
2154+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2155+#
2156+# This program is free software: you can redistribute it and/or modify it
2157+# under the terms of the GNU General Public License version 3, as published
2158+# by the Free Software Foundation.
2159+#
2160+# This program is distributed in the hope that it will be useful, but
2161+# WITHOUT ANY WARRANTY; without even the implied warranties of
2162+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2163+# PURPOSE. See the GNU General Public License for more details.
2164+#
2165+# You should have received a copy of the GNU General Public License along
2166+# with this program. If not, see <http://www.gnu.org/licenses/>.
2167+#
2168+# In addition, as a special exception, the copyright holders give
2169+# permission to link the code of portions of this program with the
2170+# OpenSSL library under certain conditions as described in each
2171+# individual source file, and distribute linked combinations
2172+# including the two.
2173+# You must obey the GNU General Public License in all respects
2174+# for all of the code used other than OpenSSL. If you modify
2175+# file(s) with this exception, you may extend this exception to your
2176+# version of the file(s), but you are not obligated to do so. If you
2177+# do not wish to do so, delete this exception statement from your
2178+# version. If you delete this exception statement from all source
2179+# files in the program, then also delete it here.
2180+"""Utilities for Ubuntu One developer tools."""
2181+
2182+from __future__ import print_function, unicode_literals
2183+
2184+import getopt
2185+import sys
2186+
2187+from devtools.errors import UsageError
2188+__all__ = ['OptionParser']
2189+
2190+
2191+def accumulate_list_attr(class_obj, attr, list_obj, base_class=None):
2192+ """Get all of the list attributes of attr from the class hierarchy,
2193+ and return a list of the lists."""
2194+ for base in class_obj.__bases__:
2195+ accumulate_list_attr(base, attr, list_obj)
2196+ if base_class is None or base_class in class_obj.__bases__:
2197+ list_obj.extend(class_obj.__dict__.get(attr, []))
2198+
2199+
2200+def unpack_padded(length, sequence, default=None):
2201+ """Pads a sequence to length with value of default.
2202+
2203+ Returns a list containing the original and padded values.
2204+ """
2205+ newlist = [default] * length
2206+ newlist[:len(sequence)] = list(sequence)
2207+ return newlist
2208+
2209+
2210+class OptionParser(dict):
2211+ """Base options for our test runner."""
2212+
2213+ def __init__(self, *args, **kwargs):
2214+ super(OptionParser, self).__init__(*args, **kwargs)
2215+
2216+ # Store info about the options and defaults
2217+ self.long_opts = []
2218+ self.short_opts = ''
2219+ self.docs = {}
2220+ self.defaults = {}
2221+ self.synonyms = {}
2222+ self.dispatch = {}
2223+
2224+ # Get the options and defaults
2225+ for _get in [self._get_flags, self._get_params]:
2226+ # We don't use variable 'syns' here. It's just to pad the result.
2227+ # pylint: disable=W0612
2228+ (long_opts, short_opts, docs, defaults, syns, dispatch) = _get()
2229+ # pylint: enable=W0612
2230+ self.long_opts.extend(long_opts)
2231+ self.short_opts = self.short_opts + short_opts
2232+ self.docs.update(docs)
2233+ self.update(defaults)
2234+ self.defaults.update(defaults)
2235+ self.synonyms.update(syns)
2236+ self.dispatch.update(dispatch)
2237+
2238+ # We use some camelcase names for trial compatibility here.
2239+ # pylint: disable=C0103
2240+ def parseOptions(self, options=None):
2241+ """Parse the options."""
2242+ if options is None:
2243+ options = sys.argv[1:]
2244+
2245+ try:
2246+ opts, args = getopt.getopt(options,
2247+ self.short_opts, self.long_opts)
2248+ except getopt.error as e:
2249+ raise UsageError(e)
2250+
2251+ for opt, arg in opts:
2252+ if opt[1] == '-':
2253+ opt = opt[2:]
2254+ else:
2255+ opt = opt[1:]
2256+
2257+ if (opt not in self.synonyms.keys()):
2258+ raise UsageError('No such options: "%s"' % opt)
2259+
2260+ opt = self.synonyms[opt]
2261+ if self.defaults[opt] is False:
2262+ self[opt] = True
2263+ else:
2264+ self.dispatch[opt](arg)
2265+
2266+ try:
2267+ self.parseArgs(*args)
2268+ except TypeError:
2269+ raise UsageError('Wrong number of arguments.')
2270+
2271+ self.postOptions()
2272+
2273+ def postOptions(self):
2274+ """Called after options are parsed."""
2275+
2276+ def parseArgs(self, *args):
2277+ """Override to handle extra arguments specially."""
2278+ # pylint: enable=C0103
2279+
2280+ def _parse_arguments(self, arg_type=None, has_default=False):
2281+ """Parse the arguments as either flags or parameters."""
2282+ long_opts, short_opts = [], ''
2283+ docs, defaults, syns, dispatch = {}, {}, {}, {}
2284+
2285+ _args = []
2286+ accumulate_list_attr(self.__class__, arg_type, _args)
2287+
2288+ for _arg in _args:
2289+ try:
2290+ if has_default:
2291+ l_opt, s_opt, default, doc, _ = unpack_padded(5, _arg)
2292+ else:
2293+ default = False
2294+ l_opt, s_opt, doc, _ = unpack_padded(4, _arg)
2295+ except ValueError:
2296+ raise ValueError('Failed to parse argument: %s' % _arg)
2297+ if not l_opt:
2298+ raise ValueError('An option must have a long name.')
2299+
2300+ opt_m_name = 'opt_' + l_opt.replace('-', '_')
2301+ opt_method = getattr(self, opt_m_name, None)
2302+ if opt_method is not None:
2303+ docs[l_opt] = getattr(opt_method, '__doc__', None)
2304+ dispatch[l_opt] = opt_method
2305+ if docs[l_opt] is None:
2306+ docs[l_opt] = doc
2307+ else:
2308+ docs[l_opt] = doc
2309+ dispatch[l_opt] = lambda arg: True
2310+
2311+ defaults[l_opt] = default
2312+ if has_default:
2313+ long_opts.append(l_opt + '=')
2314+ else:
2315+ long_opts.append(l_opt)
2316+
2317+ syns[l_opt] = l_opt
2318+ if s_opt is not None:
2319+ short_opts = short_opts + s_opt
2320+ if has_default:
2321+ short_opts = short_opts + ':'
2322+ syns[s_opt] = l_opt
2323+
2324+ return long_opts, short_opts, docs, defaults, syns, dispatch
2325+
2326+ def _get_flags(self):
2327+ """Get the flag options."""
2328+ return self._parse_arguments(arg_type='optFlags', has_default=False)
2329+
2330+ def _get_params(self):
2331+ """Get the parameters options."""
2332+ return self._parse_arguments(arg_type='optParameters',
2333+ has_default=True)
2334
2335=== added directory 'contrib/dirspec'
2336=== added file 'contrib/dirspec/__init__.py'
2337--- contrib/dirspec/__init__.py 1970-01-01 00:00:00 +0000
2338+++ contrib/dirspec/__init__.py 2018-06-03 23:09:20 +0000
2339@@ -0,0 +1,16 @@
2340+# -*- coding: utf-8 -*-
2341+#
2342+# Copyright 2011 Canonical Ltd.
2343+#
2344+# This program is free software: you can redistribute it and/or modify
2345+# it under the terms of the GNU Lesser General Public License version 3
2346+# as published by the Free Software Foundation.
2347+#
2348+# This program is distributed in the hope that it will be useful,
2349+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2350+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2351+# GNU Lesser General Public License for more details.
2352+#
2353+# You should have received a copy of the GNU Lesser General Public License
2354+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2355+"""dirspec package."""
2356
2357=== added file 'contrib/dirspec/basedir.py'
2358--- contrib/dirspec/basedir.py 1970-01-01 00:00:00 +0000
2359+++ contrib/dirspec/basedir.py 2018-06-03 23:09:20 +0000
2360@@ -0,0 +1,159 @@
2361+# -*- coding: utf-8 -*-
2362+#
2363+# Copyright 2011-2012 Canonical Ltd.
2364+#
2365+# This program is free software: you can redistribute it and/or modify
2366+# it under the terms of the GNU Lesser General Public License version 3
2367+# as published by the Free Software Foundation.
2368+#
2369+# This program is distributed in the hope that it will be useful,
2370+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2371+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2372+# GNU Lesser General Public License for more details.
2373+#
2374+# You should have received a copy of the GNU Lesser General Public License
2375+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2376+"""XDG Base Directory paths."""
2377+
2378+from __future__ import unicode_literals, print_function
2379+
2380+import os
2381+
2382+from dirspec.utils import (default_cache_home,
2383+ default_config_path, default_config_home,
2384+ default_data_path, default_data_home,
2385+ get_env_path, unicode_path)
2386+
2387+__all__ = ['xdg_cache_home',
2388+ 'xdg_config_home',
2389+ 'xdg_data_home',
2390+ 'xdg_config_dirs',
2391+ 'xdg_data_dirs',
2392+ 'load_config_paths',
2393+ 'load_data_paths',
2394+ 'load_first_config',
2395+ 'save_config_path',
2396+ 'save_data_path',
2397+ ]
2398+
2399+
2400+def get_xdg_cache_home():
2401+ """Get the path for XDG cache directory in user's HOME."""
2402+ return get_env_path('XDG_CACHE_HOME', default_cache_home)
2403+
2404+
2405+def get_xdg_config_home():
2406+ """Get the path for XDG config directory in user's HOME."""
2407+ return get_env_path('XDG_CONFIG_HOME', default_config_home)
2408+
2409+
2410+def get_xdg_data_home():
2411+ """Get the path for XDG data directory in user's HOME."""
2412+ return get_env_path('XDG_DATA_HOME', default_data_home)
2413+
2414+
2415+def get_xdg_config_dirs():
2416+ """Get the paths for the XDG config directories."""
2417+ result = [get_xdg_config_home()]
2418+ result.extend([x.encode('utf-8') for x in get_env_path(
2419+ 'XDG_CONFIG_DIRS',
2420+ default_config_path).decode('utf-8').split(os.pathsep)])
2421+ return result
2422+
2423+
2424+def get_xdg_data_dirs():
2425+ """Get the paths for the XDG data directories."""
2426+ result = [get_xdg_data_home()]
2427+ result.extend([x.encode('utf-8') for x in get_env_path(
2428+ 'XDG_DATA_DIRS',
2429+ default_data_path).decode('utf-8').split(os.pathsep)])
2430+ return result
2431+
2432+
2433+def load_config_paths(*resource):
2434+ """Iterator of configuration paths.
2435+
2436+ Return an iterator which gives each directory named 'resource' in
2437+ the configuration search path. Information provided by earlier
2438+ directories should take precedence over later ones (ie, the user's
2439+ config dir comes first).
2440+ """
2441+ resource = os.path.join(*resource)
2442+ assert not resource.startswith('/')
2443+ for config_dir in get_xdg_config_dirs():
2444+ path = os.path.join(config_dir, resource.encode('utf-8'))
2445+ # access the file system always with unicode
2446+ # to properly behave in some operating systems
2447+ if os.path.exists(unicode_path(path)):
2448+ yield path
2449+
2450+
2451+def load_data_paths(*resource):
2452+ """Iterator of data paths.
2453+
2454+ Return an iterator which gives each directory named 'resource' in
2455+ the stored data search path. Information provided by earlier
2456+ directories should take precedence over later ones.
2457+ """
2458+ resource = os.path.join(*resource)
2459+ assert not resource.startswith('/')
2460+ for data_dir in get_xdg_data_dirs():
2461+ path = os.path.join(data_dir, resource.encode('utf-8'))
2462+ # access the file system always with unicode
2463+ # to properly behave in some operating systems
2464+ if os.path.exists(unicode_path(path)):
2465+ yield path
2466+
2467+
2468+def load_first_config(*resource):
2469+ """Returns the first result from load_config_paths, or None if nothing
2470+ is found to load.
2471+ """
2472+ for path in load_config_paths(*resource):
2473+ return path
2474+ return None
2475+
2476+
2477+def save_config_path(*resource):
2478+ """Path to save configuration.
2479+
2480+ Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
2481+ 'resource' should normally be the name of your application. Use this
2482+ when SAVING configuration settings. Use the xdg_config_dirs variable
2483+ for loading.
2484+ """
2485+ resource = os.path.join(*resource)
2486+ assert not resource.startswith('/')
2487+ path = os.path.join(get_xdg_config_home(), resource.encode('utf-8'))
2488+ # access the file system always with unicode
2489+ # to properly behave in some operating systems
2490+ if not os.path.isdir(unicode_path(path)):
2491+ os.makedirs(unicode_path(path), 0o700)
2492+ return path
2493+
2494+
2495+def save_data_path(*resource):
2496+ """Path to save data.
2497+
2498+ Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path.
2499+ 'resource' should normally be the name of your application. Use this
2500+ when STORING a resource. Use the xdg_data_dirs variable for loading.
2501+ """
2502+ resource = os.path.join(*resource)
2503+ assert not resource.startswith('/')
2504+ path = os.path.join(get_xdg_data_home(), resource.encode('utf-8'))
2505+ # access the file system always with unicode
2506+ # to properly behave in some operating systems
2507+ if not os.path.isdir(unicode_path(path)):
2508+ os.makedirs(unicode_path(path), 0o700)
2509+ return path
2510+
2511+
2512+# pylint: disable=C0103
2513+xdg_cache_home = get_xdg_cache_home()
2514+xdg_config_home = get_xdg_config_home()
2515+xdg_data_home = get_xdg_data_home()
2516+
2517+xdg_config_dirs = [x for x in get_xdg_config_dirs() if x]
2518+xdg_data_dirs = [x for x in get_xdg_data_dirs() if x]
2519+# pylint: disable=C0103
2520
2521=== added file 'contrib/dirspec/utils.py'
2522--- contrib/dirspec/utils.py 1970-01-01 00:00:00 +0000
2523+++ contrib/dirspec/utils.py 2018-06-03 23:09:20 +0000
2524@@ -0,0 +1,188 @@
2525+# -*- coding: utf-8 -*-
2526+#
2527+# Copyright 2011-2012 Canonical Ltd.
2528+#
2529+# This program is free software: you can redistribute it and/or modify
2530+# it under the terms of the GNU Lesser General Public License version 3
2531+# as published by the Free Software Foundation.
2532+#
2533+# This program is distributed in the hope that it will be useful,
2534+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2535+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2536+# GNU Lesser General Public License for more details.
2537+#
2538+# You should have received a copy of the GNU Lesser General Public License
2539+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2540+"""Utilities for multiplatform support of XDG directory handling."""
2541+
2542+from __future__ import unicode_literals, print_function
2543+
2544+import errno
2545+import os
2546+import sys
2547+
2548+__all__ = ['user_home',
2549+ 'default_cache_home',
2550+ 'default_config_home',
2551+ 'default_config_path',
2552+ 'default_data_home',
2553+ 'default_data_path',
2554+ 'get_env_path',
2555+ 'get_program_path',
2556+ 'unicode_path',
2557+ ]
2558+
2559+
2560+def _get_exe_path_frozen_win32(exe_name):
2561+ """Get path to the helper .exe on packaged windows."""
2562+ # all the .exes are in the same place on windows:
2563+ cur_exec_path = os.path.abspath(sys.executable)
2564+ exe_dir = os.path.dirname(cur_exec_path)
2565+ return os.path.join(exe_dir, exe_name + ".exe")
2566+
2567+
2568+def _get_exe_path_frozen_darwin(exe_name, app_names):
2569+ """Get path to the sub-app executable on packaged darwin."""
2570+
2571+ sub_app_name = app_names[exe_name]
2572+ main_app_dir = "".join(__file__.partition(".app")[:-1])
2573+ main_app_resources_dir = os.path.join(main_app_dir,
2574+ "Contents",
2575+ "Resources")
2576+ exe_bin = os.path.join(main_app_resources_dir,
2577+ sub_app_name,
2578+ "Contents", "MacOS",
2579+ exe_name)
2580+ return exe_bin
2581+
2582+
2583+def get_program_path(program_name, *args, **kwargs):
2584+ """Given a program name, returns the path to run that program.
2585+
2586+ Raises OSError if the program is not found.
2587+
2588+ :param program_name: The name of the program to find. For darwin and win32
2589+ platforms, the behavior is changed slightly, when sys.frozen is set,
2590+ to look in the packaged program locations for the program.
2591+ :param search_dirs: A list of directories to look for the program in. This
2592+ is only available as a keyword argument.
2593+ :param app_names: A dict of program names mapped to sub-app names. Used
2594+ for discovering paths in embedded .app bundles on the darwin platform.
2595+ This is only available as a keyword argument.
2596+ :return: The path to the discovered program.
2597+ """
2598+ search_dirs = kwargs.get('fallback_dirs', None)
2599+ app_names = kwargs.get('app_names', None)
2600+
2601+ if getattr(sys, "frozen", None) is not None:
2602+ if sys.platform == 'win32':
2603+ program_path = _get_exe_path_frozen_win32(program_name)
2604+ elif sys.platform == 'darwin':
2605+ program_path = _get_exe_path_frozen_darwin(program_name,
2606+ app_names)
2607+ else:
2608+ raise Exception("Unsupported platform for frozen execution: %r" %
2609+ sys.platform)
2610+ else:
2611+ if search_dirs is not None:
2612+ for dirname in search_dirs:
2613+ program_path = os.path.join(dirname, program_name)
2614+ if os.path.exists(program_path):
2615+ return program_path
2616+ else:
2617+ # Check in normal system $PATH, if no fallback dirs specified
2618+ from distutils.spawn import find_executable
2619+ program_path = find_executable(program_name)
2620+
2621+ if program_path is None or not os.path.exists(program_path):
2622+ raise OSError(errno.ENOENT,
2623+ "Could not find executable %r" % program_name)
2624+
2625+ return program_path
2626+
2627+
2628+def get_env_path(key, default):
2629+ """Get a UTF-8 encoded path from an environment variable."""
2630+ if key in os.environ:
2631+ # on windows, environment variables are mbcs bytes
2632+ # so we must turn them into utf-8 Syncdaemon paths
2633+ try:
2634+ path = os.environb.get(key.encode('utf-8'))
2635+ except AttributeError:
2636+ path = os.environ[key]
2637+ return path.decode(sys.getfilesystemencoding()).encode('utf-8')
2638+ else:
2639+ if not isinstance(default, bytes):
2640+ return default.encode('utf-8')
2641+ return default
2642+
2643+
2644+def unicode_path(utf8path):
2645+ """Turn an utf8 path into a unicode path."""
2646+ if isinstance(utf8path, bytes):
2647+ return utf8path.decode("utf-8")
2648+ return utf8path
2649+
2650+
2651+def get_special_folders():
2652+ """ Routine to grab all the Windows Special Folders locations.
2653+
2654+ If successful, returns dictionary
2655+ of shell folder locations indexed on Windows keyword for each;
2656+ otherwise, returns an empty dictionary.
2657+ """
2658+ # pylint: disable=W0621, F0401, E0611
2659+ special_folders = {}
2660+
2661+ if sys.platform == 'win32':
2662+ from win32com.shell import shell, shellcon
2663+ # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local
2664+ # CSIDL_PROFILE = C:\Users\<username>
2665+ # CSIDL_COMMON_APPDATA = C:\ProgramData
2666+ # More information on these constants at
2667+ # http://msdn.microsoft.com/en-us/library/bb762494
2668+
2669+ # per http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181,
2670+ # SHGetFolderPath is deprecated, replaced by SHGetKnownFolderPath
2671+ # (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188)
2672+ def get_path(name):
2673+ return shell.SHGetFolderPath(0, getattr(shellcon, name),
2674+ None, 0).encode('utf8')
2675+ special_folders['Personal'] = get_path("CSIDL_PROFILE")
2676+ special_folders['Local AppData'] = get_path("CSIDL_LOCAL_APPDATA")
2677+ special_folders['AppData'] = os.path.dirname(
2678+ special_folders['Local AppData'])
2679+ special_folders['Common AppData'] = get_path("CSIDL_COMMON_APPDATA")
2680+
2681+ return special_folders
2682+
2683+
2684+# pylint: disable=C0103
2685+if sys.platform == 'win32':
2686+ special_folders = get_special_folders()
2687+ user_home = special_folders['Personal']
2688+ default_config_path = special_folders['Common AppData']
2689+ default_config_home = special_folders['Local AppData']
2690+ default_data_path = os.path.join(default_config_path, b'xdg')
2691+ default_data_home = os.path.join(default_config_home, b'xdg')
2692+ default_cache_home = os.path.join(default_data_home, b'cache')
2693+elif sys.platform == 'darwin':
2694+ user_home = os.path.expanduser(b'~')
2695+ default_cache_home = os.path.join(user_home, b'Library', b'Caches')
2696+ default_config_path = b'/Library/Preferences:/etc/xdg'
2697+ default_config_home = os.path.join(user_home, b'Library', b'Preferences')
2698+ default_data_path = b':'.join([b'/Library/Application Support',
2699+ b'/usr/local/share',
2700+ b'/usr/share'])
2701+ default_data_home = os.path.join(user_home, b'Library',
2702+ b'Application Support')
2703+else:
2704+ user_home = os.path.expanduser(b'~')
2705+ default_cache_home = os.path.join(user_home,
2706+ b'.cache')
2707+ default_config_path = b'/etc/xdg'
2708+ default_config_home = os.path.join(user_home,
2709+ b'.config')
2710+ default_data_path = b'/usr/local/share:/usr/share'
2711+ default_data_home = os.path.join(user_home,
2712+ b'.local', b'share')
2713
2714=== added file 'contrib/u1trial'
2715--- contrib/u1trial 1970-01-01 00:00:00 +0000
2716+++ contrib/u1trial 2018-06-03 23:09:20 +0000
2717@@ -0,0 +1,41 @@
2718+#! /usr/bin/python
2719+#
2720+# Copyright 2009-2012 Canonical Ltd.
2721+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2722+#
2723+# This program is free software: you can redistribute it and/or modify it
2724+# under the terms of the GNU General Public License version 3, as published
2725+# by the Free Software Foundation.
2726+#
2727+# This program is distributed in the hope that it will be useful, but
2728+# WITHOUT ANY WARRANTY; without even the implied warranties of
2729+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2730+# PURPOSE. See the GNU General Public License for more details.
2731+#
2732+# You should have received a copy of the GNU General Public License along
2733+# with this program. If not, see <http://www.gnu.org/licenses/>.
2734+#
2735+# In addition, as a special exception, the copyright holders give
2736+# permission to link the code of portions of this program with the
2737+# OpenSSL library under certain conditions as described in each
2738+# individual source file, and distribute linked combinations
2739+# including the two.
2740+# You must obey the GNU General Public License in all respects
2741+# for all of the code used other than OpenSSL. If you modify
2742+# file(s) with this exception, you may extend this exception to your
2743+# version of the file(s), but you are not obligated to do so. If you
2744+# do not wish to do so, delete this exception statement from your
2745+# version. If you delete this exception statement from all source
2746+# files in the program, then also delete it here.
2747+"""Test runner which works with special services and main loops."""
2748+
2749+import os
2750+import sys
2751+
2752+sys.path.insert(0, os.path.abspath("."))
2753+
2754+from devtools.runners import main
2755+
2756+
2757+if __name__ == '__main__':
2758+ main()
2759
2760=== modified file 'dependencies-devel.txt'
2761--- dependencies-devel.txt 2018-03-18 11:59:08 +0000
2762+++ dependencies-devel.txt 2018-06-03 23:09:20 +0000
2763@@ -1,5 +1,2 @@
2764 bzr
2765 make
2766-python-mocker
2767-ubuntuone-dev-tools
2768-virtualenv
2769
2770=== modified file 'dependencies.txt'
2771--- dependencies.txt 2018-04-14 23:34:20 +0000
2772+++ dependencies.txt 2018-06-03 23:09:20 +0000
2773@@ -1,8 +1,5 @@
2774-gir1.2-soup-2.4
2775-python-configglue
2776-python-dirspec
2777-python-distutils-extra
2778-python-gi
2779-python-protobuf
2780-python-pyinotify
2781-python-twisted
2782+libgirepository1.0-dev
2783+libgtk2.0-dev
2784+pkg-config
2785+python-dev
2786+virtualenv
2787
2788=== modified file 'magicicadaclient/platform/tests/filesystem_notifications/__init__.py'
2789--- magicicadaclient/platform/tests/filesystem_notifications/__init__.py 2018-05-19 20:44:54 +0000
2790+++ magicicadaclient/platform/tests/filesystem_notifications/__init__.py 2018-06-03 23:09:20 +0000
2791@@ -1,4 +1,5 @@
2792 # Copyright 2012 Canonical Ltd.
2793+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2794 #
2795 # This program is free software: you can redistribute it and/or modify it
2796 # under the terms of the GNU General Public License version 3, as published
2797@@ -30,10 +31,10 @@
2798
2799 from twisted.internet import defer, reactor
2800
2801-from magicicadaclient.testing import testcase
2802-from ubuntuone.devtools.handlers import MementoHandler
2803+from devtools.handlers import MementoHandler
2804 from magicicadaclient.syncdaemon import event_queue, filesystem_manager
2805 from magicicadaclient.syncdaemon.tritcask import Tritcask
2806+from magicicadaclient.testing import testcase
2807
2808
2809 class BaseFSMonitorTestCase(testcase.BaseTwistedTestCase):
2810
2811=== modified file 'magicicadaclient/platform/tests/filesystem_notifications/common.py'
2812--- magicicadaclient/platform/tests/filesystem_notifications/common.py 2018-05-19 20:44:54 +0000
2813+++ magicicadaclient/platform/tests/filesystem_notifications/common.py 2018-06-03 23:09:20 +0000
2814@@ -1,8 +1,5 @@
2815-#
2816-# Authors: Manuel de la Pena <manuel@canonical.com>
2817-# Alejandro J. Cura <alecu@canonical.com>
2818-#
2819 # Copyright 2011-2012 Canonical Ltd.
2820+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2821 #
2822 # This program is free software: you can redistribute it and/or modify it
2823 # under the terms of the GNU General Public License version 3, as published
2824@@ -38,16 +35,18 @@
2825 import itertools
2826
2827 from twisted.internet import defer
2828+
2829+from devtools.handlers import MementoHandler
2830 from magicicadaclient.testing.testcase import BaseTwistedTestCase
2831-from ubuntuone.devtools.handlers import MementoHandler
2832-from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic import (
2833- EventsCodes,
2834- ProcessEvent,
2835- IN_CLOSE_WRITE,
2836- IN_CREATE,
2837- IN_DELETE,
2838- IN_OPEN,
2839-)
2840+from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic \
2841+ import (
2842+ EventsCodes,
2843+ ProcessEvent,
2844+ IN_CLOSE_WRITE,
2845+ IN_CREATE,
2846+ IN_DELETE,
2847+ IN_OPEN,
2848+ )
2849 from magicicadaclient.platform.filesystem_notifications import notify_processor
2850 from magicicadaclient.platform.filesystem_notifications.monitor.common import (
2851 FilesystemMonitor,
2852
2853=== modified file 'magicicadaclient/platform/tests/filesystem_notifications/test_darwin.py'
2854--- magicicadaclient/platform/tests/filesystem_notifications/test_darwin.py 2018-05-19 20:44:54 +0000
2855+++ magicicadaclient/platform/tests/filesystem_notifications/test_darwin.py 2018-06-03 23:09:20 +0000
2856@@ -1,6 +1,7 @@
2857 # -*- coding: utf-8 *-*
2858 #
2859 # Copyright 2012 Canonical Ltd.
2860+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2861 #
2862 # This program is free software: you can redistribute it and/or modify it
2863 # under the terms of the GNU General Public License version 3, as published
2864@@ -38,9 +39,8 @@
2865
2866 import fsevents
2867
2868+from devtools.handlers import MementoHandler
2869 from magicicadaclient.testing.testcase import BaseTwistedTestCase
2870-
2871-from ubuntuone.devtools.handlers import MementoHandler
2872 from magicicadaclient.platform.filesystem_notifications.monitor import (
2873 common,
2874 )
2875@@ -52,13 +52,14 @@
2876 Watch,
2877 WatchManager,
2878 )
2879-from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic import (
2880- ProcessEvent,
2881- IN_CLOSE_WRITE,
2882- IN_CREATE,
2883- IN_DELETE,
2884- IN_OPEN,
2885-)
2886+from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic \
2887+ import (
2888+ ProcessEvent,
2889+ IN_CLOSE_WRITE,
2890+ IN_CREATE,
2891+ IN_DELETE,
2892+ IN_OPEN,
2893+ )
2894 from magicicadaclient.platform.tests.filesystem_notifications import (
2895 BaseFSMonitorTestCase,
2896 common as common_tests,
2897
2898=== modified file 'magicicadaclient/platform/tests/filesystem_notifications/test_filesystem_notifications.py'
2899--- magicicadaclient/platform/tests/filesystem_notifications/test_filesystem_notifications.py 2018-05-19 20:44:54 +0000
2900+++ magicicadaclient/platform/tests/filesystem_notifications/test_filesystem_notifications.py 2018-06-03 23:09:20 +0000
2901@@ -1,6 +1,7 @@
2902 # -*- coding: utf-8 -*-
2903 #
2904 # Copyright 2009-2012 Canonical Ltd.
2905+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2906 #
2907 # This program is free software: you can redistribute it and/or modify it
2908 # under the terms of the GNU General Public License version 3, as published
2909@@ -33,8 +34,8 @@
2910
2911 from twisted.internet import defer, reactor
2912 from twisted.trial import unittest
2913-from ubuntuone.devtools.handlers import MementoHandler
2914
2915+from devtools.handlers import MementoHandler
2916 from magicicadaclient.testing.testcase import (
2917 BaseTwistedTestCase,
2918 FakeVolumeManager,
2919
2920=== modified file 'magicicadaclient/platform/tests/filesystem_notifications/test_fsevents_daemon.py'
2921--- magicicadaclient/platform/tests/filesystem_notifications/test_fsevents_daemon.py 2018-05-19 20:44:54 +0000
2922+++ magicicadaclient/platform/tests/filesystem_notifications/test_fsevents_daemon.py 2018-06-03 23:09:20 +0000
2923@@ -1,6 +1,7 @@
2924 # -*- coding: utf-8 *-*
2925 #
2926 # Copyright 2012 Canonical Ltd.
2927+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2928 #
2929 # This program is free software: you can redistribute it and/or modify it
2930 # under the terms of the GNU General Public License version 3, as published
2931@@ -32,24 +33,20 @@
2932
2933 from twisted.internet import defer, protocol
2934
2935+from devtools.testcases.txsocketserver import TidyUnixServer
2936 from magicicadaclient.testing.testcase import BaseTwistedTestCase
2937 from magicicadaclient import fseventsd
2938-try:
2939- from ubuntuone.devtools.testcases import skipIf
2940- from ubuntuone.devtools.testcases.txsocketserver import TidyUnixServer
2941-except ImportError:
2942- from ubuntuone.devtools.testcase import skipIf
2943- TidyUnixServer = None
2944 from magicicadaclient.platform.filesystem_notifications.monitor.darwin import (
2945 fsevents_daemon,
2946 )
2947-from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic import (
2948- IN_CREATE,
2949- IN_DELETE,
2950- IN_MODIFY,
2951- IN_MOVED_FROM,
2952- IN_MOVED_TO,
2953-)
2954+from magicicadaclient.platform.filesystem_notifications.pyinotify_agnostic \
2955+ import (
2956+ IN_CREATE,
2957+ IN_DELETE,
2958+ IN_MODIFY,
2959+ IN_MOVED_FROM,
2960+ IN_MOVED_TO,
2961+ )
2962
2963
2964 class FakeServerProtocol(protocol.Protocol):
2965@@ -469,8 +466,6 @@
2966 yield self.monitor.add_watch(dirpath)
2967 self.assertNotIn('add_path', self.protocol.called)
2968
2969- @skipIf(TidyUnixServer is None,
2970- 'Testcases from txsocketserver not availble.')
2971 @defer.inlineCallbacks
2972 def test_is_available_monitor_running(self):
2973 """Test the method when it is indeed running."""
2974
2975=== modified file 'magicicadaclient/platform/tests/ipc/test_linux.py'
2976--- magicicadaclient/platform/tests/ipc/test_linux.py 2018-05-19 20:44:54 +0000
2977+++ magicicadaclient/platform/tests/ipc/test_linux.py 2018-06-03 23:09:20 +0000
2978@@ -1,6 +1,7 @@
2979 # -*- coding: utf-8 -*-
2980 #
2981 # Copyright 2009-2012 Canonical Ltd.
2982+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
2983 #
2984 # This program is free software: you can redistribute it and/or modify it
2985 # under the terms of the GNU General Public License version 3, as published
2986@@ -33,11 +34,8 @@
2987 import dbus
2988
2989 from twisted.internet import defer
2990-try:
2991- from ubuntuone.devtools.testcases.dbus import DBusTestCase
2992-except ImportError:
2993- from ubuntuone.devtools.testcase import DBusTestCase
2994
2995+from devtools.testcases.dbus import DBusTestCase
2996 from magicicadaclient.testing.testcase import (
2997 FakeMainTestCase,
2998 FakedService,
2999
3000=== modified file 'magicicadaclient/platform/tests/ipc/test_perspective_broker.py'
3001--- magicicadaclient/platform/tests/ipc/test_perspective_broker.py 2018-05-19 20:44:54 +0000
3002+++ magicicadaclient/platform/tests/ipc/test_perspective_broker.py 2018-06-03 23:09:20 +0000
3003@@ -1,6 +1,7 @@
3004 # -*- coding: utf-8 -*-
3005 #
3006 # Copyright 2011-2012 Canonical Ltd.
3007+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3008 #
3009 # This program is free software: you can redistribute it and/or modify it
3010 # under the terms of the GNU General Public License version 3, as published
3011@@ -41,14 +42,8 @@
3012 )
3013 from twisted.trial.unittest import TestCase
3014
3015-from magicicadaclient.testing.testcase import (
3016- FakedService,
3017- FakeMainTestCase,
3018-)
3019-try:
3020- from ubuntuone.devtools.testcases import skipIf, skipIfOS
3021-except ImportError:
3022- from ubuntuone.devtools.testcase import skipIf, skipIfOS
3023+from devtools.testcases import skipIf, skipIfOS
3024+from magicicadaclient.testing.testcase import FakedService, FakeMainTestCase
3025 from magicicadaclient.platform.ipc import perspective_broker as ipc
3026 from magicicadaclient.platform.ipc.perspective_broker import (
3027 Config,
3028
3029=== modified file 'magicicadaclient/platform/tests/os_helper/test_darwin.py'
3030--- magicicadaclient/platform/tests/os_helper/test_darwin.py 2018-05-19 20:44:54 +0000
3031+++ magicicadaclient/platform/tests/os_helper/test_darwin.py 2018-06-03 23:09:20 +0000
3032@@ -1,7 +1,7 @@
3033 # -*- encoding: utf-8 -*-
3034-# tests.platform.os_helper - darwin platform tests
3035 #
3036 # Copyright 2012 Canonical Ltd.
3037+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3038 #
3039 # This program is free software: you can redistribute it and/or modify it
3040 # under the terms of the GNU General Public License version 3, as published
3041@@ -33,13 +33,9 @@
3042 import os
3043
3044 from twisted.internet import defer
3045-from ubuntuone.devtools.handlers import MementoHandler
3046
3047-from magicicadaclient.platform import (
3048- move_to_trash,
3049- open_file,
3050- stat_path,
3051-)
3052+from devtools.handlers import MementoHandler
3053+from magicicadaclient.platform import move_to_trash, open_file, stat_path
3054 from magicicadaclient.platform.os_helper import darwin
3055 from magicicadaclient.platform.tests.os_helper import test_os_helper
3056
3057
3058=== modified file 'magicicadaclient/platform/tests/os_helper/test_linux.py'
3059--- magicicadaclient/platform/tests/os_helper/test_linux.py 2018-05-19 20:44:54 +0000
3060+++ magicicadaclient/platform/tests/os_helper/test_linux.py 2018-06-03 23:09:20 +0000
3061@@ -1,4 +1,5 @@
3062 # Copyright 2010-2013 Canonical Ltd.
3063+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3064 #
3065 # This program is free software: you can redistribute it and/or modify it
3066 # under the terms of the GNU General Public License version 3, as published
3067@@ -30,15 +31,11 @@
3068 import os
3069
3070 from twisted.internet import defer
3071-from ubuntuone.devtools.handlers import MementoHandler
3072
3073+from devtools.handlers import MementoHandler
3074+from magicicadaclient.platform import move_to_trash, open_file, stat_path
3075 from magicicadaclient.platform.tests.os_helper import test_os_helper
3076 from magicicadaclient.platform.os_helper import linux
3077-from magicicadaclient.platform import (
3078- move_to_trash,
3079- open_file,
3080- stat_path,
3081-)
3082
3083
3084 class OSWrapperTests(test_os_helper.OSWrapperTests):
3085
3086=== modified file 'magicicadaclient/platform/tests/session/test_common.py'
3087--- magicicadaclient/platform/tests/session/test_common.py 2018-05-19 20:44:54 +0000
3088+++ magicicadaclient/platform/tests/session/test_common.py 2018-06-03 23:09:20 +0000
3089@@ -1,5 +1,6 @@
3090 #
3091 # Copyright 2011-2012 Canonical Ltd.
3092+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3093 #
3094 # This program is free software: you can redistribute it and/or modify it
3095 # under the terms of the GNU General Public License version 3, as published
3096@@ -30,7 +31,7 @@
3097 from twisted.internet import defer
3098 from twisted.trial.unittest import TestCase
3099
3100-from ubuntuone.devtools.testcases import skipIfOS
3101+from devtools.testcases import skipIfOS
3102 from magicicadaclient.platform.session import Inhibitor, INHIBIT_LOGOUT_SUSPEND
3103
3104
3105
3106=== modified file 'magicicadaclient/platform/tests/session/test_linux.py'
3107--- magicicadaclient/platform/tests/session/test_linux.py 2018-05-19 20:44:54 +0000
3108+++ magicicadaclient/platform/tests/session/test_linux.py 2018-06-03 23:09:20 +0000
3109@@ -1,8 +1,5 @@
3110-# tests.platform.linux.test_session
3111-#
3112-# Author: Alejandro J. Cura <alecu@canonical.com>
3113-#
3114 # Copyright 2011-2012 Canonical Ltd.
3115+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3116 #
3117 # This program is free software: you can redistribute it and/or modify it
3118 # under the terms of the GNU General Public License version 3, as published
3119@@ -30,15 +27,13 @@
3120 # files in the program, then also delete it here.
3121 """Tests for the session inhibition DBus client."""
3122
3123+import operator
3124+
3125 import dbus
3126-import operator
3127-
3128 from dbus.mainloop.glib import DBusGMainLoop
3129 from twisted.internet.defer import inlineCallbacks
3130-try:
3131- from ubuntuone.devtools.testcases.dbus import DBusTestCase
3132-except ImportError:
3133- from ubuntuone.devtools.testcase import DBusTestCase
3134+
3135+from devtools.testcases.dbus import DBusTestCase
3136 from magicicadaclient.platform import session
3137
3138 INHIBIT_ALL = (session.INHIBIT_LOGGING_OUT |
3139@@ -137,9 +132,8 @@
3140 def test_uninhibit_call(self):
3141 """Test the uninhibit call."""
3142 fakeinhibitor = self.register_fakeserver(
3143- session.SESSION_MANAGER_BUSNAME,
3144- session.SESSION_MANAGER_PATH,
3145- FakeGnomeSessionManagerInhibitor)
3146+ session.SESSION_MANAGER_BUSNAME, session.SESSION_MANAGER_PATH,
3147+ FakeGnomeSessionManagerInhibitor)
3148 i = yield session.inhibit_logout_suspend("fake reason")
3149 yield i.cancel()
3150 result = fakeinhibitor.IsInhibited(session.INHIBIT_LOGGING_OUT)
3151
3152=== modified file 'magicicadaclient/platform/tests/test_tools.py'
3153--- magicicadaclient/platform/tests/test_tools.py 2018-05-19 20:44:54 +0000
3154+++ magicicadaclient/platform/tests/test_tools.py 2018-06-03 23:09:20 +0000
3155@@ -30,15 +30,15 @@
3156 """Tests for the syncdaemon tools module."""
3157
3158 import os
3159-
3160 from collections import defaultdict
3161
3162 from twisted.internet import defer, reactor
3163-from ubuntuone.devtools.handlers import MementoHandler
3164-from ubuntuone.devtools.testcases import skipTest, skipIfNotOS
3165
3166+from devtools.handlers import MementoHandler
3167+from devtools.testcases import skipTest, skipIfNotOS
3168 from magicicadaclient.testing.testcase import FakeCommand
3169-
3170+from magicicadaclient.platform import tools
3171+from magicicadaclient.platform.tests import IPCTestCase
3172 from magicicadaclient.syncdaemon import (
3173 action_queue,
3174 event_queue,
3175@@ -46,8 +46,6 @@
3176 states,
3177 volume_manager,
3178 )
3179-from magicicadaclient.platform import tools
3180-from magicicadaclient.platform.tests import IPCTestCase
3181
3182
3183 SOME_ERROR = 'CRASH BOOM BANG'
3184
3185=== modified file 'magicicadaclient/syncdaemon/tests/test_action_queue.py'
3186--- magicicadaclient/syncdaemon/tests/test_action_queue.py 2018-05-19 20:44:54 +0000
3187+++ magicicadaclient/syncdaemon/tests/test_action_queue.py 2018-06-03 23:09:20 +0000
3188@@ -60,16 +60,8 @@
3189 from twisted.trial.unittest import TestCase as TwistedTestCase
3190 from zope.interface.verify import verifyObject, verifyClass
3191
3192-from magicicadaclient.testing.testcase import (
3193- BaseTwistedTestCase,
3194- DummyClass,
3195- FakeActionQueue,
3196- FakeCommand,
3197- FakeMain,
3198- FakeUpload,
3199-)
3200-from ubuntuone.devtools import handlers
3201-from ubuntuone.devtools.testcases import skipTest
3202+from devtools import handlers
3203+from devtools.testcases import skipTest
3204 from magicicadaclient import logger, clientdefs
3205 from magicicadaclient.platform import open_file, platform, path_exists
3206 from magicicadaclient.syncdaemon import interfaces, config
3207@@ -88,6 +80,14 @@
3208 from magicicadaclient.syncdaemon import offload_queue
3209 from magicicadaclient.syncdaemon.marker import MDMarker
3210 from magicicadaclient.syncdaemon.volume_manager import ACCESS_LEVEL_RO
3211+from magicicadaclient.testing.testcase import (
3212+ BaseTwistedTestCase,
3213+ DummyClass,
3214+ FakeActionQueue,
3215+ FakeCommand,
3216+ FakeMain,
3217+ FakeUpload,
3218+)
3219
3220 PATH = os.path.join(u'~', u'Documents', u'pdfs', u'moño', u'')
3221 NAME = u'UDF-me'
3222@@ -3352,7 +3352,7 @@
3223
3224 def test_progress_hook(self):
3225 """Test the progress hook."""
3226- self.command.deflated_size = 2*TRANSFER_PROGRESS_THRESHOLD
3227+ self.command.deflated_size = 2 * TRANSFER_PROGRESS_THRESHOLD
3228 self.command.n_bytes_written_last = 0
3229
3230 self.command.n_bytes_written = 5
3231@@ -3368,8 +3368,8 @@
3232 self.command.n_bytes_written = TRANSFER_PROGRESS_THRESHOLD + 5
3233 self.command.progress_hook()
3234 kwargs = {'share_id': self.command.share_id, 'node_id': 'a_node_id',
3235- 'deflated_size': 2*TRANSFER_PROGRESS_THRESHOLD,
3236- 'n_bytes_written': 5+TRANSFER_PROGRESS_THRESHOLD}
3237+ 'deflated_size': 2 * TRANSFER_PROGRESS_THRESHOLD,
3238+ 'n_bytes_written': 5 + TRANSFER_PROGRESS_THRESHOLD}
3239 events = [('AQ_UPLOAD_FILE_PROGRESS', kwargs)]
3240 self.assertEqual(events, self.command.action_queue.event_queue.events)
3241 self.assertEqual(self.command.n_bytes_written_last,
3242@@ -4932,7 +4932,7 @@
3243
3244 def test_repr(self):
3245 """A DeltaList has a short representation."""
3246- a = DeltaList(["a"*1000])
3247+ a = DeltaList(["a" * 1000])
3248 self.assertTrue(len(repr(a)) < 100)
3249 self.assertTrue(len(str(a)) < 100)
3250
3251
3252=== modified file 'magicicadaclient/syncdaemon/tests/test_eq_inotify.py'
3253--- magicicadaclient/syncdaemon/tests/test_eq_inotify.py 2018-05-19 20:44:54 +0000
3254+++ magicicadaclient/syncdaemon/tests/test_eq_inotify.py 2018-06-03 23:09:20 +0000
3255@@ -1,9 +1,5 @@
3256-# tests.syncdaemon.test_eq_inotify
3257-#
3258-# Authors: Facundo Batista <facundo@canonical.com>
3259-# Manuel de la Pena <manuel@canonical.com>
3260-#
3261 # Copyright 2009-2012 Canonical Ltd.
3262+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3263 #
3264 # This program is free software: you can redistribute it and/or modify it
3265 # under the terms of the GNU General Public License version 3, as published
3266@@ -37,17 +33,9 @@
3267 import sys
3268
3269 from twisted.internet import defer, reactor
3270-from ubuntuone.devtools.handlers import MementoHandler
3271-from ubuntuone.devtools.testcases import skipIfOS, skipIfNotOS
3272
3273-from magicicadaclient.testing.testcase import (
3274- BaseTwistedTestCase,
3275- FakeMain,
3276- Listener,
3277- skip_if_darwin_missing_fs_event,
3278- skip_if_win32_missing_fs_event,
3279-)
3280-from magicicadaclient.syncdaemon.tests.test_eventqueue import BaseEQTestCase
3281+from devtools.handlers import MementoHandler
3282+from devtools.testcases import skipIfOS, skipIfNotOS
3283 from magicicadaclient.platform import (
3284 make_link,
3285 make_dir,
3286@@ -61,6 +49,14 @@
3287 set_dir_readwrite,
3288 )
3289 from magicicadaclient.syncdaemon import event_queue, volume_manager
3290+from magicicadaclient.syncdaemon.tests.test_eventqueue import BaseEQTestCase
3291+from magicicadaclient.testing.testcase import (
3292+ BaseTwistedTestCase,
3293+ FakeMain,
3294+ Listener,
3295+ skip_if_darwin_missing_fs_event,
3296+ skip_if_win32_missing_fs_event,
3297+)
3298
3299 # our logging level
3300 TRACE = logging.getLevelName('TRACE')
3301
3302=== modified file 'magicicadaclient/syncdaemon/tests/test_eventqueue.py'
3303--- magicicadaclient/syncdaemon/tests/test_eventqueue.py 2018-05-19 20:44:54 +0000
3304+++ magicicadaclient/syncdaemon/tests/test_eventqueue.py 2018-06-03 23:09:20 +0000
3305@@ -1,9 +1,5 @@
3306-#
3307-# Author: Facundo Batista <facundo@canonical.com>
3308-#
3309-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
3310-#
3311 # Copyright 2009-2012 Canonical Ltd.
3312+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3313 #
3314 # This program is free software: you can redistribute it and/or modify it
3315 # under the terms of the GNU General Public License version 3, as published
3316@@ -36,6 +32,7 @@
3317 from twisted.internet import defer
3318 from twisted.trial.unittest import TestCase
3319
3320+from devtools.handlers import MementoHandler
3321 from magicicadaclient.testing.testcase import (
3322 BaseTwistedTestCase,
3323 FakeMonitor,
3324@@ -49,7 +46,6 @@
3325 filesystem_manager,
3326 tritcask,
3327 )
3328-from ubuntuone.devtools.handlers import MementoHandler
3329
3330
3331 class BaseEQTestCase(BaseTwistedTestCase):
3332
3333=== modified file 'magicicadaclient/syncdaemon/tests/test_fileshelf.py'
3334--- magicicadaclient/syncdaemon/tests/test_fileshelf.py 2018-05-19 20:44:54 +0000
3335+++ magicicadaclient/syncdaemon/tests/test_fileshelf.py 2018-06-03 23:09:20 +0000
3336@@ -1,7 +1,5 @@
3337-#
3338-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
3339-#
3340 # Copyright 2009-2012 Canonical Ltd.
3341+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3342 #
3343 # This program is free software: you can redistribute it and/or modify it
3344 # under the terms of the GNU General Public License version 3, as published
3345@@ -37,19 +35,16 @@
3346 import unittest
3347
3348 from twisted.internet import defer
3349-from ubuntuone.devtools.testcases import skipIfOS
3350
3351-from magicicadaclient.testing.testcase import BaseTwistedTestCase
3352-from magicicadaclient.platform import (
3353- open_file,
3354- path_exists,
3355-)
3356+from devtools.testcases import skipIfOS
3357+from magicicadaclient.platform import open_file, path_exists
3358 from magicicadaclient.syncdaemon.file_shelf import (
3359 FileShelf,
3360 CachedFileShelf,
3361 LRUCache,
3362 CacheInconsistencyError,
3363 )
3364+from magicicadaclient.testing.testcase import BaseTwistedTestCase
3365
3366
3367 BROKEN_PICKLE = '\axb80\x02}q\x01(U\x01aU\x04testq\x02U\x01bU\x06brokenq\x03u.'
3368@@ -168,11 +163,11 @@
3369 """test that each time a metadata file is updated a .old is kept"""
3370 self.shelf['bad_file'] = {'value': 'old'}
3371 path = self.shelf.key_file('bad_file')
3372- self.assertFalse(path_exists(path+'.old'))
3373+ self.assertFalse(path_exists(path + '.old'))
3374 self.assertEqual({'value': 'old'}, self.shelf['bad_file'])
3375 # force the creation of the .old file
3376 self.shelf['bad_file'] = {'value': 'new'}
3377- self.assertTrue(path_exists(path+'.old'))
3378+ self.assertTrue(path_exists(path + '.old'))
3379 # check that the new value is there
3380 self.assertEqual({'value': 'new'}, self.shelf['bad_file'])
3381 # write the current md file fwith 0 bytes
3382@@ -183,11 +178,11 @@
3383 self.shelf['broken_pickle'] = {'value': 'old'}
3384 path = self.shelf.key_file('broken_pickle')
3385 # check that .old don't exist
3386- self.assertFalse(path_exists(path+'.old'))
3387+ self.assertFalse(path_exists(path + '.old'))
3388 # force the creation of the .old file
3389 self.shelf['broken_pickle'] = {'value': 'new'}
3390 # check that .old exists
3391- self.assertTrue(path_exists(path+'.old'))
3392+ self.assertTrue(path_exists(path + '.old'))
3393 # check that the new value is there
3394 self.assertEqual({'value': 'new'}, self.shelf['broken_pickle'])
3395 # write random bytes to the md file
3396@@ -200,10 +195,10 @@
3397 """test keys() with .old and .new files around"""
3398 self.shelf["foo"] = "bar"
3399 self.shelf["foo1"] = "bar1"
3400- open_file(self.shelf.key_file('foo')+'.old', 'w').close()
3401- open_file(self.shelf.key_file('foo1')+'.old', 'w').close()
3402- open_file(self.shelf.key_file('foo')+'.new', 'w').close()
3403- open_file(self.shelf.key_file('foo1')+'.new', 'w').close()
3404+ open_file(self.shelf.key_file('foo') + '.old', 'w').close()
3405+ open_file(self.shelf.key_file('foo1') + '.old', 'w').close()
3406+ open_file(self.shelf.key_file('foo') + '.new', 'w').close()
3407+ open_file(self.shelf.key_file('foo1') + '.new', 'w').close()
3408 self.assertEqual(set(['foo', 'foo1']), set(self.shelf.keys()))
3409
3410 def test_corrupted_backup(self):
3411@@ -212,7 +207,7 @@
3412 # create the .old backup
3413 self.shelf["foo"] = "bar1"
3414 # write 0 bytes to both
3415- open_file(self.shelf.key_file('foo')+'.old', 'w').close()
3416+ open_file(self.shelf.key_file('foo') + '.old', 'w').close()
3417 open_file(self.shelf.key_file('foo'), 'w').close()
3418 self.assertRaises(KeyError, self.shelf.__getitem__, 'foo')
3419
3420@@ -233,12 +228,12 @@
3421 self.shelf["foo"] = "bar1"
3422 path = self.shelf.key_file('foo')
3423 # create a .new file (a hard reboot during the rename dance)
3424- open_file(path+'.new', 'w').close()
3425+ open_file(path + '.new', 'w').close()
3426 # write 0 bytes to both
3427 del self.shelf['foo']
3428 self.assertFalse(path_exists(path))
3429- self.assertFalse(path_exists(path+'.old'), 'there is a .old file!')
3430- self.assertFalse(path_exists(path+'.new'), 'there is a .new file!')
3431+ self.assertFalse(path_exists(path + '.old'), 'there is a .old file!')
3432+ self.assertFalse(path_exists(path + '.new'), 'there is a .new file!')
3433
3434 @skipIfOS('win32', 'Skipped because code is deprecated on Windows.')
3435 def test_custom_unpickle(self):
3436@@ -330,11 +325,11 @@
3437 """overrides parent test as we have the value in the cache."""
3438 self.shelf['bad_file'] = {'value': 'old'}
3439 path = self.shelf.key_file('bad_file')
3440- self.assertFalse(path_exists(path+'.old'))
3441+ self.assertFalse(path_exists(path + '.old'))
3442 self.assertEqual({'value': 'old'}, self.shelf['bad_file'])
3443 # force the creation of the .old file
3444 self.shelf['bad_file'] = {'value': 'new'}
3445- self.assertTrue(path_exists(path+'.old'))
3446+ self.assertTrue(path_exists(path + '.old'))
3447 # check that the new value is there
3448 self.assertEqual({'value': 'new'}, self.shelf['bad_file'])
3449 # write the current md file fwith 0 bytes
3450@@ -346,11 +341,11 @@
3451 self.shelf['broken_pickle'] = {'value': 'old'}
3452 path = self.shelf.key_file('broken_pickle')
3453 # check that .old don't exist
3454- self.assertFalse(path_exists(path+'.old'))
3455+ self.assertFalse(path_exists(path + '.old'))
3456 # force the creation of the .old file
3457 self.shelf['broken_pickle'] = {'value': 'new'}
3458 # check that .old exists
3459- self.assertTrue(path_exists(path+'.old'))
3460+ self.assertTrue(path_exists(path + '.old'))
3461 # check that the new value is there
3462 self.assertEqual({'value': 'new'}, self.shelf['broken_pickle'])
3463 # write random bytes to the md file
3464@@ -368,7 +363,7 @@
3465 """test __delitem__ method"""
3466 cache = LRUCache(100, 4)
3467 # set some data in the cache
3468- values = [('key'+str(i), i) for i in range(100)]
3469+ values = [('key' + str(i), i) for i in range(100)]
3470 for i, j in values:
3471 cache[i] = j
3472 self.assertEqual(len(cache._queue), len(values))
3473@@ -378,7 +373,7 @@
3474 """test __delitem__ method"""
3475 cache = LRUCache(100, 4)
3476 # set some data in the cache
3477- values = [('key'+str(i), i) for i in range(100)]
3478+ values = [('key' + str(i), i) for i in range(100)]
3479 for i, j in values:
3480 cache[i] = j
3481 self.assertEqual(len(cache._queue), len(values))
3482@@ -390,7 +385,7 @@
3483 def test_delitem(self):
3484 """test __delitem__ method"""
3485 cache = LRUCache(100, 4)
3486- values = [('key'+str(i), i) for i in range(100)]
3487+ values = [('key' + str(i), i) for i in range(100)]
3488 for i, j in values:
3489 cache[i] = j
3490 self.assertEqual(len(cache._queue), len(values))
3491@@ -415,7 +410,7 @@
3492 def test_purge(self):
3493 """Test the queue compact and cache purge"""
3494 cache = LRUCache(100, 4)
3495- values = [('key'+str(i), j) for i in range(50) for j in range(8)]
3496+ values = [('key' + str(i), j) for i in range(50) for j in range(8)]
3497 for i, j in values:
3498 cache[i] = j
3499 # we hit the limit
3500@@ -442,7 +437,7 @@
3501 """Tests if the cache correclty keeps track of misses and hits."""
3502 cache = LRUCache(100, 4)
3503 # set some data in the cache
3504- values = [('key'+str(i), i) for i in range(10)]
3505+ values = [('key' + str(i), i) for i in range(10)]
3506 for i, j in values:
3507 cache[i] = j
3508 self.assertEqual(len(cache._queue), len(values))
3509@@ -454,7 +449,7 @@
3510 self.assertEqual(cache.hits, 4)
3511 # try to get items not present in the cache
3512 for i, j in values[5:10]:
3513- self.assertRaises(KeyError, cache.__getitem__, i*10)
3514+ self.assertRaises(KeyError, cache.__getitem__, i * 10)
3515 self.assertEqual(cache.misses, 5)
3516
3517 def test_inconsistency(self):
3518
3519=== modified file 'magicicadaclient/syncdaemon/tests/test_fsm.py'
3520--- magicicadaclient/syncdaemon/tests/test_fsm.py 2018-05-19 20:44:54 +0000
3521+++ magicicadaclient/syncdaemon/tests/test_fsm.py 2018-06-03 23:09:20 +0000
3522@@ -1,6 +1,7 @@
3523 # -*- coding: utf-8 -*-
3524 #
3525 # Copyright 2009-2012 Canonical Ltd.
3526+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
3527 #
3528 # This program is free software: you can redistribute it and/or modify it
3529 # under the terms of the GNU General Public License version 3, as published
3530@@ -47,7 +48,7 @@
3531 skip_if_win32_and_uses_readonly,
3532 )
3533
3534-from ubuntuone.devtools.handlers import MementoHandler
3535+from devtools.handlers import MementoHandler
3536 from magicicadaclient.platform import (
3537 listdir,
3538 make_dir,
3539@@ -229,7 +230,7 @@
3540 self.assertEqual(mdobj.size, None)
3541 when = mdobj.info.created
3542 now = time.time()
3543- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3544+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3545
3546 # set uuid using valid path, but not twice
3547 self.fsm.set_node_id(path, "uuid")
3548@@ -237,7 +238,7 @@
3549 mdobj = self.fsm.get_by_path(path)
3550 when = mdobj.info.node_id_assigned
3551 now = time.time()
3552- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3553+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3554
3555 def test_with_node_id(self):
3556 """Test creation with node_id"""
3557@@ -255,14 +256,14 @@
3558 self.assertEqual(mdobj.size, None)
3559 when = mdobj.info.created
3560 now = time.time()
3561- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3562+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3563
3564 # set uuid using valid path, but not twice
3565 self.assertRaises(ValueError, self.fsm.set_node_id, path, "whatever")
3566 mdobj = self.fsm.get_by_path(path)
3567 when = mdobj.info.node_id_assigned
3568 now = time.time()
3569- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3570+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3571
3572 def test_invalid_args(self):
3573 """Test using invalid args in set_node_id."""
3574@@ -351,7 +352,7 @@
3575 old_fs[k] = v
3576
3577 # start up again, and check
3578- db = Tritcask(self.tritcask_path+'.new')
3579+ db = Tritcask(self.tritcask_path + '.new')
3580 self.addCleanup(db.shutdown)
3581 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3582 self.fsm.vm, db)
3583@@ -417,7 +418,7 @@
3584 old_fs[k] = v
3585
3586 # start up again, and check
3587- db = Tritcask(self.tritcask_path+'.new')
3588+ db = Tritcask(self.tritcask_path + '.new')
3589 self.addCleanup(db.shutdown)
3590 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3591 self.fsm.vm, db)
3592@@ -474,7 +475,7 @@
3593 old_fs[k] = v
3594
3595 # start up again, and check
3596- db = Tritcask(self.tritcask_path+'.new')
3597+ db = Tritcask(self.tritcask_path + '.new')
3598 self.addCleanup(db.shutdown)
3599 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3600 self.fsm.vm, db)
3601@@ -530,7 +531,7 @@
3602 old_fs[k] = v
3603
3604 # start up again, and check
3605- db = Tritcask(self.tritcask_path+'.new')
3606+ db = Tritcask(self.tritcask_path + '.new')
3607 self.addCleanup(db.shutdown)
3608 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3609 self.fsm.vm, db)
3610@@ -595,7 +596,7 @@
3611 old_mvlimbo[k] = v
3612
3613 # start up again, and check
3614- db = Tritcask(self.tritcask_path+'.new')
3615+ db = Tritcask(self.tritcask_path + '.new')
3616 self.addCleanup(db.shutdown)
3617 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3618 self.fsm.vm, db)
3619@@ -654,7 +655,7 @@
3620 old_mvlimbo[k] = v
3621
3622 # start up again, and check
3623- db = Tritcask(self.tritcask_path+'.new')
3624+ db = Tritcask(self.tritcask_path + '.new')
3625 self.addCleanup(db.shutdown)
3626 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3627 self.fsm.vm, db)
3628@@ -710,7 +711,7 @@
3629 remove_file(version_file)
3630
3631 # start up again, and check
3632- db = Tritcask(self.tritcask_path+'.new')
3633+ db = Tritcask(self.tritcask_path + '.new')
3634 self.addCleanup(db.shutdown)
3635 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3636 self.fsm.vm, db)
3637@@ -757,7 +758,7 @@
3638 fh.write("1")
3639
3640 # start up again, and check
3641- db = Tritcask(self.tritcask_path+'.new')
3642+ db = Tritcask(self.tritcask_path + '.new')
3643 self.addCleanup(db.shutdown)
3644 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3645 self.fsm.vm, db)
3646@@ -809,7 +810,7 @@
3647 os.fsync(f.fileno())
3648
3649 # start up again, and check
3650- db = Tritcask(self.tritcask_path+'.new')
3651+ db = Tritcask(self.tritcask_path + '.new')
3652 self.addCleanup(db.shutdown)
3653 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3654 self.fsm.vm, db)
3655@@ -867,7 +868,7 @@
3656 remove_file(version_file)
3657
3658 # start up again, and check
3659- db = Tritcask(self.tritcask_path+'.new')
3660+ db = Tritcask(self.tritcask_path + '.new')
3661 self.addCleanup(db.shutdown)
3662 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3663 self.fsm.vm, db)
3664@@ -928,7 +929,7 @@
3665 fh.write("1")
3666
3667 # start up again, and check
3668- db = Tritcask(self.tritcask_path+'.new')
3669+ db = Tritcask(self.tritcask_path + '.new')
3670 self.addCleanup(db.shutdown)
3671 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3672 self.fsm.vm, db)
3673@@ -996,7 +997,7 @@
3674 os.fsync(f.fileno())
3675
3676 # start up again, and check
3677- db = Tritcask(self.tritcask_path+'.new')
3678+ db = Tritcask(self.tritcask_path + '.new')
3679 self.addCleanup(db.shutdown)
3680 newfsm = FileSystemManager(self.fsmdir, self.partials_dir,
3681 self.fsm.vm, db)
3682@@ -1548,7 +1549,7 @@
3683 """Test having similar paths (a/b, a/b1, a/b2)."""
3684 expected = [os.path.join('a', 'b', 'y.txt')]
3685 actual = sorted([d.path for d in self.fsm.get_mdobjs_in_dir(
3686- os.path.join(self.share.path, 'a', 'b'))])
3687+ os.path.join(self.share.path, 'a', 'b'))])
3688 self.assertEqual(expected, actual)
3689
3690 @defer.inlineCallbacks
3691@@ -1719,7 +1720,7 @@
3692 mdobj = self.fsm.get_by_mdid(mdid)
3693 when = mdobj.info.last_partial_created
3694 now = time.time()
3695- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3696+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3697
3698 # invalid uuid
3699 self.assertRaises(KeyError, self.fsm.create_partial, "foo", "share")
3700@@ -1756,7 +1757,7 @@
3701 self.assertEqual(mdobj.local_hash, 9876)
3702 when = mdobj.info.last_downloaded
3703 now = time.time()
3704- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3705+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3706
3707 # invalid uuid
3708 self.assertRaises(
3709@@ -1796,7 +1797,7 @@
3710 self.assertFalse(mdobj.info.is_partial)
3711 when = mdobj.info.last_partial_removed
3712 now = time.time()
3713- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3714+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3715
3716 # invalid uuid
3717 self.assertRaises(KeyError, self.fsm.remove_partial, "foo", "share")
3718@@ -1822,7 +1823,7 @@
3719 mdobj = self.fsm.get_by_mdid(mdid)
3720 when = mdobj.info.last_partial_created
3721 now = time.time()
3722- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3723+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3724
3725 # invalid uuid
3726 self.assertRaises(KeyError, self.fsm.create_partial, "foo", "share")
3727@@ -1847,7 +1848,7 @@
3728 mdobj = self.fsm.get_by_mdid(mdid)
3729 when = mdobj.info.last_partial_created
3730 now = time.time()
3731- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3732+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3733
3734 # invalid uuid
3735 self.assertRaises(KeyError, self.fsm.create_partial, "foo", "share")
3736@@ -1890,7 +1891,7 @@
3737 self.assertFalse(mdobj.info.is_partial)
3738 when = mdobj.info.last_partial_removed
3739 now = time.time()
3740- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3741+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3742
3743 # invalid uuid
3744 self.assertRaises(KeyError, self.fsm.remove_partial, "foo", "share")
3745@@ -1948,7 +1949,7 @@
3746 # find a almost too long file
3747 repeat = 300
3748 while True:
3749- testfile = os.path.join(self.share_path, "x"*repeat)
3750+ testfile = os.path.join(self.share_path, "x" * repeat)
3751 try:
3752 fh = open_file(testfile, 'w')
3753 except IOError, e:
3754@@ -2071,7 +2072,7 @@
3755 mdobj = self.fsm.get_by_mdid(mdid)
3756 when = mdobj.info.last_conflicted
3757 now = time.time()
3758- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3759+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3760
3761 # move second time, start the .N serie
3762 with open_file(testfile, "w") as fh:
3763@@ -2145,7 +2146,7 @@
3764 self.assertEqual(mdobj.server_hash, 1234567890)
3765 when = mdobj.info.last_uploaded
3766 now = time.time()
3767- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3768+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3769
3770 # invalid mdid
3771 self.assertRaises(KeyError, self.fsm.upload_finished,
3772@@ -2173,7 +2174,7 @@
3773 self.assertEqual(mdobj.info.last_moved_from, testfile)
3774 when = mdobj.info.last_moved_time
3775 now = time.time()
3776- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3777+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3778
3779 # move again, to a directory
3780 from_path = to_path
3781@@ -2244,7 +2245,7 @@
3782 self.assertEqual(mdobj.info.last_moved_from, from_path)
3783 when = mdobj.info.last_moved_time
3784 now = time.time()
3785- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3786+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3787
3788 # move again, to a directory
3789 from_path = to_path
3790@@ -2257,7 +2258,7 @@
3791 self.assertEqual(mdobj.info.last_moved_from, from_path)
3792 when = mdobj.info.last_moved_time
3793 now = time.time()
3794- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3795+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3796
3797 def test_move_file_withfulldir(self):
3798 """Test that a dir is moved from even having a file inside."""
3799@@ -2288,7 +2289,7 @@
3800 self.assertEqual(mdobj.info.last_moved_from, from_path)
3801 when = mdobj.info.last_moved_time
3802 now = time.time()
3803- self.assertTrue(now-3 <= when <= now) # 3 seconds test range
3804+ self.assertTrue(now - 3 <= when <= now) # 3 seconds test range
3805
3806 # check that file inside is ok
3807 newfilepath = os.path.join(to_path, "file.txt")
3808@@ -4086,8 +4087,10 @@
3809 def setUp(self):
3810 """Set up."""
3811 yield super(OsIntegrationTests, self).setUp()
3812- self.open_file = self.mocker.replace('magicicadaclient.platform.open_file')
3813- self.normpath = self.mocker.replace('magicicadaclient.platform.normpath')
3814+ self.open_file = self.mocker.replace(
3815+ 'magicicadaclient.platform.open_file')
3816+ self.normpath = self.mocker.replace(
3817+ 'magicicadaclient.platform.normpath')
3818 self.listdir = self.mocker.replace('magicicadaclient.platform.listdir')
3819
3820 def test_get_partial_for_writing(self):
3821
3822=== modified file 'magicicadaclient/syncdaemon/tests/test_hashqueue.py'
3823--- magicicadaclient/syncdaemon/tests/test_hashqueue.py 2018-05-19 20:44:54 +0000
3824+++ magicicadaclient/syncdaemon/tests/test_hashqueue.py 2018-06-03 23:09:20 +0000
3825@@ -41,13 +41,13 @@
3826 from magicicadaprotocol.content_hash import content_hash_factory, crc32
3827 from twisted.trial.unittest import TestCase as TwistedTestCase
3828 from twisted.internet import defer, reactor
3829-from ubuntuone.devtools.handlers import MementoHandler
3830-from ubuntuone.devtools.testcases import skipTest
3831
3832-from magicicadaclient.testing.testcase import BaseTwistedTestCase
3833+from devtools.handlers import MementoHandler
3834+from devtools.testcases import skipTest
3835 from magicicadaclient.platform import open_file, stat_path
3836 from magicicadaclient.syncdaemon import hash_queue
3837 from magicicadaclient.syncdaemon.hash_queue import HASHQUEUE_DELAY
3838+from magicicadaclient.testing.testcase import BaseTwistedTestCase
3839
3840 FAKE_TIMESTAMP = 1
3841
3842@@ -244,11 +244,11 @@
3843 should_be = []
3844 for i in range(10):
3845 hasher = content_hash_factory()
3846- text = "supercalifragilistico"+str(i)
3847+ text = "supercalifragilistico" + str(i)
3848 hasher.hash_object.update(text)
3849- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3850+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3851 with open_file(tfile, "wb") as fh:
3852- fh.write("supercalifragilistico"+str(i))
3853+ fh.write("supercalifragilistico" + str(i))
3854 d = dict(path=tfile, hash=hasher.content_hash(),
3855 crc32=crc32(text), size=len(text), stat=stat_path(tfile))
3856 should_be.append(("HQ_HASH_NEW", d))
3857@@ -262,7 +262,7 @@
3858
3859 # send what to hash
3860 for i in range(10):
3861- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3862+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3863 item = ((tfile, "mdid"), FAKE_TIMESTAMP)
3864 queue.put(item)
3865
3866@@ -554,11 +554,11 @@
3867 should_be = []
3868 for i in range(10):
3869 hasher = content_hash_factory()
3870- text = "supercalifragilistico"+str(i)
3871+ text = "supercalifragilistico" + str(i)
3872 hasher.hash_object.update(text)
3873- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3874+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3875 with open_file(tfile, "wb") as fh:
3876- fh.write("supercalifragilistico"+str(i))
3877+ fh.write("supercalifragilistico" + str(i))
3878 d = dict(path=tfile, hash=hasher.content_hash(),
3879 crc32=crc32(text), size=len(text), stat=stat_path(tfile))
3880 should_be.append(("HQ_HASH_NEW", d))
3881@@ -569,7 +569,7 @@
3882
3883 # send what to hash
3884 for i in range(10):
3885- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3886+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3887 hq.insert(tfile, "mdid")
3888
3889 # release the processor and check
3890@@ -583,11 +583,11 @@
3891 should_be = []
3892 for i in range(10):
3893 hasher = content_hash_factory()
3894- text = "supercalifragilistico"+str(i)
3895+ text = "supercalifragilistico" + str(i)
3896 hasher.hash_object.update(text)
3897- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3898+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3899 with open_file(tfile, "wb") as fh:
3900- fh.write("supercalifragilistico"+str(i))
3901+ fh.write("supercalifragilistico" + str(i))
3902 d = dict(path=tfile, hash=hasher.content_hash(),
3903 crc32=crc32(text), size=len(text), stat=stat_path(tfile))
3904 should_be.append(("HQ_HASH_NEW", d))
3905@@ -606,7 +606,7 @@
3906
3907 # send to hash twice
3908 for i in range(10):
3909- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3910+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3911 hq.insert(tfile, "mdid")
3912 hq.insert(tfile, "mdid")
3913 # start the hasher
3914@@ -615,7 +615,7 @@
3915 # insert the last item to check the uniqueness in the queue while
3916 # the hasher is running
3917 for i in range(9, 10):
3918- tfile = os.path.join(self.test_dir, "tfile"+str(i))
3919+ tfile = os.path.join(self.test_dir, "tfile" + str(i))
3920 hq.insert(tfile, "mdid")
3921
3922 # release the processor and check
3923
3924=== modified file 'magicicadaclient/syncdaemon/tests/test_interaction_interfaces.py'
3925--- magicicadaclient/syncdaemon/tests/test_interaction_interfaces.py 2018-05-19 20:44:54 +0000
3926+++ magicicadaclient/syncdaemon/tests/test_interaction_interfaces.py 2018-06-03 23:09:20 +0000
3927@@ -34,27 +34,14 @@
3928
3929 from magicicadaprotocol.protocol_pb2 import AccountInfo
3930 from twisted.internet import defer
3931-from ubuntuone.devtools.handlers import MementoHandler
3932
3933-from magicicadaclient.testing.testcase import (
3934- FAKED_CREDENTIALS,
3935- FakeCommand,
3936- FakeDownload,
3937- FakeUpload,
3938- FakedObject,
3939- FakeMainTestCase,
3940- skipIfOS,
3941-)
3942+from devtools.handlers import MementoHandler
3943 from magicicadaclient.networkstate.networkstates import ONLINE
3944 from magicicadaclient.platform import make_dir, make_link
3945 from magicicadaclient.platform.tests.ipc.test_perspective_broker import (
3946 FakeNetworkManagerState,
3947 )
3948-from magicicadaclient.syncdaemon import (
3949- config,
3950- interaction_interfaces,
3951- states,
3952-)
3953+from magicicadaclient.syncdaemon import config, interaction_interfaces, states
3954 from magicicadaclient.syncdaemon.interaction_interfaces import (
3955 bool_str,
3956 get_share_dict,
3957@@ -82,6 +69,15 @@
3958 UDF,
3959 VolumeDoesNotExist,
3960 )
3961+from magicicadaclient.testing.testcase import (
3962+ FAKED_CREDENTIALS,
3963+ FakeCommand,
3964+ FakeDownload,
3965+ FakeUpload,
3966+ FakedObject,
3967+ FakeMainTestCase,
3968+ skipIfOS,
3969+)
3970
3971
3972 class CustomError(Exception):
3973
3974=== modified file 'magicicadaclient/syncdaemon/tests/test_localrescan.py'
3975--- magicicadaclient/syncdaemon/tests/test_localrescan.py 2018-05-19 20:44:54 +0000
3976+++ magicicadaclient/syncdaemon/tests/test_localrescan.py 2018-06-03 23:09:20 +0000
3977@@ -37,14 +37,9 @@
3978
3979 from magicicadaprotocol import content_hash as storage_hash, volumes
3980 from twisted.internet import defer, reactor
3981-from ubuntuone.devtools.handlers import MementoHandler
3982-from ubuntuone.devtools.testcases import skipIfOS
3983
3984-from magicicadaclient.testing.testcase import (
3985- BaseTwistedTestCase,
3986- FakeVolumeManager,
3987- skip_if_win32_and_uses_readonly,
3988-)
3989+from devtools.handlers import MementoHandler
3990+from devtools.testcases import skipIfOS
3991 from magicicadaclient.platform import (
3992 make_dir,
3993 make_link,
3994@@ -68,6 +63,11 @@
3995 ACCESS_LEVEL_RO,
3996 ACCESS_LEVEL_RW,
3997 )
3998+from magicicadaclient.testing.testcase import (
3999+ BaseTwistedTestCase,
4000+ FakeVolumeManager,
4001+ skip_if_win32_and_uses_readonly,
4002+)
4003
4004 # our logging level
4005 TRACE = logging.getLevelName('TRACE')
4006@@ -829,7 +829,7 @@
4007 """Lot of files in a dir, and lots of dirs."""
4008 # almost all known, to force the system to go deep
4009 dirs = "abcdefghijklmnopq" * 20
4010- for i in range(1, len(dirs)+1):
4011+ for i in range(1, len(dirs) + 1):
4012 dirpath = os.path.join(*dirs[:i])
4013 self.create_node(dirpath, is_dir=True)
4014 basedir = os.path.join(*dirs)
4015@@ -840,11 +840,11 @@
4016 # some files in some dirs
4017 files = "rstuvwxyz"
4018 for f in files:
4019- path = os.path.join(*dirs[:3]+f)
4020- self.create_node(path, is_dir=False)
4021- path = os.path.join(*dirs[:6]+f)
4022- self.create_node(path, is_dir=False)
4023- sh2 = os.path.join(self.share.path, *dirs[:6]+"q")
4024+ path = os.path.join(*dirs[:3] + f)
4025+ self.create_node(path, is_dir=False)
4026+ path = os.path.join(*dirs[:6] + f)
4027+ self.create_node(path, is_dir=False)
4028+ sh2 = os.path.join(self.share.path, *dirs[:6] + "q")
4029 open_file(sh2, "w").close()
4030
4031 # scan!
4032@@ -1953,7 +1953,7 @@
4033 self.assertFalse(path_exists(partial_path))
4034 # logged in warning
4035 self.assertTrue(self.handler.check_warning(
4036- "Found a directory in SERVER"))
4037+ "Found a directory in SERVER"))
4038
4039 def test_check_stat_None(self):
4040 """Test check_stat with oldstat = None."""
4041@@ -2041,7 +2041,7 @@
4042 self.assertEqual(self.aq.unlinked, [(self.share.volume_id,
4043 "parent_id", "uuid", path, True)])
4044 self.assertTrue(self.handler.check_info(
4045- "generating Unlink from trash"))
4046+ "generating Unlink from trash"))
4047
4048 @defer.inlineCallbacks
4049 def test_trash_two(self):
4050
4051=== modified file 'magicicadaclient/syncdaemon/tests/test_logger.py'
4052--- magicicadaclient/syncdaemon/tests/test_logger.py 2018-05-19 20:44:54 +0000
4053+++ magicicadaclient/syncdaemon/tests/test_logger.py 2018-06-03 23:09:20 +0000
4054@@ -1,8 +1,7 @@
4055 # -*- coding: utf-8 -*-
4056 #
4057-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
4058-#
4059 # Copyright 2009-2012 Canonical Ltd.
4060+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
4061 #
4062 # This program is free software: you can redistribute it and/or modify it
4063 # under the terms of the GNU General Public License version 3, as published
4064@@ -37,9 +36,8 @@
4065 from twisted.internet import defer
4066 from twisted.trial import unittest
4067
4068-from ubuntuone.devtools.handlers import MementoHandler
4069-from ubuntuone.devtools.testcases import skipIfOS
4070-
4071+from devtools.handlers import MementoHandler
4072+from devtools.testcases import skipIfOS
4073 from magicicadaclient.syncdaemon.logger import (
4074 DebugCapture,
4075 NOTE,
4076@@ -302,7 +300,7 @@
4077 self.handler.addFilter(MultiFilter([self.__class__.__name__]))
4078 self.logger.debug('this msg should be logged')
4079 self.assertEqual(1, len(self.handler.records))
4080- other_logger = logging.getLogger("NO_LOG."+self.__class__.__name__)
4081+ other_logger = logging.getLogger("NO_LOG." + self.__class__.__name__)
4082 other_logger.debug('this msg shouldn\'t be logged')
4083 self.assertEqual(1, len(self.handler.records))
4084
4085@@ -311,7 +309,7 @@
4086 self.handler.addFilter(
4087 MultiFilter([self.__class__.__name__,
4088 self.__class__.__name__ + ".child"]))
4089- no_logger = logging.getLogger("NO_LOG."+self.__class__.__name__)
4090+ no_logger = logging.getLogger("NO_LOG." + self.__class__.__name__)
4091 yes_logger = logging.getLogger(self.__class__.__name__ + '.child')
4092 self.logger.debug('this msg should be logged')
4093 self.assertEqual(1, len(self.handler.records))
4094
4095=== modified file 'magicicadaclient/syncdaemon/tests/test_main.py'
4096--- magicicadaclient/syncdaemon/tests/test_main.py 2018-05-19 20:44:54 +0000
4097+++ magicicadaclient/syncdaemon/tests/test_main.py 2018-06-03 23:09:20 +0000
4098@@ -33,15 +33,12 @@
4099 import os
4100
4101 from twisted.internet import defer, reactor
4102-from ubuntuone.devtools.handlers import MementoHandler
4103-from magicicadaclient.platform import expand_user
4104
4105-from magicicadaclient.testing.testcase import (
4106- BaseTwistedTestCase, FAKED_CREDENTIALS, FakeMonitor
4107-)
4108+from devtools.handlers import MementoHandler
4109 from magicicadaclient.clientdefs import VERSION
4110 from magicicadaclient.logger import NOTE
4111 from magicicadaclient.platform import (
4112+ expand_user,
4113 is_link,
4114 make_dir,
4115 make_link,
4116@@ -49,6 +46,9 @@
4117 remove_dir,
4118 )
4119 from magicicadaclient.syncdaemon import main as main_mod
4120+from magicicadaclient.testing.testcase import (
4121+ BaseTwistedTestCase, FAKED_CREDENTIALS, FakeMonitor
4122+)
4123
4124
4125 class FakeListener(object):
4126
4127=== modified file 'magicicadaclient/syncdaemon/tests/test_offloadqueue.py'
4128--- magicicadaclient/syncdaemon/tests/test_offloadqueue.py 2018-05-19 20:44:54 +0000
4129+++ magicicadaclient/syncdaemon/tests/test_offloadqueue.py 2018-06-03 23:09:20 +0000
4130@@ -1,6 +1,7 @@
4131 # -*- coding: utf-8 -*-
4132 #
4133 # Copyright 2012 Canonical Ltd.
4134+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
4135 #
4136 # This program is free software: you can redistribute it and/or modify it
4137 # under the terms of the GNU General Public License version 3, as published
4138@@ -36,10 +37,10 @@
4139
4140 from twisted.trial.unittest import TestCase as TwistedTestCase
4141
4142-from ubuntuone.devtools.handlers import MementoHandler
4143-from magicicadaclient.syncdaemon.offload_queue import OffloadQueue, STRUCT_SIZE
4144+from devtools.handlers import MementoHandler
4145 from magicicadaclient.syncdaemon.interfaces import IMarker
4146 from magicicadaclient.syncdaemon.marker import MDMarker
4147+from magicicadaclient.syncdaemon.offload_queue import OffloadQueue, STRUCT_SIZE
4148
4149
4150 class OffloadQueueTestCase(TwistedTestCase):
4151
4152=== modified file 'magicicadaclient/syncdaemon/tests/test_pathlockingtree.py'
4153--- magicicadaclient/syncdaemon/tests/test_pathlockingtree.py 2018-05-19 20:44:54 +0000
4154+++ magicicadaclient/syncdaemon/tests/test_pathlockingtree.py 2018-06-03 23:09:20 +0000
4155@@ -1,8 +1,5 @@
4156-# ubuntuone.syncdaemon.tests.test_pathlockingtree - PathLockingTree tests
4157-#
4158-# Author: Facundo Batista <facundo@canonical.com>
4159-#
4160 # Copyright 2011-2012 Canonical Ltd.
4161+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
4162 #
4163 # This program is free software: you can redistribute it and/or modify it
4164 # under the terms of the GNU General Public License version 3, as published
4165@@ -35,7 +32,7 @@
4166 from twisted.internet import defer
4167 from twisted.trial.unittest import TestCase as TwistedTestCase
4168
4169-from ubuntuone.devtools.handlers import MementoHandler
4170+from devtools.handlers import MementoHandler
4171 from magicicadaclient.syncdaemon.action_queue import PathLockingTree
4172
4173
4174
4175=== modified file 'magicicadaclient/syncdaemon/tests/test_sync.py'
4176--- magicicadaclient/syncdaemon/tests/test_sync.py 2018-05-19 20:44:54 +0000
4177+++ magicicadaclient/syncdaemon/tests/test_sync.py 2018-06-03 23:09:20 +0000
4178@@ -43,15 +43,9 @@
4179 from magicicadaprotocol.request import ROOT
4180 from twisted.internet import defer
4181 from twisted.python.failure import Failure
4182-from ubuntuone.devtools.handlers import MementoHandler
4183-from ubuntuone.devtools.testcases import skipIfOS
4184
4185-from magicicadaclient.testing.testcase import (
4186- FakeMain,
4187- FakeVolumeManager,
4188- BaseTwistedTestCase,
4189- Listener,
4190-)
4191+from devtools.handlers import MementoHandler
4192+from devtools.testcases import skipIfOS
4193 from magicicadaclient.platform import (
4194 make_dir,
4195 open_file,
4196@@ -61,10 +55,20 @@
4197 from magicicadaclient.syncdaemon.filesystem_manager import FileSystemManager
4198 from magicicadaclient.syncdaemon.tritcask import Tritcask
4199 from magicicadaclient.syncdaemon.fsm import fsm as fsm_module
4200-from magicicadaclient.syncdaemon.sync import FSKey, Sync, SyncStateMachineRunner
4201+from magicicadaclient.syncdaemon.sync import (
4202+ FSKey,
4203+ Sync,
4204+ SyncStateMachineRunner,
4205+)
4206 from magicicadaclient.syncdaemon.volume_manager import Share
4207 from magicicadaclient.syncdaemon.event_queue import EventQueue, EVENTS
4208 from magicicadaclient.syncdaemon.marker import MDMarker
4209+from magicicadaclient.testing.testcase import (
4210+ FakeMain,
4211+ FakeVolumeManager,
4212+ BaseTwistedTestCase,
4213+ Listener,
4214+)
4215
4216
4217 class TestSyncClassAPI(unittest.TestCase):
4218
4219=== modified file 'magicicadaclient/syncdaemon/tests/test_tritcask.py'
4220--- magicicadaclient/syncdaemon/tests/test_tritcask.py 2018-05-19 20:44:54 +0000
4221+++ magicicadaclient/syncdaemon/tests/test_tritcask.py 2018-06-03 23:09:20 +0000
4222@@ -1,8 +1,5 @@
4223-# tests.syncdaemon.test_tritcask - tritcask tests
4224-#
4225-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
4226-#
4227 # Copyright 2010-2012 Canonical Ltd.
4228+# Copyright 2018 Chicharreros (https://launchpad.net/~chicharreros)
4229 #
4230 # This program is free software: you can redistribute it and/or modify it
4231 # under the terms of the GNU General Public License version 3, as published
4232@@ -42,8 +39,8 @@
4233 from operator import attrgetter
4234 from twisted.internet import defer
4235
4236+from devtools.handlers import MementoHandler
4237 from magicicadaclient.testing.testcase import BaseTwistedTestCase
4238-from ubuntuone.devtools.handlers import MementoHandler
4239 from magicicadaclient.syncdaemon import tritcask
4240 from magicicadaclient.syncdaemon.tritcask import (
4241 TOMBSTONE,
4242@@ -343,7 +340,7 @@
4243 fd.read(crc32_size + 4)
4244 fd.truncate()
4245 # write a different value -> random bytes
4246- fd.write(os.urandom(header_size/2))
4247+ fd.write(os.urandom(header_size / 2))
4248 fd.flush()
4249 fmap = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
4250 with contextlib.closing(fmap):
4251@@ -1377,7 +1374,7 @@
4252 # check that the TOMBSTONE is there for these keys
4253 with open(self.db.live_file.filename, 'r+b') as f:
4254 raw_data_len = len(key) + len(TOMBSTONE) + crc32_size + header_size
4255- f.seek(-1*raw_data_len, os.SEEK_END)
4256+ f.seek(-1 * raw_data_len, os.SEEK_END)
4257 raw_data = f.read(raw_data_len)
4258 self.assertEqual(TOMBSTONE,
4259 raw_data[crc32_size + header_size + len(key):])
4260@@ -1743,9 +1740,10 @@
4261 for i in range(20):
4262 keydir[(0, str(uuid.uuid4()))] = KeydirEntry(
4263 file_id_1, timestamp(), len(str(uuid.uuid4())), i + 10)
4264- entry_size = len(str(uuid.uuid4()))*2 + header_size + crc32_size
4265- self.assertEqual(entry_size*10, keydir._stats[file_id]['live_bytes'])
4266- self.assertEqual(entry_size*20, keydir._stats[file_id_1]['live_bytes'])
4267+ entry_size = len(str(uuid.uuid4())) * 2 + header_size + crc32_size
4268+ self.assertEqual(entry_size * 10, keydir._stats[file_id]['live_bytes'])
4269+ self.assertEqual(
4270+ entry_size * 20, keydir._stats[file_id_1]['live_bytes'])
4271
4272 def test_update_entry(self):
4273 """Test that __setitem__ updates the stats for an entry."""
4274@@ -1796,10 +1794,10 @@
4275 len(str(uuid.uuid4())), i + 10)
4276 if i % 2:
4277 keydir.remove((0, key))
4278- entry_size = len(str(uuid.uuid4()))*2 + header_size + crc32_size
4279- self.assertEqual(entry_size*(10/2),
4280+ entry_size = len(str(uuid.uuid4())) * 2 + header_size + crc32_size
4281+ self.assertEqual(entry_size * (10 / 2),
4282 keydir._stats[file_id]['live_bytes'])
4283- self.assertEqual(entry_size*(20/2),
4284+ self.assertEqual(entry_size * (20 / 2),
4285 keydir._stats[file_id_1]['live_bytes'])
4286
4287 def test_remove_missing_key(self):
4288
4289=== modified file 'magicicadaclient/syncdaemon/tests/test_vm.py'
4290--- magicicadaclient/syncdaemon/tests/test_vm.py 2018-05-19 20:44:54 +0000
4291+++ magicicadaclient/syncdaemon/tests/test_vm.py 2018-06-03 23:09:20 +0000
4292@@ -45,15 +45,16 @@
4293
4294 from mocker import Mocker, MATCH
4295 from twisted.internet import defer, reactor
4296-from ubuntuone.devtools.handlers import MementoHandler
4297-from ubuntuone.devtools.testcases import skipIfOS
4298
4299-from magicicadaclient.testing.testcase import (
4300- BaseTwistedTestCase,
4301- FakeMain,
4302-)
4303+from devtools.handlers import MementoHandler
4304+from devtools.testcases import skipIfOS
4305 from magicicadaclient import platform
4306-from magicicadaclient.syncdaemon import config, event_queue, tritcask, volume_manager
4307+from magicicadaclient.syncdaemon import (
4308+ config,
4309+ event_queue,
4310+ tritcask,
4311+ volume_manager,
4312+)
4313 from magicicadaclient.syncdaemon.volume_manager import (
4314 ACCESS_LEVEL_RO,
4315 ACCESS_LEVEL_RW,
4316@@ -83,6 +84,7 @@
4317 set_dir_readonly,
4318 set_dir_readwrite,
4319 )
4320+from magicicadaclient.testing.testcase import BaseTwistedTestCase, FakeMain
4321
4322 # grab the metadata version before tests fiddle with it
4323 CURRENT_METADATA_VERSION = VolumeManager.METADATA_VERSION
4324@@ -2760,7 +2762,7 @@
4325 root_volume = volumes.RootVolume(uuid.uuid4(), None, 10)
4326 d = defer.Deferred()
4327 self.vm._got_root = lambda node_id, free_bytes: d.callback(
4328- (node_id, free_bytes))
4329+ (node_id, free_bytes))
4330 self.main.event_q.push('AQ_LIST_VOLUMES', volumes=[root_volume])
4331 root_node_id, free_bytes = yield d
4332 self.assertEqual(str(root_volume.node_id), root_node_id)
4333@@ -4172,7 +4174,7 @@
4334 self.patch(VolumeManager, "METADATA_VERSION", self.fake_version)
4335 self.temp_dir = os.path.join(self.mktemp(), u"Ñandú")
4336 self.version_file = os.path.join(self.temp_dir, ".version").encode(
4337- sys.getfilesystemencoding())
4338+ sys.getfilesystemencoding())
4339 self.md_upgrader = MetadataUpgrader(self.temp_dir.encode("utf-8"),
4340 "", "", "", "", "", "", None)
4341
4342
4343=== modified file 'magicicadaclient/testing/testcase.py'
4344--- magicicadaclient/testing/testcase.py 2018-05-19 20:44:54 +0000
4345+++ magicicadaclient/testing/testcase.py 2018-06-03 23:09:20 +0000
4346@@ -43,10 +43,10 @@
4347
4348 from twisted.internet import defer
4349 from twisted.trial.unittest import TestCase as TwistedTestCase
4350-from ubuntuone.devtools.testcases import skipIfOS
4351 from zope.interface import implements
4352 from zope.interface.verify import verifyObject
4353
4354+from devtools.testcases import skipIfOS
4355 from magicicadaclient.syncdaemon import (
4356 config,
4357 action_queue,
4358
4359=== modified file 'magicicadaclient/utils/__init__.py'
4360--- magicicadaclient/utils/__init__.py 2016-06-04 21:14:35 +0000
4361+++ magicicadaclient/utils/__init__.py 2018-06-03 23:09:20 +0000
4362@@ -73,8 +73,8 @@
4363
4364 # otherwise, try to load 'dir_constant' from installation path
4365 try:
4366- __import__('ubuntuone.clientdefs', None, None, [''])
4367- module = sys.modules.get('ubuntuone.clientdefs')
4368+ __import__('magicicadaclient.clientdefs', None, None, [''])
4369+ module = sys.modules.get('magicicadaclient.clientdefs')
4370 return getattr(module, dir_constant)
4371 except (ImportError, AttributeError):
4372 msg = '_get_dir: can not build a valid path. Giving up. ' \
4373@@ -135,8 +135,7 @@
4374
4375 if getattr(sys, "frozen", None) is not None:
4376 if sys.platform == "win32":
4377- ssl_cert_location = list(load_config_paths(
4378- "ubuntuone"))[1]
4379+ ssl_cert_location = list(load_config_paths("ubuntuone"))[1]
4380 elif sys.platform == "darwin":
4381 main_app_dir = "".join(__file__.partition(".app")[:-1])
4382 main_app_resources_dir = os.path.join(main_app_dir,
4383
4384=== modified file 'magicicadaclient/utils/tests/test_common.py'
4385--- magicicadaclient/utils/tests/test_common.py 2018-05-19 20:44:54 +0000
4386+++ magicicadaclient/utils/tests/test_common.py 2018-06-03 23:09:20 +0000
4387@@ -1,7 +1,7 @@
4388 # -*- coding: utf-8 -*-
4389 #
4390 # Copyright 2011-2012 Canonical Ltd.
4391-# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
4392+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4393 #
4394 # This program is free software: you can redistribute it and/or modify it
4395 # under the terms of the GNU General Public License version 3, as published
4396@@ -38,13 +38,13 @@
4397
4398 from twisted.internet import defer
4399 from twisted.web import resource
4400-from ubuntuone.devtools.handlers import MementoHandler
4401-from ubuntuone.devtools.testing.txwebserver import HTTPWebServer
4402
4403+from devtools.handlers import MementoHandler
4404+from devtools.testing.txwebserver import HTTPWebServer
4405 from magicicadaclient import utils
4406 from magicicadaclient.tests import TestCase
4407
4408-CONSTANTS_MODULE = 'ubuntuone.clientdefs'
4409+CONSTANTS_MODULE = 'magicicadaclient.clientdefs'
4410 NOT_DEFINED = object()
4411
4412
4413
4414=== modified file 'magicicadaclient/utils/tests/test_ipc.py'
4415--- magicicadaclient/utils/tests/test_ipc.py 2018-05-19 20:44:54 +0000
4416+++ magicicadaclient/utils/tests/test_ipc.py 2018-06-03 23:09:20 +0000
4417@@ -1,7 +1,7 @@
4418 # -*- coding: utf-8 -*-
4419 #
4420 # Copyright 2011-2012 Canonical Ltd.
4421-# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
4422+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4423 #
4424 # This program is free software: you can redistribute it and/or modify it
4425 # under the terms of the GNU General Public License version 3, as published
4426@@ -39,13 +39,13 @@
4427 DeadReferenceError,
4428 NoSuchMethod,
4429 )
4430-from ubuntuone.devtools.handlers import MementoHandler
4431-from ubuntuone.devtools.testcases import skipIfOS
4432-from ubuntuone.devtools.testcases.txsocketserver import (
4433+
4434+from devtools.handlers import MementoHandler
4435+from devtools.testcases import skipIfOS
4436+from devtools.testcases.txsocketserver import (
4437 TidyUnixServer,
4438 TCPPbServerTestCase,
4439 )
4440-
4441 from magicicadaclient.tests import TestCase
4442 from magicicadaclient.utils import ipc
4443
4444
4445=== modified file 'magicicadaclient/utils/tests/test_tcpactivation.py'
4446--- magicicadaclient/utils/tests/test_tcpactivation.py 2018-05-19 20:44:54 +0000
4447+++ magicicadaclient/utils/tests/test_tcpactivation.py 2018-06-03 23:09:20 +0000
4448@@ -1,7 +1,7 @@
4449 # -*- coding: utf-8 -*-
4450 #
4451 # Copyright 2011-2012 Canonical Ltd.
4452-# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
4453+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4454 #
4455 # This program is free software: you can redistribute it and/or modify it
4456 # under the terms of the GNU General Public License version 3, as published
4457@@ -36,11 +36,8 @@
4458 from twisted.internet import defer, protocol, task
4459 from twisted.trial.unittest import TestCase
4460
4461-from ubuntuone.devtools.testcases.txsocketserver import (
4462- ServerTestCase,
4463- TidyTCPServer,
4464-)
4465
4466+from devtools.testcases.txsocketserver import ServerTestCase, TidyTCPServer
4467 from magicicadaclient.utils import tcpactivation
4468 from magicicadaclient.utils.tcpactivation import (
4469 ActivationClient,
4470
4471=== modified file 'magicicadaclient/utils/tests/test_translation.py'
4472--- magicicadaclient/utils/tests/test_translation.py 2018-05-19 20:44:54 +0000
4473+++ magicicadaclient/utils/tests/test_translation.py 2018-06-03 23:09:20 +0000
4474@@ -34,8 +34,8 @@
4475 import sys
4476
4477 from twisted.internet import defer
4478-from ubuntuone.devtools.testcases import TestCase, skipIfNotOS
4479
4480+from devtools.testcases import TestCase, skipIfNotOS
4481 from magicicadaclient.utils import translation
4482
4483 TEST_DOMAIN = 'test-domain'
4484
4485=== modified file 'magicicadaclient/utils/tests/test_txsecrets.py'
4486--- magicicadaclient/utils/tests/test_txsecrets.py 2018-05-19 20:44:54 +0000
4487+++ magicicadaclient/utils/tests/test_txsecrets.py 2018-06-03 23:09:20 +0000
4488@@ -1,7 +1,7 @@
4489 # -*- coding: utf-8 -*-
4490 #
4491 # Copyright 2010-2012 Canonical Ltd.
4492-# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
4493+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4494 #
4495 # This program is free software: you can redistribute it and/or modify it
4496 # under the terms of the GNU General Public License version 3, as published
4497@@ -36,8 +36,8 @@
4498 import dbus.service
4499
4500 from twisted.internet.defer import inlineCallbacks, returnValue
4501-from ubuntuone.devtools.testcases.dbus import DBusTestCase
4502
4503+from devtools.testcases.dbus import DBusTestCase
4504 from magicicadaclient.utils import txsecrets
4505
4506 KEY_TYPE_ATTR = {"key-type": "Foo credentials"}
4507
4508=== modified file 'requirements-devel.txt'
4509--- requirements-devel.txt 2018-03-18 15:03:09 +0000
4510+++ requirements-devel.txt 2018-06-03 23:09:20 +0000
4511@@ -1,1 +1,3 @@
4512+coverage==3.7.1
4513 flake8==3.5.0
4514+mocker==1.1.1
4515
4516=== modified file 'requirements.txt'
4517--- requirements.txt 2018-04-14 23:34:20 +0000
4518+++ requirements.txt 2018-06-03 23:09:20 +0000
4519@@ -1,2 +1,7 @@
4520+configglue==1.1.3.post0
4521+dbus-python==1.2.8
4522+magicicadaprotocol==2.0
4523+PyGObject==3.28.2
4524+pyinotify==0.9.6
4525 Send2Trash==1.5.0
4526-magicicadaprotocol==2.0
4527+Twisted==18.4.0
4528
4529=== modified file 'run-tests'
4530--- run-tests 2018-05-19 20:44:54 +0000
4531+++ run-tests 2018-06-03 23:09:20 +0000
4532@@ -49,5 +49,5 @@
4533
4534 echo "*** Running test suite for ""$MODULE"" ***"
4535 export SSL_CERTIFICATES_DIR=/etc/ssl/certs
4536-.env/bin/python /usr/bin/u1trial -i "$IGNORE_FILES" -p "$IGNORE_PATHS" $MODULE
4537+.env/bin/python contrib/u1trial -i "$IGNORE_FILES" -p "$IGNORE_PATHS" $MODULE
4538 rm -rf _trial_temp
4539
4540=== modified file 'setup.py'
4541--- setup.py 2018-05-31 16:29:41 +0000
4542+++ setup.py 2018-06-03 23:09:20 +0000
4543@@ -32,15 +32,9 @@
4544 import os
4545 import sys
4546
4547-try:
4548- from DistUtilsExtra.command import build_extra, build_i18n
4549- import DistUtilsExtra.auto
4550-except ImportError:
4551- print >> sys.stderr, 'To build this program you need '\
4552- 'https://launchpad.net/python-distutils-extra'
4553- raise
4554-assert DistUtilsExtra.auto.__version__ >= '2.18', \
4555- 'needs DistUtilsExtra.auto >= 2.18'
4556+from setuptools import setup
4557+from setuptools.command.install import install
4558+from distutils.command import build, clean
4559
4560
4561 PROJECT_NAME = 'magicicada-client'
4562@@ -83,7 +77,7 @@
4563 out_file.write(content)
4564
4565
4566-class Install(DistUtilsExtra.auto.install_auto):
4567+class Install(install):
4568 """Class to install proper files."""
4569
4570 def run(self):
4571@@ -110,7 +104,8 @@
4572 prefix = self.install_data.replace(
4573 self.root if self.root is not None else '', '')
4574 replace_variables(SERVICE_FILES, prefix)
4575- DistUtilsExtra.auto.install_auto.run(self)
4576+ install.run(self)
4577+
4578 # Replace the CLIENTDEFS paths here, so that we can do it directly in
4579 # the installed copy, rather than the lcoal copy. This allows us to
4580 # have a semi-generated version for use in tests, and a full version
4581@@ -127,7 +122,7 @@
4582 out_file.write(content)
4583
4584
4585-class Build(build_extra.build_extra):
4586+class Build(build.build):
4587 """Build PyQt (.ui) files and resources."""
4588
4589 description = "build PyQt GUIs (.ui) and resources (.qrc)"
4590@@ -135,10 +130,10 @@
4591 def run(self):
4592 """Execute the command."""
4593 replace_variables(BUILD_FILES)
4594- build_extra.build_extra.run(self)
4595-
4596-
4597-class Clean(DistUtilsExtra.auto.clean_build_tree):
4598+ build.build.run(self)
4599+
4600+
4601+class Clean(clean.clean):
4602 """Class to clean up after the build."""
4603
4604 def run(self):
4605@@ -147,24 +142,7 @@
4606 if os.path.exists(built_file):
4607 os.unlink(built_file)
4608
4609- DistUtilsExtra.auto.clean_build_tree.run(self)
4610-
4611-
4612-class BuildLocale(build_i18n.build_i18n):
4613- """Work around a bug in DistUtilsExtra."""
4614-
4615- def run(self):
4616- """Magic."""
4617- build_i18n.build_i18n.run(self)
4618- i = 0
4619- for df in self.distribution.data_files:
4620- if df[0].startswith('etc/xdg/'):
4621- if sys.platform not in ('darwin', 'win32'):
4622- new_df = (df[0].replace('etc/xdg/', '/etc/xdg/'), df[1])
4623- self.distribution.data_files[i] = new_df
4624- else:
4625- self.distribution.data_files.pop(i)
4626- i += 1
4627+ clean.clean.run(self)
4628
4629
4630 def set_py2exe_paths():
4631@@ -191,10 +169,9 @@
4632
4633
4634 cmdclass = {
4635- 'install': Install,
4636 'build': Build,
4637 'clean': Clean,
4638- 'build_i18n': BuildLocale,
4639+ 'install': Install,
4640 }
4641
4642 bin_scripts = [
4643@@ -236,7 +213,7 @@
4644 scripts.extend(bin_scripts)
4645 extra = {}
4646
4647-DistUtilsExtra.auto.setup(
4648+setup(
4649 name=PROJECT_NAME,
4650 version=VERSION,
4651 license='GPL v3',

Subscribers

People subscribed via source and target branches

to all changes: