├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── better_exceptions ├── __init__.py ├── __main__.py ├── color.py ├── context.py ├── formatter.py ├── integrations │ ├── __init__.py │ └── django.py ├── log.py └── repl.py ├── better_exceptions_hook.pth ├── screenshot.png ├── setup.cfg ├── setup.py ├── test ├── output │ ├── python3-dumb-UTF-8-color.out │ ├── python3-dumb-UTF-8-nocolor.out │ ├── python3-dumb-ascii-color.out │ ├── python3-dumb-ascii-nocolor.out │ ├── python3-vt100-UTF-8-color.out │ ├── python3-vt100-UTF-8-nocolor.out │ ├── python3-vt100-ascii-color.out │ ├── python3-vt100-ascii-nocolor.out │ ├── python3-xterm-UTF-8-color.out │ ├── python3-xterm-UTF-8-nocolor.out │ ├── python3-xterm-ascii-color.out │ └── python3-xterm-ascii-nocolor.out ├── test.py ├── test_chaining.py ├── test_color.py ├── test_encoding.py ├── test_indentation_error.py ├── test_interactive.sh ├── test_interactive_raw.sh ├── test_logging.py ├── test_string.sh ├── test_syntax_error.py ├── test_truncating.py ├── test_truncating_disabled.py ├── test_unittest_patch.py └── test_util │ ├── __init__.py │ └── quiet_console.py └── test_all.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml,*.py}] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: "qix-" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python,virtualenv,vim,emacs,linux,macos,windows,pycharm,jetbrains 2 | MANIFEST 3 | 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | 48 | # directory configuration 49 | .dir-locals.el 50 | 51 | ### JetBrains ### 52 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 53 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 54 | 55 | # User-specific stuff: 56 | .idea/**/workspace.xml 57 | .idea/**/tasks.xml 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | 95 | ### JetBrains Patch ### 96 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 97 | 98 | # *.iml 99 | # modules.xml 100 | # .idea/misc.xml 101 | # *.ipr 102 | 103 | ### Linux ### 104 | 105 | # temporary files which can be created if a process still has a handle open of a deleted file 106 | .fuse_hidden* 107 | 108 | # KDE directory preferences 109 | .directory 110 | 111 | # Linux trash folder which might appear on any partition or disk 112 | .Trash-* 113 | 114 | # .nfs files are created when an open file is removed but is still being accessed 115 | .nfs* 116 | 117 | ### macOS ### 118 | *.DS_Store 119 | .AppleDouble 120 | .LSOverride 121 | 122 | # Icon must end with two \r 123 | Icon 124 | 125 | 126 | # Thumbnails 127 | ._* 128 | 129 | # Files that might appear in the root of a volume 130 | .DocumentRevisions-V100 131 | .fseventsd 132 | .Spotlight-V100 133 | .TemporaryItems 134 | .Trashes 135 | .VolumeIcon.icns 136 | .com.apple.timemachine.donotpresent 137 | 138 | # Directories potentially created on remote AFP share 139 | .AppleDB 140 | .AppleDesktop 141 | Network Trash Folder 142 | Temporary Items 143 | .apdisk 144 | 145 | ### PyCharm ### 146 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 147 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 148 | 149 | # User-specific stuff: 150 | 151 | # Sensitive or high-churn files: 152 | 153 | # Gradle: 154 | 155 | # Mongo Explorer plugin: 156 | 157 | ## File-based project format: 158 | 159 | ## Plugin-specific files: 160 | 161 | # IntelliJ 162 | 163 | # mpeltonen/sbt-idea plugin 164 | 165 | # JIRA plugin 166 | 167 | # Crashlytics plugin (for Android Studio and IntelliJ) 168 | 169 | ### PyCharm Patch ### 170 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 171 | 172 | # *.iml 173 | # modules.xml 174 | # .idea/misc.xml 175 | # *.ipr 176 | 177 | ### Python ### 178 | # Byte-compiled / optimized / DLL files 179 | __pycache__/ 180 | *.py[cod] 181 | *$py.class 182 | 183 | # C extensions 184 | *.so 185 | 186 | # Distribution / packaging 187 | .Python 188 | env/ 189 | build/ 190 | develop-eggs/ 191 | downloads/ 192 | eggs/ 193 | .eggs/ 194 | lib/ 195 | lib64/ 196 | parts/ 197 | sdist/ 198 | var/ 199 | wheels/ 200 | *.egg-info/ 201 | .installed.cfg 202 | *.egg 203 | 204 | # PyInstaller 205 | # Usually these files are written by a python script from a template 206 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 207 | *.manifest 208 | *.spec 209 | 210 | # Installer logs 211 | pip-log.txt 212 | pip-delete-this-directory.txt 213 | 214 | # Unit test / coverage reports 215 | htmlcov/ 216 | .tox/ 217 | .coverage 218 | .coverage.* 219 | .cache 220 | nosetests.xml 221 | coverage.xml 222 | *,cover 223 | .hypothesis/ 224 | 225 | # Translations 226 | *.mo 227 | *.pot 228 | 229 | # Django stuff: 230 | *.log 231 | local_settings.py 232 | 233 | # Flask stuff: 234 | instance/ 235 | .webassets-cache 236 | 237 | # Scrapy stuff: 238 | .scrapy 239 | 240 | # Sphinx documentation 241 | docs/_build/ 242 | 243 | # PyBuilder 244 | target/ 245 | 246 | # Jupyter Notebook 247 | .ipynb_checkpoints 248 | 249 | # pyenv 250 | .python-version 251 | 252 | # celery beat schedule file 253 | celerybeat-schedule 254 | 255 | # dotenv 256 | .env 257 | 258 | # virtualenv 259 | .venv 260 | venv/ 261 | ENV/ 262 | 263 | # Spyder project settings 264 | .spyderproject 265 | 266 | # Rope project settings 267 | .ropeproject 268 | 269 | ### Vim ### 270 | # swap 271 | [._]*.s[a-v][a-z] 272 | [._]*.sw[a-p] 273 | [._]s[a-v][a-z] 274 | [._]sw[a-p] 275 | # session 276 | Session.vim 277 | # temporary 278 | .netrwhist 279 | # auto-generated tag files 280 | tags 281 | 282 | ### VirtualEnv ### 283 | # Virtualenv 284 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 285 | [Bb]in 286 | [Ii]nclude 287 | [Ll]ib 288 | [Ll]ib64 289 | [Ll]ocal 290 | [Ss]cripts 291 | pyvenv.cfg 292 | pip-selfcheck.json 293 | 294 | ### Windows ### 295 | # Windows thumbnail cache files 296 | Thumbs.db 297 | ehthumbs.db 298 | ehthumbs_vista.db 299 | 300 | # Folder config file 301 | Desktop.ini 302 | 303 | # Recycle Bin used on file shares 304 | $RECYCLE.BIN/ 305 | 306 | # Windows Installer files 307 | *.cab 308 | *.msi 309 | *.msm 310 | *.msp 311 | 312 | # Windows shortcuts 313 | *.lnk 314 | 315 | # End of https://www.gitignore.io/api/python,virtualenv,vim,emacs,linux,macos,windows,pycharm,jetbrains 316 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | os: 4 | - linux 5 | # - osx # travis doesn't appear to support python on OSX as of right now... 6 | python: 7 | - '3.6' 8 | - '3.7' 9 | - '3.8' 10 | - '3.9' 11 | - 'nightly' 12 | install: 13 | - pip install -e . 14 | script: 15 | - ./test_all.sh 16 | addons: 17 | apt: 18 | packages: 19 | - expect 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Josh Junon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft better_exceptions 2 | graft test 3 | 4 | include better_exceptions_hook.pth 5 | 6 | include README.md 7 | include LICENSE.txt 8 | include setup.py 9 | include setup.cfg 10 | include test_all.sh 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # better-exceptions [![Travis](https://img.shields.io/travis/Qix-/better-exceptions.svg?style=flat-square)](https://travis-ci.org/Qix-/better-exceptions) 2 | 3 | Pretty and more helpful exceptions in Python, automatically. 4 | 5 | ![Example screenshot of exceptions](screenshot.png) 6 | 7 | ## Usage 8 | 9 | Install `better_exceptions` via pip: 10 | 11 | ```console 12 | pip install better_exceptions 13 | ``` 14 | 15 | And set the `BETTER_EXCEPTIONS` environment variable to any value: 16 | 17 | ```bash 18 | export BETTER_EXCEPTIONS=1 # Linux / OSX 19 | setx BETTER_EXCEPTIONS 1 # Windows 20 | ``` 21 | 22 | That's it! 23 | 24 | ### Python REPL (Interactive Shell) 25 | 26 | In order to use `better_exceptions` in the Python REPL, first install the package (as instructed above) and run: 27 | 28 | ```console 29 | $ python -m better_exceptions 30 | Type "help", "copyright", "credits" or "license" for more information. 31 | (BetterExceptionsConsole) 32 | >>> 33 | ``` 34 | 35 | in order to drop into a `better_exceptions`-enabled Python interactive shell. 36 | 37 | ### Advanced Usage 38 | 39 | If you want to allow the entirety of values to be outputted instead of being truncated to a certain amount of characters: 40 | 41 | ```python 42 | import better_exceptions 43 | better_exceptions.MAX_LENGTH = None 44 | ``` 45 | 46 | While using `better_exceptions` in production, do not forget to unset the `BETTER_EXCEPTIONS` variable to avoid leaking sensitive data in your logs. 47 | 48 | ### Use with unittest 49 | 50 | If you want to use `better_exceptions` to format `unittest`'s exception output, you can use the monkey patch below: 51 | 52 | ```python 53 | import sys 54 | import unittest 55 | import better_exceptions 56 | 57 | def patch(self, err, test): 58 | lines = better_exceptions.format_exception(*err) 59 | if sys.version_info[0] == 2: 60 | return u"".join(lines).encode("utf-8") 61 | return "".join(lines) 62 | 63 | unittest.result.TestResult._exc_info_to_string = patch 64 | ``` 65 | 66 | Note that this uses an undocumented method override, so it is **not** guaranteed to work on all platforms or versions of Python. 67 | 68 | ### Django Usage 69 | 70 | In `settings.py`, add your new class to the `MIDDLEWARE` setting and update your logging configuration: 71 | 72 | ```python 73 | # ... 74 | 75 | MIDDLEWARE = [ 76 | # ... 77 | "better_exceptions.integrations.django.BetterExceptionsMiddleware", 78 | ] 79 | 80 | # ... 81 | 82 | from better_exceptions.integrations.django import skip_errors_filter 83 | 84 | # if you don't want to override LOGGING because you want to change the default, 85 | # you can vendor Django's default logging configuration and update it for 86 | # better-exceptions. the default for Django 3.1.4 can be found here: 87 | # https://github.com/django/django/blob/3.1.4/django/utils/log.py#L13-L63 88 | LOGGING = { 89 | 'version': 1, 90 | 'disable_existing_loggers': False, 91 | 'filters': { 92 | 'skip_errors': { 93 | '()': 'django.utils.log.CallbackFilter', 94 | 'callback': skip_errors_filter, 95 | } 96 | }, 97 | 'handlers': { 98 | 'console': { 99 | 'level': 'INFO', 100 | # without the 'filters' key, Django will log errors twice: 101 | # one time from better-exceptions and one time from Django. 102 | # with the 'skip_errors' filter, we remove the repeat log 103 | # from Django, which is unformatted. 104 | 'filters': ['skip_errors'], 105 | 'class': 'logging.StreamHandler', 106 | } 107 | }, 108 | 'loggers': { 109 | 'django': { 110 | 'handlers': [ 111 | 'console', 112 | ], 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | example output: 119 | 120 | ![image](https://user-images.githubusercontent.com/157132/56871937-5a07b480-69f1-11e9-9fd5-fac12382ebb7.png) 121 | 122 | ## Troubleshooting 123 | 124 | If you do not see beautiful exceptions, first make sure that the environment variable does exist. You can try `echo $BETTER_EXCEPTIONS` (Linux / OSX) or `echo %BETTER_EXCEPTIONS%` (Windows). On Linux and OSX, the `export` command does not add the variable permanently, you will probably need to edit the `~/.profile` file to make it persistent. On Windows, you need to open a new terminal after the `setx` command. 125 | 126 | Check that there is no conflict with another library, and that the `sys.excepthook` function has been correctly replaced with the `better_exceptions`'s one. Sometimes other components can set up their own exception handlers, such as the `python3-apport` Ubuntu package that you may need to uninstall. 127 | 128 | Make sure that you have not inadvertently deleted the `better_exceptions_hook.pth` file that should be in the same place as the `better_exceptions` folder where all of your Python packages are installed. Otherwise, try re-installing `better_exceptions`. 129 | 130 | You can also try to manually activate the hook by adding `import better_exceptions; better_exceptions.hook()` at the beginning of your script. 131 | 132 | Finally, if you still can not get this module to work, [open a new issue](https://github.com/Qix-/better-exceptions/issues/new) by describing your problem precisely and detailing your configuration (Python and `better_exceptions` versions, OS, code snippet, interpreter, etc.) so that we can reproduce the bug you are experiencing. 133 | 134 | # License 135 | 136 | Copyright © 2017, Josh Junon. Licensed under the [MIT license](LICENSE.txt). 137 | -------------------------------------------------------------------------------- /better_exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | """Beautiful and helpful exceptions 2 | 3 | Just set your `BETTER_EXCEPTIONS` environment variable. It handles the rest. 4 | 5 | 6 | Name: better_exceptions 7 | Author: Josh Junon 8 | Email: josh@junon.me 9 | URL: github.com/qix-/better-exceptions 10 | License: Copyright (c) 2017 Josh Junon, licensed under the MIT license 11 | """ 12 | 13 | from __future__ import absolute_import 14 | from __future__ import print_function 15 | 16 | import logging 17 | import sys 18 | 19 | from .formatter import THEME, MAX_LENGTH, PIPE_CHAR, CAP_CHAR, ExceptionFormatter 20 | from .context import PY3 21 | from .color import SUPPORTS_COLOR, SHOULD_ENCODE, STREAM, to_byte 22 | from .log import BetExcLogger, patch as patch_logging 23 | from .repl import interact, get_repl 24 | 25 | 26 | __version__ = '0.3.3' 27 | 28 | 29 | THEME = THEME.copy() # Users customizing the theme should not impact core 30 | 31 | 32 | def write_stream(data, stream=STREAM): 33 | if SHOULD_ENCODE: 34 | data = to_byte(data) 35 | 36 | if PY3: 37 | stream.buffer.write(data) 38 | else: 39 | stream.write(data) 40 | else: 41 | stream.write(data) 42 | 43 | 44 | def format_exception(exc, value, tb): 45 | # Rebuild each time to take into account any changes made by the user to the global parameters 46 | formatter = ExceptionFormatter(colored=SUPPORTS_COLOR, theme=THEME, max_length=MAX_LENGTH, 47 | pipe_char=PIPE_CHAR, cap_char=CAP_CHAR) 48 | return list(formatter.format_exception(exc, value, tb)) 49 | 50 | 51 | def excepthook(exc, value, tb): 52 | formatted = u''.join(format_exception(exc, value, tb)) 53 | write_stream(formatted, STREAM) 54 | 55 | 56 | def hook(): 57 | sys.excepthook = excepthook 58 | 59 | logging.setLoggerClass(BetExcLogger) 60 | patch_logging() 61 | 62 | if hasattr(sys, 'ps1'): 63 | print('WARNING: better_exceptions will only inspect code from the command line\n' 64 | ' when using: `python -m better_exceptions\'. Otherwise, only code\n' 65 | ' loaded from files will be inspected!', file=sys.stderr) 66 | -------------------------------------------------------------------------------- /better_exceptions/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | try: 5 | import importlib.machinery 6 | import importlib.util 7 | 8 | def load_module(name, filepath): 9 | loader = importlib.machinery.SourceFileLoader(name, filepath) 10 | spec = importlib.util.spec_from_loader(loader.name, loader) 11 | mod = importlib.util.module_from_spec(spec) 12 | loader.exec_module(mod) 13 | 14 | except ImportError: 15 | import imp 16 | 17 | def load_module(name, filepath): 18 | with open(filepath, 'r') as fd: 19 | imp.load_module('a_b', fd, filepath, ('.py', 'U', imp.PY_SOURCE)) 20 | 21 | from better_exceptions import interact, hook 22 | hook() 23 | 24 | parser = argparse.ArgumentParser(description='A Python REPL with better exceptions enabled', prog='python -m better_exceptions') 25 | parser.add_argument('-q', '--quiet', help="don't show a banner", action='store_true') 26 | parser.add_argument('-i', '--no-init', dest='no_init', help="don't load ~/.pyinit", action='store_true') 27 | args = parser.parse_args() 28 | 29 | startup_file = os.getenv('PYTHONSTARTUP') 30 | if not args.no_init and startup_file is not None: 31 | load_module('pystartup', startup_file) 32 | 33 | interact(args.quiet) 34 | -------------------------------------------------------------------------------- /better_exceptions/color.py: -------------------------------------------------------------------------------- 1 | """Checks if the current terminal supports colors. 2 | 3 | Also specifies the stream to write to. On Windows, this is a wrapped 4 | stream. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | 9 | import codecs 10 | import errno 11 | import os 12 | import struct 13 | import sys 14 | 15 | from .context import PY3 16 | 17 | 18 | STREAM = sys.stderr 19 | ENCODING = getattr(STREAM, "encoding", None) or "utf-8" 20 | SHOULD_ENCODE = True 21 | SUPPORTS_COLOR = False 22 | 23 | 24 | def to_byte(val): 25 | unicode_type = str if PY3 else unicode 26 | if isinstance(val, unicode_type): 27 | try: 28 | return val.encode(ENCODING) 29 | except UnicodeEncodeError: 30 | if PY3: 31 | return codecs.escape_decode(val)[0] 32 | else: 33 | return val.encode("unicode-escape").decode("string-escape") 34 | 35 | return val 36 | 37 | 38 | def to_unicode(val): 39 | if isinstance(val, bytes): 40 | try: 41 | return val.decode(ENCODING) 42 | except UnicodeDecodeError: 43 | return val.decode("unicode-escape") 44 | 45 | return val 46 | 47 | 48 | def get_terminfo_file(): 49 | term = os.getenv('TERM', None) 50 | 51 | if term is None: 52 | return None 53 | 54 | terminfo_dirs = [ 55 | os.path.expanduser('~/.terminfo'), 56 | '/etc/terminfo', 57 | '/lib/terminfo', 58 | '/usr/share/terminfo', 59 | '/usr/lib/terminfo', 60 | '/usr/share/lib/terminfo', 61 | '/usr/local/lib/terminfo', 62 | '/usr/local/share/terminfo' 63 | ] 64 | 65 | subdirs = [ 66 | ('%0.2X' % ord(term[0])), 67 | term[0] 68 | ] 69 | 70 | f = None 71 | for terminfo_dir in terminfo_dirs: 72 | for subdir in subdirs: 73 | terminfo_path = os.path.join(terminfo_dir, subdir, term) 74 | try: 75 | f = open(terminfo_path, 'rb') 76 | break 77 | except IOError as e: 78 | if e.errno != errno.ENOENT: 79 | raise 80 | 81 | return f 82 | 83 | 84 | class ProxyBufferStreamWrapper(object): 85 | 86 | def __init__(self, wrapped): 87 | self.__wrapped = wrapped 88 | 89 | def __getattr__(self, name): 90 | return getattr(self.__wrapped, name) 91 | 92 | def write(self, text): 93 | data = to_byte(text) 94 | self.__wrapped.buffer.write(data) 95 | 96 | 97 | if os.name == 'nt': 98 | from colorama import init as init_colorama, AnsiToWin32 99 | 100 | init_colorama(wrap=False) 101 | 102 | stream = sys.stderr 103 | 104 | if PY3: 105 | # Colorama cannot work with bytes-string 106 | # The stream is wrapped so that encoding of the stream is done after 107 | # (once Colorama found ANSI codes and converted them to win32 calls) 108 | # See issue #23 for more information 109 | stream = ProxyBufferStreamWrapper(stream) 110 | SHOULD_ENCODE = False 111 | 112 | STREAM = AnsiToWin32(stream).stream 113 | SUPPORTS_COLOR = True 114 | else: 115 | if os.getenv('FORCE_COLOR', None) == '1': 116 | SUPPORTS_COLOR = True 117 | else: 118 | try: 119 | # May raises an error on some exotic environment like GAE, see #28 120 | is_tty = os.isatty(2) 121 | except OSError: 122 | is_tty = False 123 | 124 | if is_tty: 125 | f = get_terminfo_file() 126 | if f is not None: 127 | with f: 128 | # f is a valid terminfo; seek and read! 129 | magic_number = struct.unpack('= 8: 143 | SUPPORTS_COLOR = True 144 | -------------------------------------------------------------------------------- /better_exceptions/context.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY3 = sys.version_info[0] >= 3 4 | -------------------------------------------------------------------------------- /better_exceptions/formatter.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import ast 4 | import inspect 5 | import keyword 6 | import linecache 7 | import os 8 | import re 9 | import sys 10 | import traceback 11 | 12 | from .color import ENCODING, STREAM, SUPPORTS_COLOR, to_byte, to_unicode 13 | from .context import PY3 14 | from .repl import get_repl 15 | 16 | 17 | PIPE_CHAR = u'\u2502' 18 | CAP_CHAR = u'\u2514' 19 | 20 | try: 21 | PIPE_CHAR.encode(ENCODING) 22 | except UnicodeEncodeError: 23 | PIPE_CHAR = '|' 24 | CAP_CHAR = '->' 25 | 26 | THEME = { 27 | 'comment': lambda s: '\x1b[2;37m{}\x1b[m'.format(s), 28 | 'keyword': lambda s: '\x1b[33;1m{}\x1b[m'.format(s), 29 | 'builtin': lambda s: '\x1b[35;1m{}\x1b[m'.format(s), 30 | 'literal': lambda s: '\x1b[31m{}\x1b[m'.format(s), 31 | 'inspect': lambda s: u'\x1b[36m{}\x1b[m'.format(s), 32 | } 33 | 34 | MAX_LENGTH = 128 35 | 36 | 37 | def isast(v): 38 | return inspect.isclass(v) and issubclass(v, ast.AST) 39 | 40 | 41 | class ExceptionFormatter(object): 42 | 43 | COMMENT_REGXP = re.compile(r'((?:(?:"(?:[^\\"]|(\\\\)*\\")*")|(?:\'(?:[^\\\']|(\\\\)*\\\')*\')|[^#])*)(#.*)$') 44 | CMDLINE_REGXP = re.compile(r'(?:[^\t ]*([\'"])(?:\\.|.)*(?:\1))[^\t ]*|([^\t ]+)') 45 | 46 | AST_ELEMENTS = { 47 | 'builtins': __builtins__.keys() if type(__builtins__) is dict else dir(__builtins__), 48 | 'keywords': [getattr(ast, cls) for cls in dir(ast) if keyword.iskeyword(cls.lower()) and isast(getattr(ast, cls))], 49 | } 50 | 51 | def __init__(self, colored=SUPPORTS_COLOR, theme=THEME, max_length=MAX_LENGTH, 52 | pipe_char=PIPE_CHAR, cap_char=CAP_CHAR): 53 | self._colored = colored 54 | self._theme = theme 55 | self._max_length = max_length 56 | self._pipe_char = pipe_char 57 | self._cap_char = cap_char 58 | 59 | def colorize_comment(self, source): 60 | match = self.COMMENT_REGXP.match(source) 61 | if match: 62 | source = '{}{}'.format(match.group(1), self._theme['comment'](match.group(4))) 63 | return source 64 | 65 | def colorize_tree(self, tree, source): 66 | if not self._colored: 67 | # quick fail 68 | return source 69 | 70 | chunks = [] 71 | 72 | offset = 0 73 | nodes = [n for n in ast.walk(tree)] 74 | 75 | def append(offset, node, s, theme): 76 | begin_col = node.col_offset 77 | src_chunk = source[offset:begin_col] 78 | chunks.append(src_chunk) 79 | chunks.append(self._theme[theme](s)) 80 | return begin_col + len(s) 81 | 82 | displayed_nodes = [] 83 | 84 | for node in nodes: 85 | nodecls = node.__class__ 86 | nodename = nodecls.__name__ 87 | 88 | if 'col_offset' not in dir(node): 89 | continue 90 | 91 | if nodecls in self.AST_ELEMENTS['keywords']: 92 | displayed_nodes.append((node, nodename.lower(), 'keyword')) 93 | 94 | if nodecls == ast.Name and node.id in self.AST_ELEMENTS['builtins']: 95 | displayed_nodes.append((node, node.id, 'builtin')) 96 | 97 | if nodecls == ast.Str: 98 | displayed_nodes.append((node, "'{}'".format(node.s), 'literal')) 99 | 100 | if nodecls == ast.Num: 101 | displayed_nodes.append((node, str(node.n), 'literal')) 102 | 103 | displayed_nodes.sort(key=lambda elem: elem[0].col_offset) 104 | 105 | for dn in displayed_nodes: 106 | offset = append(offset, *dn) 107 | 108 | chunks.append(source[offset:]) 109 | return self.colorize_comment(''.join(chunks)) 110 | 111 | def get_relevant_names(self, source, tree): 112 | return [node for node in ast.walk(tree) if isinstance(node, ast.Name)] 113 | 114 | def format_value(self, v): 115 | try: 116 | v = repr(v) 117 | except KeyboardInterrupt: 118 | raise 119 | except BaseException: 120 | v = u'' % type(v).__name__ 121 | 122 | max_length = self._max_length 123 | if max_length is not None and len(v) > max_length: 124 | v = v[:max_length] + '...' 125 | return v 126 | 127 | def get_relevant_values(self, source, frame, tree): 128 | names = self.get_relevant_names(source, tree) 129 | values = [] 130 | 131 | for name in names: 132 | text = name.id 133 | col = name.col_offset 134 | if text in frame.f_locals: 135 | val = frame.f_locals.get(text, None) 136 | values.append((text, col, self.format_value(val))) 137 | elif text in frame.f_globals: 138 | val = frame.f_globals.get(text, None) 139 | values.append((text, col, self.format_value(val))) 140 | 141 | values.sort(key=lambda e: e[1]) 142 | 143 | return values 144 | 145 | def split_cmdline(self, cmdline): 146 | return [m.group(0) for m in self.CMDLINE_REGXP.finditer(cmdline)] 147 | 148 | def get_string_source(self): 149 | import os 150 | import platform 151 | 152 | # import pdb; pdb.set_trace() 153 | 154 | cmdline = None 155 | if platform.system() == 'Windows': 156 | # TODO use winapi to obtain the command line 157 | return '' 158 | elif platform.system() == 'Linux': 159 | # TODO try to use proc 160 | pass 161 | 162 | if cmdline is None and os.name == 'posix': 163 | from shutil import which 164 | 165 | if which('ps'): 166 | from subprocess import CalledProcessError, check_output as spawn 167 | 168 | try: 169 | cmdline = spawn(['ps', '-ww', '-p', str(os.getpid()), '-o', 'command=']) 170 | except CalledProcessError: 171 | return '' 172 | else: 173 | return '' 174 | else: 175 | # current system doesn't have a way to get the command line 176 | return '' 177 | 178 | cmdline = cmdline.decode('utf-8').strip() 179 | cmdline = self.split_cmdline(cmdline) 180 | 181 | extra_args = sys.argv[1:] 182 | if len(extra_args) > 0: 183 | if cmdline[-len(extra_args):] != extra_args: 184 | # we can't rely on the output to be correct; fail! 185 | return '' 186 | 187 | cmdline = cmdline[1:-len(extra_args)] 188 | 189 | skip = 0 190 | for i in range(len(cmdline)): 191 | a = cmdline[i].strip() 192 | if not a.startswith('-c'): 193 | skip += 1 194 | else: 195 | a = a[2:].strip() 196 | if len(a) > 0: 197 | cmdline[i] = a 198 | else: 199 | skip += 1 200 | break 201 | 202 | cmdline = cmdline[skip:] 203 | source = ' '.join(cmdline) 204 | 205 | return source 206 | 207 | def get_traceback_information(self, tb): 208 | frame_info = inspect.getframeinfo(tb) 209 | filename = frame_info.filename 210 | lineno = frame_info.lineno 211 | function = frame_info.function 212 | 213 | repl = get_repl() 214 | if repl is not None and filename in repl.entries: 215 | _, filename, source = repl.entries[filename] 216 | source = source.replace('\r\n', '\n').split('\n')[lineno - 1] 217 | elif filename == '': 218 | source = self.get_string_source() 219 | else: 220 | source = linecache.getline(filename, lineno) 221 | 222 | source = source.strip() 223 | 224 | try: 225 | tree = ast.parse(source, mode='exec') 226 | except SyntaxError: 227 | return filename, lineno, function, source, source, [] 228 | 229 | relevant_values = self.get_relevant_values(source, tb.tb_frame, tree) 230 | color_source = self.colorize_tree(tree, source) 231 | 232 | return filename, lineno, function, source, color_source, relevant_values 233 | 234 | 235 | def format_traceback_frame(self, tb): 236 | filename, lineno, function, source, color_source, relevant_values = self.get_traceback_information(tb) 237 | 238 | lines = [color_source] 239 | for i in reversed(range(len(relevant_values))): 240 | _, col, val = relevant_values[i] 241 | pipe_cols = [pcol for _, pcol, _ in relevant_values[:i]] 242 | line = '' 243 | index = 0 244 | for pc in pipe_cols: 245 | line += (' ' * (pc - index)) + self._pipe_char 246 | index = pc + 1 247 | 248 | if not PY3 and isinstance(val, str): 249 | # In Python2 the Non-ASCII value will be the escaped string, 250 | # use string-escape to decode the string to show the text in human way. 251 | val = to_unicode(val.decode("string-escape")) 252 | 253 | line += u'{}{} {}'.format((' ' * (col - index)), self._cap_char, val) 254 | lines.append(self._theme['inspect'](line) if self._colored else line) 255 | formatted = u'\n '.join([to_unicode(x) for x in lines]) 256 | 257 | return (filename, lineno, function, formatted), color_source 258 | 259 | 260 | def format_traceback(self, tb=None): 261 | omit_last = False 262 | if not tb: 263 | try: 264 | raise Exception() 265 | except: 266 | omit_last = True 267 | _, _, tb = sys.exc_info() 268 | assert tb is not None 269 | 270 | frames = [] 271 | final_source = '' 272 | while tb: 273 | if omit_last and not tb.tb_next: 274 | break 275 | 276 | formatted, colored = self.format_traceback_frame(tb) 277 | 278 | # special case to ignore runcode() here. 279 | if not (os.path.basename(formatted[0]) == 'code.py' and formatted[2] == 'runcode'): 280 | final_source = colored 281 | frames.append(formatted) 282 | 283 | tb = tb.tb_next 284 | 285 | lines = traceback.format_list(frames) 286 | 287 | return ''.join(lines), final_source 288 | 289 | def _format_exception(self, value, tb, seen=None): 290 | # Implemented from built-in traceback module: 291 | # https://github.com/python/cpython/blob/a5b76167dedf4d15211a216c3ca7b98e3cec33b8/Lib/traceback.py#L468 292 | 293 | exc_type, exc_value, exc_traceback = type(value), value, tb 294 | 295 | if seen is None: 296 | seen = set() 297 | 298 | seen.add(id(exc_value)) 299 | 300 | if exc_value and PY3: 301 | if exc_value.__cause__ is not None and id(exc_value.__cause__) not in seen: 302 | for text in self._format_exception(exc_value.__cause__,exc_value.__cause__.__traceback__, seen=seen): 303 | yield text 304 | yield u"\nThe above exception was the direct cause of the following exception:\n\n" 305 | elif exc_value.__context__ is not None and id(exc_value.__context__) not in seen and not exc_value.__suppress_context__: 306 | for text in self._format_exception(exc_value.__context__, exc_value.__context__.__traceback__, seen=seen): 307 | yield text 308 | yield u"\nDuring handling of the above exception, another exception occurred:\n\n" 309 | 310 | if exc_traceback is not None: 311 | yield u'Traceback (most recent call last):\n' 312 | 313 | formatted, colored_source = self.format_traceback(exc_traceback) 314 | 315 | yield formatted 316 | 317 | if not str(value) and exc_type is AssertionError: 318 | value.args = (colored_source,) 319 | title = traceback.format_exception_only(exc_type, value) 320 | 321 | yield u''.join(title).strip() + u'\n' 322 | 323 | def format_exception(self, exc, value, tb): 324 | for line in self._format_exception(value, tb): 325 | yield line 326 | -------------------------------------------------------------------------------- /better_exceptions/integrations/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /better_exceptions/integrations/django.py: -------------------------------------------------------------------------------- 1 | from sys import exc_info 2 | from better_exceptions import excepthook 3 | 4 | def skip_errors_filter(record): 5 | return not record.exc_info 6 | 7 | class BetterExceptionsMiddleware: 8 | def __init__(self, get_response): 9 | self.get_response = get_response 10 | 11 | def __call__(self, request): 12 | return self.get_response(request) 13 | 14 | def process_exception(self, _, exception): 15 | _, _, traceback = exc_info() 16 | excepthook(exception.__class__, exception, traceback) 17 | -------------------------------------------------------------------------------- /better_exceptions/log.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import sys 4 | 5 | from logging import Logger, StreamHandler 6 | 7 | 8 | def patch(): 9 | import logging 10 | from . import format_exception 11 | 12 | logging_format_exception = lambda exc_info: u''.join(format_exception(*exc_info)) 13 | 14 | if hasattr(logging, '_defaultFormatter'): 15 | logging._defaultFormatter.format_exception = logging_format_exception 16 | 17 | patchables = [handler() for handler in logging._handlerList if isinstance(handler(), StreamHandler)] 18 | patchables = [handler for handler in patchables if handler.stream == sys.stderr] 19 | patchables = [handler for handler in patchables if handler.formatter is not None] 20 | for handler in patchables: 21 | handler.formatter.formatException = logging_format_exception 22 | 23 | 24 | class BetExcLogger(Logger): 25 | def __init__(self, *args, **kwargs): 26 | super(BetExcLogger, self).__init__(*args, **kwargs) 27 | patch() 28 | -------------------------------------------------------------------------------- /better_exceptions/repl.py: -------------------------------------------------------------------------------- 1 | from code import InteractiveConsole 2 | import sys 3 | 4 | 5 | REPL_ID_PREFIX = '@@@REPL@@@' 6 | 7 | 8 | repl = None 9 | 10 | class BetterExceptionsConsole(InteractiveConsole, object): 11 | def __init__(self): 12 | super(BetterExceptionsConsole, self).__init__() 13 | self.last_command = None 14 | self.entries = dict() 15 | self.last_code = None 16 | self.last_id = None 17 | self.counter = 0 18 | 19 | def runcode(self, code): 20 | assert self.last_code is not None 21 | self.entries[self.last_id] = (code,) + self.last_code 22 | return super(BetterExceptionsConsole, self).runcode(code) 23 | 24 | def runsource(self, source, loc='', symbol='single'): 25 | # we abuse loc here to mark different entries of code. 26 | self.last_code = (loc, source) 27 | self.last_id = loc = '{}{}'.format(REPL_ID_PREFIX, self.counter) 28 | self.counter += 1 29 | return super(BetterExceptionsConsole, self).runsource(source, loc, symbol) 30 | 31 | def showtraceback(self): 32 | try: 33 | exctype, val, tb = sys.exc_info() 34 | sys.excepthook(exctype, val, tb) 35 | finally: 36 | # this is required since tb will never be garbage collected 37 | # see notes in `sys` 38 | del tb 39 | 40 | 41 | def get_repl(): 42 | global repl 43 | return repl 44 | 45 | 46 | def interact(quiet=False): 47 | global repl 48 | repl = BetterExceptionsConsole() 49 | banner = '' if quiet else None 50 | repl.interact(banner) 51 | -------------------------------------------------------------------------------- /better_exceptions_hook.pth: -------------------------------------------------------------------------------- 1 | import os; exec("try:\n if 'BETTER_EXCEPTIONS' in os.environ:import better_exceptions;better_exceptions.hook()\nexcept:print('An error occurred while automatically hooking better_exceptions.\\nIf you uninstalled better_exceptions, you should probably delete any \\'better_exceptions_hook.pth\\' file on your system or unset your \\'BETTER_EXCEPTIONS\\' environment variable.'); raise;") 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qix-/better-exceptions/f7f1476e57129dc74d241b4377b0df39c37bc8a7/screenshot.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from itertools import chain 3 | from os.path import basename 4 | from os.path import dirname 5 | from os.path import join 6 | from os.path import splitext 7 | from distutils.core import setup 8 | from distutils.command.build import build 9 | from setuptools.command.develop import develop 10 | from setuptools.command.easy_install import easy_install 11 | from setuptools.command.install_lib import install_lib 12 | 13 | 14 | class BuildWithPTH(build): 15 | def run(self): 16 | build.run(self) 17 | path = join(dirname(__file__), 'better_exceptions_hook.pth') 18 | dest = join(self.build_lib, basename(path)) 19 | self.copy_file(path, dest) 20 | 21 | 22 | class EasyInstallWithPTH(easy_install): 23 | def run(self): 24 | easy_install.run(self) 25 | path = join(dirname(__file__), 'better_exceptions_hook.pth') 26 | dest = join(self.install_dir, basename(path)) 27 | self.copy_file(path, dest) 28 | 29 | 30 | class InstallLibWithPTH(install_lib): 31 | def run(self): 32 | install_lib.run(self) 33 | path = join(dirname(__file__), 'better_exceptions_hook.pth') 34 | dest = join(self.install_dir, basename(path)) 35 | self.copy_file(path, dest) 36 | self.outputs = [dest] 37 | 38 | def get_outputs(self): 39 | return chain(install_lib.get_outputs(self), self.outputs) 40 | 41 | 42 | class DevelopWithPTH(develop): 43 | def run(self): 44 | develop.run(self) 45 | path = join(dirname(__file__), 'better_exceptions_hook.pth') 46 | dest = join(self.install_dir, basename(path)) 47 | self.copy_file(path, dest) 48 | 49 | 50 | with open('better_exceptions/__init__.py', 'r') as file: 51 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 52 | file.read(), re.MULTILINE).group(1) 53 | 54 | setup( 55 | name = 'better_exceptions', 56 | packages = ['better_exceptions', 'better_exceptions.integrations'], 57 | version = version, 58 | description = 'Pretty and helpful exceptions, automatically', 59 | author = 'Josh Junon', 60 | author_email = 'josh@junon.me', 61 | url = 'https://github.com/qix-/better-exceptions', 62 | download_url = 'https://github.com/qix-/better-exceptions/archive/{}.tar.gz'.format(version), 63 | license = 'MIT', 64 | keywords = ['pretty', 'better', 'exceptions', 'exception', 'error', 'local', 'debug', 'debugging', 'locals'], 65 | classifiers = [], 66 | extras_require = { 67 | ':sys_platform=="win32"': ['colorama'] 68 | }, 69 | # This all comes from pytest-cov repository: 70 | # https://github.com/pytest-dev/pytest-cov/blob/cde7c378b6a1971957759f42ac91e2860b41cf89/setup.py 71 | cmdclass = { 72 | 'build': BuildWithPTH, 73 | 'easy_install': EasyInstallWithPTH, 74 | 'install_lib': InstallLibWithPTH, 75 | 'develop': DevelopWithPTH, 76 | } 77 | ) 78 | -------------------------------------------------------------------------------- /test/output/python3-dumb-UTF-8-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  │ └ 52 17 |  └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  └  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └  59 | File "", line 2, in foo 60 | assert a > 10 61 |  └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  │ │ └ 5 77 |  │ └ 5 78 |  └  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  │ │ └ 5 85 |  │ └ 5 86 |  └  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  │ │ └ 'why hello there' 95 |  │ └ 'why hello there' 96 |  └  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  │ │ └ 'why hello there' 104 |  │ └ 'why hello there' 105 |  └  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  │ │ └ 'why hello there' 115 |  │ └ 'why hello there' 116 |  └  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  │ │ └ 'why hello there' 124 |  │ └ 'why hello there' 125 |  └  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └  226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  │ └ '2' 230 |  └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-dumb-UTF-8-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └ 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └ 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | │ └ 52 17 | └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └ 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | └ 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └ 59 | File "", line 2, in foo 60 | assert a > 10 61 | └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | │ │ └ 5 77 | │ └ 5 78 | └ 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | │ │ └ 5 85 | │ └ 5 86 | └ 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | │ │ └ 'why hello there' 95 | │ └ 'why hello there' 96 | └ 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | │ │ └ 'why hello there' 104 | │ └ 'why hello there' 105 | └ 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | │ │ └ 'why hello there' 115 | │ └ 'why hello there' 116 | └ 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | │ │ └ 'why hello there' 124 | │ └ 'why hello there' 125 | └ 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └ 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └ 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └ 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └ 226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | │ └ '2' 230 | └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └ 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └ 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └ 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-dumb-ascii-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | ->  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | ->  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  | -> 52 17 |  -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | ->  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  ->  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | ->  59 | File "", line 2, in foo 60 | assert a > 10 61 |  -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  | | -> 5 77 |  | -> 5 78 |  ->  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  | | -> 5 85 |  | -> 5 86 |  ->  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  | | -> 'why hello there' 95 |  | -> 'why hello there' 96 |  ->  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  | | -> 'why hello there' 104 |  | -> 'why hello there' 105 |  ->  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  | | -> 'why hello there' 115 |  | -> 'why hello there' 116 |  ->  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  | | -> 'why hello there' 124 |  | -> 'why hello there' 125 |  ->  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | ->  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | ->  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | ->  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | ->  226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  | -> '2' 230 |  -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | ->  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | ->  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | ->  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-dumb-ascii-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | -> 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | -> 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | | -> 52 17 | -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | -> 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | -> 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | -> 59 | File "", line 2, in foo 60 | assert a > 10 61 | -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | | | -> 5 77 | | -> 5 78 | -> 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | | | -> 5 85 | | -> 5 86 | -> 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | | | -> 'why hello there' 95 | | -> 'why hello there' 96 | -> 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | | | -> 'why hello there' 104 | | -> 'why hello there' 105 | -> 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | | | -> 'why hello there' 115 | | -> 'why hello there' 116 | -> 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | | | -> 'why hello there' 124 | | -> 'why hello there' 125 | -> 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | -> 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | -> 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | -> 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | -> 226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | | -> '2' 230 | -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | -> 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | -> 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | -> 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-vt100-UTF-8-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  │ └ 52 17 |  └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  └  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └  59 | File "", line 2, in foo 60 | assert a > 10 61 |  └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  │ │ └ 5 77 |  │ └ 5 78 |  └  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  │ │ └ 5 85 |  │ └ 5 86 |  └  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  │ │ └ 'why hello there' 95 |  │ └ 'why hello there' 96 |  └  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  │ │ └ 'why hello there' 104 |  │ └ 'why hello there' 105 |  └  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  │ │ └ 'why hello there' 115 |  │ └ 'why hello there' 116 |  └  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  │ │ └ 'why hello there' 124 |  │ └ 'why hello there' 125 |  └  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └  226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  │ └ '2' 230 |  └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-vt100-UTF-8-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └ 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └ 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | │ └ 52 17 | └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └ 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | └ 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └  59 | File "", line 2, in foo 60 | assert a > 10 61 |  └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | │ │ └ 5 77 | │ └ 5 78 | └ 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | │ │ └ 5 85 | │ └ 5 86 | └ 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | │ │ └ 'why hello there' 95 | │ └ 'why hello there' 96 | └ 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | │ │ └ 'why hello there' 104 | │ └ 'why hello there' 105 | └ 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | │ │ └ 'why hello there' 115 | │ └ 'why hello there' 116 | └ 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | │ │ └ 'why hello there' 124 | │ └ 'why hello there' 125 | └ 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └ 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └ 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └ 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └ 226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | │ └ '2' 230 | └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └ 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └ 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └ 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-vt100-ascii-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | ->  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | ->  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  | -> 52 17 |  -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | ->  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  ->  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | ->  59 | File "", line 2, in foo 60 | assert a > 10 61 |  -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  | | -> 5 77 |  | -> 5 78 |  ->  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  | | -> 5 85 |  | -> 5 86 |  ->  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  | | -> 'why hello there' 95 |  | -> 'why hello there' 96 |  ->  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  | | -> 'why hello there' 104 |  | -> 'why hello there' 105 |  ->  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  | | -> 'why hello there' 115 |  | -> 'why hello there' 116 |  ->  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  | | -> 'why hello there' 124 |  | -> 'why hello there' 125 |  ->  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | ->  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | ->  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | ->  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | ->  226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  | -> '2' 230 |  -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | ->  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | ->  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | ->  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-vt100-ascii-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | -> 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | -> 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | | -> 52 17 | -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | -> 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | -> 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | ->  59 | File "", line 2, in foo 60 | assert a > 10 61 |  -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | | | -> 5 77 | | -> 5 78 | -> 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | | | -> 5 85 | | -> 5 86 | -> 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | | | -> 'why hello there' 95 | | -> 'why hello there' 96 | -> 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | | | -> 'why hello there' 104 | | -> 'why hello there' 105 | -> 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | | | -> 'why hello there' 115 | | -> 'why hello there' 116 | -> 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | | | -> 'why hello there' 124 | | -> 'why hello there' 125 | -> 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | -> 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | -> 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | -> 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | -> 226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | | -> '2' 230 | -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | -> 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | -> 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | -> 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-xterm-UTF-8-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  │ └ 52 17 |  └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  └  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └  59 | File "", line 2, in foo 60 | assert a > 10 61 |  └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  │ │ └ 5 77 |  │ └ 5 78 |  └  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  │ │ └ 5 85 |  │ └ 5 86 |  └  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  │ │ └ 'why hello there' 95 |  │ └ 'why hello there' 96 |  └  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  │ │ └ 'why hello there' 104 |  │ └ 'why hello there' 105 |  └  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  │ │ └ 'why hello there' 115 |  │ └ 'why hello there' 116 |  └  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  │ │ └ 'why hello there' 124 |  │ └ 'why hello there' 125 |  └  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └  226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  │ └ '2' 230 |  └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-xterm-UTF-8-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | │ └ 2 8 | └ 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | │ │ └ 15 12 | │ └ 2 13 | └ 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | │ └ 52 17 | └ 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | └ 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | └ 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | └ '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | └  59 | File "", line 2, in foo 60 | assert a > 10 61 |  └ 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | │ │ └ 5 77 | │ └ 5 78 | └ 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | │ │ └ 5 85 | │ └ 5 86 | └ 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | │ │ └ 'why hello there' 95 | │ └ 'why hello there' 96 | └ 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | │ │ └ 'why hello there' 104 | │ └ 'why hello there' 105 | └ 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | │ │ └ 'why hello there' 115 | │ └ 'why hello there' 116 | └ 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | │ │ └ 'why hello there' 124 | │ └ 'why hello there' 125 | └ 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | └ 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | └ 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | └ 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | └ 175 | div() 176 | └ 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | └ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | └ '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | └ '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | │ └ > 219 | └ <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | └ > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | │ └ 226 | └ <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | │ └ '2' 230 | └ 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | │ │ └ 0 242 | │ └ 1 243 | └ 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | │ └ 0 247 | └ 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | │ │ └ 0 256 | │ └ 1 257 | └ 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | └ 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-xterm-ascii-color.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | ->  9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | ->  14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 |  | -> 52 17 |  -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | True 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | ->  36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 |  ->  39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 |  -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | ->  59 | File "", line 2, in foo 60 | assert a > 10 61 |  -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 |  | | -> 5 77 |  | -> 5 78 |  ->  79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 |  | | -> 5 85 |  | -> 5 86 |  ->  87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 |  | | -> 'why hello there' 95 |  | -> 'why hello there' 96 |  ->  97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 |  | | -> 'why hello there' 104 |  | -> 'why hello there' 105 |  ->  106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 |  | | -> 'why hello there' 115 |  | -> 'why hello there' 116 |  ->  117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 |  | | -> 'why hello there' 124 |  | -> 'why hello there' 125 |  ->  126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | ->  139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | ->  148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 |  -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | ->  177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 |  -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 |  -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 |  -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | ->  226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 |  | -> '2' 230 |  -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | ->  244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | ->  258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | ->  268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/output/python3-xterm-ascii-nocolor.out: -------------------------------------------------------------------------------- 1 | python3 test/test.py 2 | 3 | 4 | Traceback (most recent call last): 5 | File "test/test.py", line 17, in 6 | shallow(bar, 15) 7 | | -> 2 8 | -> 9 | File "test/test.py", line 8, in shallow 10 | deep(a + b) 11 | | | -> 15 12 | | -> 2 13 | -> 14 | File "test/test.py", line 13, in deep 15 | assert val > 10 and foo == 60 16 | | -> 52 17 | -> 17 18 | AssertionError: assert val > 10 and foo == 60 19 | 20 | 21 | 22 | python3 test/test_color.py 23 | 24 | 25 | False 26 | 27 | 28 | 29 | python3 test/test_encoding.py 30 | 31 | 32 | Traceback (most recent call last): 33 | File "test/test_encoding.py", line 14, in 34 | div() 35 | -> 36 | File "test/test_encoding.py", line 11, in div 37 | return _deep("天") 38 | -> 39 | File "test/test_encoding.py", line 8, in _deep 40 | return 1 / val 41 | -> '天' 42 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 43 | 44 | 45 | 46 | ./test/test_interactive.sh 47 | 48 | 49 | spawn python -m better_exceptions -q 50 | >>> import better_exceptions 51 | >>> def foo(a): 52 | ... assert a > 10 53 | ... 54 | >>> foo(1) 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | foo(1) 58 | ->  59 | File "", line 2, in foo 60 | assert a > 10 61 |  -> 1 62 | AssertionError: assert a > 10 63 | >>> exit() 64 | 65 | 66 | 67 | ./test/test_string.sh 68 | 69 | 70 | 71 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 72 | 73 | Traceback (most recent call last): 74 | File "", line 1, in 75 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 76 | | | -> 5 77 | | -> 5 78 | -> 79 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 80 | 81 | Traceback (most recent call last): 82 | File "", line 1, in 83 | import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 84 | | | -> 5 85 | | -> 5 86 | -> 87 | AssertionError: import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine 88 | 89 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 90 | 91 | Traceback (most recent call last): 92 | File "", line 1, in 93 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 94 | | | -> 'why hello there' 95 | | -> 'why hello there' 96 | -> 97 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 98 | why hello there 99 | 100 | Traceback (most recent call last): 101 | File "", line 1, in 102 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 103 | | | -> 'why hello there' 104 | | -> 'why hello there' 105 | -> 106 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False 107 | why hello there 108 | 109 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 110 | 111 | Traceback (most recent call last): 112 | File "", line 1, in 113 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 114 | | | -> 'why hello there' 115 | | -> 'why hello there' 116 | -> 117 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 118 | why hello there 119 | 120 | Traceback (most recent call last): 121 | File "", line 1, in 122 | from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 123 | | | -> 'why hello there' 124 | | -> 'why hello there' 125 | -> 126 | AssertionError: from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False 127 | why hello there 128 | 129 | 130 | 131 | python3 test/test_logging.py 132 | 133 | 134 | ERROR:__main__:callback failed 135 | Traceback (most recent call last): 136 | File "test/test_logging.py", line 16, in foo 137 | cb() 138 | -> 139 | File "test/test_logging.py", line 33, in bar3 140 | raise Exception('this is a test exception') 141 | Exception: this is a test exception 142 | 143 | ERROR:__main__:callback failed 144 | Traceback (most recent call last): 145 | File "test/test_logging.py", line 16, in foo 146 | cb() 147 | -> 148 | File "test/test_logging.py", line 38, in bar4 149 | assert baz == 90 150 | -> 52 151 | AssertionError: assert baz == 90 152 | 153 | 154 | 155 | 156 | python3 test/test_truncating.py 157 | 158 | 159 | Traceback (most recent call last): 160 | File "test/test_truncating.py", line 12, in 161 | div() 162 | -> '999999999... 166 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 167 | 168 | 169 | 170 | python3 test/test_truncating_disabled.py 171 | 172 | 173 | Traceback (most recent call last): 174 | File "test/test_truncating_disabled.py", line 12, in 175 | div() 176 | -> 177 | File "test/test_truncating_disabled.py", line 9, in div 178 | return 1 / var 179 | -> '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999' 180 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 181 | 182 | 183 | 184 | python3 test/test_indentation_error.py 185 | 186 | 187 | Traceback (most recent call last): 188 | File "test/test_indentation_error.py", line 13, in 189 | exec(code) 190 | -> '\nif True:\n a = 5\n print("foobar") #intentional faulty indentation here.\n b = 7\n' 191 | File "", line 4 192 | print("foobar") #intentional faulty indentation here. 193 | ^ 194 | IndentationError: unexpected indent 195 | 196 | 197 | 198 | python3 test/test_syntax_error.py 199 | 200 | 201 | Traceback (most recent call last): 202 | File "test/test_syntax_error.py", line 12, in 203 | exec(code) 204 | -> '\nif True:\n a = 5\n b = 7 *\n' 205 | File "", line 4 206 | b = 7 * 207 | ^ 208 | SyntaxError: invalid syntax 209 | 210 | 211 | 212 | python3 test/test_unittest_patch.py 213 | 214 | 215 | return a + b 216 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 217 | self._callTestMethod(testMethod) 218 | | -> > 219 | -> <__main__.MyTestCase testMethod=test_add> 220 | File "/removed/for/test/purposes.ext", line 633, in _callTestMethod 221 | method() 222 | -> > 223 | File "test/test_unittest_patch.py", line 15, in test_add 224 | self.assertEqual(add(1, "2"), 3) 225 | | -> 226 | -> <__main__.MyTestCase testMethod=test_add> 227 | File "test/test_unittest_patch.py", line 10, in add 228 | return a + b 229 | | -> '2' 230 | -> 1 231 | TypeError: unsupported operand type(s) for +: 'int' and 'str' 232 | 233 | 234 | 235 | python3 test/test_chaining.py 236 | 237 | 238 | Traceback (most recent call last): 239 | File "test/test_chaining.py", line 11, in cause 240 | div(x, y) 241 | | | -> 0 242 | | -> 1 243 | -> 244 | File "test/test_chaining.py", line 7, in div 245 | x / y 246 | | -> 0 247 | -> 1 248 | ZeroDivisionError: division by zero 249 | 250 | During handling of the above exception, another exception occurred: 251 | 252 | Traceback (most recent call last): 253 | File "test/test_chaining.py", line 17, in context 254 | cause(x, y) 255 | | | -> 0 256 | | -> 1 257 | -> 258 | File "test/test_chaining.py", line 13, in cause 259 | raise ValueError("Division error") 260 | ValueError: Division error 261 | 262 | The above exception was the direct cause of the following exception: 263 | 264 | Traceback (most recent call last): 265 | File "test/test_chaining.py", line 21, in 266 | context(1, 0) 267 | -> 268 | File "test/test_chaining.py", line 19, in context 269 | raise ValueError("Cause error") from e 270 | ValueError: Cause error 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | import better_exceptions 2 | better_exceptions.hook() 3 | 4 | foo = 52 5 | 6 | 7 | def shallow(a, b): 8 | deep(a + b) 9 | 10 | 11 | def deep(val): 12 | global foo 13 | assert val > 10 and foo == 60 14 | 15 | 16 | bar = foo - 50 17 | shallow(bar, 15) 18 | shallow(bar, 2) 19 | -------------------------------------------------------------------------------- /test/test_chaining.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | 6 | def div(x, y): 7 | x / y 8 | 9 | def cause(x, y): 10 | try: 11 | div(x, y) 12 | except Exception: 13 | raise ValueError("Division error") 14 | 15 | def context(x, y): 16 | try: 17 | cause(x, y) 18 | except Exception as e: 19 | raise ValueError("Cause error") from e 20 | 21 | context(1, 0) 22 | -------------------------------------------------------------------------------- /test/test_color.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from better_exceptions import color 3 | print(color.SUPPORTS_COLOR) 4 | -------------------------------------------------------------------------------- /test/test_encoding.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | 6 | 7 | def _deep(val): 8 | return 1 / val 9 | 10 | def div(): 11 | return _deep("天") 12 | 13 | 14 | div() 15 | -------------------------------------------------------------------------------- /test/test_indentation_error.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | 6 | code = """ 7 | if True: 8 | a = 5 9 | print("foobar") #intentional faulty indentation here. 10 | b = 7 11 | """ 12 | 13 | exec(code) 14 | -------------------------------------------------------------------------------- /test/test_interactive.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -z "${BETEXC_PYTHON}" ] && export BETEXC_PYTHON=python 4 | 5 | /usr/bin/expect -f - <>> " 8 | send "import better_exceptions\r" 9 | expect ">>> " 10 | send "def foo(a):\r" 11 | expect "... " 12 | send " assert a > 10\r" 13 | expect "... " 14 | send "\r" 15 | expect ">>> " 16 | send "foo(1)\r" 17 | expect ">>> " 18 | send "exit()\r" 19 | expect "exit" 20 | EOF 21 | -------------------------------------------------------------------------------- /test/test_interactive_raw.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -z "${BETEXC_PYTHON}" ] && export BETEXC_PYTHON=python 4 | 5 | /usr/bin/expect -f - <>> " 8 | send "import better_exceptions\r" 9 | expect ">>> " 10 | send "def foo(a):\r" 11 | expect "... " 12 | send " assert a > 10\r" 13 | expect "... " 14 | send "\r" 15 | expect ">>> " 16 | send "foo(1)\r" 17 | expect ">>> " 18 | send "exit()\r" 19 | expect "exit" 20 | EOF 21 | -------------------------------------------------------------------------------- /test/test_logging.py: -------------------------------------------------------------------------------- 1 | import better_exceptions 2 | import logging 3 | 4 | better_exceptions.hook() 5 | logging.basicConfig() 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | logging.raiseExceptions = True 10 | 11 | qux = 15 12 | 13 | def foo(cb): 14 | qix = 20 15 | try: 16 | cb() 17 | except: 18 | logger.exception('callback failed') 19 | 20 | 21 | def bar1(): 22 | baz = 80.5 23 | logger.info('Hello') 24 | 25 | 26 | def bar2(): 27 | baz = 890.50 28 | logger.info('Hello', exc_info=True) 29 | 30 | 31 | def bar3(): 32 | baz = 600.524 33 | raise Exception('this is a test exception') 34 | 35 | 36 | def bar4(): 37 | baz = 52 38 | assert baz == 90 39 | 40 | 41 | FNS = [ 42 | bar1, 43 | bar2, 44 | bar3, 45 | bar4 46 | ] 47 | 48 | 49 | for fn in FNS: 50 | foo(fn) 51 | -------------------------------------------------------------------------------- /test/test_string.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | [ -z "${BETEXC_PYTHON}" ] && export BETEXC_PYTHON=python 5 | 6 | function test_str { 7 | echo -e "\n${1}\n" 8 | 9 | # first, test with regular args 10 | "${BETEXC_PYTHON}" -c "${@}" || true 11 | 12 | echo '' 13 | 14 | # now test with a condensed argument 15 | cmd="${1}" 16 | shift 17 | "${BETEXC_PYTHON}" -c"$cmd" "${@}" || true 18 | } 19 | 20 | test_str 'import better_exceptions; better_exceptions.hook(); a = 5; assert a > 10 # this should work fine' these extra arguments should be removed and should not show up 'in' the output 21 | test_str 'from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello there"; print(a); assert False' 22 | test_str 'from __future__ import print_function; import better_exceptions; better_exceptions.hook(); a = "why hello " + " there"; print(a); assert False' 23 | -------------------------------------------------------------------------------- /test/test_syntax_error.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | 6 | code = """ 7 | if True: 8 | a = 5 9 | b = 7 * 10 | """ 11 | 12 | exec(code) 13 | -------------------------------------------------------------------------------- /test/test_truncating.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | better_exceptions.MAX_LENGTH = 10 6 | 7 | def div(): 8 | var = "9" * 150 9 | return 1 / var 10 | 11 | 12 | div() 13 | -------------------------------------------------------------------------------- /test/test_truncating_disabled.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import better_exceptions 4 | better_exceptions.hook() 5 | better_exceptions.MAX_LENGTH = None 6 | 7 | def div(): 8 | var = "9" * 150 9 | return 1 / var 10 | 11 | 12 | div() 13 | -------------------------------------------------------------------------------- /test/test_unittest_patch.py: -------------------------------------------------------------------------------- 1 | import io 2 | import sys 3 | import unittest 4 | import better_exceptions 5 | 6 | STREAM = io.BytesIO() if sys.version_info[0] == 2 else io.StringIO() 7 | 8 | 9 | def add(a, b): 10 | return a + b 11 | 12 | 13 | class MyTestCase(unittest.TestCase): 14 | def test_add(self): 15 | self.assertEqual(add(1, "2"), 3) 16 | 17 | 18 | class SilentTestRunner(unittest.TextTestRunner): 19 | """ 20 | The default TextTestRunner will print something like 'Ran 1 test in 0.017s' 21 | into stderr, and values are different from different tests. To ensure that 22 | CI script can compare between the outputs, those information must be muted. 23 | """ 24 | 25 | def __init__(self, stream=STREAM, *args, **kwargs): 26 | super(SilentTestRunner, self).__init__(STREAM, *args, **kwargs) 27 | 28 | 29 | def print_test_error(): 30 | test = unittest.main(exit=False, testRunner=SilentTestRunner) 31 | error = test.result.errors[0][1] 32 | # unittest.TestResult.errors is "A list containing 2-tuples of TestCase 33 | # instances and strings holding formatted tracebacks. Each tuple represents 34 | # a test which raised an unexpected exception." 35 | assert isinstance(error, str) 36 | lines = error.splitlines() 37 | print("\n".join(lines[4:])) # remove '/removed/for/test/purposes.py' 38 | 39 | 40 | def main(): 41 | print_test_error() 42 | 43 | def patch(self, err, test): 44 | lines = better_exceptions.format_exception(*err) 45 | if sys.version_info[0] == 2: 46 | return u"".join(lines).encode("utf-8") 47 | else: 48 | return u"".join(lines) 49 | 50 | unittest.result.TestResult._exc_info_to_string = patch 51 | 52 | print_test_error() 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /test/test_util/__init__.py: -------------------------------------------------------------------------------- 1 | # DO NOT ADD AS setup.py MODULE! 2 | -------------------------------------------------------------------------------- /test/test_util/quiet_console.py: -------------------------------------------------------------------------------- 1 | import code 2 | 3 | class QuietConsole(code.InteractiveConsole, object): 4 | def __init__(self): 5 | super(self.__class__, self).__init__() 6 | 7 | def interact(self): 8 | return super(self.__class__, self).interact('') 9 | 10 | 11 | if __name__ == '__main__': 12 | QuietConsole().interact() 13 | -------------------------------------------------------------------------------- /test_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ "${1}" == "generate" ]]; then 6 | rm -rf test/output 7 | mkdir -p test/output 8 | GENERATE=true 9 | else 10 | GENERATE=false 11 | fi 12 | 13 | cd "$(dirname "${0}")" 14 | PYTHONPATH="$(pwd):$(pwd)/test" 15 | export PYTHONPATH 16 | 17 | if [[ -z "$PYTHON" ]]; then 18 | PYTHON=python3 19 | fi 20 | 21 | function normalize { 22 | # we translate anything that looks like an address into 0xDEADBEEF 23 | # since the addresses change from run to run and break diff testing 24 | cat | sed 's|0x[a-fA-F0-9]\{1,\}|0xDEADBEEF|g' | sed 's||"'|g' | sed 's|File "/[^"]*"|File "/removed/for/test/purposes.ext"|g' | grep -v "bash: warning:" 25 | } 26 | 27 | function test_case { 28 | echo -e "\x1b[36;1m " "$@" "\x1b[m" 1>&2 29 | 30 | echo "$@" 31 | echo -e "\n" 32 | ("$@" 2>&1 || true) | normalize 33 | echo -e "\n\n" 34 | 35 | return $? 36 | } 37 | 38 | function test_all { 39 | test_case "$PYTHON" "test/test.py" 40 | test_case "$PYTHON" "test/test_color.py" 41 | test_case "$PYTHON" "test/test_encoding.py" 42 | test_case "./test/test_interactive.sh" 43 | # test_case "./test/test_interactive_raw.sh" 44 | test_case "./test/test_string.sh" 45 | test_case "$PYTHON" "test/test_logging.py" 46 | test_case "$PYTHON" "test/test_truncating.py" 47 | test_case "$PYTHON" "test/test_truncating_disabled.py" 48 | test_case "$PYTHON" "test/test_indentation_error.py" 49 | test_case "$PYTHON" "test/test_syntax_error.py" 50 | test_case "$PYTHON" "test/test_unittest_patch.py" 51 | 52 | if [[ "$PYTHON" == "python3" ]]; then 53 | test_case "$PYTHON" "test/test_chaining.py" 54 | fi 55 | } 56 | 57 | for encoding in ascii "UTF-8"; do 58 | for term in xterm vt100 dumb; do 59 | for color in 0 1; do 60 | [[ $color == "1" ]] && color_filename="color" || color_filename="nocolor" 61 | filename="test/output/python3-${term}-${encoding}-${color_filename}.out" 62 | 63 | export PYTHONIOENCODING="${encoding}" 64 | export TERM="${term}" 65 | export FORCE_COLOR="${color}" 66 | 67 | echo -e "\x1b[35;1m${filename}\x1b[m" >&2 68 | if $GENERATE; then 69 | exec > "$filename" 70 | test_all "$filename" 71 | else 72 | test_all | diff "$(pwd)/${filename}" - 73 | fi 74 | done 75 | done 76 | done 77 | --------------------------------------------------------------------------------