Merge lp:~vila/bzr/shell-like-tests-borked into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Rejected
Rejected by: Vincent Ladeuil
Proposed branch: lp:~vila/bzr/shell-like-tests-borked
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 495 lines
To merge this branch: bzr merge lp:~vila/bzr/shell-like-tests-borked
Reviewer Review Type Date Requested Status
John A Meinel Needs Information
Review via email: mp+10983@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

This implement a new TestCase that provides the ability to run shell-like scenarios like:

class TestConflict(script.TestCaseWithScript):

    def test_content_conflict(self):
        story = """
bzr init trunk
cd trunk
echo "trunk content" >file
bzr add file
bzr commit -m 'Create trunk'
bzr branch . ../branch
cd ../branch
bzr rm file
bzr commit -m 'Delete file'
cd ../trunk
echo "more content" >>file
bzr commit -m 'Modify file'
cd ../branch
bzr merge ../trunk
"""
        self.run_script(story)
        wt, relpath = workingtree.WorkingTree.open_containing('branch')
        self.assertEqual(1, len(wt.conflicts()))

Commands implemented: bzr (ha ha), cat, echo, cd, mkdir.

cd and mkdir are jailed.
Redirections are handled for echo and cat (<, >, >>).

More can be done but I think that implementation is already usable and I don't want to
add too much features without getting some feedback first.

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.3 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/shell-like-tests into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This implement a new TestCase that provides the ability to run shell-like scenarios like:
>
> class TestConflict(script.TestCaseWithScript):
>
> def test_content_conflict(self):
> story = """
> bzr init trunk
> cd trunk
> echo "trunk content" >file
> bzr add file
> bzr commit -m 'Create trunk'
> bzr branch . ../branch
> cd ../branch
> bzr rm file
> bzr commit -m 'Delete file'
> cd ../trunk
> echo "more content" >>file
> bzr commit -m 'Modify file'
> cd ../branch
> bzr merge ../trunk
> """
> self.run_script(story)
> wt, relpath = workingtree.WorkingTree.open_containing('branch')
> self.assertEqual(1, len(wt.conflicts()))
>
> Commands implemented: bzr (ha ha), cat, echo, cd, mkdir.
>
> cd and mkdir are jailed.
> Redirections are handled for echo and cat (<, >, >>).
>
> More can be done but I think that implementation is already usable and I don't want to
> add too much features without getting some feedback first.

 review: needs_information

1) My first concern is that the above doesn't provide a way to do any
assertions. It just provides a way to run a bunch of commands.

If you look at how doctest does it, it does:

>>> command_to_run()
output

We could do something similar with:
$ command_to_run()
output

Though we then don't have a way to declare a different between stdout
and stderr. Nor do we have a way to say that this command should succeed
cleanly, or this command should fail in a particular way.

Because of that, it means that the shell scripting really only works as
a way to *set up* a test case. Which is arguably where we should be
spending the least amount of time, not the bulk of the time as we work
in 'command' level rather than internal object level.

Maybe the overhead isn't as big as it once was, because we don't spawn
anymore. But given Robert's recent threads about wanting to *speed up*
selftest, this doesn't seem to be going in the right direction.

2)

+class TestCaseWithScript(tests.TestCaseWithTransport):
...
+ def do_bzr(self, input, args):
...
+ def do_cat(self, input, args):
...

It feels to me like things such as "do_bzr" should be part of a helper
class, not part of the TestCase itself.

So something more like:

class ScriptClass(object):

  def __init__(self, actions_str):
    # parse and build up a list of actions

  def run(self):
    ...

  def do_bzr(...)
  def do_cat(...)
  def do_echo(...)

class TestCaseWithScript(tests.TestCaseWithTransport):

  _script_class = ScriptClass

  def run_script(self, actions):
    script = self._script_class(actions)
    script.run() # Should we pass the TestCase (self) in?

By making _script_class a member variable, test cases that want to
override behavior can do so by subclassing ScriptClass rather than
directly overriding self.do_foo()

3) I think the functionality is well tested. My biggest concerns are how
is this actually going to benefit us.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Com...

Read more...

review: Needs Information
Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> "jam" == John A Meinel writes:

<snip/>

    jam> 1) My first concern is that the above doesn't provide a
    jam> way to do any assertions. It just provides a way to run
    jam> a bunch of commands.

No. You can provide input, expected stdout and expected stderr:

bzr init
cat >file
<content
bzr add file
>adding file
bzr add foo
2>bzr: ERROR: No such file: u'foo'

Well, ok, I slightly lied for the last one because bzr output an
abspath and that's a bit hard to embed (still doable but
unpractical).

<snip/>

    jam> Though we then don't have a way to declare a different
    jam> between stdout and stderr.

Yes we have. I didn't distinguish between stdout and stderr at
first, but then, I realized that it will make things more
complicated.

So the feature here is that if you specify a content for stdout
or stderr, you want it to match exactly.

I feel that strict matching is a bit restrictive (as shown above
already), but I'm not yet sold to regexps there (and how), I'd
like more use cases before.

    jam> Nor do we have a way to say that this command should
    jam> succeed cleanly, or this command should fail in a
    jam> particular way.

The idea is that a script should "succeed" as described,
i.e. match the expected std[out|err] if specified for each
command.

This may not be tested correctly yet but see
test_unexpected_output.

<snip/>

    jam> Maybe the overhead isn't as big as it once was, because
    jam> we don't spawn anymore. But given Robert's recent
    jam> threads about wanting to *speed up* selftest, this
    jam> doesn't seem to be going in the right direction.

The intent is not to have faster tests but to allow more people
to write tests.

Once the tests are written that way, we can rewrite them in more
optimal ways, the bug or desired feature has been captured,
that's the goal.

And the original writers can even look at the rewritten form and
learn quicker since they know what the test is supposed to do in
its first form.

    jam> 2)

    jam> +class TestCaseWithScript(tests.TestCaseWithTransport):
    jam> ...
    jam> + def do_bzr(self, input, args):
    jam> ...
    jam> + def do_cat(self, input, args):
    jam> ...

    jam> It feels to me like things such as "do_bzr" should be part of a helper
    jam> class, not part of the TestCase itself.

That doesn't fly for jailed commands, so I didn't try.

<snip/>

    jam> By making _script_class a member variable, test cases
    jam> that want to override behavior can do so by subclassing
    jam> ScriptClass rather than directly overriding

Use cases for overriding ?

And don't forget that we can run several scripts in the same test
like we do with run_bzr().

<snip/>

    jam> 3) I think the functionality is well tested.
    jam> My biggest concerns are how is this actually going to
    jam> benefit us.

I hope I clarified things a bit.

Revision history for this message
Martin Pool (mbp) wrote :
Download full text (3.6 KiB)

2009/9/2 Vincent Ladeuil <email address hidden>:

Thanks for doing this, you beat me to it.

I haven't read the patch itself yet, but I am keen to see some
experiments in this direction.

> <snip/>
>
>    jam> 1) My first concern is that the above doesn't provide a
>    jam> way to do any assertions. It just provides a way to run
>    jam> a bunch of commands.
>
> No. You can provide input, expected stdout and expected stderr:
>
> bzr init
> cat >file
> <content
> bzr add file
>>adding file
> bzr add foo
> 2>bzr: ERROR: No such file: u'foo'

Maybe this is a hole in the documentation then, if jam didn't notice it?

>
> Well, ok, I slightly lied for the last one because bzr output an
> abspath and that's a bit hard to embed (still doable but
> unpractical).

(Also a bug that we have the unicode prefix.)

Perhaps it should eventually pretend that everything is being run in eg /test/

>
> <snip/>
>
>    jam> Though we then don't have a way to declare a different
>    jam> between stdout and stderr.
>
> Yes we have. I didn't distinguish between stdout and stderr at
> first, but then, I realized that it will make things more
> complicated.
>
> So the feature here is that if you specify a content for stdout
> or stderr, you want it to match exactly.
>
> I feel that strict matching is a bit restrictive (as shown above
> already), but I'm not yet sold to regexps there (and how), I'd
> like more use cases before.

Robert said that the loose matching in doctest can be separated out.

>    jam> Nor do we have a way to say that this command should
>    jam> succeed cleanly, or this command should fail in a
>    jam> particular way.
>
> The idea is that a script should "succeed" as described,
> i.e. match the expected std[out|err] if specified for each
> command.
>
> This may not be tested correctly yet but see
> test_unexpected_output.

You could make it pretend it's being run under a shell that prints a
message if it exits with a non-zero status, as bash and zsh can be
configured to do.

>    jam> Maybe the overhead isn't as big as it once was, because
>    jam> we don't spawn anymore. But given Robert's recent
>    jam> threads about wanting to *speed up* selftest, this
>    jam> doesn't seem to be going in the right direction.
>
> The intent is not to have faster tests but to allow more people
> to write tests.
>
> Once the tests are written that way, we can rewrite them in more
> optimal ways, the bug or desired feature has been captured,
> that's the goal.
>
> And the original writers can even look at the rewritten form and
> learn quicker since they know what the test is supposed to do in
> its first form.

I think that's a valid goal, and will lower a barrier to contribution,
but not ultimately where we want to end up. It could go wrong like
this: people who can only read the easy tests can't add new tests in
modules written in the old style, and eventually can't even read the
tests they previously wrote themselves.

I don't think we want a style of writing tests that the core
developers never use, because it will rot: so this needs to be
competitively fast and easy to use effectively, at least for some
category of tests.

But I think that's quite...

Read more...

Updating diff...

An updated diff will be available in a few minutes. Reload to see the changes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2009-08-28 21:05:31 +0000
+++ bzrlib/tests/__init__.py 2009-09-01 16:35:10 +0000
@@ -3618,6 +3618,7 @@
3618 'bzrlib.tests.test_rio',3618 'bzrlib.tests.test_rio',
3619 'bzrlib.tests.test_rules',3619 'bzrlib.tests.test_rules',
3620 'bzrlib.tests.test_sampler',3620 'bzrlib.tests.test_sampler',
3621 'bzrlib.tests.test_script',
3621 'bzrlib.tests.test_selftest',3622 'bzrlib.tests.test_selftest',
3622 'bzrlib.tests.test_serializer',3623 'bzrlib.tests.test_serializer',
3623 'bzrlib.tests.test_setup',3624 'bzrlib.tests.test_setup',
36243625
=== added file 'bzrlib/tests/script.py'
--- bzrlib/tests/script.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/script.py 2009-09-01 16:35:10 +0000
@@ -0,0 +1,256 @@
1# Copyright (C) 2009 Canonical Ltd
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
16
17from cStringIO import StringIO
18import os
19import shlex
20
21from bzrlib import (
22 osutils,
23 tests,
24 )
25
26
27def split(s):
28 """Split a command line respecting quotes."""
29 scanner = shlex.shlex(s)
30 scanner.quotes = '\'"`'
31 scanner.whitespace_split = True
32 for t in list(scanner):
33 # Strip the simple and double quotes since we don't care about them.
34 # We leave the backquotes in place though since they have a different
35 # semantic.
36 if t[0] in ('"', "'") and t[0] == t[-1]:
37 yield t[1:-1]
38 else:
39 yield t
40
41
42def _script_to_commands(text, file_name=None):
43 """Turn a script into a list of commands with their associated IOs.
44
45 Each command appears on a line by itself. It can be associated with an
46 input that will feed it and an expected output.
47 Comments starts with '#' until the end of line.
48 Empty lines are ignored.
49 Input and output are full lines terminated by a '\n'.
50 Input lines start with '<'.
51 Output lines start with '>'.
52 Error lines start with '2>'.
53 """
54
55 commands = []
56
57 def add_command(cmd, input, output, error):
58 if cmd is not None:
59 if input is not None:
60 input = ''.join(input)
61 if output is not None:
62 output = ''.join(output)
63 if error is not None:
64 error = ''.join(error)
65 commands.append((cmd, input, output, error))
66
67 cmd_cur = None
68 cmd_line = 1
69 lineno = 0
70 input, output, error = None, None, None
71 for line in text.split('\n'):
72 lineno += 1
73 # Keep a copy for error reporting
74 orig = line
75 comment = line.find('#')
76 if comment >= 0:
77 # Delete comments
78 line = line[0:comment]
79 line = line.rstrip()
80 if line == '':
81 # Ignore empty lines
82 continue
83 if line.startswith('<'):
84 if input is None:
85 if cmd_cur is None:
86 raise SyntaxError('No command for that input',
87 (file_name, lineno, 1, orig))
88 input = []
89 input.append(line[1:] + '\n')
90 continue
91 elif line.startswith('>'):
92 if output is None:
93 if cmd_cur is None:
94 raise SyntaxError('No command for that output',
95 (file_name, lineno, 1, orig))
96 output = []
97 output.append(line[1:] + '\n')
98 continue
99 elif line.startswith('2>'):
100 if error is None:
101 if cmd_cur is None:
102 raise SyntaxError('No command for that error',
103 (file_name, lineno, 1, orig))
104 error = []
105 error.append(line[2:] + '\n')
106 continue
107 else:
108 # Time to output the current command
109 add_command(cmd_cur, input, output, error)
110 # And start a new one
111 cmd_cur = list(split(line))
112 cmd_line = lineno
113 input, output, error = None, None, None
114 # Add the last seen command
115 add_command(cmd_cur, input, output, error)
116 return commands
117
118
119def _scan_redirection_options(args):
120 """Recognize and process input and output redirections.
121
122 :param args: The command line arguments
123
124 :return: A tuple containing:
125 - The file name redirected from or None
126 - The file name redirected to or None
127 - The mode to open the output file or None
128 - The reamining arguments
129 """
130 remaining = []
131 in_name = None
132 out_name, out_mode = None, None
133 for arg in args:
134 if arg.startswith('<'):
135 in_name = arg[1:]
136 elif arg.startswith('>>'):
137 out_name = arg[2:]
138 out_mode = 'ab+'
139 elif arg.startswith('>'):
140 out_name = arg[1:]
141 out_mode = 'wb+'
142 else:
143 remaining.append(arg)
144 return in_name, out_name, out_mode, remaining
145
146
147class TestCaseWithScript(tests.TestCaseWithTransport):
148
149 def setUp(self):
150 super(TestCaseWithScript, self).setUp()
151 self._vars = {}
152
153 def run_script(self, text):
154 for cmd, input, output, error in _script_to_commands(text):
155 self.run_command(cmd, input, output, error)
156
157 def _check_output(self, expected, actual):
158 if expected is None:
159 # Specifying None means: any output is accepted
160 return
161 self.assertEquals(expected, actual)
162
163 def run_command(self, cmd, input, output, error):
164 mname = 'do_' + cmd[0]
165 method = getattr(self, mname, None)
166 if method is None:
167 raise SyntaxError('Command not found "%s"' % (cmd[0],),
168 None, 1, ' '.join(cmd))
169 if input is None:
170 str_input = ''
171 else:
172 str_input = ''.join(input)
173 actual_output, actual_error = method(str_input, cmd[1:])
174
175 self._check_output(output, actual_output)
176 self._check_output(error, actual_error)
177 return actual_output, actual_error
178
179 def _read_input(self, input, in_name):
180 if in_name is not None:
181 infile = open(in_name, 'rb')
182 try:
183 # Command redirection takes precedence over provided input
184 input = infile.read()
185 finally:
186 infile.close()
187 return input
188
189 def _write_output(self, output, out_name, out_mode):
190 if out_name is not None:
191 outfile = open(out_name, out_mode)
192 try:
193 outfile.write(output)
194 finally:
195 outfile.close()
196 output = None
197 return output
198
199 def do_bzr(self, input, args):
200 out, err = self._run_bzr_core(args, retcode=None, encoding=None,
201 stdin=input, working_dir=None)
202 return out, err
203
204 def do_cat(self, input, args):
205 (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
206 if len(args) > 1:
207 raise SyntaxError('Usage: cat [file1]')
208 if args:
209 if in_name is not None:
210 raise SyntaxError('Specify a file OR use redirection')
211 in_name = args[0]
212 input = self._read_input(input, in_name)
213 # Basically cat copy input to output
214 output = input
215 # Handle output redirections
216 output = self._write_output(output, out_name, out_mode)
217 return output, None
218
219 def do_echo(self, input, args):
220 (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
221 if input and args:
222 raise SyntaxError('Specify parameters OR use redirection')
223 if args:
224 input = ''.join(args)
225 input = self._read_input(input, in_name)
226 # Always append a \n'
227 input += '\n'
228 # Process output
229 output = input
230 # Handle output redirections
231 output = self._write_output(output, out_name, out_mode)
232 return output, None
233
234 def _ensure_in_jail(self, path):
235 if not osutils.is_inside(self.test_dir, osutils.normalizepath(path)):
236 raise ValueError('%s is not inside %s' % (path, self.test_dir))
237
238 def do_cd(self, input, args):
239 if len(args) > 1:
240 raise SyntaxError('Usage: cd [dir]')
241 if len(args) == 1:
242 d = args[0]
243 self._ensure_in_jail(d)
244 else:
245 d = self.test_dir
246 os.chdir(d)
247 return None, None
248
249 def do_mkdir(self, input, args):
250 if not args or len(args) != 1:
251 raise SyntaxError('Usage: mkdir dir')
252 d = args[0]
253 self._ensure_in_jail(d)
254 os.mkdir(d)
255 return None, None
256
0257
=== added file 'bzrlib/tests/test_script.py'
--- bzrlib/tests/test_script.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/test_script.py 2009-09-01 16:35:10 +0000
@@ -0,0 +1,218 @@
1# Copyright (C) 2009 Canonical Ltd
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
16
17
18from bzrlib import (
19 osutils,
20 tests,
21 )
22from bzrlib.tests import script
23
24
25class TestScriptSyntax(tests.TestCase):
26
27 def test_comment_is_ignored(self):
28 self.assertEquals([], script._script_to_commands('#comment\n'))
29
30 def test_empty_line_is_ignored(self):
31 self.assertEquals([], script._script_to_commands('\n'))
32
33 def test_simple_command(self):
34 self.assertEquals([(['cd', 'trunk'], None, None, None)],
35 script._script_to_commands('cd trunk'))
36
37 def test_command_with_single_quoted_param(self):
38 story = """bzr commit -m 'two words'"""
39 self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
40 None, None, None)],
41 script._script_to_commands(story))
42
43 def test_command_with_double_quoted_param(self):
44 story = """bzr commit -m "two words" """
45 self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
46 None, None, None)],
47 script._script_to_commands(story))
48
49 def test_command_with_input(self):
50 self.assertEquals([(['cat', '>file'], 'content\n', None, None)],
51 script._script_to_commands('cat >file\n<content\n'))
52
53 def test_command_with_output(self):
54 story = """
55bzr add
56>adding file
57>adding file2
58"""
59 self.assertEquals([(['bzr', 'add'], None,
60 'adding file\nadding file2\n', None)],
61 script._script_to_commands(story))
62
63 def test_command_with_error(self):
64 story = """
65bzr branch foo
662>bzr: ERROR: Not a branch: "foo"
67"""
68 self.assertEquals([(['bzr', 'branch', 'foo'],
69 None, None, 'bzr: ERROR: Not a branch: "foo"\n')],
70 script._script_to_commands(story))
71 def test_input_without_command(self):
72 self.assertRaises(SyntaxError, script._script_to_commands, '<input')
73
74 def test_output_without_command(self):
75 self.assertRaises(SyntaxError, script._script_to_commands, '>input')
76
77 def test_command_with_backquotes(self):
78 story = """
79foo = `bzr file-id toto`
80"""
81 self.assertEquals([(['foo', '=', '`bzr file-id toto`'],
82 None, None, None)],
83 script._script_to_commands(story))
84
85
86class TestScriptExecution(script.TestCaseWithScript):
87
88 def test_unknown_command(self):
89 self.assertRaises(SyntaxError, self.run_script, 'foo')
90
91 def test_unexpected_output(self):
92 story = """
93mkdir dir
94cd dir
95>Hello, I have just cd into dir !
96"""
97 self.assertRaises(AssertionError, self.run_script, story)
98
99
100class TestCat(script.TestCaseWithScript):
101
102 def test_cat_usage(self):
103 self.assertRaises(SyntaxError, self.run_script, 'cat foo bar baz')
104 self.assertRaises(SyntaxError, self.run_script, 'cat foo <bar')
105
106 def test_cat_input_to_output(self):
107 out, err = self.run_command(['cat'], 'content\n', 'content\n', None)
108 self.assertEquals('content\n', out)
109 self.assertEquals(None, err)
110
111 def test_cat_file_to_output(self):
112 self.build_tree_contents([('file', 'content\n')])
113 out, err = self.run_command(['cat', 'file'], None, 'content\n', None)
114 self.assertEquals('content\n', out)
115 self.assertEquals(None, err)
116
117 def test_cat_input_to_file(self):
118 out, err = self.run_command(['cat', '>file'], 'content\n', None, None)
119 self.assertFileEqual('content\n', 'file')
120 self.assertEquals(None, out)
121 self.assertEquals(None, err)
122 out, err = self.run_command(['cat', '>>file'], 'more\n', None, None)
123 self.assertFileEqual('content\nmore\n', 'file')
124 self.assertEquals(None, out)
125 self.assertEquals(None, err)
126
127 def test_cat_file_to_file(self):
128 self.build_tree_contents([('file', 'content\n')])
129 out, err = self.run_command(['cat', 'file', '>file2'], None, None, None)
130 self.assertFileEqual('content\n', 'file2')
131
132
133class TestMkdir(script.TestCaseWithScript):
134
135 def test_mkdir_usage(self):
136 self.assertRaises(SyntaxError, self.run_script, 'mkdir')
137 self.assertRaises(SyntaxError, self.run_script, 'mkdir foo bar')
138
139 def test_mkdir_jailed(self):
140 self.assertRaises(ValueError, self.run_script, 'mkdir /out-of-jail')
141 self.assertRaises(ValueError, self.run_script, 'mkdir ../out-of-jail')
142
143 def test_mkdir_in_jail(self):
144 self.run_script("""
145mkdir dir
146cd dir
147mkdir ../dir2
148cd ..
149""")
150 self.failUnlessExists('dir')
151 self.failUnlessExists('dir2')
152
153
154class TestCd(script.TestCaseWithScript):
155
156 def test_cd_usage(self):
157 self.assertRaises(SyntaxError, self.run_script, 'cd foo bar')
158
159 def test_cd_out_of_jail(self):
160 self.assertRaises(ValueError, self.run_script, 'cd /out-of-jail')
161 self.assertRaises(ValueError, self.run_script, 'cd ..')
162
163 def test_cd_dir_and_back_home(self):
164 self.assertEquals(self.test_dir, osutils.getcwd())
165 self.run_script("""
166mkdir dir
167cd dir
168""")
169 self.assertEquals(osutils.pathjoin(self.test_dir, 'dir'),
170 osutils.getcwd())
171
172 self.run_script('cd')
173 self.assertEquals(self.test_dir, osutils.getcwd())
174
175
176class TestBzr(script.TestCaseWithScript):
177
178 def test_bzr_smoke(self):
179 self.run_script('bzr init branch')
180 self.failUnlessExists('branch')
181
182
183class TestEcho(script.TestCaseWithScript):
184
185 def test_echo_usage(self):
186 story = """
187echo foo
188<bar
189"""
190 self.assertRaises(SyntaxError, self.run_script, story)
191
192 def test_echo_to_output(self):
193 out, err = self.run_command(['echo'], None, '\n', None)
194 self.assertEquals('\n', out)
195 self.assertEquals(None, err)
196
197 def test_echo_some_to_output(self):
198 out, err = self.run_command(['echo', 'hello'], None, 'hello\n', None)
199 self.assertEquals('hello\n', out)
200 self.assertEquals(None, err)
201
202 def test_echo_more_output(self):
203 out, err = self.run_command(['echo', 'hello', 'happy', 'world'],
204 None, 'hellohappyworld\n', None)
205 self.assertEquals('hellohappyworld\n', out)
206 self.assertEquals(None, err)
207
208 def test_echo_appended(self):
209 out, err = self.run_command(['echo', 'hello', '>file'],
210 None, None, None)
211 self.assertEquals(None, out)
212 self.assertEquals(None, err)
213 self.assertFileEqual('hello\n', 'file')
214 out, err = self.run_command(['echo', 'happy', '>>file'],
215 None, None, None)
216 self.assertEquals(None, out)
217 self.assertEquals(None, err)
218 self.assertFileEqual('hello\nhappy\n', 'file')