##// END OF EJS Templates
Split blockbreaker tests into a separate file and clean up api....
Fernando Perez -
Show More
@@ -0,0 +1,173 b''
1 """Tests for the blockbreaker module.
2 """
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13 # stdlib
14 import unittest
15
16 # Third party
17 import nose.tools as nt
18
19 # Our own
20 from IPython.core import blockbreaker as BB
21
22 #-----------------------------------------------------------------------------
23 # Tests
24 #-----------------------------------------------------------------------------
25 def test_spaces():
26 tests = [('', 0),
27 (' ', 1),
28 ('\n', 0),
29 (' \n', 1),
30 ('x', 0),
31 (' x', 1),
32 (' x',2),
33 (' x',4),
34 # Note: tabs are counted as a single whitespace!
35 ('\tx', 1),
36 ('\t x', 2),
37 ]
38
39 for s, nsp in tests:
40 nt.assert_equal(BB.num_ini_spaces(s), nsp)
41
42
43 def test_remove_comments():
44 tests = [('text', 'text'),
45 ('text # comment', 'text '),
46 ('text # comment\n', 'text \n'),
47 ('text # comment \n', 'text \n'),
48 ('line # c \nline\n','line \nline\n'),
49 ('line # c \nline#c2 \nline\nline #c\n\n',
50 'line \nline\nline\nline \n\n'),
51 ]
52
53 for inp, out in tests:
54 nt.assert_equal(BB.remove_comments(inp), out)
55
56
57 def test_get_input_encoding():
58 encoding = BB.get_input_encoding()
59 nt.assert_true(isinstance(encoding, basestring))
60 # simple-minded check that at least encoding a simple string works with the
61 # encoding we got.
62 nt.assert_equal('test'.encode(encoding), 'test')
63
64
65 class BlockBreakerTestCase(unittest.TestCase):
66 def setUp(self):
67 self.bb = BB.BlockBreaker()
68
69 def test_reset(self):
70 bb = self.bb
71 bb.push('x=1')
72 bb.reset()
73 self.assertEqual(bb._buffer, [])
74 self.assertEqual(bb.indent_spaces, 0)
75 self.assertEqual(bb.get_source(), '')
76 self.assertEqual(bb.code, None)
77
78 def test_source(self):
79 self.bb._store('1')
80 self.bb._store('2')
81 out = self.bb.get_source()
82 self.assertEqual(out, '1\n2\n')
83 out = self.bb.get_source(reset=True)
84 self.assertEqual(out, '1\n2\n')
85 self.assertEqual(self.bb._buffer, [])
86 out = self.bb.get_source()
87 self.assertEqual(out, '')
88
89 def test_indent(self):
90 bb = self.bb # shorthand
91 bb.push('x=1')
92 self.assertEqual(bb.indent_spaces, 0)
93 bb.push('if 1:\n x=1')
94 self.assertEqual(bb.indent_spaces, 4)
95 bb.push('y=2\n')
96 self.assertEqual(bb.indent_spaces, 0)
97 bb.push('if 1:')
98 self.assertEqual(bb.indent_spaces, 4)
99 bb.push(' x=1')
100 self.assertEqual(bb.indent_spaces, 4)
101 # Blank lines shouldn't change the indent level
102 bb.push(' '*2)
103 self.assertEqual(bb.indent_spaces, 4)
104
105 def test_indent2(self):
106 bb = self.bb
107 # When a multiline statement contains parens or multiline strings, we
108 # shouldn't get confused.
109 bb.push("if 1:")
110 bb.push(" x = (1+\n 2)")
111 self.assertEqual(bb.indent_spaces, 4)
112
113 def test_dedent(self):
114 bb = self.bb # shorthand
115 bb.push('if 1:')
116 self.assertEqual(bb.indent_spaces, 4)
117 bb.push(' pass')
118 self.assertEqual(bb.indent_spaces, 0)
119
120 def test_push(self):
121 bb = self.bb
122 bb.push('x=1')
123 self.assertTrue(bb.is_complete)
124
125 def test_push2(self):
126 bb = self.bb
127 bb.push('if 1:')
128 self.assertFalse(bb.is_complete)
129 for line in [' x=1', '# a comment', ' y=2']:
130 bb.push(line)
131 self.assertTrue(bb.is_complete)
132
133 def test_push3(self):
134 """Test input with leading whitespace"""
135 bb = self.bb
136 bb.push(' x=1')
137 bb.push(' y=2')
138 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
139
140 def test_interactive_block_ready(self):
141 bb = self.bb
142 bb.push('x=1')
143 self.assertTrue(bb.interactive_block_ready())
144
145 def test_interactive_block_ready2(self):
146 bb = self.bb
147 bb.push('if 1:\n x=1')
148 self.assertFalse(bb.interactive_block_ready())
149 bb.push('')
150 self.assertTrue(bb.interactive_block_ready())
151
152 def test_interactive_block_ready3(self):
153 bb = self.bb
154 bb.push("x = (2+\n3)")
155 self.assertTrue(bb.interactive_block_ready())
156
157 def test_interactive_block_ready4(self):
158 bb = self.bb
159 # When a multiline statement contains parens or multiline strings, we
160 # shouldn't get confused.
161 # FIXME: we should be able to better handle de-dents in statements like
162 # multiline strings and multiline expressions (continued with \ or
163 # parens). Right now we aren't handling the indentation tracking quite
164 # correctly with this, though in practice it may not be too much of a
165 # problem. We'll need to see.
166 bb.push("if 1:")
167 bb.push(" x = (2+")
168 bb.push(" 3)")
169 self.assertFalse(bb.interactive_block_ready())
170 bb.push(" y = 3")
171 self.assertFalse(bb.interactive_block_ready())
172 bb.push('')
173 self.assertTrue(bb.interactive_block_ready())
@@ -27,6 +27,8 b' import sys'
27 27 # Utilities
28 28 #-----------------------------------------------------------------------------
29 29
30 # FIXME: move these utilities to the general ward...
31
30 32 # compiled regexps for autoindent management
31 33 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
32 34 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
@@ -76,10 +78,7 b' def get_input_encoding():'
76 78 # Classes and functions
77 79 #-----------------------------------------------------------------------------
78 80
79
80 81 class BlockBreaker(object):
81 # List
82 buffer = None
83 82 # Command compiler
84 83 compile = None
85 84 # Number of spaces of indentation
@@ -93,16 +92,22 b' class BlockBreaker(object):'
93 92 # Boolean indicating whether the current block is complete
94 93 is_complete = None
95 94
95 # Private attributes
96
97 # List
98 _buffer = None
99
96 100 def __init__(self):
97 self.buffer = []
101 self._buffer = []
98 102 self.compile = codeop.CommandCompiler()
99 103 self.encoding = get_input_encoding()
100 104
101 105 def reset(self):
102 106 """Reset the input buffer and associated state."""
103 107 self.indent_spaces = 0
104 self.buffer[:] = []
108 self._buffer[:] = []
105 109 self.source = ''
110 self.code = None
106 111
107 112 def get_source(self, reset=False):
108 113 """Return the input source.
@@ -117,34 +122,6 b' class BlockBreaker(object):'
117 122 self.reset()
118 123 return out
119 124
120 def update_indent(self, lines):
121 """Keep track of the indent level."""
122
123 for line in remove_comments(lines).splitlines():
124
125 if line and not line.isspace():
126 if self.code is not None:
127 inisp = num_ini_spaces(line)
128 if inisp < self.indent_spaces:
129 self.indent_spaces = inisp
130
131 if line[-1] == ':':
132 self.indent_spaces += 4
133 elif dedent_re.match(line):
134 self.indent_spaces -= 4
135
136 def store(self, lines):
137 """Store one or more lines of input.
138
139 If input lines are not newline-terminated, a newline is automatically
140 appended."""
141
142 if lines.endswith('\n'):
143 self.buffer.append(lines)
144 else:
145 self.buffer.append(lines+'\n')
146 self.source = ''.join(self.buffer).encode(self.encoding)
147
148 125 def push(self, lines):
149 126 """Push one ore more lines of input.
150 127
@@ -170,10 +147,10 b' class BlockBreaker(object):'
170 147 # this allows execution of indented pasted code. It is tempting
171 148 # to add '\n' at the end of source to run commands like ' a=1'
172 149 # directly, but this fails for more complicated scenarios
173 if not self.buffer and lines[:1] in [' ', '\t']:
150 if not self._buffer and lines[:1] in [' ', '\t']:
174 151 lines = 'if 1:\n%s' % lines
175 152
176 self.store(lines)
153 self._store(lines)
177 154 source = self.source
178 155
179 156 # Before calling compile(), reset the code object to None so that if an
@@ -187,7 +164,7 b' class BlockBreaker(object):'
187 164 self.is_complete = False
188 165 else:
189 166 self.is_complete = True
190 self.update_indent(lines)
167 self._update_indent(lines)
191 168 return self.is_complete
192 169
193 170 def interactive_block_ready(self):
@@ -223,163 +200,38 b' class BlockBreaker(object):'
223 200 else:
224 201 return False
225 202
226
227 203 def split_blocks(self, lines):
228 204 """Split a multiline string into multiple input blocks"""
205 raise NotImplementedError
229 206
230 #-----------------------------------------------------------------------------
231 # Tests
232 #-----------------------------------------------------------------------------
207 #------------------------------------------------------------------------
208 # Private interface
209 #------------------------------------------------------------------------
210
211 def _update_indent(self, lines):
212 """Keep track of the indent level."""
233 213
234 import unittest
235
236 import nose.tools as nt
237
238
239 def test_spaces():
240 tests = [('', 0),
241 (' ', 1),
242 ('\n', 0),
243 (' \n', 1),
244 ('x', 0),
245 (' x', 1),
246 (' x',2),
247 (' x',4),
248 # Note: tabs are counted as a single whitespace!
249 ('\tx', 1),
250 ('\t x', 2),
251 ]
252
253 for s, nsp in tests:
254 nt.assert_equal(num_ini_spaces(s), nsp)
255
256
257 def test_remove_comments():
258 tests = [('text', 'text'),
259 ('text # comment', 'text '),
260 ('text # comment\n', 'text \n'),
261 ('text # comment \n', 'text \n'),
262 ('line # c \nline\n','line \nline\n'),
263 ('line # c \nline#c2 \nline\nline #c\n\n',
264 'line \nline\nline\nline \n\n'),
265 ]
266
267 for inp, out in tests:
268 nt.assert_equal(remove_comments(inp), out)
269
270
271 def test_get_input_encoding():
272 encoding = get_input_encoding()
273 nt.assert_true(isinstance(encoding, basestring))
274 # simple-minded check that at least encoding a simple string works with the
275 # encoding we got.
276 nt.assert_equal('test'.encode(encoding), 'test')
277
278
279 class BlockBreakerTestCase(unittest.TestCase):
280 def setUp(self):
281 self.bb = BlockBreaker()
282
283 def test_reset(self):
284 self.bb.store('hello')
285 self.bb.reset()
286 self.assertEqual(self.bb.buffer, [])
287 self.assertEqual(self.bb.indent_spaces, 0)
288 self.assertEqual(self.bb.get_source(), '')
289
290 def test_source(self):
291 self.bb.store('1')
292 self.bb.store('2')
293 out = self.bb.get_source()
294 self.assertEqual(out, '1\n2\n')
295 out = self.bb.get_source(reset=True)
296 self.assertEqual(out, '1\n2\n')
297 self.assertEqual(self.bb.buffer, [])
298 out = self.bb.get_source()
299 self.assertEqual(out, '')
300
301 def test_indent(self):
302 bb = self.bb # shorthand
303 bb.push('x=1')
304 self.assertEqual(bb.indent_spaces, 0)
305 bb.push('if 1:\n x=1')
306 self.assertEqual(bb.indent_spaces, 4)
307 bb.push('y=2\n')
308 self.assertEqual(bb.indent_spaces, 0)
309 bb.push('if 1:')
310 self.assertEqual(bb.indent_spaces, 4)
311 bb.push(' x=1')
312 self.assertEqual(bb.indent_spaces, 4)
313 # Blank lines shouldn't change the indent level
314 bb.push(' '*2)
315 self.assertEqual(bb.indent_spaces, 4)
316
317 def test_indent2(self):
318 bb = self.bb
319 # When a multiline statement contains parens or multiline strings, we
320 # shouldn't get confused.
321 bb.push("if 1:")
322 bb.push(" x = (1+\n 2)")
323 self.assertEqual(bb.indent_spaces, 4)
324
325 def test_dedent(self):
326 bb = self.bb # shorthand
327 bb.push('if 1:')
328 self.assertEqual(bb.indent_spaces, 4)
329 bb.push(' pass')
330 self.assertEqual(bb.indent_spaces, 0)
331
332 def test_push(self):
333 bb = self.bb
334 bb.push('x=1')
335 self.assertTrue(bb.is_complete)
336
337 def test_push2(self):
338 bb = self.bb
339 bb.push('if 1:')
340 self.assertFalse(bb.is_complete)
341 for line in [' x=1', '# a comment', ' y=2']:
342 bb.push(line)
343 self.assertTrue(bb.is_complete)
344
345 def test_push3(self):
346 """Test input with leading whitespace"""
347 bb = self.bb
348 bb.push(' x=1')
349 bb.push(' y=2')
350 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
351
352 def test_interactive_block_ready(self):
353 bb = self.bb
354 bb.push('x=1')
355 self.assertTrue(bb.interactive_block_ready())
356
357 def test_interactive_block_ready2(self):
358 bb = self.bb
359 bb.push('if 1:\n x=1')
360 self.assertFalse(bb.interactive_block_ready())
361 bb.push('')
362 self.assertTrue(bb.interactive_block_ready())
363
364 def test_interactive_block_ready3(self):
365 bb = self.bb
366 bb.push("x = (2+\n3)")
367 self.assertTrue(bb.interactive_block_ready())
368
369 def test_interactive_block_ready4(self):
370 bb = self.bb
371 # When a multiline statement contains parens or multiline strings, we
372 # shouldn't get confused.
373 # FIXME: we should be able to better handle de-dents in statements like
374 # multiline strings and multiline expressions (continued with \ or
375 # parens). Right now we aren't handling the indentation tracking quite
376 # correctly with this, though in practice it may not be too much of a
377 # problem. We'll need to see.
378 bb.push("if 1:")
379 bb.push(" x = (2+")
380 bb.push(" 3)")
381 self.assertFalse(bb.interactive_block_ready())
382 bb.push(" y = 3")
383 self.assertFalse(bb.interactive_block_ready())
384 bb.push('')
385 self.assertTrue(bb.interactive_block_ready())
214 for line in remove_comments(lines).splitlines():
215
216 if line and not line.isspace():
217 if self.code is not None:
218 inisp = num_ini_spaces(line)
219 if inisp < self.indent_spaces:
220 self.indent_spaces = inisp
221
222 if line[-1] == ':':
223 self.indent_spaces += 4
224 elif dedent_re.match(line):
225 self.indent_spaces -= 4
226
227 def _store(self, lines):
228 """Store one or more lines of input.
229
230 If input lines are not newline-terminated, a newline is automatically
231 appended."""
232
233 if lines.endswith('\n'):
234 self._buffer.append(lines)
235 else:
236 self._buffer.append(lines+'\n')
237 self.source = ''.join(self._buffer).encode(self.encoding)
General Comments 0
You need to be logged in to leave comments. Login now