]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Added 'function_call_count' assertion decorator. The full-suite vs. isolated run...
authorJason Kirtland <jek@discorporate.us>
Sat, 5 Jan 2008 21:56:22 +0000 (21:56 +0000)
committerJason Kirtland <jek@discorporate.us>
Sat, 5 Jan 2008 21:56:22 +0000 (21:56 +0000)
test/testlib/profiling.py

index 947bf962e0db61638e7586c7a5b486ca2cde3408..61f6bb8f29da94b0a000b3aaf2bad08a3c0d8ec6 100644 (file)
@@ -1,11 +1,11 @@
 """Profiling support for unit and performance tests."""
 
 import testbase
+import os, sys
 from testlib.config import parser, post_configure
 import testlib.config
-import os
 
-__all__ = 'profiled',
+__all__ = 'profiled', 'function_call_count'
 
 all_targets = set()
 profile_config = { 'targets': set(),
@@ -19,7 +19,7 @@ def profiled(target=None, **target_opts):
     @profiled('label')
     or
     @profiled('label', report=True, sort=('calls',), limit=20)
-    
+
     Enables profiling for a function when 'label' is targetted for
     profiling.  Report options can be supplied, and override the global
     configuration and command-line options.
@@ -55,7 +55,7 @@ def profiled(target=None, **target_opts):
             if not testlib.config.options.quiet:
                 print "Profiled target '%s', wall time: %.2f seconds" % (
                     target, ended - began)
-            
+
             report = target_opts.get('report', profile_config['report'])
             if report and testlib.config.options.verbose:
                 sort_ = target_opts.get('sort', profile_config['sort'])
@@ -76,7 +76,7 @@ def profiled(target=None, **target_opts):
                     assert_range = assert_range.get(testlib.config.db, 'default')
                 stats = hotshot.stats.load(filename)
                 assert stats.total_calls >= assert_range[0] and stats.total_calls <= assert_range[1], stats.total_calls
-            
+
             os.unlink(filename)
             return result
         try:
@@ -85,3 +85,72 @@ def profiled(target=None, **target_opts):
             pass
         return profiled
     return decorator
+
+def function_call_count(count=None, versions={}, variance=0.05):
+    """Assert a target for a test case's function call count.
+
+    count
+      Optional, general target function call count.
+
+    versions
+      Optional, a dictionary of Python version strings to counts,
+      for example::
+
+        { '2.5.1': 110,
+          '2.5': 100,
+          '2.4': 150 }
+
+      The best match for the current running python will be used.
+      If none match, 'count' will be used as the fallback.
+
+    variance
+      An +/- deviation percentage, defaults to 5%.
+    """
+
+    version_info = list(sys.version_info)
+    py_version = '.'.join([str(v) for v in sys.version_info])
+
+    while version_info:
+        version = '.'.join([str(v) for v in version_info])
+        if version in versions:
+            count = versions[version]
+            break
+        version_info.pop()
+
+    if count is None:
+        return lambda fn: fn
+
+    import hotshot, hotshot.stats
+
+    def decorator(fn):
+        def counted(*args, **kw):
+            try:
+                filename = "%s.prof" % fn.__name__
+
+                prof = hotshot.Profile(filename)
+                prof.start()
+                try:
+                    result = fn(*args, **kw)
+                finally:
+                    prof.stop()
+                    prof.close()
+
+                stats = hotshot.stats.load(filename)
+                calls = stats.total_calls
+                deviance = int(count * variance)
+                if (calls < (count - deviance) or
+                    calls > (count + deviance)):
+                    raise AssertionError(
+                        "Function call count %s not within %s%% "
+                        "of expected %s. (Python version %s)" % (
+                        calls, variance, count, py_version))
+                return result
+            finally:
+                if os.path.exists(filename):
+                    os.unlink(filename)
+        try:
+            counted.__name__ = fn.__name__
+        except:
+            pass
+        return counted
+    return decorator