Add unittest patch (#77)

This commit is contained in:
ocavue 2019-09-28 23:41:45 +08:00 committed by Qix
parent 4dab3e218a
commit 7b41752775
27 changed files with 509 additions and 2 deletions

View File

@ -45,6 +45,26 @@ better_exceptions.MAX_LENGTH = None
While using `better_exceptions` in production, do not forget to unset the `BETTER_EXCEPTIONS` variable to avoid leaking sensitive data in your logs.
### Use with unittest
If you want to use `better_exceptions` to format `unittest`'s exception output, you can use the monkey patch below:
```python
import sys
import unittest
import better_exceptions
def patch(self, err, test):
lines = better_exceptions.format_exception(*err)
if sys.version_info[0] == 2:
return u"".join(lines).encode("utf-8")
return "".join(lines)
unittest.result.TestResult._exc_info_to_string = patch
```
Note that this uses an undocumented method override, so it is **not** guaranteed to work on all platforms or versions of Python.
### Django Usage
_Tested with Django 1.11_
@ -81,8 +101,6 @@ example output:
![image](https://user-images.githubusercontent.com/157132/56871937-5a07b480-69f1-11e9-9fd5-fac12382ebb7.png)
## Troubleshooting
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.

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -219,3 +219,20 @@ SyntaxError: invalid syntax
python2 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 │ └ '2'
 └ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
└ <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
│ └ <function add at 0xDEADBEEF>
└ <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
│ └ '2'
└ 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, '2'), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
 | -> '2'
 -> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -209,6 +209,25 @@ SyntaxError: invalid syntax
python3 test/test_unittest_patch.py
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
testMethod()
-> <bound method MyTestCase.test_add of <__main__.MyTestCase testMethod=test_add>>
File "test/test_unittest_patch.py", line 15, in test_add
self.assertEqual(add(1, "2"), 3)
| -> <function add at 0xDEADBEEF>
-> <__main__.MyTestCase testMethod=test_add>
File "test/test_unittest_patch.py", line 10, in add
return a + b
| -> '2'
-> 1
TypeError: unsupported operand type(s) for +: 'int' and 'str'
python3 test/test_chaining.py

View File

@ -0,0 +1,56 @@
import io
import sys
import unittest
import better_exceptions
STREAM = io.BytesIO() if sys.version_info[0] == 2 else io.StringIO()
def add(a, b):
return a + b
class MyTestCase(unittest.TestCase):
def test_add(self):
self.assertEqual(add(1, "2"), 3)
class SilentTestRunner(unittest.TextTestRunner):
"""
The default TextTestRunner will print something like 'Ran 1 test in 0.017s'
into stderr, and values are different from different tests. To ensure that
CI script can compare between the outputs, those information must be muted.
"""
def __init__(self, stream=STREAM, *args, **kwargs):
super(SilentTestRunner, self).__init__(STREAM, *args, **kwargs)
def print_test_error():
test = unittest.main(exit=False, testRunner=SilentTestRunner)
error = test.result.errors[0][1]
# unittest.TestResult.errors is "A list containing 2-tuples of TestCase
# instances and strings holding formatted tracebacks. Each tuple represents
# a test which raised an unexpected exception."
assert isinstance(error, str)
lines = error.splitlines()
print("\n".join(lines[4:])) # remove '/removed/for/test/purposes.py'
def main():
print_test_error()
def patch(self, err, test):
lines = better_exceptions.format_exception(*err)
if sys.version_info[0] == 2:
return u"".join(lines).encode("utf-8")
else:
return u"".join(lines)
unittest.result.TestResult._exc_info_to_string = patch
print_test_error()
if __name__ == "__main__":
main()

View File

@ -43,6 +43,7 @@ function test_all {
test_case "$BETEXC_PYTHON" "test/test_truncating_disabled.py"
test_case "$BETEXC_PYTHON" "test/test_indentation_error.py"
test_case "$BETEXC_PYTHON" "test/test_syntax_error.py"
test_case "$BETEXC_PYTHON" "test/test_unittest_patch.py"
if [[ "$BETEXC_PYTHON" == "python3" ]]; then
test_case "$BETEXC_PYTHON" "test/test_chaining.py"