Pytest hangs when running with other threads - python

I am trying to run pytest from within python after running a thread,
here is a simple example of the case:
import time
import threading
import pytest
class A(object):
def __init__(self):
self._thread_a = threading.Thread(target=self.do_a)
self._thread_a.start()
pytest.main()
def do_a(self):
print "a"
time.sleep(2)
self.do_a()
if __name__ == "__main__":
a = A()
but pytest keeps hanging. this is what the output looks like:
============================= test session starts ==============================
platform darwin -- Python 2.7.10, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
metadata: {'Python': '2.7.10', 'Platform': 'Darwin-17.3.0-x86_64-i386-64bit', 'Packages': {'py': '1.5.2', 'pytest': '3.3.2', 'pluggy': '0.6.0'}, 'Plugins': {'session2file': '0.1.9', 'celery': '4.0.0','html': '1.16.1', 'metadata': '1.5.1'}}
rootdir: /Users/path/to/root/dir, inifile:
plugins: session2file-0.1.9, metadata-1.5.1, html-1.16.1, celery-4.0.0
and it just hangs like this until I force quit it.
is there any way to make this work?

Two things to mention:
you possibly want to stop the thread if you code is done with something.
you may also separate your code from the test code.
Lets say:
file.py
import time
import threading
class A(object):
def __init__(self):
self._thread_a = threading.Thread(target=self.do_a)
self._thread_a.daemon = True
self._thread_a.start()
def do_a(self):
print "a"
time.sleep(2)
self.do_a()
def stop(self, timeout):
self._thread_a.join(timeout)
test_file.py
import pytest
from .file import A
#pytest.fixture()
def a():
return A()
def test_sth(a):
a.start()
print('I can do sth else')
a.stop(timeout=1)

Related

pytest only runs first test in file

I'm working on the Code-dojo Tennis Kata and I'm stuck with the silliest problem. My pytest stops after running the first test in the file. My test file looks like this:
from tennis import game
import re
def test_game_start(capsys):
MaxVsMimi = game()
MaxVsMimi.result()
out, err = capsys.readouterr()
assert bool(re.search(r'Love : Love', out))
def p1_scores_once(capsys):
MaxVsMimi = game()
MaxVsMimi.score("p1")
MaxVsMimi.result()
out, err = capsys.readouterr()
assert bool(re.match(r'^abc', out))
This is the code:
class game:
def __init__(self):
self.scorep1 = "Love"
self.scorep2 = "Love"
def result(self):
print(self.scorep1, ":", self.scorep2)
and the output:
:stdout:
============================= test session starts ==============================
platform linux -- Python 3.8.6, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python
cachedir: .pytest_cache
rootdir: /sandbox
collecting ... collected 1 item
test_tennis.py::test_game_start PASSED [100%]
============================== 1 passed in 0.01s ===============================
Why does it not run the second "p1_scores_once" test?
Thanks!
Pytest, like many testing libraries including unittest require all test functions to have the word test in the start of the name. You can fix your code simply by changing the name of p1_scores_once:
def test_p1_scores_once(capsys):
...
Now the automatic test-finding system will treat the function like a test case.

Pytest passing test I think it should fail on sys.exit() call

I am trying to check the exit code I have for a script I'm writing in python3 on a Mac (10.14.4). When I run the test it doesn't fail which I think is wrong. But I can't see what it is that I've got wrong.
The test file looks like this:
import pytest
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import my_script
class TestMyScript():
def test_exit(self):
with pytest.raises(SystemExit) as pytest_wrapped_e:
my_script.main()
assert pytest_wrapped_e.type == SystemExit
def test_exit_code(self):
with pytest.raises(SystemExit) as pytest_wrapped_e:
my_script.main()
self.assertEqual(pytest_wrapped_e.exception.code, 42)
My script looks like:
#!/usr/bin/env python3
import sys
def main():
print('Hello World!')
sys.exit(0)
if __name__ == '__main__':
main()
The output I get is:
$ py.test -v
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-3.10.1, py-1.8.0, pluggy-0.9.0 -- /usr/local/opt/python/bin/python3.7
cachedir: .pytest_cache
rootdir: /Users/robertpostill/software/gateway, inifile:
plugins: shutil-1.6.0
collected 2 items
test/test_git_refresh.py::TestGitRefresh::test_exit PASSED [ 50%]
test/test_git_refresh.py::TestGitRefresh::test_exit_code PASSED [100%]
=========================== 2 passed in 0.02 seconds ===========================
$
I would expect the second test(test_exit_code) to fail as the exit call is getting a code of 0, not 42. But for some reason, the assert is happy whatever value I put in the sys.exit call.
Good question, that's because your Asserts are never called (either of them). When exit() is called the program is done (at least within the with clause), it turns off the lights, packs up its bags, and goes home. No further functions will be called. To see this add an assert before and after you call main:
def test_exit_code(self):
with pytest.raises(SystemExit) as pytest_wrapped_e:
self.assertEqual(0, 1) # This will make it fail
my_script.main()
self.assertEqual(0, 1) # This will never be called because main `exits`
self.assertEqual(pytest_wrapped_e.exception.code, 42)
A test passes if no asserts fail and nothing breaks, so in your case both tests passed because the assert was never hit.
To fix this pull your asserts out of the with statement:
def test_exit_code(self):
with pytest.raises(SystemExit) as pytest_wrapped_e:
my_script.main()
self.assertEqual(pytest_wrapped_e.exception.code, 42)
Though now you will need to fix the pytest syntax because you are missing some other stuff.
See: Testing sys.exit() with pytest

pytest fails when tested function is Click command

Using Python 3.6.4, Click==7.0, and pytest==4.4.0.
I got into a trouble when using Click & pytest at the same time.
test_foo.py
import unittest
import click
import pytest
#click.command()
def foo():
print(1)
class TestFoo(unittest.TestCase):
def test_foo(self):
foo()
And when execute pytest test_foo.py::TestFoo::test_foo , it says
Usage: pytest [OPTIONS]
Try "pytest --help" for help.
Error: Got unexpected extra argument
(tests/test_foo.py::TestFoo::test_foo)
All of the pytest options (such as -k or -m) does not work when Click command is enabled to the tested method.
It works fine when I comment out the line of #click.command(), of course.
How does everyone solve this when using both of them at the same time?
You should use the ClickRunner to isolate the execution of click commands in tests. Your example, reworked:
import unittest
import click
import click.testing
#click.command()
def foo():
print(1)
class TestFoo(unittest.TestCase):
def test_foo(self):
runner = click.testing.CliRunner()
result = runner.invoke(foo)
assert result.exit_code == 0
assert result.output == '1\n'
Check out the relevant doc page for more examples.

how to auto generate test cases for parameter-less methods?

To reduce boiler plate code i came upon the idea to generate test cases in the class Tester for all parameter-less methods.
On running py.test, it only recognizes the statically written test cases (test_a, test_b) but not the dynamically created test cases using setattr(Tester,'test_' + name, member)
Perhaps py.test has already inspected the class Tester for methods with 'test_*' before setUpClass is called? Any hints how to get this running?
import inspect
import unittest
class Testee:
def a(self):
print('a')
def b(self):
print('b')
#...
#...
def z(self):
print('z')
class Tester(unittest.TestCase):
#classmethod
def setUpClass(cls):
testee = Testee()
for name, member in inspect.getmembers(object=testee, predicate=inspect.ismethod or inspect.iscoroutine):
if len(inspect.signature(member).parameters):
print(str(inspect.signature(member).parameters))
setattr(Tester,'test_' + name, member)
if inspect.isfunction(member) or inspect.ismethod(member):
setattr(Tester,'test_' + name, member)
elif inspect.iscoroutinefunction(member):
setattr(Tester,'test_' + name, functools.partialmethod(TestInstrument.run_coro, member))
else:
print(member)
return super().setUpClass()
def test_a(self):
Tester.testee.a()
def test_b(self):
Tester.testee.b()
============================= test session starts ============================= platform win32 -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- c:\program
files\python35\python.exe cachedir: .cache rootdir: C:\tests, inifile:
collected 2 items
sandbox.py::Tester::test_a PASSED sandbox.py::Tester::test_b PASSED
========================== 2 passed in 0.03 seconds ===========================
EDIT: If i move the code in setupClass to the global scope (outside the class), then py.test detects and runs the auto-generated test cases.
To elaborate on my 'EDIT', one option would be so. I'm unhappy with the solution because global code execution would be open to side-effects and other subtle bugs. any suggestions on how to get this into class Tester scope?
import inspect
import unittest
import functools
def auto_generate_test_cases(Tester, Testee):
def run(self, fn):
fn(Tester._testee)
for name, member in inspect.getmembers(
object=Testee, predicate=inspect.isfunction or inspect.iscoroutine):
if len(inspect.signature(member).parameters) == 1:
setattr(Tester,'test_' + name, functools.partialmethod(run, member))
class Testee:
def __init__(self):
self._a = 'a'
def a(self):
print(self._a)
def ab(self, a, b):
print('a')
def b(self):
print('b')
def h(self):
print('h')
async def q(self):
print('async q')
#...
#...
def z(self):
print('z')
class Tester(unittest.TestCase):
_testee = Testee()
auto_generate_test_cases(Tester, Testee)
py.test. output:
C:\tests>py.test sandbox.py --verbose
============================= test session starts =============================
platform win32 -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- c:\program files\python35\python.exe
cachedir: .cache
rootdir: C:\\tests, inifile:
collected 5 items
sandbox.py::Tester::test_a PASSED
sandbox.py::Tester::test_b PASSED
sandbox.py::Tester::test_h PASSED
sandbox.py::Tester::test_q PASSED
sandbox.py::Tester::test_z PASSED
========================== 5 passed in 0.07 seconds ===========================

Twisted trial hangs between tests

I'm pretty new to twisted and I'm attempting to write some unit tests using the trial test framework. My tests run and pass as expected, but for some reason trial is hanging between tests. I have to hit CTRL+C after each test to get it to move on to the next one. I'm guessing I have something configured incorrectly or I'm not calling some method I should be to tell trial the test is done.
Here is the class under test:
from twisted.internet import reactor, defer
import threading
import time
class SomeClass:
def doSomething(self):
return self.asyncMethod()
def asyncMethod(self):
d = defer.Deferred()
t = SomeThread(d)
t.start()
return d
class SomeThread(threading.Thread):
def __init__(self, d):
super(SomeThread, self).__init__()
self.d = d
def run(self):
time.sleep(2) # pretend to do something
retVal = 123
self.d.callback(retVal)
Here is the unit test class:
from twisted.trial import unittest
import tested
class SomeTest(unittest.TestCase):
def testOne(self):
sc = tested.SomeClass()
d = sc.doSomething()
return d.addCallback(self.allDone)
def allDone(self, retVal):
self.assertEquals(retVal, 123)
def testTwo(self):
sc = tested.SomeClass()
d = sc.doSomething()
return d.addCallback(self.allDone2)
def allDone2(self, retVal):
self.assertEquals(retVal, 123)
This is what the command line output looks like:
me$ trial test.py
test
SomeTest
testOne ... ^C [OK]
testTwo ... ^C [OK]
-------------------------------------------------------------------------------
Ran 2 tests in 8.499s
PASSED (successes=2)
I guess your problem has to do with your threads. Twisted is not thread-safe, and if you need to interface with threads you should let the reactor handle things by using deferToThread, callInThread, callFromThread.
See here for info on how to be thread-safe with Twisted.

Categories