Rev 4610: merge shell-like tests improvements in file:///home/vila/src/bzr/experimental/conflict-manager/

Vincent Ladeuil v.ladeuil+lp at free.fr
Thu Sep 17 20:26:13 BST 2009


At file:///home/vila/src/bzr/experimental/conflict-manager/

------------------------------------------------------------
revno: 4610
revision-id: v.ladeuil+lp at free.fr-20090917192613-tx4zk542hbm59ykb
parent: v.ladeuil+lp at free.fr-20090917192306-wuyks6kne7l51z3h
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: shell-like-tests
timestamp: Thu 2009-09-17 21:26:13 +0200
message:
  merge shell-like tests improvements
-------------- 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