Here is my test sample (test_time.py):
#!/usr/bin/python
# -*- coding: utf-8 -*-
import pytest
from datetime import datetime, timedelta
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
#pytest.mark.parametrize("a,b,expected", testdata, ids=[u"中文", u"English"])
def test_timedistance_v1(a, b, expected):
diff = a - b
assert diff != expected
Here is the pytest output:
============================================================================== FAILURES ==============================================================================
_________________________________________________________________ test_timedistance_v1[\u4e2d\u6587] _________________________________________________________________
a = datetime.datetime(2001, 12, 12, 0, 0), b = datetime.datetime(2001, 12, 11, 0, 0), expected = datetime.timedelta(1)
#pytest.mark.parametrize("a,b,expected", testdata, ids=[u"中文", u"English"])
def test_timedistance_v1(a, b, expected):
diff = a - b
> assert diff != expected
E assert datetime.timedelta(1) != datetime.timedelta(1)
test_time.py:15: AssertionError
___________________________________________________________________ test_timedistance_v1[English] ____________________________________________________________________
a = datetime.datetime(2001, 12, 11, 0, 0), b = datetime.datetime(2001, 12, 12, 0, 0), expected = datetime.timedelta(-1)
#pytest.mark.parametrize("a,b,expected", testdata, ids=[u"中文", u"English"])
def test_timedistance_v1(a, b, expected):
diff = a - b
> assert diff != expected
E assert datetime.timedelta(-1) != datetime.timedelta(-1)
test_time.py:15: AssertionError
====================================================================== 2 failed in 0.05 seconds ======================================================================
For the second line in output , the test name is "test_timedistance_v1[\u4e2d\u6587]" , I hope it's "test_timedistance_v1[中文]", does py.test support it?
(my pytest version is 3.1.2, OS: macOS 10.12.5)
It does not depend of pytest but of your computer locale.
Here the trace-log of test (LC_ALL="en_US.UTF-8") :
================================ test session starts ================================
platform linux -- Python 3.5.3, pytest-2.9.2, py-1.4.34, pluggy-0.3.1
rootdir: /home/..../tmp, inifile:
collected 2 items
pytest_chin.py FF
===================================== FAILURES ======================================
_____________________________ test_timedistance_v1[中文] ______________________________
...
And with with LC_ALL="fr_FR.iso8859-1" :
================================ test session starts ================================
platform linux -- Python 3.5.3, pytest-2.9.2, py-1.4.34, pluggy-0.3.1
rootdir: /home/gustavi/tmp, inifile:
collected 2 items
pytest_chin.py FF
===================================== FAILURES ======================================
\x1b[1m\x1b[31m_____________________________ test_timedistance_v1[\u4e2d\u6587] ______________________________\x1b[0m
...
Here an usefull link to setup your locale on OS X.
Related
I am trying to learn pytest and testing my knowledge on the below code.
src.py
def a(s):
if s == 1:
return True
if s == 2:
return False
return 3
def abc(u):
if u ==1:
if a(u):
return 1
else:
if a(u):
return 2
else:
return 3
and this is my test file:
import pytest
import src
#pytest.mark.parametrize("input_data, expected", [(1,1), (2,2), (3,2)])
def test_abc(input_data, expected, mocker):
s = mocker.patch('src.a', side_effect=[True, False])
assert src.abc(input_data) == expected
s.assert_called_once_with(input_data)
#pytest.mark.parametrize("input_data, expected", [(1,True), (2,False), (3,3)])
def test_a(input_data, expected,):
assert src.a(input_data) == expected
Testing the code returns all passed, but the coverage reports that the line 16 is not being tested:
% pytest -v --cov=. . --cov-report term-missing
==================================================================== test session starts ====================================================================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 -- /Users/tomhanks/.pyenv/versions/3.10.4/bin/python3.10
cachedir: .pytest_cache
rootdir: /Users/tomhanks/projects/pytest
plugins: mock-3.7.0, cov-3.0.0
collected 6 items
test_me.py::test_abc[1-1] PASSED [ 16%]
test_me.py::test_abc[2-2] PASSED [ 33%]
test_me.py::test_abc[3-2] PASSED [ 50%]
test_me.py::test_a[1-True] PASSED [ 66%]
test_me.py::test_a[2-False] PASSED [ 83%]
test_me.py::test_a[3-3] PASSED [100%]
---------- coverage: platform darwin, python 3.10.4-final-0 ----------
Name Stmts Miss Cover Missing
------------------------------------------
src.py 13 1 92% 16
test_me.py 10 0 100%
------------------------------------------
TOTAL 23 1 96%
===================================================================== 6 passed in 0.07s =====================================================================
Can somebody please help me understand why the line 16 is not being tested?
Thanks in advance!
Trying to do test files in PyCharm with pytest and I repeatedly get the "fixture [variable name] not found. All that I could find regarding this issue are cases of misspelling parametrize.
liste_paie = []
def calculer_paie_employe(tauxh,heures):
total = tauxh * heures
impot = total * 0.20
net = total - impot
liste_paie = [heures, tauxh, total, impot, net]
return liste_paie
pytest.mark.parametrize("var1,var2,expected_1,expected_2,expected_3", [(14.7 , 25,367.5,73.5,294), (20 , 15, 300, 60, 240),
(15.6 , 23.9, 372.84, 75.568, 300)])
def test_calculer_paie_employe(var1,var2, expected_1, expected_2, expected_3):
calculer_paie_employe(var1,var2)
assert liste_paie[2] == expected_1 and liste_paie[3] == expected_2 and liste_paie[4] == expected_3
When I run it I get:
test setup failed
E fixture 'var1' not found
available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
use 'pytest --fixtures [testpath]' for help on them.
Final set of data should fail to pass. (this is intentional)
You must use it as a decorator, i.e. use the # syntax:
liste_paie = []
def calculer_paie_employe(tauxh,heures):
total = tauxh * heures
impot = total * 0.20
net = total - impot
liste_paie = [heures, tauxh, total, impot, net]
return liste_paie
import pytest
#pytest.mark.parametrize(
"var1,var2,expected_1,expected_2,expected_3", [
(14.7, 25, 367.5, 73.5, 294),
(20, 15, 300, 60, 240),
(15.6, 23.9, 372.84, 75.568, 300)
])
def test_calculer_paie_employe(var1,var2, expected_1, expected_2, expected_3):
liste_paie = calculer_paie_employe(var1,var2)
assert liste_paie[2] == expected_1 and liste_paie[3] == expected_2 and liste_paie[4] == expected_3
a pytest run will then produce:
================================================= test session starts =================================================
platform win32 -- Python 3.5.4, pytest-3.10.1, py-1.8.0, pluggy-0.9.0
rootdir: c:\srv\tmp, inifile:
plugins: django-3.10.0, cov-2.6.1
collected 3 items
pytestparm.py ..F [100%]
====================================================== FAILURES =======================================================
_______________________________ test_calculer_paie_employe[15.6-23.9-372.84-75.568-300] _______________________________
var1 = 15.6, var2 = 23.9, expected_1 = 372.84, expected_2 = 75.568, expected_3 = 300
#pytest.mark.parametrize(
"var1,var2,expected_1,expected_2,expected_3", [
(14.7, 25, 367.5, 73.5, 294),
(20, 15, 300, 60, 240),
(15.6, 23.9, 372.84, 75.568, 300)
])
def test_calculer_paie_employe(var1,var2, expected_1, expected_2, expected_3):
liste_paie = calculer_paie_employe(var1,var2)
> assert liste_paie[2] == expected_1 and liste_paie[3] == expected_2 and liste_paie[4] == expected_3
E assert (372.84 == 372.84 and 74.568 == 75.568)
pytestparm.py:19: AssertionError
========================================= 1 failed, 2 passed in 0.04 seconds ==========================================
Note that I've changed the code to use the return value, since the assignment to liste_paie in calculer_paie_employe doesn't change the global variable (because you're missing the global keyword - but using the return value is better practice anyways...)
I would love to see the last 10 lines which were executed by the python interpreter before this exception occured:
test_has_perm_in_foobar.py F
Traceback (most recent call last):
File "/.../test_has_perm_in_foobar.py", line 50, in test_has_perm
self.assertFalse(check_perm(request, some_object))
File "/usr/lib/python2.7/unittest/case.py", line 416, in assertFalse
raise self.failureException(msg)
AssertionError: True is not false
I want to see where check_perm() returned True.
I know that I could use interactive debugging to find the matching line, but I am lazy and want to find a easier way to the line where check_perm() returned the return value.
I use pyCharm, but a text based tool, would solve my need, too.
BTW: Please don't tell me how to use the debugger with step-over and step-into. I know this.
Here is some code to illustrate it.
def check_perm(request, some_object):
if condition_1:
return True
if condition_2:
return sub_check(some_object)
if condition_3:
return sub_check2(some_object)
...
There are several ways where check_perm() could return True. If True was returned because of condition_1, then I want to see something like this
+ if condition_1:
+ return True
The output I have in mind is like set -x on the shell.
Update
cgitb, pytest and other tools can show the lines before the line where the assertion failed. BUT, they only show the lines of the current python file. This question is about the lines which were executed before the assertion happens, but covering all files. In my case I want to know where the return value of check_perm() was created. The tools pytest, cgitb, ... don't show this.
What I am searching is like set -x on the shell:
help set
-x Print commands and their arguments as they are executed.
For this reason I've switched testing to pytest.
It can show local variables and traceback with different detalization level. Line where call was done is marked with >.
For example in my django project:
$ py.test --showlocals --tb=long
=============================== test session starts ===============================
platform darwin -- Python 3.5.1, pytest-3.0.3, py-1.4.31, pluggy-0.4.0
Django settings: dj_tg_bot.settings (from ini file)
rootdir: /Users/el/Projects/dj-tg-alpha-bot, inifile: tox.ini
plugins: django-3.0.0, cov-2.4.0
collected 8 items
tests/test_commands.py ....F
tests/test_logger.py .
tests/test_simple.py ..
==================================== FAILURES =====================================
__________________________ TestSimpleCommands.test_start __________________________
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
def test_start(self,):
"""
Test bot accept normally command /start and replies as it should.
"""
> self._test_message_ok(self.start)
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
tests/test_commands.py:56:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
action = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}}
update = <telegram.update.Update object at 0x113e16cf8>, number = 1
def _test_message_ok(self, action, update=None, number=1):
if not update:
update = self.update
with mock.patch("telegram.bot.Bot.sendMessage", callable=mock.MagicMock()) as mock_send:
if 'in' in action:
update.message.text = action['in']
response = self.client.post(self.webhook_url, update.to_json(), **self.kwargs)
# Check response 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Check
> self.assertBotResponse(mock_send, action)
action = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}}
mock_send = <MagicMock name='sendMessage' id='4619939344'>
number = 1
response = <Response status_code=200, "application/json">
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
update = <telegram.update.Update object at 0x113e16cf8>
../../.pyenv/versions/3.5.1/lib/python3.5/site-packages/telegrambot/test/testcases.py:83:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
mock_send = <MagicMock name='sendMessage' id='4619939344'>
command = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}}
def assertBotResponse(self, mock_send, command):
> args, kwargs = mock_send.call_args
E TypeError: 'NoneType' object is not iterable
command = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}}
mock_send = <MagicMock name='sendMessage' id='4619939344'>
self = <tests.test_commands.TestSimpleCommands testMethod=test_start>
../../.pyenv/versions/3.5.1/lib/python3.5/site-packages/telegrambot/test/testcases.py:61: TypeError
------------------------------ Captured stderr call -------------------------------
Handler not found for {'message': {'from': {'username': 'username_4', 'last_name': 'last_name_4', 'id': 5, 'first_name': 'first_name_4'}, 'chat': {'username': 'username_4', 'last_name': 'last_name_4', 'first_name': 'first_name_4', 'title': 'title_4', 'type': 'private', 'id': 5}, 'text': ' /start', 'message_id': 5, 'date': 1482500826}, 'update_id': 5}
======================= 1 failed, 7 passed in 2.29 seconds ========================
(.env) ✘-1 ~/Projects/dj-tg-alpha-bot [master|✚ 1…8⚑ 12]
16:47 $
What about cgitb? You just need import this module to your code.
import cgitb
cgitb.enable(format='text')
def f():
a = 1
b = 2
c = 3
x = 0
d = a * b * c / x
return d
if __name__ == "__main__":
f()
Gives:
ZeroDivisionError
Python 3.5.2: /usr/bin/python3
Mon Dec 19 17:42:34 2016
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.
/home/user1/123.py in <module>()
10 d = a * b * c / x
11 return x
12
13 if __name__ == "__main__":
14 f()
f = <function f>
/home/user1/123.py in f()
8 c = 3
9 x = 0
10 d = a * b * c / x
11 return x
12
d undefined
a = 1
b = 2
c = 3
x = 0
ZeroDivisionError: division by zero
...
The above is a description of an error in a Python program. Here is
the original traceback:
Traceback (most recent call last):
File "123.py", line 14, in <module>
f()
File "123.py", line 10, in f
d = a * b * c / x
ZeroDivisionError: division by zero
Since I could not find a solution, I wrote this myself:
with trace_function_calls():
self.assertFalse(check_perm(request, some_object))
Implementation of trace_function_calls():
class trace_function_calls(object):
depth_symbol = '+'
def __init__(self, write_method=None, log_lines=True):
'''
write_method: A method which gets called for every executed line. Defauls to logger.info
# Simple example:
with debugutils.trace_function_calls():
method_you_want_to_trace()
'''
if write_method is None:
write_method=logger.info
self.write_method = write_method
self.log_lines = log_lines
def __enter__(self):
self.old = sys.gettrace()
self.depth = 0
sys.settrace(self.trace_callback)
def __exit__(self, type, value, traceback):
sys.settrace(self.old)
def trace_callback(self, frame, event, arg):
# from http://pymotw.com/2/sys/tracing.html#tracing-function-calls
if event == 'return':
self.depth -= 1
return self.trace_callback
if event == 'line':
if not self.log_lines:
return self.trace_callback
elif event == 'call':
self.depth += 1
else:
# self.write_method('unknown: %s' % event)
return self.trace_callback
msg = []
msg.append(self.depth_symbol * self.depth)
co = frame.f_code
func_name = co.co_name
func_line_no = frame.f_lineno
func_filename = co.co_filename
if not is_python_file_from_my_codebase(func_filename):
return self.trace_callback
code_line = linecache.getline(func_filename, func_line_no).rstrip()
msg.append('%s: %s %r on line %s of %s' % (
event, func_name, code_line, func_line_no, func_filename))
self.write_method(' '.join(msg))
return self.trace_callback
PS: This is open source software. If you want to create a python package, do it, tell me, it would make me glad.
The trace module has bourne compatible shell set -x like feature. The trace parameter of trace.Trace class enables line execution tracing. This class takes an ignoredirs parameter which is used to ignore tracing modules or packages located below the specified directory. I use it here to keep the tracer from tracing the unittest module.
test_has_perm_in_foobar.py
import sys
import trace
import unittest
from app import check_perm
tracer = trace.Trace(trace=1, ignoredirs=(sys.prefix, sys.exec_prefix))
class Test(unittest.TestCase):
def test_one(self):
tracer.runctx('self.assertFalse(check_perm("dummy", 3))', globals(), locals())
if __name__ == '__main__':
unittest.main()
app.py
def sub_check1(some_object):
if some_object * 10 == 20:
return True
def sub_check2(some_object):
if some_object * 10 == 30:
return True
def check_perm(request, some_object):
if some_object == 1:
return True
if some_object == 2:
return sub_check1(some_object)
if some_object == 3:
return sub_check2(some_object)
Test;
$ python test_has_perm_in_foobar.py
--- modulename: test_has_perm_in_foobar, funcname: <module>
<string>(1): --- modulename: app, funcname: check_perm
app.py(10): if some_object == 1:
app.py(12): if some_object == 2:
app.py(14): if some_object == 3:
app.py(15): return sub_check2(some_object)
--- modulename: app, funcname: sub_check2
app.py(6): if some_object * 10 == 30:
app.py(7): return True
F
======================================================================
FAIL: test_one (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_has_perm_in_foobar.py", line 23, in test_one
tracer.runctx('self.assertFalse(check_perm("dummy", 3))', globals(), locals())
File "/usr/lib/python2.7/trace.py", line 513, in runctx
exec cmd in globals, locals
File "<string>", line 1, in <module>
AssertionError: True is not false
----------------------------------------------------------------------
Ran 1 test in 0.006s
FAILED (failures=1)
To make the code and the output even more shorter, just trace the required function alone.
import trace
import unittest
from app import check_perm
tracer = trace.Trace(trace=1)
class Test(unittest.TestCase):
def test_one(self):
self.assertFalse(tracer.runfunc(check_perm, 'dummy', 3))
if __name__ == '__main__':
unittest.main()
Test;
$ python test_has_perm_in_foobar.py
--- modulename: app, funcname: check_perm
app.py(10): if some_object == 1:
app.py(12): if some_object == 2:
app.py(14): if some_object == 3:
app.py(15): return sub_check2(some_object)
--- modulename: app, funcname: sub_check2
app.py(6): if some_object * 10 == 30:
app.py(7): return True
F
======================================================================
FAIL: test_one (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_has_perm_in_foobar.py", line 19, in test_one
self.assertFalse(tracer.runfunc(check_perm, 'dummy', 3))
AssertionError: True is not false
----------------------------------------------------------------------
Ran 1 test in 0.005s
FAILED (failures=1)
Have you considered the following workflow? I read your BTW but hard rules sometimes stop us from solving our problems(especially if you are in an XY trap) so I'm going to suggest you use the debugger anyway. I run into tests that fail all the time. When a full stack trace is critical to solving the problem, I use a combination of pdb and py.test to get the whole shebang. Considering the following program...
import pytest
#pytest.mark.A
def test_add():
a = 1
b = 2
add(a,b)
def add(a, b):
assert a>b
return a+b
def main():
add(1,2)
add(2,1)
if __name__ == "__main__":
# execute only if run as a script
main()
Running the command py.test -v -tb=short -m A code.py results in the following output...
art#macky ~/src/python/so-answer-stacktrace: py.test -v --tb=short -m A code.py
============================= test session starts ==============================
platform darwin -- Python 2.7.5 -- pytest-2.5.0 -- /Users/art/.pyenv/versions/2.7.5/bin/python
collected 1 items
code.py:3: test_add FAILED
=================================== FAILURES ===================================
___________________________________ test_add ___________________________________
code.py:9: in test_add
> add(a,b)
code.py:12: in add
> assert a>b
E assert 1 > 2
=========================== 1 failed in 0.01 seconds ===========================
One simple way to investigate the stack trace is to drop a pdb debug point in the test, Mark the individual test with a pytest mark, invoke that test, and inspect the stack inside the debugger. like so...
def add(a, b):
from pdb import set_trace;set_trace()
assert a>b
return a+b
Now when I run the same test command again I get a suspended pdb debugger. Like so...
art#macky ~/src/python/so-answer-stacktrace: py.test -v --tb=short -m A code.py
=========================================================================================== test session starts ============================================================================================
platform darwin -- Python 2.7.5 -- pytest-2.5.0 -- /Users/art/.pyenv/versions/2.7.5/bin/python
collected 1 items
code.py:3: test_add
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /Users/art/src/python/so-answer-stacktrace/code.py(13)add()
-> assert a>b
(Pdb)
If at this point I type the magic w for where and hit enter I see the full stack trace in all its glory...
(Pdb) w
/Users/art/.pyenv/versions/2.7.5/bin/py.test(9)<module>()
-> load_entry_point('pytest==2.5.0', 'console_scripts', 'py.test')()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/config.py(19)main()
-> return config.hook.pytest_cmdline_main(config=config)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__()
-> return self._docall(methods, kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall()
-> res = mc.execute()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute()
-> res = method(**kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(111)pytest_cmdline_main()
-> return wrap_session(config, _main)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(81)wrap_session()
-> doit(config, session)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(117)_main()
-> config.hook.pytest_runtestloop(session=session)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__()
-> return self._docall(methods, kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall()
-> res = mc.execute()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute()
-> res = method(**kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(137)pytest_runtestloop()
-> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__()
-> return self._docall(methods, kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall()
-> res = mc.execute()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute()
-> res = method(**kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(62)pytest_runtest_protocol()
-> runtestprotocol(item, nextitem=nextitem)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(72)runtestprotocol()
-> reports.append(call_and_report(item, "call", log))
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(106)call_and_report()
-> call = call_runtest_hook(item, when, **kwds)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(124)call_runtest_hook()
-> return CallInfo(lambda: ihook(item=item, **kwds), when=when)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(137)__init__()
-> self.result = func()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(124)<lambda>()
-> return CallInfo(lambda: ihook(item=item, **kwds), when=when)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(161)call_matching_hooks()
-> return hookmethod.pcall(plugins, **kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(380)pcall()
-> return self._docall(methods, kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall()
-> res = mc.execute()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute()
-> res = method(**kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(86)pytest_runtest_call()
-> item.runtest()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/python.py(1076)runtest()
-> self.ihook.pytest_pyfunc_call(pyfuncitem=self)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(161)call_matching_hooks()
-> return hookmethod.pcall(plugins, **kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(380)pcall()
-> return self._docall(methods, kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall()
-> res = mc.execute()
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute()
-> res = method(**kwargs)
/Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/python.py(188)pytest_pyfunc_call()
-> testfunction(**testargs)
/Users/art/src/python/so-answer-stacktrace/code.py(9)test_add()
-> add(a,b)
> /Users/art/src/python/so-answer-stacktrace/code.py(13)add()
-> assert a>b
(Pdb)
I do a lot of work in frameworks. pdb + where gives you everything up to the actual entry point of the program. You can see buried in there my functions as well as the test runner's frames. If this were Django or Flask I would see all the stack frames involved with the internals of those frameworks. Its my full stop gap for when things go really wrong.
If you have a test with lots of iterations or conditionals you might find yourself getting hung up on the same lines again and again. The solution is to be clever about where you choose to instrument with pdb. Nesting it inside a conditional or instrumenting an iteration/recursion with a conditional(essentially saying When this becomes True then suspend so I can inspect what is going on). Furthermore pdb lets you look at all the runtime context(assignments, state, etc.)
For your case it looks like a creative instrumentation of check_perm is in order.
I'm using dbus api for NetworkManager 1.4.0 with python wrapper. This code scan wifi networks:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import NetworkManager as NM
def ap_sec_to_str2(sec):
list = []
if sec & (0x1 | 0x2 | 0x10 | 0x20):
list.append("WEP")
if sec & (0x4 | 0x40):
list.append("WPA")
if sec & (0x8 | 0x80):
list.append("WPA2")
if sec & 0x100:
list.append("PSK")
if sec & 0x200:
list.append("802.1X")
return list
def MHZ2Chan(mhz):
return {
2412: 1,
2417: 2,
2422: 3,
2427: 4,
2432: 5,
2437: 6,
2442: 7,
2447: 8,
2452: 9,
2457: 10,
2462: 11,
2467: 12,
2472: 13,
2484: 14
}.get(mhz)
"""WIFI NetwokList"""
def APList(iface):
check = lambda dev: dev.DeviceType == NM.NM_DEVICE_TYPE_WIFI and dev.Interface == iface
dev = next( (dev for dev in NM.NetworkManager.GetDevices() if check(dev) ))
rslt = []
dev = dev.SpecificDevice()
dev.RequestScan({}) #this is wrong!!!
for ap in dev.GetAllAccessPoints():
info = {
'ssid': ap.Ssid,
'channel': MHZ2Chan(ap.Frequency),
'level': ap.Strength,
'security': ap_sec_to_str2(ap.WpaFlags | ap.RsnFlags)
}
rslt.append(info)
return rslt
If I often call this function sometimes occurs error
org.freedesktop.NetworkManager.Device.NotAllowed:Scanningnotallowedimmediatelyfollowingpreviousscan.
Whether it's hardware features?
Platform ARM CortexA9 Yocto 1.6
WIFI usb dongle:
ID 20f4:648b TRENDnetTEW-648UBM802.11n150MbpsMicroWirelessNAdapter[RealtekRTL8188CUS]
Running mkpasswd -m sha-512 -S salt1234 password results in the following:
$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
I have this snippet of Python code that I thought would output the same, but isn't:
import hashlib, base64
print(base64.b64encode(hashlib.sha512('password' + 'salt1234').digest()))
It instead results in:
nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig==
Not sure what I am doing wrong.
Another question I have is, how do I tell sha512 function to do custom rounds. It seems to take only 1 argument.
mkpasswd is a front-end to the crypt() function. I don't think it is a straight-forward SHA512 hash here.
A little research points to the specification for SHA256-crypt and SHA512-crypt, which shows the hash is applied a default 5000 times. You can specify a different number of rounds using the -R switch to mkpasswd; -R 5000 indeed gives you the same output:
$ mkpasswd -m sha-512 -S salt1234 -R 5000 password
$6$rounds=5000$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
The minimum number of rounds offered by the command-line tool is 1000:
$ mkpasswd -m sha-512 -S salt1234 -R 999 password
$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
$ mkpasswd -m sha-512 -S salt1234 -R 1 password
$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
The algorithm is a bit more involved, requiring you to create several digests. You could instead access the C crypt() function through the crypt.crypt() function, and drive it the same way the mkpasswd commandline does.
It depends on your platform if the SHA512-crypt method is available; the Python 3 version of the crypt module offers a crypt.methods list that tells you what methods your platform supports. Since this use the exact same library mkpasswd uses, your OS obviously does support SHA512-crypt and Python will have access too.
You need to prefix the salt with '$6$ to specify the different method. You can specify the number of rounds by adding a 'rounds=<N>$' string between the '$6$' string and your salt:
import crypt
import os
import string
try: # 3.6 or above
from secrets import choice as randchoice
except ImportError:
from random import SystemRandom
randchoice = SystemRandom().choice
def sha512_crypt(password, salt=None, rounds=None):
if salt is None:
salt = ''.join([randchoice(string.ascii_letters + string.digits)
for _ in range(8)])
prefix = '$6$'
if rounds is not None:
rounds = max(1000, min(999999999, rounds or 5000))
prefix += 'rounds={0}$'.format(rounds)
return crypt.crypt(password, prefix + salt)
This then produces the same output as the mkpasswd command line:
>>> sha512_crypt('password', 'salt1234')
'$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
>>> sha512_crypt('password', 'salt1234', rounds=1000)
'$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//'
You need to use crypt.crypt:
>>> import crypt
>>> crypt.crypt('password', '$6$' + 'salt1234')
'$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
Here is a pure python3 implementation of the sha512_crypt function based on the specification. This is for illustration only, always use crypt.crypt instead!
import hashlib, base64
SHUFFLE_SHA512_INDICES = [
42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25,
26, 5, 47, 48, 27, 6, 7, 49, 28, 29, 8, 50, 51, 30, 9,
10, 52, 31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56,
57, 36, 15, 16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40,
41, 20, 62, 63
]
def shuffle_sha512(data):
return bytes(data[i] for i in SHUFFLE_SHA512_INDICES)
def extend_by_repeat(data, length):
return (data * (length // len(data) + 1))[:length]
CUSTOM_ALPHABET = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
''' Base64 encode based on SECTION 22.e)
'''
def custom_b64encode(data, alphabet = CUSTOM_ALPHABET):
buffer,count,result = 0,0,[]
for byte in data:
buffer |= byte << count
count += 8
while count >= 6:
result.append(buffer & 0x3f)
buffer >>= 6
count -= 6
if count > 0:
result.append(buffer)
return ''.join(alphabet[idx] for idx in result)
''' From http://www.akkadia.org/drepper/SHA-crypt.txt
'''
def sha512_crypt(password, salt, rounds_in = None):
rounds,rounds_defined = 5000, False
if rounds_in is not None:
rounds,rounds_defined = rounds_in, True
assert 1000 <= rounds <= 999999999
hash = hashlib.sha512
salt_prefix = '$6$'
password = password.encode('utf8')
salt = salt.encode('ascii')[:16]
A = hash() # SECTION 1.
A.update(password) # SECTION 2.
A.update(salt) # SECTION 3.
B = hash() # SECTION 4.
B.update(password) # SECTION 5.
B.update(salt) # SECTION 6.
B.update(password) # SECTION 7.
digestB = B.digest(); # SECTION 8.
A.update(extend_by_repeat(digestB, len(password))) # SECTION 9., 10.
# SECTION 11.
i = len(password)
while i > 0:
if i & 1:
A.update(digestB) # SECTION 11.a)
else:
A.update(password) # SECTION 11.b)
i = i >> 1
digestA = A.digest() # SECTION 12.
DP = hash() # SECTION 13.
# SECTION 14.
for _ in range(len(password)):
DP.update(password)
digestDP = DP.digest() # SECTION 15.
P = extend_by_repeat(digestDP, len(password)) # SECTION 16.a), 16.b)
DS = hash() # SECTION 17.
# SECTION 18.
for _ in range(16 + digestA[0]):
DS.update(salt)
digestDS = DS.digest() # SECTION 19.
S = extend_by_repeat(digestDS, len(salt)) # SECTION 20.a), 20.b)
# SECTION 21.
digest_iteration_AC = digestA
for i in range(rounds):
C = hash() # SECTION 21.a)
if i % 2:
C.update(P) # SECTION 21.b)
else:
C.update(digest_iteration_AC) # SECTION 21.c)
if i % 3:
C.update(S) # SECTION 21.d)
if i % 7:
C.update(P) # SECTION 21.e)
if i % 2:
C.update(digest_iteration_AC) # SECTION 21.f)
else:
C.update(P) # SECTION 21.g)
digest_iteration_AC = C.digest() # SECTION 21.h)
shuffled_digest = shuffle_sha512(digest_iteration_AC)
prefix = salt_prefix # SECTION 22.a)
# SECTION 22.b)
if rounds_defined:
prefix += 'rounds={0}$'.format(rounds_in)
return (prefix
+ salt.decode('ascii') # SECTION 22.c)
+ '$' # SECTION 22.d)
+ custom_b64encode(shuffled_digest) # SECTION 22.e)
)
actual = sha512_crypt('password', 'salt1234')
expected = '$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
print(actual)
print(expected)
assert actual == expected