/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 (
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
43
    branch,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
44
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
45
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
46
    option,
47
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
48
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).
49
import stat
50
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
51
from bzrlib import (
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.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
61
version_info = (0, 1, 1)
0.152.23 by Martin Albisetti
Added version_info and plugin_name
62
plugin_name = 'upload'
63
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
64
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
65
def _get_branch_option(branch, option):
66
    return branch.get_config().get_user_option(option)
67
68
def _set_branch_option(branch, option, value):
69
    branch.get_config().set_user_option(option, value)
70
71
def get_upload_location(branch):
72
    return _get_branch_option(branch, 'upload_location')
73
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
74
def set_upload_location(branch, location):
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
75
    _set_branch_option(branch, 'upload_location', location)
76
77
def get_upload_auto(branch):
78
    result = _get_branch_option(branch, 'upload_auto')
79
    # FIXME: is there a better way to do this with bzr's config API?
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
80
    if result is not None and result.strip() == "True":
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
81
        return True
82
    return False
83
84
def set_upload_auto(branch, auto):
85
    if auto:
86
        auto_str = "True"
87
    else:
88
        auto_str = "False"
89
    _set_branch_option(branch, 'upload_auto', auto_str)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
90
91
92
class BzrUploader(object):
93
94
    def __init__(self, branch, to_transport, outf, tree, rev_id,
95
            quiet=False):
96
        self.branch = branch
97
        self.to_transport = to_transport
98
        self.outf = outf
99
        self.tree = tree
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
100
        self.rev_id = rev_id
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
101
        self.quiet = quiet
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
102
        self._pending_deletions = []
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
103
        self._pending_renames = []
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
104
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
105
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
106
107
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
108
        # XXX: Add tests for concurrent updates, etc.
109
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
110
111
    def get_uploaded_revid(self):
112
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
113
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
114
    def upload_file(self, relpath, id, mode=None):
115
        if mode is None:
116
            if self.tree.is_executable(id):
117
                mode = 0775
118
            else:
119
                mode = 0664
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
120
        if not self.quiet:
121
            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
122
        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.
123
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
124
    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).
125
        """Upload a file, clearing the way on the remote side.
126
127
        When doing a full upload, it may happen that a directory exists where
128
        we want to put our file.
129
        """
130
        try:
131
            st = self.to_transport.stat(relpath)
132
            if stat.S_ISDIR(st.st_mode):
133
                # A simple rmdir may not be enough
134
                if not self.quiet:
135
                    self.outf.write('Clearing %s/%s\n' % (
136
                            self.to_transport.external_url(), relpath))
137
                self.to_transport.delete_tree(relpath)
138
        except errors.PathError:
139
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
140
        self.upload_file(relpath, id, mode)
141
142
    def make_remote_dir(self, relpath, mode=None):
143
        if mode is None:
144
            mode = 0775
145
        self.to_transport.mkdir(relpath, mode)
146
147
    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).
148
        """Create a remote directory, clearing the way on the remote side.
149
150
        When doing a full upload, it may happen that a file exists where we
151
        want to create our directory.
152
        """
153
        try:
154
            st = self.to_transport.stat(relpath)
155
            if not stat.S_ISDIR(st.st_mode):
156
                if not self.quiet:
157
                    self.outf.write('Deleting %s/%s\n' % (
158
                            self.to_transport.external_url(), relpath))
159
                self.to_transport.delete(relpath)
0.152.47 by Vincent Ladeuil
Don't fail a full upload on an already existing dir.
160
            else:
161
                # Ok the remote dir already exists, nothing to do
162
                return
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
163
        except errors.PathError:
164
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
165
        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).
166
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
167
    def delete_remote_file(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
168
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
169
            self.outf.write('Deleting %s\n' % relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
170
        self.to_transport.delete(relpath)
171
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
172
    def delete_remote_dir(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
173
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
174
            self.outf.write('Deleting %s\n' % relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
175
        self.to_transport.rmdir(relpath)
176
177
    def delete_remote_dir_maybe(self, relpath):
178
        """Try to delete relpath, keeping failures to retry later."""
179
        try:
180
            self.to_transport.rmdir(relpath)
181
        # any kind of PathError would be OK, though we normally expect
182
        # DirectoryNotEmpty
183
        except errors.PathError:
184
            self._pending_deletions.append(relpath)
185
186
    def finish_deletions(self):
187
        if self._pending_deletions:
188
            # Process the previously failed deletions in reverse order to
189
            # delete children before parents
190
            for relpath in reversed(self._pending_deletions):
191
                self.to_transport.rmdir(relpath)
192
            # The following shouldn't be needed since we use it once per
193
            # upload, but better safe than sorry ;-)
194
            self._pending_deletions = []
195
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
196
    def rename_remote(self, old_relpath, new_relpath):
197
        """Rename a remote file or directory taking care of collisions.
198
199
        To avoid collisions during bulk renames, each renamed target is
200
        temporarily assigned a unique name. When all renames have been done,
201
        each target get its proper name.
202
        """
203
        # We generate a sufficiently random name to *assume* that
204
        # no collisions will occur and don't worry about it (nor
205
        # handle it).
206
        import os
207
        import random
208
        import time
209
210
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
211
                                     os.getpid(),
212
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
213
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
214
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
215
        self.to_transport.rename(old_relpath, stamp)
216
        self._pending_renames.append((stamp, new_relpath))
217
218
    def finish_renames(self):
219
        for (stamp, new_path) in self._pending_renames:
220
            self.to_transport.rename(stamp, new_path)
221
        # The following shouldn't be needed since we use it once per upload,
222
        # but better safe than sorry ;-)
223
        self._pending_renames = []
224
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
225
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
226
        self.to_transport.ensure_base() # XXX: Handle errors (add
227
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
228
        self.tree.lock_read()
229
        try:
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
230
            for relpath, ie in self.tree.inventory.iter_entries():
231
                if relpath in ('', '.bzrignore'):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
232
                    # skip root ('')
233
                    # .bzrignore has no meaning outside of a working tree
234
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
235
                    continue
0.152.8 by Vincent Ladeuil
Handle uploading directories.
236
                if ie.kind == 'file':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
237
                    self.upload_file_robustly(relpath, ie.file_id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
238
                elif ie.kind == 'directory':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
239
                    self.make_remote_dir_robustly(relpath)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
240
                else:
241
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
242
            self.set_uploaded_revid(self.rev_id)
243
        finally:
244
            self.tree.unlock()
245
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
246
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
247
        # If we can't find the revid file on the remote location, upload the
248
        # full tree instead
249
        try:
250
            rev_id = self.get_uploaded_revid()
251
        except errors.NoSuchFile:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
252
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
253
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
254
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
255
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
256
            # We're done
257
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
258
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
259
        # Check if the revision hasn't already been uploaded
260
        if rev_id == self.rev_id:
261
            if not self.quiet:
262
                self.outf.write('Remote location already up to date\n')
263
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
264
        # XXX: errors out if rev_id not in branch history (probably someone
265
        # uploaded from a different branch).
266
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
267
        self.to_transport.ensure_base() # XXX: Handle errors (add
268
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
269
        changes = self.tree.changes_from(from_tree)
270
        self.tree.lock_read()
271
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
272
            for (path, id, kind) in changes.removed:
273
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
274
                    self.delete_remote_file(path)
275
                elif kind is  'directory':
276
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
277
                else:
278
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
279
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
280
            for (old_path, new_path, id, kind,
281
                 content_change, exec_change) in changes.renamed:
0.152.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
282
                if content_change:
283
                    # We update the old_path content because renames and
284
                    # deletions are differed.
285
                    self.upload_file(old_path, id)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
286
                self.rename_remote(old_path, new_path)
287
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
288
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
289
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
290
            for (path, id, old_kind, new_kind) in changes.kind_changed:
291
                if old_kind is 'file':
292
                    self.delete_remote_file(path)
293
                elif old_kind is  'directory':
294
                    self.delete_remote_dir(path)
295
                else:
296
                    raise NotImplementedError
297
298
                if new_kind is 'file':
299
                    self.upload_file(path, id)
300
                elif new_kind is 'directory':
301
                    self.make_remote_dir(path)
302
                else:
303
                    raise NotImplementedError
304
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
305
            for (path, id, kind) in changes.added:
306
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
307
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
308
                elif kind is 'directory':
309
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
310
                else:
311
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
312
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
313
            # XXX: Add a test for exec_change
314
            for (path, id, kind,
315
                 content_change, exec_change) in changes.modified:
316
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
317
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
318
                else:
319
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
320
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
321
            self.set_uploaded_revid(self.rev_id)
322
        finally:
323
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
324
0.152.1 by Vincent Ladeuil
Empty shell
325
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
326
class cmd_upload(commands.Command):
327
    """Upload a working tree, as a whole or incrementally.
328
329
    If no destination is specified use the last one used.
330
    If no revision is specified upload the changes since the last upload.
331
    """
332
    takes_args = ['location?']
333
    takes_options = [
334
        'revision',
335
        'remember',
336
        option.Option('full', 'Upload the full working tree.'),
337
        option.Option('quiet', 'Do not output what is being done.',
338
                       short_name='q'),
339
        option.Option('directory',
340
                      help='Branch to upload from, '
341
                      'rather than the one containing the working directory.',
342
                      short_name='d',
343
                      type=unicode,
344
                      ),
0.155.5 by James Westby
Hook up the --upload option and document it.
345
        option.Option('auto',
346
                      'Trigger an upload from this branch whenever the tip '
347
                      'revision changes.')
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
348
       ]
349
350
    def run(self, location=None, full=False, revision=None, remember=None,
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
351
            directory=None, quiet=False, auto=None
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
352
            ):
353
        if directory is None:
354
            directory = u'.'
355
0.156.1 by James Westby
Use open_containing rather than open, so that you can upload from a subdir.
356
        wt = workingtree.WorkingTree.open_containing(directory)[0]
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
357
        changes = wt.changes_from(wt.basis_tree())
358
359
        if revision is None and  changes.has_changed():
360
            raise errors.UncommittedChanges(wt)
361
362
        branch = wt.branch
363
364
        if location is None:
365
            stored_loc = get_upload_location(branch)
366
            if stored_loc is None:
367
                raise errors.BzrCommandError('No upload location'
368
                                             ' known or specified.')
369
            else:
370
                # FIXME: Not currently tested
371
                display_url = urlutils.unescape_for_display(stored_loc,
372
                        self.outf.encoding)
373
                self.outf.write("Using saved location: %s\n" % display_url)
374
                location = stored_loc
375
376
        to_transport = transport.get_transport(location)
377
        if revision is None:
378
            rev_id = branch.last_revision()
379
        else:
380
            if len(revision) != 1:
381
                raise errors.BzrCommandError(
382
                    'bzr upload --revision takes exactly 1 argument')
383
            rev_id = revision[0].in_history(branch).rev_id
384
385
        tree = branch.repository.revision_tree(rev_id)
386
387
        uploader = BzrUploader(branch, to_transport, self.outf, tree,
388
                rev_id, quiet=quiet)
389
390
        if full:
391
            uploader.upload_full_tree()
392
        else:
393
            uploader.upload_tree()
394
395
        # We uploaded successfully, remember it
396
        if get_upload_location(branch) is None or remember:
397
            set_upload_location(branch, to_transport.base)
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
398
        if auto is not None:
399
            set_upload_auto(branch, auto)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
400
401
0.152.1 by Vincent Ladeuil
Empty shell
402
commands.register_command(cmd_upload)
403
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
404
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
405
from bzrlib.plugins.upload.auto_upload_hook import auto_upload_hook
406
407
branch.Branch.hooks.install_named_hook('post_change_branch_tip',
408
        auto_upload_hook,
409
        'Auto upload code from a branch when it is changed.')
410
411
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
412
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
413
    # 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.
414
    # that. I prefer to update basic_tests with the other tests to detect
415
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
416
417
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
418
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
419
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
420
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
421
            ["%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.
422
    return basic_tests