~bzr-pqm/bzr/bzr.dev

5557.1.15 by John Arbash Meinel
Merge bzr.dev 5597 to resolve NEWS, aka bzr-2.3.txt
1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4665.5.20 by Vincent Ladeuil
Fixed as per Martin's review.
16
4665.5.9 by Vincent Ladeuil
Start adding doc.
17
"""Shell-like test scripts.
18
4665.5.21 by Vincent Ladeuil
Fix some typos mentioned by Martin.
19
See developers/testing.html for more explanations.
4665.5.9 by Vincent Ladeuil
Start adding doc.
20
"""
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
21
4665.5.12 by Vincent Ladeuil
Support '...' in expected strings.
22
import doctest
4665.5.17 by Vincent Ladeuil
Implement 'rm' command.
23
import errno
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
24
import glob
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
25
import os
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
26
import shlex
5283.1.2 by Martin Pool
ScriptRunner strips consistent leading indentation from scripts
27
import textwrap
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
28
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
29
from bzrlib import (
30
    osutils,
31
    tests,
32
    )
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
33
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
34
4665.5.2 by Vincent Ladeuil
Handle simple, double and back quotes.
35
def split(s):
36
    """Split a command line respecting quotes."""
37
    scanner = shlex.shlex(s)
38
    scanner.quotes = '\'"`'
39
    scanner.whitespace_split = True
40
    for t in list(scanner):
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
41
        yield t
4665.5.2 by Vincent Ladeuil
Handle simple, double and back quotes.
42
43
44
def _script_to_commands(text, file_name=None):
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
45
    """Turn a script into a list of commands with their associated IOs.
46
4665.5.21 by Vincent Ladeuil
Fix some typos mentioned by Martin.
47
    Each command appears on a line by itself starting with '$ '. It can be
48
    associated with an input that will feed it and an expected output.
49
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
50
    Comments starts with '#' until the end of line.
51
    Empty lines are ignored.
4665.5.21 by Vincent Ladeuil
Fix some typos mentioned by Martin.
52
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
53
    Input and output are full lines terminated by a '\n'.
4665.5.21 by Vincent Ladeuil
Fix some typos mentioned by Martin.
54
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
55
    Input lines start with '<'.
4665.5.21 by Vincent Ladeuil
Fix some typos mentioned by Martin.
56
    Output lines start with nothing.
4665.5.3 by Vincent Ladeuil
Separate error from normal output.
57
    Error lines start with '2>'.
5417.1.4 by Martin Pool
doc
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.
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
62
    """
4665.5.7 by Vincent Ladeuil
Simplify output/errors handling.
63
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
64
    commands = []
4665.5.7 by Vincent Ladeuil
Simplify output/errors handling.
65
66
    def add_command(cmd, input, output, error):
67
        if cmd is not None:
68
            if input is not None:
69
                input = ''.join(input)
70
            if output is not None:
71
                output = ''.join(output)
72
            if error is not None:
73
                error = ''.join(error)
74
            commands.append((cmd, input, output, error))
75
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
76
    cmd_cur = None
77
    cmd_line = 1
78
    lineno = 0
4665.5.3 by Vincent Ladeuil
Separate error from normal output.
79
    input, output, error = None, None, None
5283.1.2 by Martin Pool
ScriptRunner strips consistent leading indentation from scripts
80
    text = textwrap.dedent(text)
5417.1.5 by Martin Pool
Better handling of blank lines in test scripts
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:
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
89
        lineno += 1
90
        # Keep a copy for error reporting
91
        orig = line
92
        comment =  line.find('#')
93
        if comment >= 0:
94
            # Delete comments
5417.1.5 by Martin Pool
Better handling of blank lines in test scripts
95
            # NB: this syntax means comments are allowed inside output, which
96
            # may be confusing...
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
97
            line = line[0:comment]
98
            line = line.rstrip()
5417.1.5 by Martin Pool
Better handling of blank lines in test scripts
99
            if line == '':
100
                continue
4665.5.20 by Vincent Ladeuil
Fixed as per Martin's review.
101
        if line.startswith('$'):
102
            # Time to output the current command
103
            add_command(cmd_cur, input, output, error)
104
            # And start a new one
105
            cmd_cur = list(split(line[1:]))
106
            cmd_line = lineno
107
            input, output, error = None, None, None
108
        elif line.startswith('<'):
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
109
            if input is None:
110
                if cmd_cur is None:
111
                    raise SyntaxError('No command for that input',
112
                                      (file_name, lineno, 1, orig))
113
                input = []
114
            input.append(line[1:] + '\n')
4665.5.3 by Vincent Ladeuil
Separate error from normal output.
115
        elif line.startswith('2>'):
116
            if error is None:
117
                if cmd_cur is None:
118
                    raise SyntaxError('No command for that error',
119
                                      (file_name, lineno, 1, orig))
120
                error = []
121
            error.append(line[2:] + '\n')
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
122
        else:
4889.1.1 by Martin Pool
Clearer syntax error message from ScriptRunner
123
            # can happen if the first line is not recognized as a command, eg
124
            # if the prompt has leading whitespace
4665.5.20 by Vincent Ladeuil
Fixed as per Martin's review.
125
            if output is None:
126
                if cmd_cur is None:
4889.1.1 by Martin Pool
Clearer syntax error message from ScriptRunner
127
                    raise SyntaxError('No command for line %r' % (line,),
4665.5.20 by Vincent Ladeuil
Fixed as per Martin's review.
128
                                      (file_name, lineno, 1, orig))
129
                output = []
130
            output.append(line + '\n')
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
131
    # Add the last seen command
4665.5.7 by Vincent Ladeuil
Simplify output/errors handling.
132
    add_command(cmd_cur, input, output, error)
4665.5.1 by Vincent Ladeuil
Start some shell-like capability to write tests.
133
    return commands
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
134
135
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
136
def _scan_redirection_options(args):
137
    """Recognize and process input and output redirections.
138
139
    :param args: The command line arguments
140
141
    :return: A tuple containing: 
142
        - The file name redirected from or None
143
        - The file name redirected to or None
144
        - The mode to open the output file or None
145
        - The reamining arguments
146
    """
4665.5.18 by Vincent Ladeuil
Better redirection handling.
147
    def redirected_file_name(direction, name, args):
148
        if name == '':
149
            try:
150
                name = args.pop(0)
151
            except IndexError:
152
                # We leave the error handling to higher levels, an empty name
153
                # can't be legal.
154
                name = ''
155
        return name
156
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
157
    remaining = []
158
    in_name = None
159
    out_name, out_mode = None, None
4665.5.18 by Vincent Ladeuil
Better redirection handling.
160
    while args:
161
        arg = args.pop(0)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
162
        if arg.startswith('<'):
4665.5.18 by Vincent Ladeuil
Better redirection handling.
163
            in_name = redirected_file_name('<', arg[1:], args)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
164
        elif arg.startswith('>>'):
4665.5.18 by Vincent Ladeuil
Better redirection handling.
165
            out_name = redirected_file_name('>>', arg[2:], args)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
166
            out_mode = 'ab+'
4665.5.18 by Vincent Ladeuil
Better redirection handling.
167
        elif arg.startswith('>',):
168
            out_name = redirected_file_name('>', arg[1:], args)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
169
            out_mode = 'wb+'
170
        else:
171
            remaining.append(arg)
172
    return in_name, out_name, out_mode, remaining
173
174
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
175
class ScriptRunner(object):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
176
    """Run a shell-like script from a test.
177
    
178
    Can be used as:
179
180
    from bzrlib.tests import script
181
182
    ...
183
184
        def test_bug_nnnnn(self):
185
            sr = script.ScriptRunner()
186
            sr.run_script(self, '''
187
            $ bzr init
188
            $ bzr do-this
189
            # Boom, error
190
            ''')
191
    """
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
192
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
193
    def __init__(self):
4665.5.12 by Vincent Ladeuil
Support '...' in expected strings.
194
        self.output_checker = doctest.OutputChecker()
195
        self.check_options = doctest.ELLIPSIS
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
196
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
197
    def run_script(self, test_case, text, null_output_matches_anything=False):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
198
        """Run a shell-like script as a test.
199
200
        :param test_case: A TestCase instance that should provide the fail(),
201
            assertEqualDiff and _run_bzr_core() methods as well as a 'test_dir'
202
            attribute used as a jail root.
203
204
        :param text: A shell-like script (see _script_to_commands for syntax).
5509.1.2 by Neil Martinsen-Burrell
address vilas review comments
205
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
206
        :param null_output_matches_anything: For commands with no specified
5509.1.3 by Neil Martinsen-Burrell
change option name and mention the effect on standard error
207
            output, ignore any output that does happen, including output on
208
            standard error.
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
209
        """
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
210
        self.null_output_matches_anything = null_output_matches_anything
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
211
        for cmd, input, output, error in _script_to_commands(text):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
212
            self.run_command(test_case, cmd, input, output, error)
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
213
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
214
    def run_command(self, test_case, cmd, input, output, error):
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
215
        mname = 'do_' + cmd[0]
216
        method = getattr(self, mname, None)
217
        if method is None:
218
            raise SyntaxError('Command not found "%s"' % (cmd[0],),
5508.2.1 by Martin
In shell-like tests raise a valid SyntaxError if a command is not found
219
                              (None, 1, 1, ' '.join(cmd)))
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
220
        if input is None:
221
            str_input = ''
222
        else:
223
            str_input = ''.join(input)
224
        args = list(self._pre_process_args(cmd[1:]))
225
        retcode, actual_output, actual_error = method(test_case,
226
                                                      str_input, args)
227
5422.3.1 by Martin Pool
Blank scriptrunner output only matches blank
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)
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
237
        if retcode and not error and actual_error:
238
            test_case.fail('In \n\t%s\nUnexpected error: %s'
239
                           % (' '.join(cmd), actual_error))
240
        return retcode, actual_output, actual_error
241
242
    def _check_output(self, expected, actual, test_case):
5422.3.1 by Martin Pool
Blank scriptrunner output only matches blank
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,))
5509.1.1 by Neil Martinsen-Burrell
add ignore_blanks to run_script to recover old behavior
251
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
252
        null_output_matches_anything = getattr(
253
            self, 'null_output_matches_anything', False)
254
        if null_output_matches_anything and expected is None:
5509.1.1 by Neil Martinsen-Burrell
add ignore_blanks to run_script to recover old behavior
255
            return
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
256
5422.3.1 by Martin Pool
Blank scriptrunner output only matches blank
257
        expected = expected or ''
4665.5.12 by Vincent Ladeuil
Support '...' in expected strings.
258
        matching = self.output_checker.check_output(
259
            expected, actual, self.check_options)
260
        if not matching:
261
            # Note that we can't use output_checker.output_difference() here
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
262
            # because... the API is broken ('expected' must be a doctest
263
            # specific object of which a 'want' attribute will be our
264
            # 'expected' parameter. So we just fallback to our good old
265
            # assertEqualDiff since we know there *are* differences and the
266
            # output should be decently readable.
5417.1.1 by Martin Pool
ScriptRunner can now cope with commands that prompt for input.
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:
5422.3.5 by Martin Pool
Go back to using assertEqualDiff when a script fails
275
                test_case.assertEqualDiff(expected, actual)
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
276
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
277
    def _pre_process_args(self, args):
278
        new_args = []
279
        for arg in args:
280
            # Strip the simple and double quotes since we don't care about
281
            # them.  We leave the backquotes in place though since they have a
282
            # different semantic.
283
            if arg[0] in  ('"', "'") and arg[0] == arg[-1]:
284
                yield arg[1:-1]
285
            else:
286
                if glob.has_magic(arg):
287
                    matches = glob.glob(arg)
288
                    if matches:
289
                        # We care more about order stability than performance
290
                        # here
291
                        matches.sort()
292
                        for m in matches:
293
                            yield m
294
                else:
295
                    yield arg
296
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
297
    def _read_input(self, input, in_name):
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
298
        if in_name is not None:
299
            infile = open(in_name, 'rb')
300
            try:
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
301
                # Command redirection takes precedence over provided input
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
302
                input = infile.read()
303
            finally:
304
                infile.close()
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
305
        return input
306
307
    def _write_output(self, output, out_name, out_mode):
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
308
        if out_name is not None:
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
309
            outfile = open(out_name, out_mode)
4665.5.4 by Vincent Ladeuil
Implement a 'cat' command.
310
            try:
311
                outfile.write(output)
312
            finally:
313
                outfile.close()
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
314
            output = None
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
315
        return output
316
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
317
    def do_bzr(self, test_case, input, args):
318
        retcode, out, err = test_case._run_bzr_core(
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
319
            args, retcode=None, encoding=None, stdin=input, working_dir=None)
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
320
        return retcode, out, err
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
321
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
322
    def do_cat(self, test_case, input, args):
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
323
        (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
324
        if args and in_name is not None:
325
            raise SyntaxError('Specify a file OR use redirection')
326
327
        inputs = []
328
        if input:
329
            inputs.append(input)
330
        input_names = args
331
        if in_name:
332
            args.append(in_name)
333
        for in_name in input_names:
334
            try:
335
                inputs.append(self._read_input(None, in_name))
336
            except IOError, e:
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
337
                # Some filenames are illegal on Windows and generate EINVAL
338
                # rather than just saying the filename doesn't exist
339
                if e.errno in (errno.ENOENT, errno.EINVAL):
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
340
                    return (1, None,
341
                            '%s: No such file or directory\n' % (in_name,))
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
342
                raise
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
343
        # Basically cat copy input to output
4665.5.19 by Vincent Ladeuil
Implement globbing and enhance cat to accept multiple files.
344
        output = ''.join(inputs)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
345
        # Handle output redirections
4665.5.18 by Vincent Ladeuil
Better redirection handling.
346
        try:
347
            output = self._write_output(output, out_name, out_mode)
348
        except IOError, e:
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
349
            # If out_name cannot be created, we may get 'ENOENT', however if
350
            # out_name is something like '', we can get EINVAL
351
            if e.errno in (errno.ENOENT, errno.EINVAL):
4665.5.18 by Vincent Ladeuil
Better redirection handling.
352
                return 1, None, '%s: No such file or directory\n' % (out_name,)
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
353
            raise
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
354
        return 0, output, None
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
355
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
356
    def do_echo(self, test_case, input, args):
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
357
        (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
358
        if input or in_name:
359
            raise SyntaxError('echo doesn\'t read from stdin')
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
360
        if args:
4665.5.20 by Vincent Ladeuil
Fixed as per Martin's review.
361
            input = ' '.join(args)
4665.5.8 by Vincent Ladeuil
Implement 'echo' command.
362
        # Always append a \n'
363
        input += '\n'
364
        # Process output
365
        output = input
366
        # Handle output redirections
4665.5.18 by Vincent Ladeuil
Better redirection handling.
367
        try:
368
            output = self._write_output(output, out_name, out_mode)
369
        except IOError, e:
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
370
            if e.errno in (errno.ENOENT, errno.EINVAL):
4665.5.18 by Vincent Ladeuil
Better redirection handling.
371
                return 1, None, '%s: No such file or directory\n' % (out_name,)
4789.18.1 by John Arbash Meinel
Rework test_script a little bit.
372
            raise
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
373
        return 0, output, None
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
374
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
375
    def _get_jail_root(self, test_case):
376
        return test_case.test_dir
377
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
378
    def _ensure_in_jail(self, test_case, path):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
379
        jail_root = self._get_jail_root(test_case)
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
380
        if not osutils.is_inside(jail_root, osutils.normalizepath(path)):
381
            raise ValueError('%s is not inside %s' % (path, jail_root))
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
382
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
383
    def do_cd(self, test_case, input, args):
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
384
        if len(args) > 1:
385
            raise SyntaxError('Usage: cd [dir]')
386
        if len(args) == 1:
387
            d = args[0]
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
388
            self._ensure_in_jail(test_case, d)
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
389
        else:
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
390
            # The test "home" directory is the root of its jail
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
391
            d = self._get_jail_root(test_case)
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
392
        os.chdir(d)
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
393
        return 0, None, None
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
394
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
395
    def do_mkdir(self, test_case, input, args):
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
396
        if not args or len(args) != 1:
397
            raise SyntaxError('Usage: mkdir dir')
398
        d = args[0]
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
399
        self._ensure_in_jail(test_case, d)
4665.5.5 by Vincent Ladeuil
Implement 'cd' and 'mkdir'.
400
        os.mkdir(d)
4665.5.15 by Vincent Ladeuil
Catch the retcode for all commands.
401
        return 0, None, None
4665.5.6 by Vincent Ladeuil
Implement 'bzr' command.
402
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
403
    def do_rm(self, test_case, input, args):
4665.5.17 by Vincent Ladeuil
Implement 'rm' command.
404
        err = None
405
406
        def error(msg, path):
407
            return  "rm: cannot remove '%s': %s\n" % (path, msg)
408
409
        force, recursive = False, False
410
        opts = None
411
        if args and args[0][0] == '-':
412
            opts = args.pop(0)[1:]
413
            if 'f' in opts:
414
                force = True
415
                opts = opts.replace('f', '', 1)
416
            if 'r' in opts:
417
                recursive = True
418
                opts = opts.replace('r', '', 1)
419
        if not args or opts:
420
            raise SyntaxError('Usage: rm [-fr] path+')
421
        for p in args:
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
422
            self._ensure_in_jail(test_case, p)
4665.5.17 by Vincent Ladeuil
Implement 'rm' command.
423
            # FIXME: Should we put that in osutils ?
424
            try:
425
                os.remove(p)
426
            except OSError, e:
4707.1.1 by Vincent Ladeuil
Fix OSX and FreeBSD failures.
427
                # Various OSes raises different exceptions (linux: EISDIR,
428
                #   win32: EACCES, OSX: EPERM) when invoked on a directory
429
                if e.errno in (errno.EISDIR, errno.EPERM, errno.EACCES):
4665.5.17 by Vincent Ladeuil
Implement 'rm' command.
430
                    if recursive:
431
                        osutils.rmtree(p)
432
                    else:
433
                        err = error('Is a directory', p)
434
                        break
435
                elif e.errno == errno.ENOENT:
436
                    if not force:
437
                        err =  error('No such file or directory', p)
438
                        break
439
                else:
440
                    raise
441
        if err:
442
            retcode = 1
443
        else:
444
            retcode = 0
445
        return retcode, None, err
446
4953.2.1 by Neil Martinsen-Burrell
add an mv command to shell-like tests
447
    def do_mv(self, test_case, input, args):
448
        err = None
4953.2.3 by Vincent Ladeuil
Use os.rename() and fix some typos.
449
        def error(msg, src, dst):
450
            return "mv: cannot move %s to %s: %s\n" % (src, dst, msg)
4953.2.1 by Neil Martinsen-Burrell
add an mv command to shell-like tests
451
452
        if not args or len(args) != 2:
453
            raise SyntaxError("Usage: mv path1 path2")
4953.2.3 by Vincent Ladeuil
Use os.rename() and fix some typos.
454
        src, dst = args
4953.2.1 by Neil Martinsen-Burrell
add an mv command to shell-like tests
455
        try:
4953.2.3 by Vincent Ladeuil
Use os.rename() and fix some typos.
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)
4953.2.1 by Neil Martinsen-Burrell
add an mv command to shell-like tests
460
        except OSError, e:
461
            if e.errno == errno.ENOENT:
4953.2.3 by Vincent Ladeuil
Use os.rename() and fix some typos.
462
                err = error('No such file or directory', src, dst)
4953.2.1 by Neil Martinsen-Burrell
add an mv command to shell-like tests
463
            else:
464
                raise
465
        if err:
466
            retcode = 1
467
        else:
468
            retcode = 0
469
        return retcode, None, err
470
471
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
472
4665.5.11 by Vincent Ladeuil
Create a new test case based on TestCaseWithMemoryTransport.
473
class TestCaseWithMemoryTransportAndScript(tests.TestCaseWithMemoryTransport):
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
474
    """Helper class to experiment shell-like test and memory fs.
475
476
    This not intended to be used outside of experiments in implementing memoy
477
    based file systems and evolving bzr so that test can use only memory based
478
    resources.
479
    """
4665.5.11 by Vincent Ladeuil
Create a new test case based on TestCaseWithMemoryTransport.
480
481
    def setUp(self):
482
        super(TestCaseWithMemoryTransportAndScript, self).setUp()
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
483
        self.script_runner = ScriptRunner()
4665.5.11 by Vincent Ladeuil
Create a new test case based on TestCaseWithMemoryTransport.
484
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
485
    def run_script(self, script, null_output_matches_anything=False):
5509.1.2 by Neil Martinsen-Burrell
address vilas review comments
486
        return self.script_runner.run_script(self, script, 
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
487
                   null_output_matches_anything=null_output_matches_anything)
4665.5.11 by Vincent Ladeuil
Create a new test case based on TestCaseWithMemoryTransport.
488
489
    def run_command(self, cmd, input, output, error):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
490
        return self.script_runner.run_command(self, cmd, input, output, error)
4665.5.11 by Vincent Ladeuil
Create a new test case based on TestCaseWithMemoryTransport.
491
492
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
493
class TestCaseWithTransportAndScript(tests.TestCaseWithTransport):
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
494
    """Helper class to quickly define shell-like tests.
495
496
    Can be used as:
497
498
    from bzrlib.tests import script
499
500
501
    class TestBug(script.TestCaseWithTransportAndScript):
502
503
        def test_bug_nnnnn(self):
504
            self.run_script('''
505
            $ bzr init
506
            $ bzr do-this
507
            # Boom, error
508
            ''')
509
    """
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
510
511
    def setUp(self):
512
        super(TestCaseWithTransportAndScript, self).setUp()
4665.5.23 by Vincent Ladeuil
Revert the jail_root parameter addition.
513
        self.script_runner = ScriptRunner()
514
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
515
    def run_script(self, script, null_output_matches_anything=False):
5509.1.2 by Neil Martinsen-Burrell
address vilas review comments
516
        return self.script_runner.run_script(self, script,
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
517
                   null_output_matches_anything=null_output_matches_anything)
4665.5.10 by Vincent Ladeuil
Start separating the script runner from the test case.
518
519
    def run_command(self, cmd, input, output, error):
4665.5.24 by Vincent Ladeuil
Scripts can be used without imposing a test base class.
520
        return self.script_runner.run_command(self, cmd, input, output, error)
4665.5.22 by Vincent Ladeuil
Try to relax the test_case/script runner coupling.
521
5283.1.1 by Martin Pool
Add helper function script.run_script and suggest using it
522
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
523
def run_script(test_case, script_string, null_output_matches_anything=False):
5283.1.1 by Martin Pool
Add helper function script.run_script and suggest using it
524
    """Run the given script within a testcase"""
5509.1.2 by Neil Martinsen-Burrell
address vilas review comments
525
    return ScriptRunner().run_script(test_case, script_string,
5531.1.2 by Vincent Ladeuil
s/blank_output/null_output/
526
               null_output_matches_anything=null_output_matches_anything)
5455.1.1 by Vincent Ladeuil
Add a new hidden ``test-script`` command.
527