/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 bzrlib/tests/script.py

  • Committer: Robert Collins
  • Date: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

Show diffs side-by-side

added added

removed removed

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