/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/script.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Canonical Ltd
 
1
# Copyright (C) 2009, 2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
107
107
                error = []
108
108
            error.append(line[2:] + '\n')
109
109
        else:
 
110
            # can happen if the first line is not recognized as a command, eg
 
111
            # if the prompt has leading whitespace
110
112
            if output is None:
111
113
                if cmd_cur is None:
112
 
                    raise SyntaxError('No command for that output',
 
114
                    raise SyntaxError('No command for line %r' % (line,),
113
115
                                      (file_name, lineno, 1, orig))
114
116
                output = []
115
117
            output.append(line + '\n')
158
160
 
159
161
 
160
162
class ScriptRunner(object):
161
 
 
162
 
    def __init__(self, test_case):
163
 
        self.test_case = test_case
 
163
    """Run a shell-like script from a test.
 
164
    
 
165
    Can be used as:
 
166
 
 
167
    from bzrlib.tests import script
 
168
 
 
169
    ...
 
170
 
 
171
        def test_bug_nnnnn(self):
 
172
            sr = script.ScriptRunner()
 
173
            sr.run_script(self, '''
 
174
            $ bzr init
 
175
            $ bzr do-this
 
176
            # Boom, error
 
177
            ''')
 
178
    """
 
179
 
 
180
    def __init__(self):
164
181
        self.output_checker = doctest.OutputChecker()
165
182
        self.check_options = doctest.ELLIPSIS
166
183
 
167
 
    def run_script(self, text):
 
184
    def run_script(self, test_case, text):
 
185
        """Run a shell-like script as a test.
 
186
 
 
187
        :param test_case: A TestCase instance that should provide the fail(),
 
188
            assertEqualDiff and _run_bzr_core() methods as well as a 'test_dir'
 
189
            attribute used as a jail root.
 
190
 
 
191
        :param text: A shell-like script (see _script_to_commands for syntax).
 
192
        """
168
193
        for cmd, input, output, error in _script_to_commands(text):
169
 
            self.run_command(cmd, input, output, error)
170
 
 
171
 
    def _check_output(self, expected, actual):
 
194
            self.run_command(test_case, cmd, input, output, error)
 
195
 
 
196
    def run_command(self, test_case, cmd, input, output, error):
 
197
        mname = 'do_' + cmd[0]
 
198
        method = getattr(self, mname, None)
 
199
        if method is None:
 
200
            raise SyntaxError('Command not found "%s"' % (cmd[0],),
 
201
                              None, 1, ' '.join(cmd))
 
202
        if input is None:
 
203
            str_input = ''
 
204
        else:
 
205
            str_input = ''.join(input)
 
206
        args = list(self._pre_process_args(cmd[1:]))
 
207
        retcode, actual_output, actual_error = method(test_case,
 
208
                                                      str_input, args)
 
209
 
 
210
        self._check_output(output, actual_output, test_case)
 
211
        self._check_output(error, actual_error, test_case)
 
212
        if retcode and not error and actual_error:
 
213
            test_case.fail('In \n\t%s\nUnexpected error: %s'
 
214
                           % (' '.join(cmd), actual_error))
 
215
        return retcode, actual_output, actual_error
 
216
 
 
217
    def _check_output(self, expected, actual, test_case):
172
218
        if expected is None:
173
219
            # Specifying None means: any output is accepted
174
220
            return
175
221
        if actual is None:
176
 
            self.test_case.fail('Unexpected: %s' % actual)
 
222
            test_case.fail('We expected output: %r, but found None'
 
223
                           % (expected,))
177
224
        matching = self.output_checker.check_output(
178
225
            expected, actual, self.check_options)
179
226
        if not matching:
183
230
            # 'expected' parameter. So we just fallback to our good old
184
231
            # assertEqualDiff since we know there *are* differences and the
185
232
            # output should be decently readable.
186
 
            self.test_case.assertEqualDiff(expected, actual)
 
233
            test_case.assertEqualDiff(expected, actual)
187
234
 
188
235
    def _pre_process_args(self, args):
189
236
        new_args = []
205
252
                else:
206
253
                    yield arg
207
254
 
208
 
    def run_command(self, cmd, input, output, error):
209
 
        mname = 'do_' + cmd[0]
210
 
        method = getattr(self, mname, None)
211
 
        if method is None:
212
 
            raise SyntaxError('Command not found "%s"' % (cmd[0],),
213
 
                              None, 1, ' '.join(cmd))
214
 
        if input is None:
215
 
            str_input = ''
216
 
        else:
217
 
            str_input = ''.join(input)
218
 
        args = list(self._pre_process_args(cmd[1:]))
219
 
        retcode, actual_output, actual_error = method(str_input, args)
220
 
 
221
 
        self._check_output(output, actual_output)
222
 
        self._check_output(error, actual_error)
223
 
        if retcode and not error and actual_error:
224
 
            self.test_case.fail('In \n\t%s\nUnexpected error: %s'
225
 
                                % (' '.join(cmd), actual_error))
226
 
        return retcode, actual_output, actual_error
227
 
 
228
255
    def _read_input(self, input, in_name):
229
256
        if in_name is not None:
230
257
            infile = open(in_name, 'rb')
245
272
            output = None
246
273
        return output
247
274
 
248
 
    def do_bzr(self, input, args):
249
 
        retcode, out, err = self.test_case._run_bzr_core(
 
275
    def do_bzr(self, test_case, input, args):
 
276
        retcode, out, err = test_case._run_bzr_core(
250
277
            args, retcode=None, encoding=None, stdin=input, working_dir=None)
251
278
        return retcode, out, err
252
279
 
253
 
    def do_cat(self, input, args):
 
280
    def do_cat(self, test_case, input, args):
254
281
        (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
255
282
        if args and in_name is not None:
256
283
            raise SyntaxError('Specify a file OR use redirection')
265
292
            try:
266
293
                inputs.append(self._read_input(None, in_name))
267
294
            except IOError, e:
268
 
                if e.errno == errno.ENOENT:
 
295
                # Some filenames are illegal on Windows and generate EINVAL
 
296
                # rather than just saying the filename doesn't exist
 
297
                if e.errno in (errno.ENOENT, errno.EINVAL):
269
298
                    return (1, None,
270
299
                            '%s: No such file or directory\n' % (in_name,))
 
300
                raise
271
301
        # Basically cat copy input to output
272
302
        output = ''.join(inputs)
273
303
        # Handle output redirections
274
304
        try:
275
305
            output = self._write_output(output, out_name, out_mode)
276
306
        except IOError, e:
277
 
            if e.errno == errno.ENOENT:
 
307
            # If out_name cannot be created, we may get 'ENOENT', however if
 
308
            # out_name is something like '', we can get EINVAL
 
309
            if e.errno in (errno.ENOENT, errno.EINVAL):
278
310
                return 1, None, '%s: No such file or directory\n' % (out_name,)
 
311
            raise
279
312
        return 0, output, None
280
313
 
281
 
    def do_echo(self, input, args):
 
314
    def do_echo(self, test_case, input, args):
282
315
        (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
283
 
        if input and args:
284
 
                raise SyntaxError('Specify parameters OR use redirection')
 
316
        if input or in_name:
 
317
            raise SyntaxError('echo doesn\'t read from stdin')
285
318
        if args:
286
319
            input = ' '.join(args)
287
 
        try:
288
 
            input = self._read_input(input, in_name)
289
 
        except IOError, e:
290
 
            if e.errno == errno.ENOENT:
291
 
                return 1, None, '%s: No such file or directory\n' % (in_name,)
292
320
        # Always append a \n'
293
321
        input += '\n'
294
322
        # Process output
297
325
        try:
298
326
            output = self._write_output(output, out_name, out_mode)
299
327
        except IOError, e:
300
 
            if e.errno == errno.ENOENT:
 
328
            if e.errno in (errno.ENOENT, errno.EINVAL):
301
329
                return 1, None, '%s: No such file or directory\n' % (out_name,)
 
330
            raise
302
331
        return 0, output, None
303
332
 
304
 
    def _ensure_in_jail(self, path):
305
 
        jail_root = self.test_case.get_jail_root()
 
333
    def _get_jail_root(self, test_case):
 
334
        return test_case.test_dir
 
335
 
 
336
    def _ensure_in_jail(self, test_case, path):
 
337
        jail_root = self._get_jail_root(test_case)
306
338
        if not osutils.is_inside(jail_root, osutils.normalizepath(path)):
307
339
            raise ValueError('%s is not inside %s' % (path, jail_root))
308
340
 
309
 
    def do_cd(self, input, args):
 
341
    def do_cd(self, test_case, input, args):
310
342
        if len(args) > 1:
311
343
            raise SyntaxError('Usage: cd [dir]')
312
344
        if len(args) == 1:
313
345
            d = args[0]
314
 
            self._ensure_in_jail(d)
 
346
            self._ensure_in_jail(test_case, d)
315
347
        else:
316
 
            d = self.test_case.get_jail_root()
 
348
            # The test "home" directory is the root of its jail
 
349
            d = self._get_jail_root(test_case)
317
350
        os.chdir(d)
318
351
        return 0, None, None
319
352
 
320
 
    def do_mkdir(self, input, args):
 
353
    def do_mkdir(self, test_case, input, args):
321
354
        if not args or len(args) != 1:
322
355
            raise SyntaxError('Usage: mkdir dir')
323
356
        d = args[0]
324
 
        self._ensure_in_jail(d)
 
357
        self._ensure_in_jail(test_case, d)
325
358
        os.mkdir(d)
326
359
        return 0, None, None
327
360
 
328
 
    def do_rm(self, input, args):
 
361
    def do_rm(self, test_case, input, args):
329
362
        err = None
330
363
 
331
364
        def error(msg, path):
344
377
        if not args or opts:
345
378
            raise SyntaxError('Usage: rm [-fr] path+')
346
379
        for p in args:
347
 
            self._ensure_in_jail(p)
 
380
            self._ensure_in_jail(test_case, p)
348
381
            # FIXME: Should we put that in osutils ?
349
382
            try:
350
383
                os.remove(p)
351
384
            except OSError, e:
352
 
                if e.errno == errno.EISDIR:
 
385
                # Various OSes raises different exceptions (linux: EISDIR,
 
386
                #   win32: EACCES, OSX: EPERM) when invoked on a directory
 
387
                if e.errno in (errno.EISDIR, errno.EPERM, errno.EACCES):
353
388
                    if recursive:
354
389
                        osutils.rmtree(p)
355
390
                    else:
367
402
            retcode = 0
368
403
        return retcode, None, err
369
404
 
 
405
    def do_mv(self, test_case, input, args):
 
406
        err = None
 
407
        def error(msg, src, dst):
 
408
            return "mv: cannot move %s to %s: %s\n" % (src, dst, msg)
 
409
 
 
410
        if not args or len(args) != 2:
 
411
            raise SyntaxError("Usage: mv path1 path2")
 
412
        src, dst = args
 
413
        try:
 
414
            real_dst = dst
 
415
            if os.path.isdir(dst):
 
416
                real_dst = os.path.join(dst, os.path.basename(src))
 
417
            os.rename(src, real_dst)
 
418
        except OSError, e:
 
419
            if e.errno == errno.ENOENT:
 
420
                err = error('No such file or directory', src, dst)
 
421
            else:
 
422
                raise
 
423
        if err:
 
424
            retcode = 1
 
425
        else:
 
426
            retcode = 0
 
427
        return retcode, None, err
 
428
 
 
429
 
370
430
 
371
431
class TestCaseWithMemoryTransportAndScript(tests.TestCaseWithMemoryTransport):
 
432
    """Helper class to experiment shell-like test and memory fs.
 
433
 
 
434
    This not intended to be used outside of experiments in implementing memoy
 
435
    based file systems and evolving bzr so that test can use only memory based
 
436
    resources.
 
437
    """
372
438
 
373
439
    def setUp(self):
374
440
        super(TestCaseWithMemoryTransportAndScript, self).setUp()
375
 
        self.script_runner = ScriptRunner(self)
376
 
        # Break the circular dependency
377
 
        def break_dependency():
378
 
            self.script_runner = None
379
 
        self.addCleanup(break_dependency)
380
 
 
381
 
    def get_jail_root(self):
382
 
        raise NotImplementedError(self.get_jail_root)
 
441
        self.script_runner = ScriptRunner()
383
442
 
384
443
    def run_script(self, script):
385
 
        return self.script_runner.run_script(script)
 
444
        return self.script_runner.run_script(self, script)
386
445
 
387
446
    def run_command(self, cmd, input, output, error):
388
 
        return self.script_runner.run_command(cmd, input, output, error)
 
447
        return self.script_runner.run_command(self, cmd, input, output, error)
389
448
 
390
449
 
391
450
class TestCaseWithTransportAndScript(tests.TestCaseWithTransport):
 
451
    """Helper class to quickly define shell-like tests.
 
452
 
 
453
    Can be used as:
 
454
 
 
455
    from bzrlib.tests import script
 
456
 
 
457
 
 
458
    class TestBug(script.TestCaseWithTransportAndScript):
 
459
 
 
460
        def test_bug_nnnnn(self):
 
461
            self.run_script('''
 
462
            $ bzr init
 
463
            $ bzr do-this
 
464
            # Boom, error
 
465
            ''')
 
466
    """
392
467
 
393
468
    def setUp(self):
394
469
        super(TestCaseWithTransportAndScript, self).setUp()
395
 
        self.script_runner = ScriptRunner(self)
396
 
        # Break the circular dependency
397
 
        def break_dependency():
398
 
            self.script_runner = None
399
 
        self.addCleanup(break_dependency)
400
 
 
401
 
    def get_jail_root(self):
402
 
        return self.test_dir
 
470
        self.script_runner = ScriptRunner()
403
471
 
404
472
    def run_script(self, script):
405
 
        return self.script_runner.run_script(script)
 
473
        return self.script_runner.run_script(self, script)
406
474
 
407
475
    def run_command(self, cmd, input, output, error):
408
 
        return self.script_runner.run_command(cmd, input, output, error)
 
476
        return self.script_runner.run_command(self, cmd, input, output, error)
 
477