Format: text/x-python
⬇ Download File (8.7 KB)
   1  """
   2  Test for async helpers.
   3  
   4  Should only trigger on python 3.5+ or will have syntax errors.
   5  """
   6  from itertools import chain, repeat
   7  from textwrap import dedent, indent
   8  from typing import TYPE_CHECKING
   9  from unittest import TestCase
  10  
  11  import pytest
  12  
  13  from IPython.core.async_helpers import _should_be_async
  14  from IPython.testing.decorators import skip_without
  15  
  16  if TYPE_CHECKING:
  17      from IPython import get_ipython
  18  
  19      ip = get_ipython()
  20  
  21  
  22  def iprc(x):
  23      return ip.run_cell(dedent(x)).raise_error()
  24  
  25  
  26  def iprc_nr(x):
  27      return ip.run_cell(dedent(x))
  28  
  29  
  30  class AsyncTest(TestCase):
  31      def test_should_be_async(self):
  32          self.assertFalse(_should_be_async("False"))
  33          self.assertTrue(_should_be_async("await bar()"))
  34          self.assertTrue(_should_be_async("x = await bar()"))
  35          self.assertFalse(
  36              _should_be_async(
  37                  dedent(
  38                      """
  39              async def awaitable():
  40                  pass
  41          """
  42                  )
  43              )
  44          )
  45  
  46      def _get_top_level_cases(self):
  47          # These are test cases that should be valid in a function
  48          # but invalid outside of a function.
  49          test_cases = []
  50          test_cases.append(('basic', "{val}"))
  51  
  52          # Note, in all conditional cases, I use True instead of
  53          # False so that the peephole optimizer won't optimize away
  54          # the return, so CPython will see this as a syntax error:
  55          #
  56          # while True:
  57          #    break
  58          #    return
  59          #
  60          # But not this:
  61          #
  62          # while False:
  63          #    return
  64          #
  65          # See https://bugs.python.org/issue1875
  66  
  67          test_cases.append(('if', dedent("""
  68          if True:
  69              {val}
  70          """)))
  71  
  72          test_cases.append(('while', dedent("""
  73          while True:
  74              {val}
  75              break
  76          """)))
  77  
  78          test_cases.append(('try', dedent("""
  79          try:
  80              {val}
  81          except:
  82              pass
  83          """)))
  84  
  85          test_cases.append(('except', dedent("""
  86          try:
  87              pass
  88          except:
  89              {val}
  90          """)))
  91  
  92          test_cases.append(('finally', dedent("""
  93          try:
  94              pass
  95          except:
  96              pass
  97          finally:
  98              {val}
  99          """)))
 100  
 101          test_cases.append(('for', dedent("""
 102          for _ in range(4):
 103              {val}
 104          """)))
 105  
 106  
 107          test_cases.append(('nested', dedent("""
 108          if True:
 109              while True:
 110                  {val}
 111                  break
 112          """)))
 113  
 114          test_cases.append(('deep-nested', dedent("""
 115          if True:
 116              while True:
 117                  break
 118                  for x in range(3):
 119                      if True:
 120                          while True:
 121                              for x in range(3):
 122                                  {val}
 123          """)))
 124  
 125          return test_cases
 126  
 127      def _get_ry_syntax_errors(self):
 128          # This is a mix of tests that should be a syntax error if
 129          # return or yield whether or not they are in a function
 130  
 131          test_cases = []
 132  
 133          test_cases.append(('class', dedent("""
 134          class V:
 135              {val}
 136          """)))
 137  
 138          test_cases.append(('nested-class', dedent("""
 139          class V:
 140              class C:
 141                  {val}
 142          """)))
 143  
 144          return test_cases
 145  
 146  
 147      def test_top_level_return_error(self):
 148          tl_err_test_cases = self._get_top_level_cases()
 149          tl_err_test_cases.extend(self._get_ry_syntax_errors())
 150  
 151          vals = ('return', 'yield', 'yield from (_ for _ in range(3))',
 152                  dedent('''
 153                      def f():
 154                          pass
 155                      return
 156                      '''),
 157                  )
 158  
 159          for test_name, test_case in tl_err_test_cases:
 160              # This example should work if 'pass' is used as the value
 161              with self.subTest((test_name, 'pass')):
 162                  iprc(test_case.format(val='pass'))
 163  
 164              # It should fail with all the values
 165              for val in vals:
 166                  with self.subTest((test_name, val)):
 167                      msg = "Syntax error not raised for %s, %s" % (test_name, val)
 168                      with self.assertRaises(SyntaxError, msg=msg):
 169                          iprc(test_case.format(val=val))
 170  
 171      def test_in_func_no_error(self):
 172          # Test that the implementation of top-level return/yield
 173          # detection isn't *too* aggressive, and works inside a function
 174          func_contexts = []
 175  
 176          func_contexts.append(('func', False, dedent("""
 177          def f():""")))
 178  
 179          func_contexts.append(('method', False, dedent("""
 180          class MyClass:
 181              def __init__(self):
 182          """)))
 183  
 184          func_contexts.append(('async-func', True,  dedent("""
 185          async def f():""")))
 186  
 187          func_contexts.append(('async-method', True,  dedent("""
 188          class MyClass:
 189              async def f(self):""")))
 190  
 191          func_contexts.append(('closure', False, dedent("""
 192          def f():
 193              def g():
 194          """)))
 195  
 196          def nest_case(context, case):
 197              # Detect indentation
 198              lines = context.strip().splitlines()
 199              prefix_len = 0
 200              for c in lines[-1]:
 201                  if c != ' ':
 202                      break
 203                  prefix_len += 1
 204  
 205              indented_case = indent(case, ' ' * (prefix_len + 4))
 206              return context + '\n' + indented_case
 207  
 208          # Gather and run the tests
 209  
 210          # yield is allowed in async functions, starting in Python 3.6,
 211          # and yield from is not allowed in any version
 212          vals = ('return', 'yield', 'yield from (_ for _ in range(3))')
 213  
 214          success_tests = zip(self._get_top_level_cases(), repeat(False))
 215          failure_tests = zip(self._get_ry_syntax_errors(), repeat(True))
 216  
 217          tests = chain(success_tests, failure_tests)
 218  
 219          for context_name, async_func, context in func_contexts:
 220              for (test_name, test_case), should_fail in tests:
 221                  nested_case = nest_case(context, test_case)
 222  
 223                  for val in vals:
 224                      test_id = (context_name, test_name, val)
 225                      cell = nested_case.format(val=val)
 226  
 227                      with self.subTest(test_id):
 228                          if should_fail:
 229                              msg = ("SyntaxError not raised for %s" %
 230                                      str(test_id))
 231                              with self.assertRaises(SyntaxError, msg=msg):
 232                                  iprc(cell)
 233  
 234                                  print(cell)
 235                          else:
 236                              iprc(cell)
 237  
 238      def test_nonlocal(self):
 239          # fails if outer scope is not a function scope or if var not defined
 240          with self.assertRaises(SyntaxError):
 241              iprc("nonlocal x")
 242              iprc("""
 243              x = 1
 244              def f():
 245                  nonlocal x
 246                  x = 10000
 247                  yield x
 248              """)
 249              iprc("""
 250              def f():
 251                  def g():
 252                      nonlocal x
 253                      x = 10000
 254                      yield x
 255              """)
 256  
 257          # works if outer scope is a function scope and var exists
 258          iprc("""
 259          def f():
 260              x = 20
 261              def g():
 262                  nonlocal x
 263                  x = 10000
 264                  yield x
 265          """)
 266  
 267  
 268      def test_execute(self):
 269          iprc("""
 270          import asyncio
 271          await asyncio.sleep(0.001)
 272          """
 273          )
 274  
 275      def test_autoawait(self):
 276          iprc("%autoawait False")
 277          iprc("%autoawait True")
 278          iprc("""
 279          from asyncio import sleep
 280          await sleep(0.1)
 281          """
 282          )
 283  
 284      def test_memory_error(self):
 285          """
 286          The pgen parser in 3.8 or before use to raise MemoryError on too many
 287          nested parens anymore"""
 288  
 289          iprc("(" * 200 + ")" * 200)
 290  
 291      @pytest.mark.xfail(reason="fail on curio 1.6 and before on Python 3.12")
 292      @pytest.mark.skip(
 293          reason="skip_without(curio) fails on 3.12 for now even with other skip so must uncond skip"
 294      )
 295      # @skip_without("curio")
 296      def test_autoawait_curio(self):
 297          iprc("%autoawait curio")
 298  
 299      @skip_without('trio')
 300      def test_autoawait_trio(self):
 301          iprc("%autoawait trio")
 302  
 303      @skip_without('trio')
 304      def test_autoawait_trio_wrong_sleep(self):
 305          iprc("%autoawait trio")
 306          res = iprc_nr("""
 307          import asyncio
 308          await asyncio.sleep(0)
 309          """)
 310          with self.assertRaises(TypeError):
 311              res.raise_error()
 312  
 313      @skip_without('trio')
 314      def test_autoawait_asyncio_wrong_sleep(self):
 315          iprc("%autoawait asyncio")
 316          res = iprc_nr("""
 317          import trio
 318          await trio.sleep(0)
 319          """)
 320          with self.assertRaises(RuntimeError):
 321              res.raise_error()
 322  
 323  
 324      def tearDown(self):
 325          ip.loop_runner = "asyncio"
 326