~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/script.py

  • Committer: Andrew Bennetts
  • Date: 2009-11-25 07:27:43 UTC
  • mto: This revision was merged to the branch mainline in revision 4825.
  • Revision ID: andrew.bennetts@canonical.com-20091125072743-v6sv4m2mkt9iyslp
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2009 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
24
24
import glob
25
25
import os
26
26
import shlex
27
 
import textwrap
 
27
from cStringIO import StringIO
28
28
 
29
29
from bzrlib import (
30
30
    osutils,
55
55
    Input lines start with '<'.
56
56
    Output lines start with nothing.
57
57
    Error lines start with '2>'.
58
 
 
59
 
    :return: A sequence of ([args], input, output, errors), where the args are
60
 
        split in to words, and the input, output, and errors are just strings,
61
 
        typically containing newlines.
62
58
    """
63
59
 
64
60
    commands = []
77
73
    cmd_line = 1
78
74
    lineno = 0
79
75
    input, output, error = None, None, None
80
 
    text = textwrap.dedent(text)
81
 
    lines = text.split('\n')
82
 
    # to make use of triple-quoted strings easier, we ignore a blank line
83
 
    # right at the start and right at the end; the rest are meaningful
84
 
    if lines and lines[0] == '':
85
 
        del lines[0]
86
 
    if lines and lines[-1] == '':
87
 
        del lines[-1]
88
 
    for line in lines:
 
76
    for line in text.split('\n'):
89
77
        lineno += 1
90
78
        # Keep a copy for error reporting
91
79
        orig = line
92
80
        comment =  line.find('#')
93
81
        if comment >= 0:
94
82
            # Delete comments
95
 
            # NB: this syntax means comments are allowed inside output, which
96
 
            # may be confusing...
97
83
            line = line[0:comment]
98
84
            line = line.rstrip()
99
 
            if line == '':
100
 
                continue
 
85
        if line == '':
 
86
            # Ignore empty lines
 
87
            continue
101
88
        if line.startswith('$'):
102
89
            # Time to output the current command
103
90
            add_command(cmd_cur, input, output, error)
120
107
                error = []
121
108
            error.append(line[2:] + '\n')
122
109
        else:
123
 
            # can happen if the first line is not recognized as a command, eg
124
 
            # if the prompt has leading whitespace
125
110
            if output is None:
126
111
                if cmd_cur is None:
127
 
                    raise SyntaxError('No command for line %r' % (line,),
 
112
                    raise SyntaxError('No command for that output',
128
113
                                      (file_name, lineno, 1, orig))
129
114
                output = []
130
115
            output.append(line + '\n')
194
179
        self.output_checker = doctest.OutputChecker()
195
180
        self.check_options = doctest.ELLIPSIS
196
181
 
197
 
    def run_script(self, test_case, text, null_output_matches_anything=False):
 
182
    def run_script(self, test_case, text):
198
183
        """Run a shell-like script as a test.
199
184
 
200
185
        :param test_case: A TestCase instance that should provide the fail(),
202
187
            attribute used as a jail root.
203
188
 
204
189
        :param text: A shell-like script (see _script_to_commands for syntax).
205
 
 
206
 
        :param null_output_matches_anything: For commands with no specified
207
 
            output, ignore any output that does happen, including output on
208
 
            standard error.
209
190
        """
210
 
        self.null_output_matches_anything = null_output_matches_anything
211
191
        for cmd, input, output, error in _script_to_commands(text):
212
192
            self.run_command(test_case, cmd, input, output, error)
213
193
 
216
196
        method = getattr(self, mname, None)
217
197
        if method is None:
218
198
            raise SyntaxError('Command not found "%s"' % (cmd[0],),
219
 
                              (None, 1, 1, ' '.join(cmd)))
 
199
                              None, 1, ' '.join(cmd))
220
200
        if input is None:
221
201
            str_input = ''
222
202
        else:
225
205
        retcode, actual_output, actual_error = method(test_case,
226
206
                                                      str_input, args)
227
207
 
228
 
        try:
229
 
            self._check_output(output, actual_output, test_case)
230
 
        except AssertionError, e:
231
 
            raise AssertionError(str(e) + " in stdout of command %s" % cmd)
232
 
        try:
233
 
            self._check_output(error, actual_error, test_case)
234
 
        except AssertionError, e:
235
 
            raise AssertionError(str(e) +
236
 
                " in stderr of running command %s" % cmd)
 
208
        self._check_output(output, actual_output, test_case)
 
209
        self._check_output(error, actual_error, test_case)
237
210
        if retcode and not error and actual_error:
238
211
            test_case.fail('In \n\t%s\nUnexpected error: %s'
239
212
                           % (' '.join(cmd), actual_error))
240
213
        return retcode, actual_output, actual_error
241
214
 
242
215
    def _check_output(self, expected, actual, test_case):
243
 
        if not actual:
244
 
            if expected is None:
245
 
                return
246
 
            elif expected == '...\n':
247
 
                return
248
 
            else:
249
 
                test_case.fail('expected output: %r, but found nothing'
250
 
                            % (expected,))
251
 
 
252
 
        null_output_matches_anything = getattr(
253
 
            self, 'null_output_matches_anything', False)
254
 
        if null_output_matches_anything and expected is None:
 
216
        if expected is None:
 
217
            # Specifying None means: any output is accepted
255
218
            return
256
 
 
257
 
        expected = expected or ''
 
219
        if actual is None:
 
220
            test_case.fail('We expected output: %r, but found None'
 
221
                           % (expected,))
258
222
        matching = self.output_checker.check_output(
259
223
            expected, actual, self.check_options)
260
224
        if not matching:
264
228
            # 'expected' parameter. So we just fallback to our good old
265
229
            # assertEqualDiff since we know there *are* differences and the
266
230
            # output should be decently readable.
267
 
            #
268
 
            # As a special case, we allow output that's missing a final
269
 
            # newline to match an expected string that does have one, so that
270
 
            # we can match a prompt printed on one line, then input given on
271
 
            # the next line.
272
 
            if expected == actual + '\n':
273
 
                pass
274
 
            else:
275
 
                test_case.assertEqualDiff(expected, actual)
 
231
            test_case.assertEqualDiff(expected, actual)
276
232
 
277
233
    def _pre_process_args(self, args):
278
234
        new_args = []
444
400
            retcode = 0
445
401
        return retcode, None, err
446
402
 
447
 
    def do_mv(self, test_case, input, args):
448
 
        err = None
449
 
        def error(msg, src, dst):
450
 
            return "mv: cannot move %s to %s: %s\n" % (src, dst, msg)
451
 
 
452
 
        if not args or len(args) != 2:
453
 
            raise SyntaxError("Usage: mv path1 path2")
454
 
        src, dst = args
455
 
        try:
456
 
            real_dst = dst
457
 
            if os.path.isdir(dst):
458
 
                real_dst = os.path.join(dst, os.path.basename(src))
459
 
            os.rename(src, real_dst)
460
 
        except OSError, e:
461
 
            if e.errno == errno.ENOENT:
462
 
                err = error('No such file or directory', src, dst)
463
 
            else:
464
 
                raise
465
 
        if err:
466
 
            retcode = 1
467
 
        else:
468
 
            retcode = 0
469
 
        return retcode, None, err
470
 
 
471
 
 
472
403
 
473
404
class TestCaseWithMemoryTransportAndScript(tests.TestCaseWithMemoryTransport):
474
405
    """Helper class to experiment shell-like test and memory fs.
482
413
        super(TestCaseWithMemoryTransportAndScript, self).setUp()
483
414
        self.script_runner = ScriptRunner()
484
415
 
485
 
    def run_script(self, script, null_output_matches_anything=False):
486
 
        return self.script_runner.run_script(self, script, 
487
 
                   null_output_matches_anything=null_output_matches_anything)
 
416
    def run_script(self, script):
 
417
        return self.script_runner.run_script(self, script)
488
418
 
489
419
    def run_command(self, cmd, input, output, error):
490
420
        return self.script_runner.run_command(self, cmd, input, output, error)
512
442
        super(TestCaseWithTransportAndScript, self).setUp()
513
443
        self.script_runner = ScriptRunner()
514
444
 
515
 
    def run_script(self, script, null_output_matches_anything=False):
516
 
        return self.script_runner.run_script(self, script,
517
 
                   null_output_matches_anything=null_output_matches_anything)
 
445
    def run_script(self, script):
 
446
        return self.script_runner.run_script(self, script)
518
447
 
519
448
    def run_command(self, cmd, input, output, error):
520
449
        return self.script_runner.run_command(self, cmd, input, output, error)
521
450
 
522
 
 
523
 
def run_script(test_case, script_string, null_output_matches_anything=False):
524
 
    """Run the given script within a testcase"""
525
 
    return ScriptRunner().run_script(test_case, script_string,
526
 
               null_output_matches_anything=null_output_matches_anything)
527