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
1=== modified file 'bzrlib/tests/__init__.py'
2--- bzrlib/tests/__init__.py 2009-08-28 21:05:31 +0000
3+++ bzrlib/tests/__init__.py 2009-09-01 16:35:10 +0000
4@@ -3618,6 +3618,7 @@
5 'bzrlib.tests.test_rio',
6 'bzrlib.tests.test_rules',
7 'bzrlib.tests.test_sampler',
8+ 'bzrlib.tests.test_script',
9 'bzrlib.tests.test_selftest',
10 'bzrlib.tests.test_serializer',
11 'bzrlib.tests.test_setup',
12
13=== added file 'bzrlib/tests/script.py'
14--- bzrlib/tests/script.py 1970-01-01 00:00:00 +0000
15+++ bzrlib/tests/script.py 2009-09-01 16:35:10 +0000
16@@ -0,0 +1,256 @@
17+# Copyright (C) 2009 Canonical Ltd
18+#
19+# This program is free software; you can redistribute it and/or modify
20+# it under the terms of the GNU General Public License as published by
21+# the Free Software Foundation; either version 2 of the License, or
22+# (at your option) any later version.
23+#
24+# This program is distributed in the hope that it will be useful,
25+# but WITHOUT ANY WARRANTY; without even the implied warranty of
26+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+# GNU General Public License for more details.
28+#
29+# You should have received a copy of the GNU General Public License
30+# along with this program; if not, write to the Free Software
31+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32+
33+from cStringIO import StringIO
34+import os
35+import shlex
36+
37+from bzrlib import (
38+ osutils,
39+ tests,
40+ )
41+
42+
43+def split(s):
44+ """Split a command line respecting quotes."""
45+ scanner = shlex.shlex(s)
46+ scanner.quotes = '\'"`'
47+ scanner.whitespace_split = True
48+ for t in list(scanner):
49+ # Strip the simple and double quotes since we don't care about them.
50+ # We leave the backquotes in place though since they have a different
51+ # semantic.
52+ if t[0] in ('"', "'") and t[0] == t[-1]:
53+ yield t[1:-1]
54+ else:
55+ yield t
56+
57+
58+def _script_to_commands(text, file_name=None):
59+ """Turn a script into a list of commands with their associated IOs.
60+
61+ Each command appears on a line by itself. It can be associated with an
62+ input that will feed it and an expected output.
63+ Comments starts with '#' until the end of line.
64+ Empty lines are ignored.
65+ Input and output are full lines terminated by a '\n'.
66+ Input lines start with '<'.
67+ Output lines start with '>'.
68+ Error lines start with '2>'.
69+ """
70+
71+ commands = []
72+
73+ def add_command(cmd, input, output, error):
74+ if cmd is not None:
75+ if input is not None:
76+ input = ''.join(input)
77+ if output is not None:
78+ output = ''.join(output)
79+ if error is not None:
80+ error = ''.join(error)
81+ commands.append((cmd, input, output, error))
82+
83+ cmd_cur = None
84+ cmd_line = 1
85+ lineno = 0
86+ input, output, error = None, None, None
87+ for line in text.split('\n'):
88+ lineno += 1
89+ # Keep a copy for error reporting
90+ orig = line
91+ comment = line.find('#')
92+ if comment >= 0:
93+ # Delete comments
94+ line = line[0:comment]
95+ line = line.rstrip()
96+ if line == '':
97+ # Ignore empty lines
98+ continue
99+ if line.startswith('<'):
100+ if input is None:
101+ if cmd_cur is None:
102+ raise SyntaxError('No command for that input',
103+ (file_name, lineno, 1, orig))
104+ input = []
105+ input.append(line[1:] + '\n')
106+ continue
107+ elif line.startswith('>'):
108+ if output is None:
109+ if cmd_cur is None:
110+ raise SyntaxError('No command for that output',
111+ (file_name, lineno, 1, orig))
112+ output = []
113+ output.append(line[1:] + '\n')
114+ continue
115+ elif line.startswith('2>'):
116+ if error is None:
117+ if cmd_cur is None:
118+ raise SyntaxError('No command for that error',
119+ (file_name, lineno, 1, orig))
120+ error = []
121+ error.append(line[2:] + '\n')
122+ continue
123+ else:
124+ # Time to output the current command
125+ add_command(cmd_cur, input, output, error)
126+ # And start a new one
127+ cmd_cur = list(split(line))
128+ cmd_line = lineno
129+ input, output, error = None, None, None
130+ # Add the last seen command
131+ add_command(cmd_cur, input, output, error)
132+ return commands
133+
134+
135+def _scan_redirection_options(args):
136+ """Recognize and process input and output redirections.
137+
138+ :param args: The command line arguments
139+
140+ :return: A tuple containing:
141+ - The file name redirected from or None
142+ - The file name redirected to or None
143+ - The mode to open the output file or None
144+ - The reamining arguments
145+ """
146+ remaining = []
147+ in_name = None
148+ out_name, out_mode = None, None
149+ for arg in args:
150+ if arg.startswith('<'):
151+ in_name = arg[1:]
152+ elif arg.startswith('>>'):
153+ out_name = arg[2:]
154+ out_mode = 'ab+'
155+ elif arg.startswith('>'):
156+ out_name = arg[1:]
157+ out_mode = 'wb+'
158+ else:
159+ remaining.append(arg)
160+ return in_name, out_name, out_mode, remaining
161+
162+
163+class TestCaseWithScript(tests.TestCaseWithTransport):
164+
165+ def setUp(self):
166+ super(TestCaseWithScript, self).setUp()
167+ self._vars = {}
168+
169+ def run_script(self, text):
170+ for cmd, input, output, error in _script_to_commands(text):
171+ self.run_command(cmd, input, output, error)
172+
173+ def _check_output(self, expected, actual):
174+ if expected is None:
175+ # Specifying None means: any output is accepted
176+ return
177+ self.assertEquals(expected, actual)
178+
179+ def run_command(self, cmd, input, output, error):
180+ mname = 'do_' + cmd[0]
181+ method = getattr(self, mname, None)
182+ if method is None:
183+ raise SyntaxError('Command not found "%s"' % (cmd[0],),
184+ None, 1, ' '.join(cmd))
185+ if input is None:
186+ str_input = ''
187+ else:
188+ str_input = ''.join(input)
189+ actual_output, actual_error = method(str_input, cmd[1:])
190+
191+ self._check_output(output, actual_output)
192+ self._check_output(error, actual_error)
193+ return actual_output, actual_error
194+
195+ def _read_input(self, input, in_name):
196+ if in_name is not None:
197+ infile = open(in_name, 'rb')
198+ try:
199+ # Command redirection takes precedence over provided input
200+ input = infile.read()
201+ finally:
202+ infile.close()
203+ return input
204+
205+ def _write_output(self, output, out_name, out_mode):
206+ if out_name is not None:
207+ outfile = open(out_name, out_mode)
208+ try:
209+ outfile.write(output)
210+ finally:
211+ outfile.close()
212+ output = None
213+ return output
214+
215+ def do_bzr(self, input, args):
216+ out, err = self._run_bzr_core(args, retcode=None, encoding=None,
217+ stdin=input, working_dir=None)
218+ return out, err
219+
220+ def do_cat(self, input, args):
221+ (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
222+ if len(args) > 1:
223+ raise SyntaxError('Usage: cat [file1]')
224+ if args:
225+ if in_name is not None:
226+ raise SyntaxError('Specify a file OR use redirection')
227+ in_name = args[0]
228+ input = self._read_input(input, in_name)
229+ # Basically cat copy input to output
230+ output = input
231+ # Handle output redirections
232+ output = self._write_output(output, out_name, out_mode)
233+ return output, None
234+
235+ def do_echo(self, input, args):
236+ (in_name, out_name, out_mode, args) = _scan_redirection_options(args)
237+ if input and args:
238+ raise SyntaxError('Specify parameters OR use redirection')
239+ if args:
240+ input = ''.join(args)
241+ input = self._read_input(input, in_name)
242+ # Always append a \n'
243+ input += '\n'
244+ # Process output
245+ output = input
246+ # Handle output redirections
247+ output = self._write_output(output, out_name, out_mode)
248+ return output, None
249+
250+ def _ensure_in_jail(self, path):
251+ if not osutils.is_inside(self.test_dir, osutils.normalizepath(path)):
252+ raise ValueError('%s is not inside %s' % (path, self.test_dir))
253+
254+ def do_cd(self, input, args):
255+ if len(args) > 1:
256+ raise SyntaxError('Usage: cd [dir]')
257+ if len(args) == 1:
258+ d = args[0]
259+ self._ensure_in_jail(d)
260+ else:
261+ d = self.test_dir
262+ os.chdir(d)
263+ return None, None
264+
265+ def do_mkdir(self, input, args):
266+ if not args or len(args) != 1:
267+ raise SyntaxError('Usage: mkdir dir')
268+ d = args[0]
269+ self._ensure_in_jail(d)
270+ os.mkdir(d)
271+ return None, None
272+
273
274=== added file 'bzrlib/tests/test_script.py'
275--- bzrlib/tests/test_script.py 1970-01-01 00:00:00 +0000
276+++ bzrlib/tests/test_script.py 2009-09-01 16:35:10 +0000
277@@ -0,0 +1,218 @@
278+# Copyright (C) 2009 Canonical Ltd
279+#
280+# This program is free software; you can redistribute it and/or modify
281+# it under the terms of the GNU General Public License as published by
282+# the Free Software Foundation; either version 2 of the License, or
283+# (at your option) any later version.
284+#
285+# This program is distributed in the hope that it will be useful,
286+# but WITHOUT ANY WARRANTY; without even the implied warranty of
287+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
288+# GNU General Public License for more details.
289+#
290+# You should have received a copy of the GNU General Public License
291+# along with this program; if not, write to the Free Software
292+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
293+
294+
295+from bzrlib import (
296+ osutils,
297+ tests,
298+ )
299+from bzrlib.tests import script
300+
301+
302+class TestScriptSyntax(tests.TestCase):
303+
304+ def test_comment_is_ignored(self):
305+ self.assertEquals([], script._script_to_commands('#comment\n'))
306+
307+ def test_empty_line_is_ignored(self):
308+ self.assertEquals([], script._script_to_commands('\n'))
309+
310+ def test_simple_command(self):
311+ self.assertEquals([(['cd', 'trunk'], None, None, None)],
312+ script._script_to_commands('cd trunk'))
313+
314+ def test_command_with_single_quoted_param(self):
315+ story = """bzr commit -m 'two words'"""
316+ self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
317+ None, None, None)],
318+ script._script_to_commands(story))
319+
320+ def test_command_with_double_quoted_param(self):
321+ story = """bzr commit -m "two words" """
322+ self.assertEquals([(['bzr', 'commit', '-m', 'two words'],
323+ None, None, None)],
324+ script._script_to_commands(story))
325+
326+ def test_command_with_input(self):
327+ self.assertEquals([(['cat', '>file'], 'content\n', None, None)],
328+ script._script_to_commands('cat >file\n<content\n'))
329+
330+ def test_command_with_output(self):
331+ story = """
332+bzr add
333+>adding file
334+>adding file2
335+"""
336+ self.assertEquals([(['bzr', 'add'], None,
337+ 'adding file\nadding file2\n', None)],
338+ script._script_to_commands(story))
339+
340+ def test_command_with_error(self):
341+ story = """
342+bzr branch foo
343+2>bzr: ERROR: Not a branch: "foo"
344+"""
345+ self.assertEquals([(['bzr', 'branch', 'foo'],
346+ None, None, 'bzr: ERROR: Not a branch: "foo"\n')],
347+ script._script_to_commands(story))
348+ def test_input_without_command(self):
349+ self.assertRaises(SyntaxError, script._script_to_commands, '<input')
350+
351+ def test_output_without_command(self):
352+ self.assertRaises(SyntaxError, script._script_to_commands, '>input')
353+
354+ def test_command_with_backquotes(self):
355+ story = """
356+foo = `bzr file-id toto`
357+"""
358+ self.assertEquals([(['foo', '=', '`bzr file-id toto`'],
359+ None, None, None)],
360+ script._script_to_commands(story))
361+
362+
363+class TestScriptExecution(script.TestCaseWithScript):
364+
365+ def test_unknown_command(self):
366+ self.assertRaises(SyntaxError, self.run_script, 'foo')
367+
368+ def test_unexpected_output(self):
369+ story = """
370+mkdir dir
371+cd dir
372+>Hello, I have just cd into dir !
373+"""
374+ self.assertRaises(AssertionError, self.run_script, story)
375+
376+
377+class TestCat(script.TestCaseWithScript):
378+
379+ def test_cat_usage(self):
380+ self.assertRaises(SyntaxError, self.run_script, 'cat foo bar baz')
381+ self.assertRaises(SyntaxError, self.run_script, 'cat foo <bar')
382+
383+ def test_cat_input_to_output(self):
384+ out, err = self.run_command(['cat'], 'content\n', 'content\n', None)
385+ self.assertEquals('content\n', out)
386+ self.assertEquals(None, err)
387+
388+ def test_cat_file_to_output(self):
389+ self.build_tree_contents([('file', 'content\n')])
390+ out, err = self.run_command(['cat', 'file'], None, 'content\n', None)
391+ self.assertEquals('content\n', out)
392+ self.assertEquals(None, err)
393+
394+ def test_cat_input_to_file(self):
395+ out, err = self.run_command(['cat', '>file'], 'content\n', None, None)
396+ self.assertFileEqual('content\n', 'file')
397+ self.assertEquals(None, out)
398+ self.assertEquals(None, err)
399+ out, err = self.run_command(['cat', '>>file'], 'more\n', None, None)
400+ self.assertFileEqual('content\nmore\n', 'file')
401+ self.assertEquals(None, out)
402+ self.assertEquals(None, err)
403+
404+ def test_cat_file_to_file(self):
405+ self.build_tree_contents([('file', 'content\n')])
406+ out, err = self.run_command(['cat', 'file', '>file2'], None, None, None)
407+ self.assertFileEqual('content\n', 'file2')
408+
409+
410+class TestMkdir(script.TestCaseWithScript):
411+
412+ def test_mkdir_usage(self):
413+ self.assertRaises(SyntaxError, self.run_script, 'mkdir')
414+ self.assertRaises(SyntaxError, self.run_script, 'mkdir foo bar')
415+
416+ def test_mkdir_jailed(self):
417+ self.assertRaises(ValueError, self.run_script, 'mkdir /out-of-jail')
418+ self.assertRaises(ValueError, self.run_script, 'mkdir ../out-of-jail')
419+
420+ def test_mkdir_in_jail(self):
421+ self.run_script("""
422+mkdir dir
423+cd dir
424+mkdir ../dir2
425+cd ..
426+""")
427+ self.failUnlessExists('dir')
428+ self.failUnlessExists('dir2')
429+
430+
431+class TestCd(script.TestCaseWithScript):
432+
433+ def test_cd_usage(self):
434+ self.assertRaises(SyntaxError, self.run_script, 'cd foo bar')
435+
436+ def test_cd_out_of_jail(self):
437+ self.assertRaises(ValueError, self.run_script, 'cd /out-of-jail')
438+ self.assertRaises(ValueError, self.run_script, 'cd ..')
439+
440+ def test_cd_dir_and_back_home(self):
441+ self.assertEquals(self.test_dir, osutils.getcwd())
442+ self.run_script("""
443+mkdir dir
444+cd dir
445+""")
446+ self.assertEquals(osutils.pathjoin(self.test_dir, 'dir'),
447+ osutils.getcwd())
448+
449+ self.run_script('cd')
450+ self.assertEquals(self.test_dir, osutils.getcwd())
451+
452+
453+class TestBzr(script.TestCaseWithScript):
454+
455+ def test_bzr_smoke(self):
456+ self.run_script('bzr init branch')
457+ self.failUnlessExists('branch')
458+
459+
460+class TestEcho(script.TestCaseWithScript):
461+
462+ def test_echo_usage(self):
463+ story = """
464+echo foo
465+<bar
466+"""
467+ self.assertRaises(SyntaxError, self.run_script, story)
468+
469+ def test_echo_to_output(self):
470+ out, err = self.run_command(['echo'], None, '\n', None)
471+ self.assertEquals('\n', out)
472+ self.assertEquals(None, err)
473+
474+ def test_echo_some_to_output(self):
475+ out, err = self.run_command(['echo', 'hello'], None, 'hello\n', None)
476+ self.assertEquals('hello\n', out)
477+ self.assertEquals(None, err)
478+
479+ def test_echo_more_output(self):
480+ out, err = self.run_command(['echo', 'hello', 'happy', 'world'],
481+ None, 'hellohappyworld\n', None)
482+ self.assertEquals('hellohappyworld\n', out)
483+ self.assertEquals(None, err)
484+
485+ def test_echo_appended(self):
486+ out, err = self.run_command(['echo', 'hello', '>file'],
487+ None, None, None)
488+ self.assertEquals(None, out)
489+ self.assertEquals(None, err)
490+ self.assertFileEqual('hello\n', 'file')
491+ out, err = self.run_command(['echo', 'happy', '>>file'],
492+ None, None, None)
493+ self.assertEquals(None, out)
494+ self.assertEquals(None, err)
495+ self.assertFileEqual('hello\nhappy\n', 'file')