summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-05-01 12:40:12 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-05-01 12:40:12 -0400
commit3fa87b2933ba2f596297297f97b8fc3e335ba581 (patch)
tree52ad1cbce3718d49e775afc4322b4cb8475ab9ef
parent0f99c2a8d6106b980bb9b3c687669ae9fca5eb94 (diff)
downloadpasslib-3fa87b2933ba2f596297297f97b8fc3e335ba581.tar.gz
bunch bugfixes to the unittests
* timer issues under windows * id() issues under jython * mtime issues under jython & darwin * corrected expectations of test_ext_django's patch checker * added darwin flags to platform_os_crypt info * fixed regression in os_crypt backend test mocking
-rw-r--r--passlib/tests/test_apache.py4
-rw-r--r--passlib/tests/test_context.py13
-rw-r--r--passlib/tests/test_ext_django.py57
-rw-r--r--passlib/tests/test_handlers.py33
-rw-r--r--passlib/tests/test_utils_crypto.py4
-rw-r--r--passlib/tests/utils.py41
-rw-r--r--passlib/utils/__init__.py25
-rw-r--r--passlib/utils/compat.py23
8 files changed, 116 insertions, 84 deletions
diff --git a/passlib/tests/test_apache.py b/passlib/tests/test_apache.py
index f88fb33..5023903 100644
--- a/passlib/tests/test_apache.py
+++ b/passlib/tests/test_apache.py
@@ -12,7 +12,7 @@ import time
#pkg
from passlib import apache
from passlib.utils.compat import irange, unicode
-from passlib.tests.utils import TestCase, get_file, set_file, catch_warnings
+from passlib.tests.utils import TestCase, get_file, set_file, catch_warnings, HAS_INTEGER_MTIME
from passlib.utils.compat import b, bytes, u
#module
log = getLogger(__name__)
@@ -474,7 +474,7 @@ class HtdigestFileTest(TestCase):
self.assertEqual(hc.to_string(), self.sample_01)
#change file, test deprecated force=True kwd
- time.sleep(.1) # pause so mtime changes
+ time.sleep(1 if HAS_INTEGER_MTIME else .1) # pause so mtime changes
set_file(path, "")
with self.assertWarningList(r"load\(force=False\) is deprecated"):
ha.load(force=False)
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py
index 85eccbc..90709d1 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_context.py
@@ -24,7 +24,7 @@ from passlib.exc import PasslibConfigWarning
from passlib.utils import tick, to_bytes, to_unicode
from passlib.utils.compat import irange, u, unicode, str_to_uascii
import passlib.utils.handlers as uh
-from passlib.tests.utils import TestCase, catch_warnings, set_file, get_timer_resolution
+from passlib.tests.utils import TestCase, catch_warnings, set_file, TICK_RESOLUTION, quicksleep
from passlib.registry import (register_crypt_handler_path,
_has_crypt_handler as has_crypt_handler,
_unload_handler_name as unload_handler_name,
@@ -289,7 +289,7 @@ sha512_crypt__min_rounds = 45000
def test_09_repr(self):
"test repr()"
cc1 = CryptContext(**self.sample_1_dict)
- self.assertRegex(repr(cc1), "^<CryptContext at 0x[0-9a-f]{6,}>$")
+ self.assertRegex(repr(cc1), "^<CryptContext at 0x[0-9a-f]+>$")
#=========================================================
# modifiers
@@ -1353,8 +1353,8 @@ sha512_crypt__min_rounds = 45000
#=========================================================
def test_60_min_verify_time(self):
"test verify() honors min_verify_time"
- delta = .01
- if get_timer_resolution(tick) >= delta:
+ delta = .05
+ if TICK_RESOLUTION >= delta/10:
raise self.skipTest("timer not accurate enough")
min_delay = 2*delta
min_verify_time = 5*delta
@@ -1370,7 +1370,7 @@ sha512_crypt__min_rounds = 45000
return True
def _calc_checksum(self, secret):
- time.sleep(self.delay)
+ quicksleep(self.delay)
return to_unicode(secret + 'x')
# check mvt issues a warning, and then filter for remainder of test
@@ -1382,8 +1382,7 @@ sha512_crypt__min_rounds = 45000
def timecall(func, *args, **kwds):
start = tick()
result = func(*args, **kwds)
- end = tick()
- return end-start, result
+ return tick()-start, result
# verify genhash delay works
TimedHash.delay = min_delay
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index c0ea4b6..c7cdecf 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -110,7 +110,7 @@ def create_mock_setter():
if has_django14:
# have to modify this a little -
# all but pbkdf2_sha256 will be deprecated here,
- # whereas stock passlib policy is more permissive
+ # whereas preconfigured passlib policy is more permissive
stock_config = django14_context.to_dict()
stock_config['deprecated'] = ["django_pbkdf2_sha1", "django_bcrypt"] + stock_config['deprecated']
elif has_django1:
@@ -128,27 +128,40 @@ class _ExtensionSupport(object):
#=========================================================
# support funcs
#=========================================================
- # attrs we're patching in various modules.
- _patched_attrs = ["set_password", "check_password",
- "make_password", "get_hasher", "identify_hasher"]
-
@classmethod
def _iter_patch_candidates(cls):
- "helper to scan for monkeypatches"
+ """helper to scan for monkeypatches.
+
+ returns tuple containing:
+ * object (module or class)
+ * attribute of object
+ * value of attribute
+ * whether it should or should not be patched
+ """
+ # XXX: this and assert_unpatched() could probably be refactored to use
+ # the PatchManager class to do the heavy lifting.
from django.contrib.auth import models
- objs = [models, models.User]
+ user_attrs = ["check_password", "set_password"]
+ model_attrs = ["check_password"]
+ objs = [(models, model_attrs), (models.User, user_attrs)]
if has_django14:
from django.contrib.auth import hashers
- objs.append(hashers)
- for obj in objs:
+ model_attrs.append("make_password")
+ objs.append((hashers, ["check_password", "make_password",
+ "get_hasher", "identify_hasher"]))
+ if has_django0:
+ user_attrs.extend(["has_usable_password", "set_unusable_password"])
+ for obj, patched in objs:
for attr in dir(obj):
if attr.startswith("_"):
continue
- value = getattr(obj, attr)
+ value = obj.__dict__.get(attr, UNSET) # can't use getattr() due to GAE
+ if value is UNSET and attr not in patched:
+ continue
value = get_method_function(value)
source = getattr(value, "__module__", None)
if source:
- yield obj, attr, source
+ yield obj, attr, source, (attr in patched)
#=========================================================
# verify current patch state
@@ -160,8 +173,8 @@ class _ExtensionSupport(object):
self.assertFalse(mod and mod._patched, "patch should not be enabled")
# make sure no objects have been replaced, by checking __module__
- for obj, attr, source in self._iter_patch_candidates():
- if attr in self._patched_attrs:
+ for obj, attr, source, patched in self._iter_patch_candidates():
+ if patched:
self.assertTrue(source.startswith("django.contrib.auth."),
"obj=%r attr=%r was not reverted: %r" %
(obj, attr, source))
@@ -177,8 +190,8 @@ class _ExtensionSupport(object):
self.assertTrue(mod and mod._patched, "patch should have been enabled")
# make sure only the expected objects have been patched
- for obj, attr, source in self._iter_patch_candidates():
- if attr in self._patched_attrs:
+ for obj, attr, source, patched in self._iter_patch_candidates():
+ if patched:
self.assertTrue(source == "passlib.ext.django.models",
"obj=%r attr=%r should have been patched: %r" %
(obj, attr, source))
@@ -261,7 +274,8 @@ class DjangoBehaviorTest(_ExtensionTest):
def assert_unusable_password(self, user):
self.assertEqual(user.password, "!")
- self.assertFalse(user.has_usable_password())
+ if has_django1 or self.patched:
+ self.assertFalse(user.has_usable_password())
self.assertEqual(user.pop_saved_passwords(), [])
def assert_valid_password(self, user, hash=UNSET, saved=None):
@@ -270,7 +284,8 @@ class DjangoBehaviorTest(_ExtensionTest):
self.assertNotEqual(user.password, None)
else:
self.assertEqual(user.password, hash)
- self.assertTrue(user.has_usable_password())
+ if has_django1 or self.patched:
+ self.assertTrue(user.has_usable_password())
self.assertEqual(user.pop_saved_passwords(),
[] if saved is None else [saved])
@@ -431,7 +446,8 @@ class DjangoBehaviorTest(_ExtensionTest):
self.assertFalse(user.check_password(PASS1))
else:
self.assertRaises(TypeError, user.check_password, PASS1)
- self.assertFalse(user.has_usable_password())
+ if has_django1 or patched:
+ self.assertFalse(user.has_usable_password())
# make_password() - n/a
@@ -507,7 +523,10 @@ class DjangoBehaviorTest(_ExtensionTest):
user.password = hash
# check against invalid password
- self.assertFalse(user.check_password(None))
+ if has_django1 or patched:
+ self.assertFalse(user.check_password(None))
+ else:
+ self.assertRaises(TypeError, user.check_password, None)
##self.assertFalse(user.check_password(''))
self.assertFalse(user.check_password(other))
self.assert_valid_password(user, hash)
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index d6d6ede..dac8849 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -183,6 +183,7 @@ class _bcrypt_test(HandlerCase):
freebsd=True,
openbsd=True,
netbsd=True,
+ darwin=False,
# linux - some systems
# solaris - depends on policy
)
@@ -461,7 +462,7 @@ class _bsdi_crypt_test(HandlerCase):
netbsd=True,
linux=False,
solaris=False,
- # darwin ?
+ darwin=True,
)
def setUp(self):
@@ -690,7 +691,7 @@ class _des_crypt_test(HandlerCase):
netbsd=True,
linux=True,
solaris=True,
- # darwin?
+ darwin=True,
)
des_crypt_os_crypt_test, des_crypt_builtin_test = \
@@ -1216,10 +1217,9 @@ class ldap_plaintext_test(HandlerCase):
pwd = super(ldap_plaintext_test, self).get_fuzz_password()
return pwd
-#NOTE: since the ldap_{crypt} handlers are all wrappers,
-# don't need separate test. have just one for end-to-end testing purposes.
-
class _ldap_md5_crypt_test(HandlerCase):
+ #NOTE: since the ldap_{crypt} handlers are all wrappers,
+ # don't need separate test. this is just to test the codebase end-to-end
handler = hash.ldap_md5_crypt
known_correct_hashes = [
@@ -1245,6 +1245,25 @@ class _ldap_md5_crypt_test(HandlerCase):
ldap_md5_crypt_os_crypt_test, ldap_md5_crypt_builtin_test = \
_ldap_md5_crypt_test.create_backend_cases(["os_crypt","builtin"])
+class _ldap_sha1_crypt_test(HandlerCase):
+ # NOTE: this isn't for testing the hash (see ldap_md5_crypt note)
+ # but as a self-test of the os_crypt patching code in HandlerCase.
+ handler = hash.ldap_sha1_crypt
+
+ known_correct_hashes = [
+ ('password', '{CRYPT}$sha1$10$c.mcTzCw$gF8UeYst9yXX7WNZKc5Fjkq0.au7'),
+ (UPASS_TABLE, '{CRYPT}$sha1$10$rnqXlOsF$aGJf.cdRPewJAXo1Rn1BkbaYh0fP'),
+ ]
+
+ def populate_settings(self, kwds):
+ kwds.setdefault("rounds", 10)
+ super(_ldap_sha1_crypt_test, self).populate_settings(kwds)
+
+ def test_77_fuzz_input(self):
+ raise self.skipTest("unneeded")
+
+ldap_sha1_crypt_os_crypt_test, = _ldap_sha1_crypt_test.create_backend_cases(["os_crypt"])
+
#=========================================================
#ldap_pbkdf2_{digest}
#=========================================================
@@ -1381,7 +1400,7 @@ class _md5_crypt_test(HandlerCase):
netbsd=True,
linux=True,
solaris=True,
- # darwin?
+ darwin=False,
)
md5_crypt_os_crypt_test, md5_crypt_builtin_test = \
@@ -2486,7 +2505,7 @@ class _sha256_crypt_test(HandlerCase):
netbsd=False,
linux=True,
# solaris - depends on policy
- # darwin ?,
+ darwin=False,
)
sha256_crypt_os_crypt_test, sha256_crypt_builtin_test = \
diff --git a/passlib/tests/test_utils_crypto.py b/passlib/tests/test_utils_crypto.py
index 76d22b8..bbf4146 100644
--- a/passlib/tests/test_utils_crypto.py
+++ b/passlib/tests/test_utils_crypto.py
@@ -18,7 +18,7 @@ except ImportError:
#pkg
#module
from passlib.utils.compat import b, bytes, bascii_to_str, irange, PY2, PY3, u, \
- unicode, join_bytes, PYPY
+ unicode, join_bytes, PYPY, JYTHON
from passlib.tests.utils import TestCase, TEST_MODE, catch_warnings, skipUnless, skipIf
#=========================================================
@@ -363,7 +363,7 @@ class Pbkdf1_Test(TestCase):
(b('password'), b('salt'), 1000, None, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
(b('password'), b('salt'), 1000, None, 'sha1', hb('4a8fd48e426ed081b535be5769892fa396293efb')),
]
- if not PYPY:
+ if not (PYPY or JYTHON):
pbkdf1_tests.append(
(b('password'), b('salt'), 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453'))
)
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index be3eb61..4c30b20 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -4,14 +4,13 @@
#=========================================================
from __future__ import with_statement
#core
-import atexit
import logging; log = logging.getLogger(__name__)
import re
import os
import sys
import tempfile
from passlib.exc import PasslibHashWarning
-from passlib.utils.compat import PY27, PY_MIN_32, PY3
+from passlib.utils.compat import PY27, PY_MIN_32, PY3, JYTHON
import warnings
from warnings import warn
#site
@@ -21,7 +20,7 @@ import passlib.registry as registry
from passlib.tests.backports import TestCase as _TestCase, catch_warnings, skip, skipIf, skipUnless
from passlib.utils import has_rounds_info, has_salt_info, rounds_cost_values, \
classproperty, rng, getrandstr, is_ascii_safe, to_native_str, \
- repeat_string
+ repeat_string, tick
from passlib.utils.compat import b, bytes, iteritems, irange, callable, \
base_string_types, exc_err, u, unicode, PY2
import passlib.utils.handlers as uh
@@ -49,6 +48,17 @@ except ImportError:
else:
GAE = True
+HAS_INTEGER_MTIME = JYTHON or sys.platform.startswith("darwin")
+
+def _get_timer_resolution(timer):
+ def sample():
+ start = cur = timer()
+ while start == cur:
+ cur = timer()
+ return cur-start
+ return min(sample() for _ in range(3))
+TICK_RESOLUTION = _get_timer_resolution(tick)
+
#=========================================================
# test mode
#=========================================================
@@ -176,13 +186,11 @@ def randintgauss(lower, upper, mu, sigma):
"hack used by fuzz testing"
return int(limit(rng.normalvariate(mu, sigma), lower, upper))
-def get_timer_resolution(timer):
- def sample():
- start = cur = timer()
- while start == cur:
- cur = timer()
- return cur-start
- return min(sample() for _ in range(3))
+def quicksleep(delay):
+ "because time.sleep() doesn't even have 10ms accuracy on some OSes"
+ start = tick()
+ while tick()-start < delay:
+ pass
#=========================================================
# custom test harness
@@ -702,13 +710,13 @@ class HandlerCase(TestCase):
if handler.name == "bcrypt" and backend == "builtin" and TEST_MODE("full"):
# this will be auto-enabled under TEST_MODE 'full'.
return None
- if backend == "os_crypt":
- if cls.find_crypt_replacement() and TEST_MODE("full"):
+ from passlib.utils import has_crypt
+ if backend == "os_crypt" and has_crypt:
+ if TEST_MODE("full") and cls.find_crypt_replacement():
#in this case, HandlerCase will monkeypatch os_crypt
#to use another backend, just so we can test os_crypt fully.
return None
- from passlib.utils import has_crypt
- if has_crypt:
+ else:
return "hash not supported by os crypt()"
return "backend not available"
@@ -1905,6 +1913,9 @@ class OsCryptMixin(HandlerCase):
as possible.
"""
handler = self.handler
+ # resolve wrappers, since we want to return crypt compatible hash.
+ while hasattr(handler, "wrapped"):
+ handler = handler.wrapped
alt_backend = self.find_crypt_replacement()
if not alt_backend:
raise AssertionError("handler has no available backends!")
@@ -1971,6 +1982,8 @@ class OsCryptMixin(HandlerCase):
def test_82_crypt_support(self):
"test platform-specific crypt() support detection"
+ if hasattr(self.handler, "orig_prefix"):
+ raise self.skipTest("not applicable to wrappers")
platform = sys.platform
for name, flag in self.platform_crypt_support.items():
if not platform.startswith(name):
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index 1e086d3..a40b744 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -1396,31 +1396,6 @@ else:
# On most other platforms the best timer is time.time()
from time import time as tick
-# works but not used
-##def _get_timer_resolution(timer=timer, repeat=3):
-## best = None
-## i = 0
-## while i < repeat:
-## start = end = timer()
-## while start == end:
-## end = timer()
-## delta = end-start
-## if delta < 0:
-## # probably NTP adjust or some such.
-## log.error("timer jumped backwards! (%r => %r)", start, end)
-## continue
-## if delta > 1:
-## # should have at least this resolution,
-## # so probably NTP adjust or some such.
-## log.error("timer jumped too far! (%r => %r)", start, end)
-## continue
-## if best is None or delta < best:
-## best = delta
-## i += 1
-## return best
-##
-##timer_resolution = _get_timer_resolution()
-
#=================================================================================
# randomness
#=================================================================================
diff --git a/passlib/utils/compat.py b/passlib/utils/compat.py
index 02b808b..428e765 100644
--- a/passlib/utils/compat.py
+++ b/passlib/utils/compat.py
@@ -1,7 +1,11 @@
"""passlib.utils.compat - python 2/3 compatibility helpers"""
#=============================================================================
-# figure out what version we're running
+# figure out what we're running
#=============================================================================
+
+#-----------------------------------------------------
+# python version
+#-----------------------------------------------------
import sys
PY2 = sys.version_info < (3,0)
PY3 = sys.version_info >= (3,0)
@@ -9,16 +13,19 @@ PY_MAX_25 = sys.version_info < (2,6) # py 2.5 or earlier
PY27 = sys.version_info[:2] == (2,7) # supports last 2.x release
PY_MIN_32 = sys.version_info >= (3,2) # py 3.2 or later
-# __dir__() added in py2.6
-# NOTE: testing shows pypy1.5 doesn't either; but added somewhere <= 1.8
-SUPPORTS_DIR_METHOD = not PY_MAX_25
-
-#=============================================================================
-# figure out what VM we're running
-#=============================================================================
+#-----------------------------------------------------
+# python implementation
+#-----------------------------------------------------
PYPY = hasattr(sys, "pypy_version_info")
JYTHON = sys.platform.startswith('java')
+#-----------------------------------------------------
+# capabilities
+#-----------------------------------------------------
+
+# __dir__() added in py2.6
+SUPPORTS_DIR_METHOD = not PY_MAX_25 and not (PYPY and sys.pypy_version_info < (1,6))
+
#=============================================================================
# common imports
#=============================================================================