/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
1
# Copyright (C) 2008 Canonical Ltd
0.152.1 by Vincent Ladeuil
Empty shell
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
17
"""Upload a working tree, incrementally.
18
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
19
The only bzr-related info uploaded with the working tree is the corresponding
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
20
revision id. The uploaded working tree is not linked to any other bzr data.
21
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
22
The intended use is for web developers which keep their web sites versioned
23
with bzr, can can use either FTP or SFTP to upload their site.
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
24
25
Known limitations:
26
- Symlinks are ignored,
27
28
- chmod bits are not supported.
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
29
"""
0.152.1 by Vincent Ladeuil
Empty shell
30
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
31
# TODO: the chmod bits *can* be supported via the upload protocols
32
# (i.e. poorly), but since the web developers use these protocols to upload
33
# manually, it is expected that the associated web server is coherent with
34
# their presence/absence. In other words, if a web hosting provider requires
35
# chmod bits but don't provide an ftp server that support them, well, better
36
# find another provider ;-)
37
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
38
# TODO: The message emitted in verbose mode displays local paths. That may be
39
# scary for the user when we say 'Deleting <path>' and are referring to
40
# remote files...
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
41
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
42
from bzrlib import (
43
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
44
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
45
    option,
46
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
47
lazy_import.lazy_import(globals(), """
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
48
import stat
49
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
50
from bzrlib import (
51
    branch,
52
    errors,
53
    revisionspec,
54
    transport,
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
55
    osutils,
0.152.40 by Martin Albisetti
We need to import urlutils if we are going to use it
56
    urlutils,
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
57
    workingtree,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
58
    )
59
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
60
0.152.23 by Martin Albisetti
Added version_info and plugin_name
61
version_info = (0,1,0)
62
plugin_name = 'upload'
63
0.152.1 by Vincent Ladeuil
Empty shell
64
class cmd_upload(commands.Command):
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
65
    """Upload a working tree, as a whole or incrementally.
66
67
    If no destination is specified use the last one used.
68
    If no revision is specified upload the changes since the last upload.
69
    """
0.152.7 by Vincent Ladeuil
Slight refactoring.
70
    takes_args = ['location?']
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
71
    takes_options = [
72
        'revision',
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
73
        'remember',
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
74
        option.Option('full', 'Upload the full working tree.'),
0.152.43 by Vincent Ladeuil
Cosmetic change.
75
        option.Option('quiet', 'Do not output what is being done.',
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
76
                       short_name='q'),
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
77
        option.Option('directory',
78
                      help='Branch to upload from, '
79
                      'rather than the one containing the working directory.',
80
                      short_name='d',
81
                      type=unicode,
82
                      ),
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
83
       ]
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
84
0.152.21 by Martin Albisetti
Fix uploading using the remembered location
85
    def run(self, location=None, full=False, revision=None, remember=None,
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
86
            directory=None, quiet=False,
0.152.8 by Vincent Ladeuil
Handle uploading directories.
87
            ):
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
88
        if directory is None:
89
            directory = u'.'
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
90
91
        wt = workingtree.WorkingTree.open(directory)
92
        changes = wt.changes_from(wt.basis_tree())
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
93
0.152.42 by Bazzani Marco
Fixed bug #235241
94
        if revision is None and  changes.has_changed():
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
95
            raise errors.UncommittedChanges(wt)
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
96
0.152.30 by Vincent Ladeuil
Comply to verbose.
97
        self.branch = wt.branch
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
98
0.152.7 by Vincent Ladeuil
Slight refactoring.
99
        if location is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
100
            stored_loc = self.get_upload_location()
101
            if stored_loc is None:
102
                raise errors.BzrCommandError('No upload location'
103
                                             ' known or specified.')
104
            else:
0.152.41 by Martin Albisetti
Add a comment where tests are not reaching
105
                # FIXME: Not currently tested
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
106
                display_url = urlutils.unescape_for_display(stored_loc,
107
                        self.outf.encoding)
108
                self.outf.write("Using saved location: %s\n" % display_url)
109
                location = stored_loc
110
111
        self.to_transport = transport.get_transport(location)
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
112
        if revision is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
113
            rev_id = self.branch.last_revision()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
114
        else:
115
            if len(revision) != 1:
116
                raise errors.BzrCommandError(
117
                    'bzr upload --revision takes exactly 1 argument')
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
118
            rev_id = revision[0].in_history(self.branch).rev_id
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
119
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
120
        self.tree = self.branch.repository.revision_tree(rev_id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
121
        self.rev_id = rev_id
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
122
        self._pending_renames = []
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
123
        self._pending_deletions = []
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
124
        self.quiet = quiet
0.152.22 by Martin Albisetti
Changed default behaviour to do a full upload if no previous upload has been detected
125
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
126
        if full:
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
127
            self.upload_full_tree()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
128
        else:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
129
            self.upload_tree()
130
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
131
        # We uploaded successfully, remember it
132
        if self.get_upload_location() is None or remember:
133
            self.set_upload_location(self.to_transport.base)
134
135
    def set_upload_location(self, location):
136
        self.branch.get_config().set_user_option('upload_location', location)
137
138
    def get_upload_location(self):
139
        return self.branch.get_config().get_user_option('upload_location')
140
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
141
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
142
143
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
144
        # XXX: Add tests for concurrent updates, etc.
145
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
146
147
    def get_uploaded_revid(self):
148
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
149
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
150
    def upload_file(self, relpath, id, mode=None):
151
        if mode is None:
152
            if self.tree.is_executable(id):
153
                mode = 0775
154
            else:
155
                mode = 0664
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
156
        if not self.quiet:
157
            self.outf.write('Uploading %s\n' % relpath)
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
158
        self.to_transport.put_bytes(relpath, self.tree.get_file_text(id), mode)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
159
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
160
    def upload_file_robustly(self, relpath, id, mode=None):
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
161
        """Upload a file, clearing the way on the remote side.
162
163
        When doing a full upload, it may happen that a directory exists where
164
        we want to put our file.
165
        """
166
        try:
167
            st = self.to_transport.stat(relpath)
168
            if stat.S_ISDIR(st.st_mode):
169
                # A simple rmdir may not be enough
170
                if not self.quiet:
171
                    self.outf.write('Clearing %s/%s\n' % (
172
                            self.to_transport.external_url(), relpath))
173
                self.to_transport.delete_tree(relpath)
174
        except errors.PathError:
175
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
176
        self.upload_file(relpath, id, mode)
177
178
    def make_remote_dir(self, relpath, mode=None):
179
        if mode is None:
180
            mode = 0775
181
        self.to_transport.mkdir(relpath, mode)
182
183
    def make_remote_dir_robustly(self, relpath, mode=None):
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
184
        """Create a remote directory, clearing the way on the remote side.
185
186
        When doing a full upload, it may happen that a file exists where we
187
        want to create our directory.
188
        """
189
        try:
190
            st = self.to_transport.stat(relpath)
191
            if not stat.S_ISDIR(st.st_mode):
192
                if not self.quiet:
193
                    self.outf.write('Deleting %s/%s\n' % (
194
                            self.to_transport.external_url(), relpath))
195
                self.to_transport.delete(relpath)
0.152.47 by Vincent Ladeuil
Don't fail a full upload on an already existing dir.
196
            else:
197
                # Ok the remote dir already exists, nothing to do
198
                return
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
199
        except errors.PathError:
200
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
201
        self.make_remote_dir(relpath, mode)
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
202
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
203
    def delete_remote_file(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
204
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
205
            self.outf.write('Deleting %s\n' % relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
206
        self.to_transport.delete(relpath)
207
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
208
    def delete_remote_dir(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
209
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
210
            self.outf.write('Deleting %s\n' % relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
211
        self.to_transport.rmdir(relpath)
212
213
    def delete_remote_dir_maybe(self, relpath):
214
        """Try to delete relpath, keeping failures to retry later."""
215
        try:
216
            self.to_transport.rmdir(relpath)
217
        # any kind of PathError would be OK, though we normally expect
218
        # DirectoryNotEmpty
219
        except errors.PathError:
220
            self._pending_deletions.append(relpath)
221
222
    def finish_deletions(self):
223
        if self._pending_deletions:
224
            # Process the previously failed deletions in reverse order to
225
            # delete children before parents
226
            for relpath in reversed(self._pending_deletions):
227
                self.to_transport.rmdir(relpath)
228
            # The following shouldn't be needed since we use it once per
229
            # upload, but better safe than sorry ;-)
230
            self._pending_deletions = []
231
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
232
    def rename_remote(self, old_relpath, new_relpath):
233
        """Rename a remote file or directory taking care of collisions.
234
235
        To avoid collisions during bulk renames, each renamed target is
236
        temporarily assigned a unique name. When all renames have been done,
237
        each target get its proper name.
238
        """
239
        # We generate a sufficiently random name to *assume* that
240
        # no collisions will occur and don't worry about it (nor
241
        # handle it).
242
        import os
243
        import random
244
        import time
245
246
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
247
                                     os.getpid(),
248
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
249
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
250
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
251
        self.to_transport.rename(old_relpath, stamp)
252
        self._pending_renames.append((stamp, new_relpath))
253
254
    def finish_renames(self):
255
        for (stamp, new_path) in self._pending_renames:
256
            self.to_transport.rename(stamp, new_path)
257
        # The following shouldn't be needed since we use it once per upload,
258
        # but better safe than sorry ;-)
259
        self._pending_renames = []
260
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
261
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
262
        self.to_transport.ensure_base() # XXX: Handle errors (add
263
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
264
        self.tree.lock_read()
265
        try:
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
266
            for relpath, ie in self.tree.inventory.iter_entries():
267
                if relpath in ('', '.bzrignore'):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
268
                    # skip root ('')
269
                    # .bzrignore has no meaning outside of a working tree
270
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
271
                    continue
0.152.8 by Vincent Ladeuil
Handle uploading directories.
272
                if ie.kind == 'file':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
273
                    self.upload_file_robustly(relpath, ie.file_id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
274
                elif ie.kind == 'directory':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
275
                    self.make_remote_dir_robustly(relpath)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
276
                else:
277
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
278
            self.set_uploaded_revid(self.rev_id)
279
        finally:
280
            self.tree.unlock()
281
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
282
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
283
        # If we can't find the revid file on the remote location, upload the
284
        # full tree instead
285
        try:
286
            rev_id = self.get_uploaded_revid()
287
        except errors.NoSuchFile:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
288
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
289
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
290
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
291
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
292
            # We're done
293
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
294
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
295
        # Check if the revision hasn't already been uploaded
296
        if rev_id == self.rev_id:
297
            if not self.quiet:
298
                self.outf.write('Remote location already up to date\n')
299
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
300
        # XXX: errors out if rev_id not in branch history (probably someone
301
        # uploaded from a different branch).
302
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
303
        self.to_transport.ensure_base() # XXX: Handle errors (add
304
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
305
        changes = self.tree.changes_from(from_tree)
306
        self.tree.lock_read()
307
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
308
            for (path, id, kind) in changes.removed:
309
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
310
                    self.delete_remote_file(path)
311
                elif kind is  'directory':
312
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
313
                else:
314
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
315
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
316
            for (old_path, new_path, id, kind,
317
                 content_change, exec_change) in changes.renamed:
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
318
                self.rename_remote(old_path, new_path)
319
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
320
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
321
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
322
            for (path, id, old_kind, new_kind) in changes.kind_changed:
323
                if old_kind is 'file':
324
                    self.delete_remote_file(path)
325
                elif old_kind is  'directory':
326
                    self.delete_remote_dir(path)
327
                else:
328
                    raise NotImplementedError
329
330
                if new_kind is 'file':
331
                    self.upload_file(path, id)
332
                elif new_kind is 'directory':
333
                    self.make_remote_dir(path)
334
                else:
335
                    raise NotImplementedError
336
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
337
            for (path, id, kind) in changes.added:
338
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
339
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
340
                elif kind is 'directory':
341
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
342
                else:
343
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
344
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
345
            # XXX: Add a test for exec_change
346
            for (path, id, kind,
347
                 content_change, exec_change) in changes.modified:
348
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
349
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
350
                else:
351
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
352
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
353
            self.set_uploaded_revid(self.rev_id)
354
        finally:
355
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
356
0.152.1 by Vincent Ladeuil
Empty shell
357
358
commands.register_command(cmd_upload)
359
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
360
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
361
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
362
    # This module shouldn't define any tests but I don't know how to report
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
363
    # that. I prefer to update basic_tests with the other tests to detect
364
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
365
366
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
367
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
368
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
369
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
370
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
371
    return basic_tests