/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/script.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
22
22
import doctest
23
23
import errno
24
24
import glob
 
25
import logging
25
26
import os
26
27
import shlex
27
 
from cStringIO import StringIO
 
28
import sys
 
29
import textwrap
28
30
 
29
 
from bzrlib import (
 
31
from .. import (
30
32
    osutils,
31
33
    tests,
 
34
    trace,
32
35
    )
 
36
from ..tests import ui_testing
33
37
 
34
38
 
35
39
def split(s):
55
59
    Input lines start with '<'.
56
60
    Output lines start with nothing.
57
61
    Error lines start with '2>'.
 
62
 
 
63
    :return: A sequence of ([args], input, output, errors), where the args are
 
64
        split in to words, and the input, output, and errors are just strings,
 
65
        typically containing newlines.
58
66
    """
59
67
 
60
68
    commands = []
73
81
    cmd_line = 1
74
82
    lineno = 0
75
83
    input, output, error = None, None, None
76
 
    for line in text.split('\n'):
 
84
    text = textwrap.dedent(text)
 
85
    lines = text.split('\n')
 
86
    # to make use of triple-quoted strings easier, we ignore a blank line
 
87
    # right at the start and right at the end; the rest are meaningful
 
88
    if lines and lines[0] == '':
 
89
        del lines[0]
 
90
    if lines and lines[-1] == '':
 
91
        del lines[-1]
 
92
    for line in lines:
77
93
        lineno += 1
78
94
        # Keep a copy for error reporting
79
95
        orig = line
80
 
        comment =  line.find('#')
 
96
        comment = line.find('#')
81
97
        if comment >= 0:
82
98
            # Delete comments
 
99
            # NB: this syntax means comments are allowed inside output, which
 
100
            # may be confusing...
83
101
            line = line[0:comment]
84
102
            line = line.rstrip()
85
 
        if line == '':
86
 
            # Ignore empty lines
87
 
            continue
 
103
            if line == '':
 
104
                continue
88
105
        if line.startswith('$'):
89
106
            # Time to output the current command
90
107
            add_command(cmd_cur, input, output, error)
125
142
 
126
143
    :param args: The command line arguments
127
144
 
128
 
    :return: A tuple containing: 
 
145
    :return: A tuple containing:
129
146
        - The file name redirected from or None
130
147
        - The file name redirected to or None
131
148
        - The mode to open the output file or None
150
167
            in_name = redirected_file_name('<', arg[1:], args)
151
168
        elif arg.startswith('>>'):
152
169
            out_name = redirected_file_name('>>', arg[2:], args)
153
 
            out_mode = 'ab+'
 
170
            out_mode = 'a+'
154
171
        elif arg.startswith('>',):
155
172
            out_name = redirected_file_name('>', arg[1:], args)
156
 
            out_mode = 'wb+'
 
173
            out_mode = 'w+'
157
174
        else:
158
175
            remaining.append(arg)
159
176
    return in_name, out_name, out_mode, remaining
161
178
 
162
179
class ScriptRunner(object):
163
180
    """Run a shell-like script from a test.
164
 
    
 
181
 
165
182
    Can be used as:
166
183
 
167
 
    from bzrlib.tests import script
 
184
    from breezy.tests import script
168
185
 
169
186
    ...
170
187
 
171
188
        def test_bug_nnnnn(self):
172
189
            sr = script.ScriptRunner()
173
190
            sr.run_script(self, '''
174
 
            $ bzr init
175
 
            $ bzr do-this
 
191
            $ brz init
 
192
            $ brz do-this
176
193
            # Boom, error
177
194
            ''')
178
195
    """
181
198
        self.output_checker = doctest.OutputChecker()
182
199
        self.check_options = doctest.ELLIPSIS
183
200
 
184
 
    def run_script(self, test_case, text):
 
201
    def run_script(self, test_case, text, null_output_matches_anything=False):
185
202
        """Run a shell-like script as a test.
186
203
 
187
204
        :param test_case: A TestCase instance that should provide the fail(),
189
206
            attribute used as a jail root.
190
207
 
191
208
        :param text: A shell-like script (see _script_to_commands for syntax).
 
209
 
 
210
        :param null_output_matches_anything: For commands with no specified
 
211
            output, ignore any output that does happen, including output on
 
212
            standard error.
192
213
        """
 
214
        self.null_output_matches_anything = null_output_matches_anything
193
215
        for cmd, input, output, error in _script_to_commands(text):
194
216
            self.run_command(test_case, cmd, input, output, error)
195
217
 
198
220
        method = getattr(self, mname, None)
199
221
        if method is None:
200
222
            raise SyntaxError('Command not found "%s"' % (cmd[0],),
201
 
                              None, 1, ' '.join(cmd))
 
223
                              (None, 1, 1, ' '.join(cmd)))
202
224
        if input is None:
203
225
            str_input = ''
204
226
        else:
207
229
        retcode, actual_output, actual_error = method(test_case,
208
230
                                                      str_input, args)
209
231
 
210
 
        self._check_output(output, actual_output, test_case)
211
 
        self._check_output(error, actual_error, test_case)
 
232
        try:
 
233
            self._check_output(output, actual_output, test_case)
 
234
        except AssertionError as e:
 
235
            raise AssertionError(str(e) + " in stdout of command %s" % cmd)
 
236
        try:
 
237
            self._check_output(error, actual_error, test_case)
 
238
        except AssertionError as e:
 
239
            raise AssertionError(str(e)
 
240
                                 + " in stderr of running command %s" % cmd)
212
241
        if retcode and not error and actual_error:
213
242
            test_case.fail('In \n\t%s\nUnexpected error: %s'
214
243
                           % (' '.join(cmd), actual_error))
215
244
        return retcode, actual_output, actual_error
216
245
 
217
246
    def _check_output(self, expected, actual, test_case):
218
 
        if expected is None:
219
 
            # Specifying None means: any output is accepted
 
247
        if not actual:
 
248
            if expected is None:
 
249
                return
 
250
            elif expected == '...\n':
 
251
                return
 
252
            else:
 
253
                test_case.fail('expected output: %r, but found nothing'
 
254
                               % (expected,))
 
255
 
 
256
        null_output_matches_anything = getattr(
 
257
            self, 'null_output_matches_anything', False)
 
258
        if null_output_matches_anything and expected is None:
220
259
            return
221
 
        if actual is None:
222
 
            test_case.fail('We expected output: %r, but found None'
223
 
                           % (expected,))
 
260
 
 
261
        expected = expected or ''
224
262
        matching = self.output_checker.check_output(
225
263
            expected, actual, self.check_options)
226
264
        if not matching:
230
268
            # 'expected' parameter. So we just fallback to our good old
231
269
            # assertEqualDiff since we know there *are* differences and the
232
270
            # output should be decently readable.
233
 
            test_case.assertEqualDiff(expected, actual)
 
271
            #
 
272
            # As a special case, we allow output that's missing a final
 
273
            # newline to match an expected string that does have one, so that
 
274
            # we can match a prompt printed on one line, then input given on
 
275
            # the next line.
 
276
            if expected == actual + '\n':
 
277
                pass
 
278
            else:
 
279
                test_case.assertEqualDiff(expected, actual)
234
280
 
235
281
    def _pre_process_args(self, args):
236
282
        new_args = []
238
284
            # Strip the simple and double quotes since we don't care about
239
285
            # them.  We leave the backquotes in place though since they have a
240
286
            # different semantic.
241
 
            if arg[0] in  ('"', "'") and arg[0] == arg[-1]:
 
287
            if arg[0] in ('"', "'") and arg[0] == arg[-1]:
242
288
                yield arg[1:-1]
243
289
            else:
244
290
                if glob.has_magic(arg):
254
300
 
255
301
    def _read_input(self, input, in_name):
256
302
        if in_name is not None:
257
 
            infile = open(in_name, 'rb')
 
303
            infile = open(in_name, 'r')
258
304
            try:
259
305
                # Command redirection takes precedence over provided input
260
306
                input = infile.read()
272
318
            output = None
273
319
        return output
274
320
 
275
 
    def do_bzr(self, test_case, input, args):
276
 
        retcode, out, err = test_case._run_bzr_core(
277
 
            args, retcode=None, encoding=None, stdin=input, working_dir=None)
278
 
        return retcode, out, err
 
321
    def do_brz(self, test_case, input, args):
 
322
        encoding = osutils.get_user_encoding()
 
323
        stdout = ui_testing.StringIOWithEncoding()
 
324
        stderr = ui_testing.StringIOWithEncoding()
 
325
        stdout.encoding = stderr.encoding = encoding
 
326
        handler = logging.StreamHandler(stderr)
 
327
        handler.setLevel(logging.INFO)
 
328
 
 
329
        logger = logging.getLogger('')
 
330
        logger.addHandler(handler)
 
331
        try:
 
332
            retcode = test_case._run_bzr_core(
 
333
                args, encoding=encoding, stdin=input, stdout=stdout,
 
334
                stderr=stderr, working_dir=None)
 
335
        finally:
 
336
            logger.removeHandler(handler)
 
337
 
 
338
        return retcode, stdout.getvalue(), stderr.getvalue()
279
339
 
280
340
    def do_cat(self, test_case, input, args):
281
341
        (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
291
351
        for in_name in input_names:
292
352
            try:
293
353
                inputs.append(self._read_input(None, in_name))
294
 
            except IOError, e:
 
354
            except IOError as e:
295
355
                # Some filenames are illegal on Windows and generate EINVAL
296
356
                # rather than just saying the filename doesn't exist
297
357
                if e.errno in (errno.ENOENT, errno.EINVAL):
303
363
        # Handle output redirections
304
364
        try:
305
365
            output = self._write_output(output, out_name, out_mode)
306
 
        except IOError, e:
 
366
        except IOError as e:
307
367
            # If out_name cannot be created, we may get 'ENOENT', however if
308
368
            # out_name is something like '', we can get EINVAL
309
369
            if e.errno in (errno.ENOENT, errno.EINVAL):
324
384
        # Handle output redirections
325
385
        try:
326
386
            output = self._write_output(output, out_name, out_mode)
327
 
        except IOError, e:
 
387
        except IOError as e:
328
388
            if e.errno in (errno.ENOENT, errno.EINVAL):
329
389
                return 1, None, '%s: No such file or directory\n' % (out_name,)
330
390
            raise
362
422
        err = None
363
423
 
364
424
        def error(msg, path):
365
 
            return  "rm: cannot remove '%s': %s\n" % (path, msg)
 
425
            return "rm: cannot remove '%s': %s\n" % (path, msg)
366
426
 
367
427
        force, recursive = False, False
368
428
        opts = None
381
441
            # FIXME: Should we put that in osutils ?
382
442
            try:
383
443
                os.remove(p)
384
 
            except OSError, e:
 
444
            except OSError as e:
385
445
                # Various OSes raises different exceptions (linux: EISDIR,
386
446
                #   win32: EACCES, OSX: EPERM) when invoked on a directory
387
447
                if e.errno in (errno.EISDIR, errno.EPERM, errno.EACCES):
392
452
                        break
393
453
                elif e.errno == errno.ENOENT:
394
454
                    if not force:
395
 
                        err =  error('No such file or directory', p)
 
455
                        err = error('No such file or directory', p)
396
456
                        break
397
457
                else:
398
458
                    raise
404
464
 
405
465
    def do_mv(self, test_case, input, args):
406
466
        err = None
 
467
 
407
468
        def error(msg, src, dst):
408
469
            return "mv: cannot move %s to %s: %s\n" % (src, dst, msg)
409
470
 
415
476
            if os.path.isdir(dst):
416
477
                real_dst = os.path.join(dst, os.path.basename(src))
417
478
            os.rename(src, real_dst)
418
 
        except OSError, e:
 
479
        except OSError as e:
419
480
            if e.errno == errno.ENOENT:
420
481
                err = error('No such file or directory', src, dst)
421
482
            else:
427
488
        return retcode, None, err
428
489
 
429
490
 
430
 
 
431
491
class TestCaseWithMemoryTransportAndScript(tests.TestCaseWithMemoryTransport):
432
492
    """Helper class to experiment shell-like test and memory fs.
433
493
 
439
499
    def setUp(self):
440
500
        super(TestCaseWithMemoryTransportAndScript, self).setUp()
441
501
        self.script_runner = ScriptRunner()
 
502
        # FIXME: See shelf_ui.Shelver._char_based. This allow using shelve in
 
503
        # scripts while providing a line-based input (better solution in
 
504
        # progress). -- vila 2011-09-28
 
505
        self.overrideEnv('INSIDE_EMACS', '1')
442
506
 
443
 
    def run_script(self, script):
444
 
        return self.script_runner.run_script(self, script)
 
507
    def run_script(self, script, null_output_matches_anything=False):
 
508
        return self.script_runner.run_script(self, script,
 
509
                                             null_output_matches_anything=null_output_matches_anything)
445
510
 
446
511
    def run_command(self, cmd, input, output, error):
447
512
        return self.script_runner.run_command(self, cmd, input, output, error)
452
517
 
453
518
    Can be used as:
454
519
 
455
 
    from bzrlib.tests import script
 
520
    from breezy.tests import script
456
521
 
457
522
 
458
523
    class TestBug(script.TestCaseWithTransportAndScript):
459
524
 
460
525
        def test_bug_nnnnn(self):
461
526
            self.run_script('''
462
 
            $ bzr init
463
 
            $ bzr do-this
 
527
            $ brz init
 
528
            $ brz do-this
464
529
            # Boom, error
465
530
            ''')
466
531
    """
468
533
    def setUp(self):
469
534
        super(TestCaseWithTransportAndScript, self).setUp()
470
535
        self.script_runner = ScriptRunner()
 
536
        # FIXME: See shelf_ui.Shelver._char_based. This allow using shelve in
 
537
        # scripts while providing a line-based input (better solution in
 
538
        # progress). -- vila 2011-09-28
 
539
        self.overrideEnv('INSIDE_EMACS', '1')
471
540
 
472
 
    def run_script(self, script):
473
 
        return self.script_runner.run_script(self, script)
 
541
    def run_script(self, script, null_output_matches_anything=False):
 
542
        return self.script_runner.run_script(self, script,
 
543
                                             null_output_matches_anything=null_output_matches_anything)
474
544
 
475
545
    def run_command(self, cmd, input, output, error):
476
546
        return self.script_runner.run_command(self, cmd, input, output, error)
477
547
 
 
548
 
 
549
def run_script(test_case, script_string, null_output_matches_anything=False):
 
550
    """Run the given script within a testcase"""
 
551
    return ScriptRunner().run_script(test_case, script_string,
 
552
                                     null_output_matches_anything=null_output_matches_anything)