Rev 4612: Merge shell-like-tests into description in file:///home/vila/src/bzr/experimental/conflict-manager/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Thu Sep 17 20:26:19 BST 2009
At file:///home/vila/src/bzr/experimental/conflict-manager/
------------------------------------------------------------
revno: 4612 [merge]
revision-id: v.ladeuil+lp at free.fr-20090917192619-4ep6hpj6wus7kp02
parent: v.ladeuil+lp at free.fr-20090917192311-y0chbtasqty04mhe
parent: v.ladeuil+lp at free.fr-20090917192613-tx4zk542hbm59ykb
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: description
timestamp: Thu 2009-09-17 21:26:19 +0200
message:
Merge shell-like-tests into description
modified:
bzrlib/tests/script.py script.py-20090901081155-yk3tiy1nunxg16ne-1
bzrlib/tests/test_script.py test_script.py-20090901081156-y90z4w2t62fv7e7b-1
doc/developers/testing.txt testing.txt-20080812140359-i70zzh6v2z7grqex-1
-------------- next part --------------
=== modified file 'bzrlib/tests/script.py'
--- a/bzrlib/tests/script.py 2009-09-10 10:19:46 +0000
+++ b/bzrlib/tests/script.py 2009-09-17 19:26:13 +0000
@@ -13,90 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
"""Shell-like test scripts.
-This allows users to write tests in a syntax very close to a shell session,
-using a restricted and limited set of commands that should be enough to mimic
-most of the behaviours.
-
-A script is a set of commands, each command is composed of:
-- one mandatory command line,
-- one optional set of input lines to feed the command,
-- one optional set of output expected lines,
-- one optional set of error expected lines.
-
-The optional lines starts with a special string (mnemonic: shell redirection):
-- '<' for input,
-- '>' for output,
-- '2>' for errors,
-
-The execution stops as soon as an expected output or an expected error is not
-matched.
-
-When no output is specified, any ouput from the command is accepted
-and let the execution continue.
-
-If an error occurs and no expected error is specified, the execution stops.
-
-An error is defined by a returned status different from zero, not by the
-presence of text on the error stream.
-
-The matching is done on a full string comparison basis unless '...' is used, in
-which case expected output/errors can be lees precise.
-
-Examples:
-
-The following will succeeds only if 'bzr add' outputs 'adding file'.
-
- bzr add file
- >adding file
-
-If you want the command to succeed for any output, just use:
-
- bzr add file
-
-The following will stop with an error:
-
- bzr not-a-command
-
-If you want it to succeed, use:
-
- bzr not-a-command
- 2> bzr: ERROR: unknown command "not-a-command"
-
-You can use ellipsis (...) to replace any piece of text you don't want to be
-matched exactly:
-
- bzr branch not-a-branch
- 2>bzr: ERROR: Not a branch...not-a-branch/".
-
-
-This can be used to ignore entire lines too:
-
-cat
-<first line
-<second line
-<third line
-<fourth line
-<last line
->first line
->...
->last line
-
-You can check the content of a file with cat:
-
- cat <file
- >expected content
-
-You can also check the existence of a file with cat, the following will fail if
-the file doesn't exist:
-
- cat file
-
+See developpers/testing.html for more explanations.
"""
import doctest
import errno
+import glob
import os
import shlex
from cStringIO import StringIO
@@ -113,13 +38,7 @@
scanner.quotes = '\'"`'
scanner.whitespace_split = True
for t in list(scanner):
- # Strip the simple and double quotes since we don't care about them.
- # We leave the backquotes in place though since they have a different
- # semantic.
- if t[0] in ('"', "'") and t[0] == t[-1]:
- yield t[1:-1]
- else:
- yield t
+ yield t
def _script_to_commands(text, file_name=None):
@@ -163,22 +82,20 @@
if line == '':
# Ignore empty lines
continue
- if line.startswith('<'):
+ if line.startswith('$'):
+ # Time to output the current command
+ add_command(cmd_cur, input, output, error)
+ # And start a new one
+ cmd_cur = list(split(line[1:]))
+ cmd_line = lineno
+ input, output, error = None, None, None
+ elif line.startswith('<'):
if input is None:
if cmd_cur is None:
raise SyntaxError('No command for that input',
(file_name, lineno, 1, orig))
input = []
input.append(line[1:] + '\n')
- continue
- elif line.startswith('>'):
- if output is None:
- if cmd_cur is None:
- raise SyntaxError('No command for that output',
- (file_name, lineno, 1, orig))
- output = []
- output.append(line[1:] + '\n')
- continue
elif line.startswith('2>'):
if error is None:
if cmd_cur is None:
@@ -186,14 +103,13 @@
(file_name, lineno, 1, orig))
error = []
error.append(line[2:] + '\n')
- continue
else:
- # Time to output the current command
- add_command(cmd_cur, input, output, error)
- # And start a new one
- cmd_cur = list(split(line))
- cmd_line = lineno
- input, output, error = None, None, None
+ if output is None:
+ if cmd_cur is None:
+ raise SyntaxError('No command for that output',
+ (file_name, lineno, 1, orig))
+ output = []
+ output.append(line + '\n')
# Add the last seen command
add_command(cmd_cur, input, output, error)
return commands
@@ -210,17 +126,28 @@
- The mode to open the output file or None
- The reamining arguments
"""
+ def redirected_file_name(direction, name, args):
+ if name == '':
+ try:
+ name = args.pop(0)
+ except IndexError:
+ # We leave the error handling to higher levels, an empty name
+ # can't be legal.
+ name = ''
+ return name
+
remaining = []
in_name = None
out_name, out_mode = None, None
- for arg in args:
+ while args:
+ arg = args.pop(0)
if arg.startswith('<'):
- in_name = arg[1:]
+ in_name = redirected_file_name('<', arg[1:], args)
elif arg.startswith('>>'):
- out_name = arg[2:]
+ out_name = redirected_file_name('>>', arg[2:], args)
out_mode = 'ab+'
- elif arg.startswith('>'):
- out_name = arg[1:]
+ elif arg.startswith('>',):
+ out_name = redirected_file_name('>', arg[1:], args)
out_mode = 'wb+'
else:
remaining.append(arg)
@@ -255,6 +182,26 @@
# output should be decently readable.
self.test_case.assertEqualDiff(expected, actual)
+ def _pre_process_args(self, args):
+ new_args = []
+ for arg in args:
+ # Strip the simple and double quotes since we don't care about
+ # them. We leave the backquotes in place though since they have a
+ # different semantic.
+ if arg[0] in ('"', "'") and arg[0] == arg[-1]:
+ yield arg[1:-1]
+ else:
+ if glob.has_magic(arg):
+ matches = glob.glob(arg)
+ if matches:
+ # We care more about order stability than performance
+ # here
+ matches.sort()
+ for m in matches:
+ yield m
+ else:
+ yield arg
+
def run_command(self, cmd, input, output, error):
mname = 'do_' + cmd[0]
method = getattr(self, mname, None)
@@ -265,12 +212,14 @@
str_input = ''
else:
str_input = ''.join(input)
- retcode, actual_output, actual_error = method(str_input, cmd[1:])
+ args = list(self._pre_process_args(cmd[1:]))
+ retcode, actual_output, actual_error = method(str_input, args)
self._check_output(output, actual_output)
self._check_output(error, actual_error)
if retcode and not error and actual_error:
- self.test_case.fail('Unexpected error: %s' % actual_error)
+ self.test_case.fail('In \n\t%s\nUnexpected error: %s'
+ % (' '.join(cmd), actual_error))
return retcode, actual_output, actual_error
def _read_input(self, input, in_name):
@@ -300,17 +249,30 @@
def do_cat(self, input, args):
(in_name, out_name, out_mode, args) = _scan_redirection_options(args)
- if len(args) > 1:
- raise SyntaxError('Usage: cat [file1]')
- if args:
- if in_name is not None:
- raise SyntaxError('Specify a file OR use redirection')
- in_name = args[0]
- input = self._read_input(input, in_name)
+ if args and in_name is not None:
+ raise SyntaxError('Specify a file OR use redirection')
+
+ inputs = []
+ if input:
+ inputs.append(input)
+ input_names = args
+ if in_name:
+ args.append(in_name)
+ for in_name in input_names:
+ try:
+ inputs.append(self._read_input(None, in_name))
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return (1, None,
+ '%s: No such file or directory\n' % (in_name,))
# Basically cat copy input to output
- output = input
+ output = ''.join(inputs)
# Handle output redirections
- output = self._write_output(output, out_name, out_mode)
+ try:
+ output = self._write_output(output, out_name, out_mode)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return 1, None, '%s: No such file or directory\n' % (out_name,)
return 0, output, None
def do_echo(self, input, args):
@@ -318,14 +280,22 @@
if input and args:
raise SyntaxError('Specify parameters OR use redirection')
if args:
- input = ''.join(args)
- input = self._read_input(input, in_name)
+ input = ' '.join(args)
+ try:
+ input = self._read_input(input, in_name)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return 1, None, '%s: No such file or directory\n' % (in_name,)
# Always append a \n'
input += '\n'
# Process output
output = input
# Handle output redirections
- output = self._write_output(output, out_name, out_mode)
+ try:
+ output = self._write_output(output, out_name, out_mode)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return 1, None, '%s: No such file or directory\n' % (out_name,)
return 0, output, None
def _ensure_in_jail(self, path):
=== modified file 'bzrlib/tests/test_script.py'
--- a/bzrlib/tests/test_script.py 2009-09-10 10:19:46 +0000
+++ b/bzrlib/tests/test_script.py 2009-09-17 19:26:13 +0000
@@ -22,7 +22,7 @@
from bzrlib.tests import script
-class TestScriptSyntax(tests.TestCase):
+class TestSyntax(tests.TestCase):
def test_comment_is_ignored(self):
self.assertEquals([], script._script_to_commands('#comment\n'))
@@ -32,29 +32,30 @@
def test_simple_command(self):
self.assertEquals([(['cd', 'trunk'], None, None, None)],
- script._script_to_commands('cd trunk'))
+ script._script_to_commands('$ cd trunk'))
def test_command_with_single_quoted_param(self):
- story = """bzr commit -m 'two words'"""
- self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
+ story = """$ bzr commit -m 'two words'"""
+ self.assertEquals([(['bzr', 'commit', '-m', "'two words'"],
None, None, None)],
script._script_to_commands(story))
def test_command_with_double_quoted_param(self):
- story = """bzr commit -m "two words" """
- self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
+ story = """$ bzr commit -m "two words" """
+ self.assertEquals([(['bzr', 'commit', '-m', '"two words"'],
None, None, None)],
script._script_to_commands(story))
def test_command_with_input(self):
- self.assertEquals([(['cat', '>file'], 'content\n', None, None)],
- script._script_to_commands('cat >file\n<content\n'))
+ self.assertEquals(
+ [(['cat', '>file'], 'content\n', None, None)],
+ script._script_to_commands('$ cat >file\n<content\n'))
def test_command_with_output(self):
story = """
-bzr add
->adding file
->adding file2
+$ bzr add
+adding file
+adding file2
"""
self.assertEquals([(['bzr', 'add'], None,
'adding file\nadding file2\n', None)],
@@ -62,7 +63,7 @@
def test_command_with_error(self):
story = """
-bzr branch foo
+$ bzr branch foo
2>bzr: ERROR: Not a branch: "foo"
"""
self.assertEquals([(['bzr', 'branch', 'foo'],
@@ -77,38 +78,71 @@
def test_command_with_backquotes(self):
story = """
-foo = `bzr file-id toto`
+$ foo = `bzr file-id toto`
"""
self.assertEquals([(['foo', '=', '`bzr file-id toto`'],
None, None, None)],
script._script_to_commands(story))
-class TestScriptExecution(script.TestCaseWithTransportAndScript):
+class TestRedirections(tests.TestCase):
+
+ def _check(self, in_name, out_name, out_mode, remaining, args):
+ self.assertEqual(script._scan_redirection_options(args),
+ (in_name, out_name, out_mode, remaining))
+
+ def test_no_redirection(self):
+ self._check(None, None, None, [], [])
+ self._check(None, None, None, ['foo', 'bar'], ['foo', 'bar'])
+
+ def test_input_redirection(self):
+ self._check('foo', None, None, [], ['<foo'])
+ self._check('foo', None, None, ['bar'], ['bar', '<foo'])
+ self._check('foo', None, None, ['bar'], ['bar', '<', 'foo'])
+ self._check('foo', None, None, ['bar'], ['<foo', 'bar'])
+ self._check('foo', None, None, ['bar', 'baz'], ['bar', '<foo', 'baz'])
+
+ def test_output_redirection(self):
+ self._check(None, 'foo', 'wb+', [], ['>foo'])
+ self._check(None, 'foo', 'wb+', ['bar'], ['bar', '>foo'])
+ self._check(None, 'foo', 'wb+', ['bar'], ['bar', '>', 'foo'])
+ self._check(None, 'foo', 'ab+', [], ['>>foo'])
+ self._check(None, 'foo', 'ab+', ['bar'], ['bar', '>>foo'])
+ self._check(None, 'foo', 'ab+', ['bar'], ['bar', '>>', 'foo'])
+
+ def test_redirection_syntax_errors(self):
+ self._check('', None, None, [], ['<'])
+ self._check(None, '', 'wb+', [], ['>'])
+ self._check(None, '', 'ab+', [], ['>>'])
+ self._check('>', '', 'ab+', [], ['<', '>', '>>'])
+
+
+
+class TestExecution(script.TestCaseWithTransportAndScript):
def test_unknown_command(self):
self.assertRaises(SyntaxError, self.run_script, 'foo')
def test_stops_on_unexpected_output(self):
story = """
-mkdir dir
-cd dir
->The cd command ouputs nothing
+$ mkdir dir
+$ cd dir
+The cd command ouputs nothing
"""
self.assertRaises(AssertionError, self.run_script, story)
def test_stops_on_unexpected_error(self):
story = """
-cat
+$ cat
<Hello
-bzr not-a-command
+$ bzr not-a-command
"""
self.assertRaises(AssertionError, self.run_script, story)
def test_continue_on_expected_error(self):
story = """
-bzr not-a-command
+$ bzr not-a-command
2>..."not-a-command"
"""
self.run_script(story)
@@ -116,42 +150,66 @@
def test_continue_on_error_output(self):
# The status matters, not the output
story = """
-bzr init
-cat >file
+$ bzr init
+$ cat >file
<Hello
-bzr add file
-bzr commit -m 'adding file'
+$ bzr add file
+$ bzr commit -m 'adding file'
"""
self.run_script(story)
def test_ellipsis_output(self):
story = """
-cat
+$ cat
<first line
<second line
<last line
->first line
->...
->last line
+first line
+...
+last line
"""
self.run_script(story)
story = """
-bzr not-a-command
+$ bzr not-a-command
2>..."not-a-command"
"""
self.run_script(story)
story = """
-bzr branch not-a-branch
+$ bzr branch not-a-branch
2>bzr: ERROR: Not a branch...not-a-branch/".
"""
self.run_script(story)
+class TestArgumentProcessing(script.TestCaseWithTransportAndScript):
+
+ def test_globing(self):
+ self.run_script("""
+$ echo cat >cat
+$ echo dog >dog
+$ cat *
+cat
+dog
+""")
+
+ def test_quoted_globbing(self):
+ self.run_script("""
+$ echo cat >cat
+$ cat '*'
+2>*: No such file or directory
+""")
+
+ def test_quotes_removal(self):
+ self.run_script("""
+$ echo 'cat' "dog" '"chicken"' "'dragon'"
+cat dog "chicken" 'dragon'
+""")
+
+
class TestCat(script.TestCaseWithTransportAndScript):
def test_cat_usage(self):
- self.assertRaises(SyntaxError, self.run_script, 'cat foo bar baz')
self.assertRaises(SyntaxError, self.run_script, 'cat foo <bar')
def test_cat_input_to_output(self):
@@ -185,23 +243,58 @@
None, None, None)
self.assertFileEqual('content\n', 'file2')
+ def test_cat_files_to_file(self):
+ self.build_tree_contents([('cat', 'cat\n')])
+ self.build_tree_contents([('dog', 'dog\n')])
+ retcode, out, err = self.run_command(['cat', 'cat', 'dog', '>file'],
+ None, None, None)
+ self.assertFileEqual('cat\ndog\n', 'file')
+
+ def test_cat_bogus_input_file(self):
+ self.run_script("""
+$ cat <file
+2>file: No such file or directory
+""")
+
+ def test_cat_bogus_output_file(self):
+ self.run_script("""
+$ cat >
+2>: No such file or directory
+""")
+
+ def test_echo_bogus_input_file(self):
+ # We need a backing file sysytem for that test so it can't be in
+ # TestEcho
+ self.run_script("""
+$ echo <file
+2>file: No such file or directory
+""")
+
+ def test_echo_bogus_output_file(self):
+ # We need a backing file sysytem for that test so it can't be in
+ # TestEcho
+ self.run_script("""
+$ echo >
+2>: No such file or directory
+""")
+
class TestMkdir(script.TestCaseWithTransportAndScript):
def test_mkdir_usage(self):
- self.assertRaises(SyntaxError, self.run_script, 'mkdir')
- self.assertRaises(SyntaxError, self.run_script, 'mkdir foo bar')
+ self.assertRaises(SyntaxError, self.run_script, '$ mkdir')
+ self.assertRaises(SyntaxError, self.run_script, '$ mkdir foo bar')
def test_mkdir_jailed(self):
- self.assertRaises(ValueError, self.run_script, 'mkdir /out-of-jail')
- self.assertRaises(ValueError, self.run_script, 'mkdir ../out-of-jail')
+ self.assertRaises(ValueError, self.run_script, '$ mkdir /out-of-jail')
+ self.assertRaises(ValueError, self.run_script, '$ mkdir ../out-of-jail')
def test_mkdir_in_jail(self):
self.run_script("""
-mkdir dir
-cd dir
-mkdir ../dir2
-cd ..
+$ mkdir dir
+$ cd dir
+$ mkdir ../dir2
+$ cd ..
""")
self.failUnlessExists('dir')
self.failUnlessExists('dir2')
@@ -210,29 +303,29 @@
class TestCd(script.TestCaseWithTransportAndScript):
def test_cd_usage(self):
- self.assertRaises(SyntaxError, self.run_script, 'cd foo bar')
+ self.assertRaises(SyntaxError, self.run_script, '$ cd foo bar')
def test_cd_out_of_jail(self):
- self.assertRaises(ValueError, self.run_script, 'cd /out-of-jail')
- self.assertRaises(ValueError, self.run_script, 'cd ..')
+ self.assertRaises(ValueError, self.run_script, '$ cd /out-of-jail')
+ self.assertRaises(ValueError, self.run_script, '$ cd ..')
def test_cd_dir_and_back_home(self):
self.assertEquals(self.test_dir, osutils.getcwd())
self.run_script("""
-mkdir dir
-cd dir
+$ mkdir dir
+$ cd dir
""")
self.assertEquals(osutils.pathjoin(self.test_dir, 'dir'),
osutils.getcwd())
- self.run_script('cd')
+ self.run_script('$ cd')
self.assertEquals(self.test_dir, osutils.getcwd())
class TestBzr(script.TestCaseWithTransportAndScript):
def test_bzr_smoke(self):
- self.run_script('bzr init branch')
+ self.run_script('$ bzr init branch')
self.failUnlessExists('branch')
@@ -240,7 +333,7 @@
def test_echo_usage(self):
story = """
-echo foo
+$ echo foo
<bar
"""
self.assertRaises(SyntaxError, self.run_script, story)
@@ -259,8 +352,8 @@
def test_echo_more_output(self):
retcode, out, err = self.run_command(
['echo', 'hello', 'happy', 'world'],
- None, 'hellohappyworld\n', None)
- self.assertEquals('hellohappyworld\n', out)
+ None, 'hello happy world\n', None)
+ self.assertEquals('hello happy world\n', out)
self.assertEquals(None, err)
def test_echo_appended(self):
@@ -279,41 +372,41 @@
class TestRm(script.TestCaseWithTransportAndScript):
def test_rm_usage(self):
- self.assertRaises(SyntaxError, self.run_script, 'rm')
- self.assertRaises(SyntaxError, self.run_script, 'rm -ff foo')
+ self.assertRaises(SyntaxError, self.run_script, '$ rm')
+ self.assertRaises(SyntaxError, self.run_script, '$ rm -ff foo')
def test_rm_file(self):
- self.run_script('echo content >file')
+ self.run_script('$ echo content >file')
self.failUnlessExists('file')
- self.run_script('rm file')
+ self.run_script('$ rm file')
self.failIfExists('file')
def test_rm_file_force(self):
self.failIfExists('file')
- self.run_script('rm -f file')
+ self.run_script('$ rm -f file')
self.failIfExists('file')
def test_rm_files(self):
self.run_script("""
-echo content >file
-echo content >file2
+$ echo content >file
+$ echo content >file2
""")
self.failUnlessExists('file2')
- self.run_script('rm file file2')
+ self.run_script('$ rm file file2')
self.failIfExists('file2')
def test_rm_dir(self):
- self.run_script('mkdir dir')
+ self.run_script('$ mkdir dir')
self.failUnlessExists('dir')
self.run_script("""
-rm dir
+$ rm dir
2>rm: cannot remove 'dir': Is a directory
""")
self.failUnlessExists('dir')
def test_rm_dir_recursive(self):
self.run_script("""
-mkdir dir
-rm -r dir
+$ mkdir dir
+$ rm -r dir
""")
self.failIfExists('dir')
=== modified file 'doc/developers/testing.txt'
--- a/doc/developers/testing.txt 2009-09-11 07:55:48 +0000
+++ b/doc/developers/testing.txt 2009-09-17 19:26:13 +0000
@@ -197,6 +197,98 @@
__ http://docs.python.org/lib/module-doctest.html
+Shell-like tests
+~~~~~~~~~~~~~~~~
+
+``bzrlib/tests/script.py`` allows users to write tests in a syntax very close to a shell session,
+using a restricted and limited set of commands that should be enough to mimic
+most of the behaviours.
+
+A script is a set of commands, each command is composed of:
+
+ * one mandatory command line,
+ * one optional set of input lines to feed the command,
+ * one optional set of output expected lines,
+ * one optional set of error expected lines.
+
+Input, output and error lines can be specified in any order.
+
+Except for the expected output, all lines start with a special
+string (based on their origin when used under a Unix shell):
+
+ * '$ ' for the command,
+ * '<' for input,
+ * nothing for output,
+ * '2>' for errors,
+
+Comments can be added anywhere, they start with '#' and end with
+the line.
+
+The execution stops as soon as an expected output or an expected error is not
+matched.
+
+When no output is specified, any ouput from the command is accepted
+and execution continue.
+
+If an error occurs and no expected error is specified, the execution stops.
+
+An error is defined by a returned status different from zero, not by the
+presence of text on the error stream.
+
+The matching is done on a full string comparison basis unless '...' is used, in
+which case expected output/errors can be less precise.
+
+Examples:
+
+The following will succeeds only if 'bzr add' outputs 'adding file'::
+
+ $ bzr add file
+ >adding file
+
+If you want the command to succeed for any output, just use::
+
+ $ bzr add file
+
+The following will stop with an error::
+
+ $ bzr not-a-command
+
+If you want it to succeed, use::
+
+ $ bzr not-a-command
+ 2> bzr: ERROR: unknown command "not-a-command"
+
+You can use ellipsis (...) to replace any piece of text you don't want to be
+matched exactly::
+
+ $ bzr branch not-a-branch
+ 2>bzr: ERROR: Not a branch...not-a-branch/".
+
+This can be used to ignore entire lines too::
+
+ $ cat
+ <first line
+ <second line
+ <third line
+ # And here we explain that surprising fourth line
+ <fourth line
+ <last line
+ >first line
+ >...
+ >last line
+
+You can check the content of a file with cat::
+
+ $ cat <file
+ >expected content
+
+You can also check the existence of a file with cat, the following will fail if
+the file doesn't exist::
+
+ $ cat file
+
+
+
.. Effort tests
.. ~~~~~~~~~~~~
More information about the bazaar-commits
mailing list