~bzr-pqm/bzr/bzr.dev

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