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