blob: 6554b0fa5c6d0e597906479644521023a696208c [file] [log] [blame]
Benjamin Peterson9a80fa82012-01-13 14:39:38 -05001# -*- coding: utf-8 -*-
2
3"""
4Test suite for PEP 380 implementation
5
6adapted from original tests written by Greg Ewing
7see <http://www.cosc.canterbury.ac.nz/greg.ewing/python/yield-from/YieldFrom-Python3.1.2-rev5.zip>
8"""
9
10import unittest
11import io
12import sys
13import traceback
14import parser
15
16from test.support import captured_stderr
17
18class TestPEP380Operation(unittest.TestCase):
19 """
20 Test semantics.
21 """
22
23 def test_delegation_of_initial_next_to_subgenerator(self):
24 """
25 Test delegation of initial next() call to subgenerator
26 """
27 trace = []
28 def g1():
29 trace.append("Starting g1")
30 yield from g2()
31 trace.append("Finishing g1")
32 def g2():
33 trace.append("Starting g2")
34 yield 42
35 trace.append("Finishing g2")
36 for x in g1():
37 trace.append("Yielded %s" % (x,))
38 self.assertEqual(trace,[
39 "Starting g1",
40 "Starting g2",
41 "Yielded 42",
42 "Finishing g2",
43 "Finishing g1",
44 ])
45
46 def test_raising_exception_in_initial_next_call(self):
47 """
48 Test raising exception in initial next() call
49 """
50 trace = []
51 def g1():
52 try:
53 trace.append("Starting g1")
54 yield from g2()
55 finally:
56 trace.append("Finishing g1")
57 def g2():
58 try:
59 trace.append("Starting g2")
60 raise ValueError("spanish inquisition occurred")
61 finally:
62 trace.append("Finishing g2")
63 try:
64 for x in g1():
65 trace.append("Yielded %s" % (x,))
66 except ValueError as e:
67 self.assertEqual(e.args[0], "spanish inquisition occurred")
68 else:
69 self.fail("subgenerator failed to raise ValueError")
70 self.assertEqual(trace,[
71 "Starting g1",
72 "Starting g2",
73 "Finishing g2",
74 "Finishing g1",
75 ])
76
77 def test_delegation_of_next_call_to_subgenerator(self):
78 """
79 Test delegation of next() call to subgenerator
80 """
81 trace = []
82 def g1():
83 trace.append("Starting g1")
84 yield "g1 ham"
85 yield from g2()
86 yield "g1 eggs"
87 trace.append("Finishing g1")
88 def g2():
89 trace.append("Starting g2")
90 yield "g2 spam"
91 yield "g2 more spam"
92 trace.append("Finishing g2")
93 for x in g1():
94 trace.append("Yielded %s" % (x,))
95 self.assertEqual(trace,[
96 "Starting g1",
97 "Yielded g1 ham",
98 "Starting g2",
99 "Yielded g2 spam",
100 "Yielded g2 more spam",
101 "Finishing g2",
102 "Yielded g1 eggs",
103 "Finishing g1",
104 ])
105
106 def test_raising_exception_in_delegated_next_call(self):
107 """
108 Test raising exception in delegated next() call
109 """
110 trace = []
111 def g1():
112 try:
113 trace.append("Starting g1")
114 yield "g1 ham"
115 yield from g2()
116 yield "g1 eggs"
117 finally:
118 trace.append("Finishing g1")
119 def g2():
120 try:
121 trace.append("Starting g2")
122 yield "g2 spam"
123 raise ValueError("hovercraft is full of eels")
124 yield "g2 more spam"
125 finally:
126 trace.append("Finishing g2")
127 try:
128 for x in g1():
129 trace.append("Yielded %s" % (x,))
130 except ValueError as e:
131 self.assertEqual(e.args[0], "hovercraft is full of eels")
132 else:
133 self.fail("subgenerator failed to raise ValueError")
134 self.assertEqual(trace,[
135 "Starting g1",
136 "Yielded g1 ham",
137 "Starting g2",
138 "Yielded g2 spam",
139 "Finishing g2",
140 "Finishing g1",
141 ])
142
143 def test_delegation_of_send(self):
144 """
145 Test delegation of send()
146 """
147 trace = []
148 def g1():
149 trace.append("Starting g1")
150 x = yield "g1 ham"
151 trace.append("g1 received %s" % (x,))
152 yield from g2()
153 x = yield "g1 eggs"
154 trace.append("g1 received %s" % (x,))
155 trace.append("Finishing g1")
156 def g2():
157 trace.append("Starting g2")
158 x = yield "g2 spam"
159 trace.append("g2 received %s" % (x,))
160 x = yield "g2 more spam"
161 trace.append("g2 received %s" % (x,))
162 trace.append("Finishing g2")
163 g = g1()
164 y = next(g)
165 x = 1
166 try:
167 while 1:
168 y = g.send(x)
169 trace.append("Yielded %s" % (y,))
170 x += 1
171 except StopIteration:
172 pass
173 self.assertEqual(trace,[
174 "Starting g1",
175 "g1 received 1",
176 "Starting g2",
177 "Yielded g2 spam",
178 "g2 received 2",
179 "Yielded g2 more spam",
180 "g2 received 3",
181 "Finishing g2",
182 "Yielded g1 eggs",
183 "g1 received 4",
184 "Finishing g1",
185 ])
186
187 def test_handling_exception_while_delegating_send(self):
188 """
189 Test handling exception while delegating 'send'
190 """
191 trace = []
192 def g1():
193 trace.append("Starting g1")
194 x = yield "g1 ham"
195 trace.append("g1 received %s" % (x,))
196 yield from g2()
197 x = yield "g1 eggs"
198 trace.append("g1 received %s" % (x,))
199 trace.append("Finishing g1")
200 def g2():
201 trace.append("Starting g2")
202 x = yield "g2 spam"
203 trace.append("g2 received %s" % (x,))
204 raise ValueError("hovercraft is full of eels")
205 x = yield "g2 more spam"
206 trace.append("g2 received %s" % (x,))
207 trace.append("Finishing g2")
208 def run():
209 g = g1()
210 y = next(g)
211 x = 1
212 try:
213 while 1:
214 y = g.send(x)
215 trace.append("Yielded %s" % (y,))
216 x += 1
217 except StopIteration:
218 trace.append("StopIteration")
219 self.assertRaises(ValueError,run)
220 self.assertEqual(trace,[
221 "Starting g1",
222 "g1 received 1",
223 "Starting g2",
224 "Yielded g2 spam",
225 "g2 received 2",
226 ])
227
228 def test_delegating_close(self):
229 """
230 Test delegating 'close'
231 """
232 trace = []
233 def g1():
234 try:
235 trace.append("Starting g1")
236 yield "g1 ham"
237 yield from g2()
238 yield "g1 eggs"
239 finally:
240 trace.append("Finishing g1")
241 def g2():
242 try:
243 trace.append("Starting g2")
244 yield "g2 spam"
245 yield "g2 more spam"
246 finally:
247 trace.append("Finishing g2")
248 g = g1()
249 for i in range(2):
250 x = next(g)
251 trace.append("Yielded %s" % (x,))
252 g.close()
253 self.assertEqual(trace,[
254 "Starting g1",
255 "Yielded g1 ham",
256 "Starting g2",
257 "Yielded g2 spam",
258 "Finishing g2",
259 "Finishing g1"
260 ])
261
262 def test_handing_exception_while_delegating_close(self):
263 """
264 Test handling exception while delegating 'close'
265 """
266 trace = []
267 def g1():
268 try:
269 trace.append("Starting g1")
270 yield "g1 ham"
271 yield from g2()
272 yield "g1 eggs"
273 finally:
274 trace.append("Finishing g1")
275 def g2():
276 try:
277 trace.append("Starting g2")
278 yield "g2 spam"
279 yield "g2 more spam"
280 finally:
281 trace.append("Finishing g2")
282 raise ValueError("nybbles have exploded with delight")
283 try:
284 g = g1()
285 for i in range(2):
286 x = next(g)
287 trace.append("Yielded %s" % (x,))
288 g.close()
289 except ValueError as e:
290 self.assertEqual(e.args[0], "nybbles have exploded with delight")
291 self.assertIsInstance(e.__context__, GeneratorExit)
292 else:
293 self.fail("subgenerator failed to raise ValueError")
294 self.assertEqual(trace,[
295 "Starting g1",
296 "Yielded g1 ham",
297 "Starting g2",
298 "Yielded g2 spam",
299 "Finishing g2",
300 "Finishing g1",
301 ])
302
303 def test_delegating_throw(self):
304 """
305 Test delegating 'throw'
306 """
307 trace = []
308 def g1():
309 try:
310 trace.append("Starting g1")
311 yield "g1 ham"
312 yield from g2()
313 yield "g1 eggs"
314 finally:
315 trace.append("Finishing g1")
316 def g2():
317 try:
318 trace.append("Starting g2")
319 yield "g2 spam"
320 yield "g2 more spam"
321 finally:
322 trace.append("Finishing g2")
323 try:
324 g = g1()
325 for i in range(2):
326 x = next(g)
327 trace.append("Yielded %s" % (x,))
328 e = ValueError("tomato ejected")
329 g.throw(e)
330 except ValueError as e:
331 self.assertEqual(e.args[0], "tomato ejected")
332 else:
333 self.fail("subgenerator failed to raise ValueError")
334 self.assertEqual(trace,[
335 "Starting g1",
336 "Yielded g1 ham",
337 "Starting g2",
338 "Yielded g2 spam",
339 "Finishing g2",
340 "Finishing g1",
341 ])
342
343 def test_value_attribute_of_StopIteration_exception(self):
344 """
345 Test 'value' attribute of StopIteration exception
346 """
347 trace = []
348 def pex(e):
349 trace.append("%s: %s" % (e.__class__.__name__, e))
350 trace.append("value = %s" % (e.value,))
351 e = StopIteration()
352 pex(e)
353 e = StopIteration("spam")
354 pex(e)
355 e.value = "eggs"
356 pex(e)
357 self.assertEqual(trace,[
358 "StopIteration: ",
359 "value = None",
360 "StopIteration: spam",
361 "value = spam",
362 "StopIteration: spam",
363 "value = eggs",
364 ])
365
366
367 def test_generator_return_value(self):
368 """
369 Test generator return value
370 """
371 trace = []
372 def g1():
373 trace.append("Starting g1")
374 yield "g1 ham"
375 ret = yield from g2()
376 trace.append("g2 returned %s" % (ret,))
377 ret = yield from g2(42)
378 trace.append("g2 returned %s" % (ret,))
379 yield "g1 eggs"
380 trace.append("Finishing g1")
381 def g2(v = None):
382 trace.append("Starting g2")
383 yield "g2 spam"
384 yield "g2 more spam"
385 trace.append("Finishing g2")
386 if v:
387 return v
388 for x in g1():
389 trace.append("Yielded %s" % (x,))
390 self.assertEqual(trace,[
391 "Starting g1",
392 "Yielded g1 ham",
393 "Starting g2",
394 "Yielded g2 spam",
395 "Yielded g2 more spam",
396 "Finishing g2",
397 "g2 returned None",
398 "Starting g2",
399 "Yielded g2 spam",
400 "Yielded g2 more spam",
401 "Finishing g2",
402 "g2 returned 42",
403 "Yielded g1 eggs",
404 "Finishing g1",
405 ])
406
407 def test_delegation_of_next_to_non_generator(self):
408 """
409 Test delegation of next() to non-generator
410 """
411 trace = []
412 def g():
413 yield from range(3)
414 for x in g():
415 trace.append("Yielded %s" % (x,))
416 self.assertEqual(trace,[
417 "Yielded 0",
418 "Yielded 1",
419 "Yielded 2",
420 ])
421
422
423 def test_conversion_of_sendNone_to_next(self):
424 """
425 Test conversion of send(None) to next()
426 """
427 trace = []
428 def g():
429 yield from range(3)
430 gi = g()
431 for x in range(3):
432 y = gi.send(None)
433 trace.append("Yielded: %s" % (y,))
434 self.assertEqual(trace,[
435 "Yielded: 0",
436 "Yielded: 1",
437 "Yielded: 2",
438 ])
439
440 def test_delegation_of_close_to_non_generator(self):
441 """
442 Test delegation of close() to non-generator
443 """
444 trace = []
445 def g():
446 try:
447 trace.append("starting g")
448 yield from range(3)
449 trace.append("g should not be here")
450 finally:
451 trace.append("finishing g")
452 gi = g()
453 next(gi)
454 with captured_stderr() as output:
455 gi.close()
456 self.assertEqual(output.getvalue(), '')
457 self.assertEqual(trace,[
458 "starting g",
459 "finishing g",
460 ])
461
462 def test_delegating_throw_to_non_generator(self):
463 """
464 Test delegating 'throw' to non-generator
465 """
466 trace = []
467 def g():
468 try:
469 trace.append("Starting g")
470 yield from range(10)
471 finally:
472 trace.append("Finishing g")
473 try:
474 gi = g()
475 for i in range(5):
476 x = next(gi)
477 trace.append("Yielded %s" % (x,))
478 e = ValueError("tomato ejected")
479 gi.throw(e)
480 except ValueError as e:
481 self.assertEqual(e.args[0],"tomato ejected")
482 else:
483 self.fail("subgenerator failed to raise ValueError")
484 self.assertEqual(trace,[
485 "Starting g",
486 "Yielded 0",
487 "Yielded 1",
488 "Yielded 2",
489 "Yielded 3",
490 "Yielded 4",
491 "Finishing g",
492 ])
493
494 def test_attempting_to_send_to_non_generator(self):
495 """
496 Test attempting to send to non-generator
497 """
498 trace = []
499 def g():
500 try:
501 trace.append("starting g")
502 yield from range(3)
503 trace.append("g should not be here")
504 finally:
505 trace.append("finishing g")
506 try:
507 gi = g()
508 next(gi)
509 for x in range(3):
510 y = gi.send(42)
511 trace.append("Should not have yielded:", y)
512 except AttributeError as e:
513 self.assertIn("send", e.args[0])
514 else:
515 self.fail("was able to send into non-generator")
516 self.assertEqual(trace,[
517 "starting g",
518 "finishing g",
519 ])
520
521 def test_broken_getattr_handling(self):
522 """
523 Test subiterator with a broken getattr implementation
524 """
525 class Broken:
526 def __iter__(self):
527 return self
528 def __next__(self):
529 return 1
530 def __getattr__(self, attr):
531 1/0
532
533 def g():
534 yield from Broken()
535
536 with self.assertRaises(ZeroDivisionError):
537 gi = g()
538 self.assertEqual(next(gi), 1)
539 gi.send(1)
540
541 with self.assertRaises(ZeroDivisionError):
542 gi = g()
543 self.assertEqual(next(gi), 1)
544 gi.throw(AttributeError)
545
546 with captured_stderr() as output:
547 gi = g()
548 self.assertEqual(next(gi), 1)
549 gi.close()
550 self.assertIn('ZeroDivisionError', output.getvalue())
551
552 def test_exception_in_initial_next_call(self):
553 """
554 Test exception in initial next() call
555 """
556 trace = []
557 def g1():
558 trace.append("g1 about to yield from g2")
559 yield from g2()
560 trace.append("g1 should not be here")
561 def g2():
562 yield 1/0
563 def run():
564 gi = g1()
565 next(gi)
566 self.assertRaises(ZeroDivisionError,run)
567 self.assertEqual(trace,[
568 "g1 about to yield from g2"
569 ])
570
571 def test_attempted_yield_from_loop(self):
572 """
573 Test attempted yield-from loop
574 """
575 trace = []
576 def g1():
577 trace.append("g1: starting")
578 yield "y1"
579 trace.append("g1: about to yield from g2")
580 yield from g2()
581 trace.append("g1 should not be here")
582
583 def g2():
584 trace.append("g2: starting")
585 yield "y2"
586 trace.append("g2: about to yield from g1")
587 yield from gi
588 trace.append("g2 should not be here")
589 try:
590 gi = g1()
591 for y in gi:
592 trace.append("Yielded: %s" % (y,))
593 except ValueError as e:
594 self.assertEqual(e.args[0],"generator already executing")
595 else:
596 self.fail("subgenerator didn't raise ValueError")
597 self.assertEqual(trace,[
598 "g1: starting",
599 "Yielded: y1",
600 "g1: about to yield from g2",
601 "g2: starting",
602 "Yielded: y2",
603 "g2: about to yield from g1",
604 ])
605
606 def test_returning_value_from_delegated_throw(self):
607 """
608 Test returning value from delegated 'throw'
609 """
610 trace = []
611 def g1():
612 try:
613 trace.append("Starting g1")
614 yield "g1 ham"
615 yield from g2()
616 yield "g1 eggs"
617 finally:
618 trace.append("Finishing g1")
619 def g2():
620 try:
621 trace.append("Starting g2")
622 yield "g2 spam"
623 yield "g2 more spam"
624 except LunchError:
625 trace.append("Caught LunchError in g2")
626 yield "g2 lunch saved"
627 yield "g2 yet more spam"
628 class LunchError(Exception):
629 pass
630 g = g1()
631 for i in range(2):
632 x = next(g)
633 trace.append("Yielded %s" % (x,))
634 e = LunchError("tomato ejected")
635 g.throw(e)
636 for x in g:
637 trace.append("Yielded %s" % (x,))
638 self.assertEqual(trace,[
639 "Starting g1",
640 "Yielded g1 ham",
641 "Starting g2",
642 "Yielded g2 spam",
643 "Caught LunchError in g2",
644 "Yielded g2 yet more spam",
645 "Yielded g1 eggs",
646 "Finishing g1",
647 ])
648
649 def test_next_and_return_with_value(self):
650 """
651 Test next and return with value
652 """
653 trace = []
654 def f(r):
655 gi = g(r)
656 next(gi)
657 try:
658 trace.append("f resuming g")
659 next(gi)
660 trace.append("f SHOULD NOT BE HERE")
661 except StopIteration as e:
662 trace.append("f caught %s" % (repr(e),))
663 def g(r):
664 trace.append("g starting")
665 yield
666 trace.append("g returning %s" % (r,))
667 return r
668 f(None)
669 f(42)
670 self.assertEqual(trace,[
671 "g starting",
672 "f resuming g",
673 "g returning None",
674 "f caught StopIteration()",
675 "g starting",
676 "f resuming g",
677 "g returning 42",
678 "f caught StopIteration(42,)",
679 ])
680
681 def test_send_and_return_with_value(self):
682 """
683 Test send and return with value
684 """
685 trace = []
686 def f(r):
687 gi = g(r)
688 next(gi)
689 try:
690 trace.append("f sending spam to g")
691 gi.send("spam")
692 trace.append("f SHOULD NOT BE HERE")
693 except StopIteration as e:
694 trace.append("f caught %r" % (e,))
695 def g(r):
696 trace.append("g starting")
697 x = yield
698 trace.append("g received %s" % (x,))
699 trace.append("g returning %s" % (r,))
700 return r
701 f(None)
702 f(42)
703 self.assertEqual(trace,[
704 "g starting",
705 "f sending spam to g",
706 "g received spam",
707 "g returning None",
708 "f caught StopIteration()",
709 "g starting",
710 "f sending spam to g",
711 "g received spam",
712 "g returning 42",
713 "f caught StopIteration(42,)",
714 ])
715
716 def test_catching_exception_from_subgen_and_returning(self):
717 """
718 Test catching an exception thrown into a
719 subgenerator and returning a value
720 """
721 trace = []
722 def inner():
723 try:
724 yield 1
725 except ValueError:
726 trace.append("inner caught ValueError")
727 return 2
728
729 def outer():
730 v = yield from inner()
731 trace.append("inner returned %r to outer" % v)
732 yield v
733 g = outer()
734 trace.append(next(g))
735 trace.append(g.throw(ValueError))
736 self.assertEqual(trace,[
737 1,
738 "inner caught ValueError",
739 "inner returned 2 to outer",
740 2,
741 ])
742
743 def test_throwing_GeneratorExit_into_subgen_that_returns(self):
744 """
745 Test throwing GeneratorExit into a subgenerator that
746 catches it and returns normally.
747 """
748 trace = []
749 def f():
750 try:
751 trace.append("Enter f")
752 yield
753 trace.append("Exit f")
754 except GeneratorExit:
755 return
756 def g():
757 trace.append("Enter g")
758 yield from f()
759 trace.append("Exit g")
760 try:
761 gi = g()
762 next(gi)
763 gi.throw(GeneratorExit)
764 except GeneratorExit:
765 pass
766 else:
767 self.fail("subgenerator failed to raise GeneratorExit")
768 self.assertEqual(trace,[
769 "Enter g",
770 "Enter f",
771 ])
772
773 def test_throwing_GeneratorExit_into_subgenerator_that_yields(self):
774 """
775 Test throwing GeneratorExit into a subgenerator that
776 catches it and yields.
777 """
778 trace = []
779 def f():
780 try:
781 trace.append("Enter f")
782 yield
783 trace.append("Exit f")
784 except GeneratorExit:
785 yield
786 def g():
787 trace.append("Enter g")
788 yield from f()
789 trace.append("Exit g")
790 try:
791 gi = g()
792 next(gi)
793 gi.throw(GeneratorExit)
794 except RuntimeError as e:
795 self.assertEqual(e.args[0], "generator ignored GeneratorExit")
796 else:
797 self.fail("subgenerator failed to raise GeneratorExit")
798 self.assertEqual(trace,[
799 "Enter g",
800 "Enter f",
801 ])
802
803 def test_throwing_GeneratorExit_into_subgen_that_raises(self):
804 """
805 Test throwing GeneratorExit into a subgenerator that
806 catches it and raises a different exception.
807 """
808 trace = []
809 def f():
810 try:
811 trace.append("Enter f")
812 yield
813 trace.append("Exit f")
814 except GeneratorExit:
815 raise ValueError("Vorpal bunny encountered")
816 def g():
817 trace.append("Enter g")
818 yield from f()
819 trace.append("Exit g")
820 try:
821 gi = g()
822 next(gi)
823 gi.throw(GeneratorExit)
824 except ValueError as e:
825 self.assertEqual(e.args[0], "Vorpal bunny encountered")
826 self.assertIsInstance(e.__context__, GeneratorExit)
827 else:
828 self.fail("subgenerator failed to raise ValueError")
829 self.assertEqual(trace,[
830 "Enter g",
831 "Enter f",
832 ])
833
Benjamin Peterson0296a562012-01-13 14:54:31 -0500834 def test_yield_from_empty(self):
835 def g():
836 yield from ()
837 self.assertRaises(StopIteration, next, g())
838
Benjamin Peterson9a80fa82012-01-13 14:39:38 -0500839
840def test_main():
841 from test import support
842 test_classes = [TestPEP380Operation]
843 support.run_unittest(*test_classes)
844
845
846if __name__ == '__main__':
847 test_main()