test_format_stack.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """
  2. Unit tests for the stack formatting utilities
  3. """
  4. # Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
  5. # Copyright (c) 2010 Gael Varoquaux
  6. # License: BSD Style, 3 clauses.
  7. import imp
  8. import os
  9. import re
  10. import sys
  11. import pytest
  12. from joblib.format_stack import safe_repr, _fixed_getframes, format_records
  13. from joblib.format_stack import format_exc
  14. from joblib.test.common import with_numpy, np
  15. ###############################################################################
  16. class Vicious(object):
  17. def __repr__(self):
  18. raise ValueError
  19. def test_safe_repr():
  20. safe_repr(Vicious())
  21. def _change_file_extensions_to_pyc(record):
  22. _1, filename, _2, _3, _4, _5 = record
  23. if filename.endswith('.py'):
  24. filename += 'c'
  25. return _1, filename, _2, _3, _4, _5
  26. def _raise_exception(a, b):
  27. """Function that raises with a non trivial call stack
  28. """
  29. def helper(a, b):
  30. raise ValueError('Nope, this can not work')
  31. helper(a, b)
  32. def test_format_records():
  33. try:
  34. _raise_exception('a', 42)
  35. except ValueError:
  36. etb = sys.exc_info()[2]
  37. records = _fixed_getframes(etb)
  38. # Modify filenames in traceback records from .py to .pyc
  39. pyc_records = [_change_file_extensions_to_pyc(record)
  40. for record in records]
  41. formatted_records = format_records(pyc_records)
  42. # Check that the .py file and not the .pyc one is listed in
  43. # the traceback
  44. for fmt_rec in formatted_records:
  45. assert 'test_format_stack.py in' in fmt_rec
  46. # Check exception stack
  47. arrow_regex = r'^-+>\s+\d+\s+'
  48. assert re.search(arrow_regex + r"_raise_exception\('a', 42\)",
  49. formatted_records[0],
  50. re.MULTILINE)
  51. assert re.search(arrow_regex + r'helper\(a, b\)',
  52. formatted_records[1],
  53. re.MULTILINE)
  54. assert "a = 'a'" in formatted_records[1]
  55. assert 'b = 42' in formatted_records[1]
  56. assert re.search(arrow_regex +
  57. r"raise ValueError\('Nope, this can not work'\)",
  58. formatted_records[2],
  59. re.MULTILINE)
  60. def test_format_records_file_with_less_lines_than_context(tmpdir):
  61. # See https://github.com/joblib/joblib/issues/420
  62. filename = os.path.join(tmpdir.strpath, 'small_file.py')
  63. code_lines = ['def func():', ' 1/0']
  64. code = '\n'.join(code_lines)
  65. with open(filename, 'w') as f:
  66. f.write(code)
  67. small_file = imp.load_source('small_file', filename)
  68. if not hasattr(small_file, 'func'):
  69. pytest.skip("PyPy bug?")
  70. try:
  71. small_file.func()
  72. except ZeroDivisionError:
  73. etb = sys.exc_info()[2]
  74. records = _fixed_getframes(etb, context=10)
  75. # Check that if context is bigger than the number of lines in
  76. # the file you do not get padding
  77. frame, tb_filename, line, func_name, context, _ = records[-1]
  78. assert [l.rstrip() for l in context] == code_lines
  79. formatted_records = format_records(records)
  80. # 2 lines for header in the traceback: lines of ...... +
  81. # filename with function
  82. len_header = 2
  83. nb_lines_formatted_records = len(formatted_records[1].splitlines())
  84. assert (nb_lines_formatted_records == len_header + len(code_lines))
  85. # Check exception stack
  86. arrow_regex = r'^-+>\s+\d+\s+'
  87. assert re.search(arrow_regex + r'1/0',
  88. formatted_records[1],
  89. re.MULTILINE)
  90. @with_numpy
  91. def test_format_exc_with_compiled_code():
  92. # Trying to tokenize compiled C code raise SyntaxError.
  93. # See https://github.com/joblib/joblib/issues/101 for more details.
  94. try:
  95. np.random.uniform('invalid_value')
  96. except Exception:
  97. exc_type, exc_value, exc_traceback = sys.exc_info()
  98. formatted_exc = format_exc(exc_type, exc_value,
  99. exc_traceback, context=10)
  100. # The name of the extension can be something like
  101. # mtrand.cpython-33m.so
  102. pattern = r'mtrand[a-z0-9._-]*\.(so|pyd)'
  103. assert re.search(pattern, formatted_exc)