When I run my tests that include calling a @classmethod
using setuptools and nose2, the testing suite doesn't finish it just keeps on running. However I have checked that the test does indeed pass and reach the end of the function, the test suite just doesn't finish running. If I remove the tests using decode_auth_token
it works fine. I was able to narrow it done to the class methods because I tested other class methods as well and they causes the same problem
Does anyone have any idea why this might be happening? Below are the relevant pieces of code without posting too much of my code
Code in my User Model
@classmethod
def decode_auth_token(cls, auth_token):
try:
payload = jwt.decode(auth_token, config.SECRET_KEY, algorithms=['HS256'])
# check the hash of what we expect the token to be and token we got to be the same
if bcrypt.check_password_hash(User.by_id(payload['sub']).api_token_hash, auth_token):
return payload['sub']
else:
return 'Token does not match Api Token.'
except jwt.ExpiredSignatureError:
return 'Signature expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid Token. Please log in again.'
The following two functions also cause the problem when called
@classmethod
def is_username_taken(cls, username):
return db.session.query(db.exists().where(User.username==username)).scalar()
@classmethod
def is_email_taken(cls, email):
return db.session.query(db.exists().where(User.email==email)).scalar()
This function does not cause the problem when called though
@classmethod
def by_username(cls, username):
return User.query.filter(User.username == username).first()
Here are the tests
import unittest
import sys
from . import AppTestCase, API_ROOT
from app.extensions import db, bcrypt
from app.models import User, UserSchema, Location, Company
class TestUserModel(AppTestCase):
def test_encode_auth_token(self):
user = User.by_username('jdoe')
auth_token = user.encode_auth_token(user.id)
self.assertTrue(isinstance(auth_token, bytes))
def test_decode_auth_token(self):
user = User.by_username('jdoe')
auth_token = user.encode_auth_token(user.id)
self.assertTrue(isinstance(auth_token, bytes))
self.assertEqual(User.decode_auth_token(auth_token), user.id)
print('DONE')
The first test works fine, the second test prints out Done
and properly decodes the auth_token returning the proper user id but does not cause the test suite to finish. It just keeps running after printing done.
And here is the setup script, I run the tests using python setup.py test
import os
from setuptools import setup, find_packages, Command
# Thanks https://mcmap.net/q/278304/-why-does-quot-python-setup-py-sdist-quot-create-unwanted-quot-project-egg-info-quot-in-project-root-directory
class CleanCommand(Command):
"""Custom clean command to tidy up the project root."""
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
os.system('rm -vrf ./build ./dist ./*.pyc ./*.tgz ./*.egg-info')
with open('requirements.txt') as f:
requirements = f.read().splitlines()
setup(
name="XXX",
description="XXX",
version=1.0,
packages=find_packages(),
install_requires=requirements,
include_package_data=True,
test_suite='nose2.collector.collector',
tests_require=['nose2'],
cmdclass={
'clean': CleanCommand,
}
)
Output when Running and Not Stopping
running test
Searching for nose2
Best match: nose2 0.6.5
Processing nose2-0.6.5-py3.6.egg
Using XXX/.eggs/nose2-0.6.5-py3.6.egg
running egg_info
writing doomfist.egg-info/PKG-INFO
writing dependency_links to XXX.egg-info/dependency_links.txt
writing requirements to XXX.egg-info/requires.txt
writing top-level names to XXX.egg-info/top_level.txt
reading manifest file 'XXX.egg-info/SOURCES.txt'
writing manifest file 'XXX.egg-info/SOURCES.txt'
running build_ext
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/parser.py:50: DeprecationWarning: invalid escape sequence \.
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/parser.py:50: DeprecationWarning: invalid escape sequence \.
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/tz/win.py:197: DeprecationWarning: invalid escape sequence \{
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/tz/win.py:247: DeprecationWarning: invalid escape sequence \{
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/tz/win.py:197: DeprecationWarning: invalid escape sequence \{
/Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/python_dateutil-2.6.0-py3.6.egg/dateutil/tz/win.py:247: DeprecationWarning: invalid escape sequence \{
NOT running in debug mode
DONE
^]^\[1] 35752 quit python setup.py test
EDIT----- Sorry for the Huge post now With someone's advice in the comments I used a debugger to determine it does indeed finish the tests. And where it actually get's stuck is during tearDown(). The following function of mine is where it gets stuck.
def tearDown(self):
"""Clean db session and drop all tables."""
db.drop_all()
Following the debugger farther down I determined it ultimately gets stuck
for table, fkcs in collection:
if table is not None:
self.traverse_single(table, drop_ok=True, _is_metadata_operation=True)
else:
for fkc in fkcs:
...
More specifically on this method self.traverse_single(table, drop_ok=True, _is_metadata_operation=True)
. I'm assuming it gets stuck waiting for the generator to return? Unsure but below is the last lines I got before it gets stuck again.
> /Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/SQLAlchemy-1.1.11-py3.6-macosx-10.7-x86_64.egg/sqlalchemy/sql/ddl.py(929)visit_table()->None
-> _is_metadata_operation=_is_metadata_operation)
(Pdb) n
--Call--
> /Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/SQLAlchemy-1.1.11-py3.6-macosx-10.7-x86_64.egg/sqlalchemy/sql/visitors.py(150)_visitor_iterator()-><sqlalchemy.s...t 0x112045630>
-> yield v
(Pdb) n
GeneratorExit
> /Users/XXX/anaconda3/envs/XXX/lib/python3.6/site-packages/SQLAlchemy-1.1.11-py3.6-macosx-10.7-x86_64.egg/sqlalchemy/sql/visitors.py(150)_visitor_iterator()-><sqlalchemy.s...t 0x112045630>
-> yield v
(Pdb) l
145 def _visitor_iterator(self):
146 """iterate through this visitor and each 'chained' visitor."""
147
148 v = self
149 while v:
150 -> yield v
151 v = getattr(v, '_next', None)
152
153 def chain(self, visitor):
154 """'chain' an additional ClauseVisitor onto this ClauseVisitor.
155
(Pdb) n
I believe it gets stuck on the following table of mine
from ..helpers import get_current_time
from ..extensions import db, ma
from ..constants import STRING_LEN, DESCRIPTION_LEN
from .worker import WorkerSchema
class Injury(db.Model):
__tablename__ = "injuries"
def __repr__(self):
return '<Injury %r>' % (self.id)
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(STRING_LEN), nullable=False)
description = db.Column(db.String(DESCRIPTION_LEN), nullable=False)
worker_id = db.Column(db.Integer, db.ForeignKey('workers.id'))
created_at = db.Column(db.DateTime, nullable=False, default = get_current_time)
updated_at = db.Column(db.DateTime, nullable=False, default = get_current_time, onupdate=get_current_time)
# Relationships
worker = db.relationship('Worker', back_populates='injuries')
# ================================================================
# ================================================================
# methods
# ================================================================
# Class methods
@classmethod
def by_id(cls, id):
return cls.query.filter(Injury.id==id).first()
class InjurySchema(ma.Schema):
class Meta:
fields = ('id', 'title', 'description', 'worker')
worker = ma.Nested(WorkerSchema)