Merge lp:~vila/bzr/shell-like-tests-borked into lp:~bzr/bzr/trunk-old
- shell-like-tests-borked
- Merge into trunk-old
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Needs Information | ||
Review via email: mp+10983@code.launchpad.net |
Commit message
Description of the change
Vincent Ladeuil (vila) wrote : | # |
John A Meinel (jameinel) wrote : | # |
-----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(
>
> def test_content_
> 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_
> wt, relpath = workingtree.
> self.assertEqual(1, len(wt.
>
> 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 TestCaseWithScr
...
+ 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(
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 TestCaseWithScr
_script_class = ScriptClass
def run_script(self, actions):
script = self._script_
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...
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
<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 TestCaseWithScr
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.
Martin Pool (mbp) wrote : | # |
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
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...
Updating diff...
An updated diff will be available in a few minutes. Reload to see the changes.
Preview Diff
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') |
This implement a new TestCase that provides the ability to run shell-like scenarios like:
class TestConflict( script. TestCaseWithScr ipt):
def test_content_ conflict( self):
self.run_ script( story) WorkingTree. open_containing ('branch' )
self.assertEqu al(1, len(wt. conflicts( )))
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
"""
wt, relpath = workingtree.
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.