From 159c75b0cae26b51141f665c376fdb2b9c245b63 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 11 Aug 2012 18:12:45 -0400 Subject: [PATCH] - add the new profiling system to 0.7 --- test/aaa_profiling/test_compiler.py | 10 +- test/aaa_profiling/test_orm.py | 66 ++--- test/aaa_profiling/test_pool.py | 12 +- test/aaa_profiling/test_resultset.py | 37 +-- test/aaa_profiling/test_zoomark.py | 25 +- test/aaa_profiling/test_zoomark_orm.py | 23 +- test/bootstrap/noseplugin.py | 8 +- test/lib/profiles.txt | 252 ++++++++++++++++++ test/lib/profiling.py | 344 ++++++++++++++----------- test/lib/requires.py | 1 + 10 files changed, 513 insertions(+), 265 deletions(-) create mode 100644 test/lib/profiles.txt diff --git a/test/aaa_profiling/test_compiler.py b/test/aaa_profiling/test_compiler.py index 53df3b28ea..129d0bf06c 100644 --- a/test/aaa_profiling/test_compiler.py +++ b/test/aaa_profiling/test_compiler.py @@ -34,21 +34,19 @@ class CompileTest(fixtures.TestBase, AssertsExecutionResults): cls.dialect = default.DefaultDialect() - @profiling.function_call_count(versions={'2.7':62, '2.6':62, - '3':68}) + @profiling.function_call_count(62) def test_insert(self): t1.insert().compile(dialect=self.dialect) - @profiling.function_call_count(versions={'2.6':56, '2.7':56}) + @profiling.function_call_count(56) def test_update(self): t1.update().compile(dialect=self.dialect) - @profiling.function_call_count(versions={'2.6':117, '2.7':117, '3':124}) + @profiling.function_call_count(110) def test_update_whereclause(self): t1.update().where(t1.c.c2==12).compile(dialect=self.dialect) - @profiling.function_call_count(versions={'2.7':148, '2.6':148, - '3':161}) + @profiling.function_call_count(139) def test_select(self): s = select([t1], t1.c.c2==t2.c.c1) s.compile(dialect=self.dialect) diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 0e9cda226f..480f90465a 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -10,15 +10,13 @@ import sys class MergeTest(fixtures.MappedTest): - __requires__ = 'cpython', - @classmethod def define_tables(cls, metadata): - parent = Table('parent', metadata, Column('id', Integer, + Table('parent', metadata, Column('id', Integer, primary_key=True, test_needs_autoincrement=True), Column('data', String(20))) - child = Table('child', metadata, Column('id', Integer, + Table('child', metadata, Column('id', Integer, primary_key=True, test_needs_autoincrement=True), Column('data', String(20)), Column('parent_id', Integer, ForeignKey('parent.id'), nullable=False)) @@ -38,8 +36,8 @@ class MergeTest(fixtures.MappedTest): cls.tables.parent, cls.tables.child) - mapper(Parent, parent, properties={'children' - : relationship(Child, backref='parent')}) + mapper(Parent, parent, properties={'children': + relationship(Child, backref='parent')}) mapper(Child, child) @classmethod @@ -47,8 +45,7 @@ class MergeTest(fixtures.MappedTest): parent, child = cls.tables.parent, cls.tables.child parent.insert().execute({'id': 1, 'data': 'p1'}) - child.insert().execute({'id': 1, 'data': 'p1c1', 'parent_id' - : 1}) + child.insert().execute({'id': 1, 'data': 'p1c1', 'parent_id': 1}) def test_merge_no_load(self): Parent = self.classes.Parent @@ -61,21 +58,18 @@ class MergeTest(fixtures.MappedTest): # down from 185 on this this is a small slice of a usually # bigger operation so using a small variance - @profiling.function_call_count(variance=0.05, - versions={'2.7':80, '2.6':80, '2.5':94, '3': 83}) + @profiling.function_call_count() def go(): return sess2.merge(p1, load=False) p2 = go() # third call, merge object already present. almost no calls. - @profiling.function_call_count(variance=0.05, - versions={'2.7':11, '2.6':11, '2.5':15, '3': 12}) + @profiling.function_call_count() def go(): return sess2.merge(p2, load=False) p3 = go() - @testing.only_on('sqlite', 'Call counts tailored to pysqlite') def test_merge_load(self): Parent = self.classes.Parent @@ -88,12 +82,7 @@ class MergeTest(fixtures.MappedTest): # using sqlite3 the C extension took it back up to approx. 1257 # (py2.6) - @profiling.function_call_count(variance=0.10, - versions={'2.5':1050, '2.6':1050, - '2.6+cextension':1005, - '2.7':1005, - '3':1050} - ) + @profiling.function_call_count() def go(): p2 = sess2.merge(p1) go() @@ -113,23 +102,17 @@ class LoadManyToOneFromIdentityTest(fixtures.MappedTest): """ - # only need to test for unexpected variance in a large call - # count here, - # so remove some platforms that have wildly divergent - # callcounts. - __requires__ = 'python25', 'cpython' - __unsupported_on__ = 'postgresql+pg8000', 'mysql+pymysql' @classmethod def define_tables(cls, metadata): - parent = Table('parent', metadata, + Table('parent', metadata, Column('id', Integer, primary_key=True), Column('data', String(20)), Column('child_id', Integer, ForeignKey('child.id')) ) - child = Table('child', metadata, - Column('id', Integer,primary_key=True), + Table('child', metadata, + Column('id', Integer, primary_key=True), Column('data', String(20)) ) @@ -176,7 +159,7 @@ class LoadManyToOneFromIdentityTest(fixtures.MappedTest): parents = sess.query(Parent).all() - @profiling.function_call_count(108019, variance=.2) + @profiling.function_call_count() def go(): for p in parents: p.child @@ -189,14 +172,13 @@ class LoadManyToOneFromIdentityTest(fixtures.MappedTest): parents = sess.query(Parent).all() children = sess.query(Child).all() - @profiling.function_call_count(17987, {'3':18987}) + @profiling.function_call_count() def go(): for p in parents: p.child go() class MergeBackrefsTest(fixtures.MappedTest): - __only_on__ = 'sqlite' # keep things simple @classmethod def define_tables(cls, metadata): @@ -231,12 +213,12 @@ class MergeBackrefsTest(fixtures.MappedTest): def setup_mappers(cls): A, B, C, D = cls.classes.A, cls.classes.B, \ cls.classes.C, cls.classes.D - a, b, c, d= cls.tables.a, cls.tables.b, \ + a, b, c, d = cls.tables.a, cls.tables.b, \ cls.tables.c, cls.tables.d mapper(A, a, properties={ - 'bs':relationship(B, backref='a'), - 'c':relationship(C, backref='as'), - 'ds':relationship(D, backref='a'), + 'bs': relationship(B, backref='a'), + 'c': relationship(C, backref='as'), + 'ds': relationship(D, backref='a'), }) mapper(B, b) mapper(C, c) @@ -249,26 +231,26 @@ class MergeBackrefsTest(fixtures.MappedTest): s = Session() s.add_all([ A(id=i, - bs=[B(id=(i * 50) + j) for j in xrange(1, 50)], + bs=[B(id=(i * 5) + j) for j in xrange(1, 5)], c=C(id=i), - ds=[D(id=(i * 50) + j) for j in xrange(1, 50)] + ds=[D(id=(i * 5) + j) for j in xrange(1, 5)] ) - for i in xrange(1, 50) + for i in xrange(1, 5) ]) s.commit() - @profiling.function_call_count(1092497, variance=.10) + @profiling.function_call_count() def test_merge_pending_with_all_pks(self): A, B, C, D = self.classes.A, self.classes.B, \ self.classes.C, self.classes.D s = Session() for a in [ A(id=i, - bs=[B(id=(i * 50) + j) for j in xrange(1, 50)], + bs=[B(id=(i * 5) + j) for j in xrange(1, 5)], c=C(id=i), - ds=[D(id=(i * 50) + j) for j in xrange(1, 50)] + ds=[D(id=(i * 5) + j) for j in xrange(1, 5)] ) - for i in xrange(1, 50) + for i in xrange(1, 5) ]: s.merge(a) diff --git a/test/aaa_profiling/test_pool.py b/test/aaa_profiling/test_pool.py index 096225180a..bccb9b94d0 100644 --- a/test/aaa_profiling/test_pool.py +++ b/test/aaa_profiling/test_pool.py @@ -32,10 +32,7 @@ class QueuePoolTest(fixtures.TestBase, AssertsExecutionResults): # probably # due to the event mechanics being established # or not already... - @profiling.function_call_count(72, {'2.4': 68, '2.7': 75, - '2.7+cextension': 75, - '3': 62}, - variance=.15) + @profiling.function_call_count() def test_first_connect(self): conn = pool.connect() @@ -43,10 +40,7 @@ class QueuePoolTest(fixtures.TestBase, AssertsExecutionResults): conn = pool.connect() conn.close() - @profiling.function_call_count(32, {'2.4': 21, '2.7':29, - '3.2':25, - '2.7+cextension':29}, - variance=.10) + @profiling.function_call_count() def go(): conn2 = pool.connect() return conn2 @@ -55,7 +49,7 @@ class QueuePoolTest(fixtures.TestBase, AssertsExecutionResults): def test_second_samethread_connect(self): conn = pool.connect() - @profiling.function_call_count(6, {'2.4': 4, '3':7}) + @profiling.function_call_count() def go(): return pool.connect() c2 = go() diff --git a/test/aaa_profiling/test_resultset.py b/test/aaa_profiling/test_resultset.py index e07cb3c502..92ed948392 100644 --- a/test/aaa_profiling/test_resultset.py +++ b/test/aaa_profiling/test_resultset.py @@ -6,17 +6,15 @@ NUM_RECORDS = 1000 class ResultSetTest(fixtures.TestBase, AssertsExecutionResults): - __requires__ = 'cpython', - __only_on__ = 'sqlite' @classmethod def setup_class(cls): global t, t2, metadata metadata = MetaData(testing.db) - t = Table('table', metadata, *[Column('field%d' % fnum, String) + t = Table('table', metadata, *[Column('field%d' % fnum, String(50)) for fnum in range(NUM_FIELDS)]) t2 = Table('table2', metadata, *[Column('field%d' % fnum, - Unicode) for fnum in range(NUM_FIELDS)]) + Unicode(50)) for fnum in range(NUM_FIELDS)]) def setup(self): metadata.create_all() @@ -34,36 +32,25 @@ class ResultSetTest(fixtures.TestBase, AssertsExecutionResults): def teardown(self): metadata.drop_all() - @profiling.function_call_count(versions={ - '2.4': 13214, - '2.6':14416, - '2.7':14416, - '2.6+cextension': 365, - '2.7+cextension':365}) + @profiling.function_call_count() def test_string(self): [tuple(row) for row in t.select().execute().fetchall()] # sqlite3 returns native unicode. so shouldn't be an increase here. - @profiling.function_call_count(versions={ - '2.7':14396, - '2.6':14396, - '2.6+cextension': 365, - '2.7+cextension':365}) + @profiling.function_call_count() def test_unicode(self): [tuple(row) for row in t2.select().execute().fetchall()] def test_contains_doesnt_compile(self): row = t.select().execute().first() c1 = Column('some column', Integer) + Column("some other column", Integer) - @profiling.function_call_count(9, variance=.15) + @profiling.function_call_count() def go(): c1 in row go() class ExecutionTest(fixtures.TestBase): - __requires__ = 'cpython', - __only_on__ = 'sqlite' def test_minimal_connection_execute(self): # create an engine without any instrumentation. @@ -72,9 +59,7 @@ class ExecutionTest(fixtures.TestBase): # ensure initial connect activities complete c.execute("select 1") - @profiling.function_call_count(versions={'2.7':40, '2.6':40, '2.5':35, - '2.4':21, '3':40}, - variance=.10) + @profiling.function_call_count() def go(): c.execute("select 1") go() @@ -85,17 +70,15 @@ class ExecutionTest(fixtures.TestBase): # ensure initial connect activities complete e.execute("select 1") - @profiling.function_call_count(versions={'2.4':41, '2.5':65, - '2.6':65, '3':61, - '2.7':65, - '2.6+cextension':65}, - variance=.05) + @profiling.function_call_count() def go(): e.execute("select 1") go() class RowProxyTest(fixtures.TestBase): + __requires__ = 'cpython', + def _rowproxy_fixture(self, keys, processors, row): from sqlalchemy.engine.base import RowProxy class MockMeta(object): @@ -119,7 +102,7 @@ class RowProxyTest(fixtures.TestBase): return value value1, value2 = "x", "y" row = self._rowproxy_fixture( - [(col1, "a"),(col2, "b")], + [(col1, "a"), (col2, "b")], [proc1, None], seq_factory([value1, value2]) ) diff --git a/test/aaa_profiling/test_zoomark.py b/test/aaa_profiling/test_zoomark.py index 564d35f0c0..97d9f1243b 100644 --- a/test/aaa_profiling/test_zoomark.py +++ b/test/aaa_profiling/test_zoomark.py @@ -365,45 +365,34 @@ class ZooMarkTest(fixtures.TestBase): metadata = MetaData(engine) engine.connect() - @profiling.function_call_count(3896, {'2.4': 1711}) def test_profile_1_create_tables(self): self.test_baseline_1_create_tables() - @profiling.function_call_count(5045, {'2.6':5099, '2.4': 3650, '3.2':4699}) + @profiling.function_call_count() def test_profile_1a_populate(self): self.test_baseline_1a_populate() - @profiling.function_call_count(245, {'2.4': 172}) + @profiling.function_call_count() def test_profile_2_insert(self): self.test_baseline_2_insert() - @profiling.function_call_count(3333, {'2.4': 2358}) + @profiling.function_call_count() def test_profile_3_properties(self): self.test_baseline_3_properties() - @profiling.function_call_count(11624, {'2.4': 7963, '2.6+cextension' - : 10736, '2.7+cextension': 10736}, - variance=0.10) + @profiling.function_call_count() def test_profile_4_expressions(self): self.test_baseline_4_expressions() - @profiling.function_call_count(1059, {'2.4': 904, '2.6+cextension' - : 1027, '2.6.4':1167, '2.7+cextension': 1027}, - variance=0.10) + @profiling.function_call_count() def test_profile_5_aggregates(self): self.test_baseline_5_aggregates() - @profiling.function_call_count(1788, {'2.4': 1118, '3.2':1647, - '2.7+cextension':1698}) + @profiling.function_call_count() def test_profile_6_editing(self): self.test_baseline_6_editing() - @profiling.function_call_count(2252, {'2.4': 1673, - '2.6':2412, - '2.7':2412, - '3.2':2396, - '2.7+cextension':2110, - '2.6+cextension': 2252}) + @profiling.function_call_count() def test_profile_7_multiview(self): self.test_baseline_7_multiview() diff --git a/test/aaa_profiling/test_zoomark_orm.py b/test/aaa_profiling/test_zoomark_orm.py index 9947e94f28..ed40740966 100644 --- a/test/aaa_profiling/test_zoomark_orm.py +++ b/test/aaa_profiling/test_zoomark_orm.py @@ -331,44 +331,35 @@ class ZooMarkTest(fixtures.TestBase): session = sessionmaker(engine)() engine.connect() - @profiling.function_call_count(5600, {"3.2":5928}) + @profiling.function_call_count() def test_profile_1_create_tables(self): self.test_baseline_1_create_tables() - @profiling.function_call_count(5786, {'2.7+cextension':5683, - '2.6+cextension':5992}) + @profiling.function_call_count() def test_profile_1a_populate(self): self.test_baseline_1a_populate() - @profiling.function_call_count(413, {'3.2':398}) + @profiling.function_call_count() def test_profile_2_insert(self): self.test_baseline_2_insert() # this number... - @profiling.function_call_count(6783, { - '2.6': 6058, - '2.7': 5922, - '2.7+cextension': 5714, - '2.6+cextension': 5714, - '3.2':5787, - }) + @profiling.function_call_count() def test_profile_3_properties(self): self.test_baseline_3_properties() # and this number go down slightly when using the C extensions - @profiling.function_call_count(17698, {'2.7+cextension':17698, '2.6': 18943, '2.7':19110, '3.2':19264}) + @profiling.function_call_count() def test_profile_4_expressions(self): self.test_baseline_4_expressions() - @profiling.function_call_count(1172, {'2.6+cextension': 1090, - '2.7+cextension': 1086}, - variance=0.1) + @profiling.function_call_count() def test_profile_5_aggregates(self): self.test_baseline_5_aggregates() - @profiling.function_call_count(2545) + @profiling.function_call_count() def test_profile_6_editing(self): self.test_baseline_6_editing() diff --git a/test/bootstrap/noseplugin.py b/test/bootstrap/noseplugin.py index d653fa5020..dc14b48b33 100644 --- a/test/bootstrap/noseplugin.py +++ b/test/bootstrap/noseplugin.py @@ -19,6 +19,10 @@ from test.bootstrap.config import ( _set_table_options, base_config, db, db_label, db_url, file_config, post_configure, pre_configure) +testing = None +engines = None +util = None + log = logging.getLogger('nose.plugins.sqlalchemy') class NoseSQLAlchemy(Plugin): @@ -78,7 +82,8 @@ class NoseSQLAlchemy(Plugin): "a db-default/InnoDB combo.") opt("--table-option", action="append", dest="tableopts", default=[], help="Add a dialect-specific table option, key=value") - + opt("--write-profiles", action="store_true", dest="write_profiles", default=False, + help="Write/update profiling data.") global file_config file_config = ConfigParser.ConfigParser() file_config.readfp(StringIO.StringIO(base_config)) @@ -178,6 +183,7 @@ class NoseSQLAlchemy(Plugin): def beforeTest(self, test): testing.resetwarnings() + testing.current_test = test.id() def afterTest(self, test): engines.testing_reaper._after_test_ctx() diff --git a/test/lib/profiles.txt b/test/lib/profiles.txt new file mode 100644 index 0000000000..9d8610806f --- /dev/null +++ b/test/lib/profiles.txt @@ -0,0 +1,252 @@ +# /Users/classic/dev/sa07/./test/lib/profiles.txt +# This file is written out on a per-environment basis. +# For each test in aaa_profiling, the corresponding function and +# environment is located within this file. If it doesn't exist, +# the test is skipped. +# If a callcount does exist, it is compared to what we received. +# assertions are raised if the counts do not match. +# +# To add a new callcount test, apply the function_call_count +# decorator and re-run the tests using the --write-profiles option - +# this file will be rewritten including the new count. +# + +# TEST: test.aaa_profiling.test_compiler.CompileTest.test_insert + +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.6_sqlite_pysqlite_nocextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_mysql_mysqldb_cextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_mysql_mysqldb_nocextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_postgresql_psycopg2_cextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_postgresql_psycopg2_nocextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_cextensions 62 +test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_nocextensions 62 + +# TEST: test.aaa_profiling.test_compiler.CompileTest.test_select + +test.aaa_profiling.test_compiler.CompileTest.test_select 2.6_sqlite_pysqlite_nocextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_cextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_nocextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_cextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_nocextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_cextensions 152 +test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_nocextensions 152 + +# TEST: test.aaa_profiling.test_compiler.CompileTest.test_update + +test.aaa_profiling.test_compiler.CompileTest.test_update 2.6_sqlite_pysqlite_nocextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_cextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_nocextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_cextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_nocextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_cextensions 56 +test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_nocextensions 56 + +# TEST: test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause + +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.6_sqlite_pysqlite_nocextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_cextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_nocextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_cextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_nocextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_cextensions 117 +test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_nocextensions 117 + +# TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity + +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.6_sqlite_pysqlite_nocextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_cextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_nocextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_cextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_nocextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_cextensions 17987 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_nocextensions 17987 + +# TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity + +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.6_sqlite_pysqlite_nocextensions 112545 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_cextensions 119045 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_nocextensions 121045 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_cextensions 111517 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_nocextensions 113545 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_cextensions 110545 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_nocextensions 112517 + +# TEST: test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks + +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.6_sqlite_pysqlite_nocextensions 18493 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_cextensions 18845 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_nocextensions 19057 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_cextensions 18313 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_nocextensions 18525 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_cextensions 18281 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 18493 + +# TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load + +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.6_sqlite_pysqlite_nocextensions 1071,1024 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_cextensions 1244,1098 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_nocextensions 1263,1117 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_cextensions 1103,1040 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_nocextensions 1122,1059 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cextensions 1052,1005 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1071,1024 + +# TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load + +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.6_sqlite_pysqlite_nocextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_mysql_mysqldb_cextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_mysql_mysqldb_nocextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_postgresql_psycopg2_cextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_postgresql_psycopg2_nocextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_sqlite_pysqlite_cextensions 82,11 +test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_sqlite_pysqlite_nocextensions 82,11 + +# TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect + +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.6_sqlite_pysqlite_nocextensions 75 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_mysql_mysqldb_cextensions 67 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_mysql_mysqldb_nocextensions 67 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_postgresql_psycopg2_cextensions 67 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_postgresql_psycopg2_nocextensions 67 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_sqlite_pysqlite_cextensions 75 +test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_sqlite_pysqlite_nocextensions 75 + +# TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect + +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.6_sqlite_pysqlite_nocextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_mysql_mysqldb_cextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_mysql_mysqldb_nocextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_postgresql_psycopg2_cextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_postgresql_psycopg2_nocextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_sqlite_pysqlite_cextensions 29 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_sqlite_pysqlite_nocextensions 29 + +# TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect + +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.6_sqlite_pysqlite_nocextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_mysql_mysqldb_cextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_mysql_mysqldb_nocextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_postgresql_psycopg2_cextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_postgresql_psycopg2_nocextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_sqlite_pysqlite_cextensions 6 +test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_sqlite_pysqlite_nocextensions 6 + +# TEST: test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute + +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.6_sqlite_pysqlite_nocextensions 43 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_mysql_mysqldb_cextensions 41 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_mysql_mysqldb_nocextensions 43 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_postgresql_psycopg2_cextensions 41 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_postgresql_psycopg2_nocextensions 43 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_sqlite_pysqlite_cextensions 41 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_sqlite_pysqlite_nocextensions 43 + +# TEST: test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute + +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.6_sqlite_pysqlite_nocextensions 66 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_mysql_mysqldb_cextensions 64 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_mysql_mysqldb_nocextensions 66 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_postgresql_psycopg2_cextensions 64 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_postgresql_psycopg2_nocextensions 66 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_sqlite_pysqlite_cextensions 64 +test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_sqlite_pysqlite_nocextensions 66 + +# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile + +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.6_sqlite_pysqlite_nocextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_mysql_mysqldb_cextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_mysql_mysqldb_nocextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_cextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_nocextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_sqlite_pysqlite_cextensions 9 +test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_sqlite_pysqlite_nocextensions 9 + +# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string + +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.6_sqlite_pysqlite_nocextensions 14388 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_cextensions 426 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_nocextensions 14446 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_cextensions 20412 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_nocextensions 34432 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_cextensions 368 +test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_nocextensions 14388 + +# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_unicode + +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.6_sqlite_pysqlite_nocextensions 14388 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_cextensions 426 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_nocextensions 44446 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_cextensions 20412 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_nocextensions 34432 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_cextensions 368 +test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_nocextensions 14388 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 4948 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 4992 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 247 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 247 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3293 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 3517 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 10719 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 12335 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1008 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1112 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 1711 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 1754 + +# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview + +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_cextensions 2172 +test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_nocextensions 2402 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1_create_tables + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1_create_tables 2.7_postgresql_psycopg2_cextensions 5752 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1_create_tables 2.7_postgresql_psycopg2_nocextensions 5760 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 5745 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 5813 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 395 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 399 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 5876 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 6084 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 18057 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 19425 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1021 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1117 + +# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing + +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 2542 +test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 2590 diff --git a/test/lib/profiling.py b/test/lib/profiling.py index f471457185..6142c41c9e 100644 --- a/test/lib/profiling.py +++ b/test/lib/profiling.py @@ -5,24 +5,26 @@ in a more fine-grained way than nose's profiling plugin. """ -import os, sys +import os +import sys from test.lib.util import gc_collect, decorator +from test.lib import testing from nose import SkipTest - -__all__ = 'profiled', 'function_call_count', 'conditional_call_count' - -all_targets = set() -profile_config = { 'targets': set(), - 'report': True, - 'print_callers':False, - 'print_callees':False, - 'graphic':False, - 'sort': ('time', 'calls'), - 'limit': None } -profiler = None +import pstats +import time +import collections +from sqlalchemy import util +try: + import cProfile +except ImportError: + cProfile = None +from sqlalchemy.util.compat import jython, pypy, win32 + +from test.lib.requires import _has_cextensions +_has_cext = _has_cextensions() def profiled(target=None, **target_opts): - """Optional function profiling. + """Function profiling. @profiled('label') or @@ -33,21 +35,20 @@ def profiled(target=None, **target_opts): configuration and command-line options. """ - # manual or automatic namespacing by module would remove conflict issues + profile_config = {'targets': set(), + 'report': True, + 'print_callers': False, + 'print_callees': False, + 'graphic': False, + 'sort': ('time', 'calls'), + 'limit': None} if target is None: target = 'anonymous_target' - elif target in all_targets: - print "Warning: redefining profile target '%s'" % target - all_targets.add(target) filename = "%s.prof" % target @decorator def decorate(fn, *args, **kw): - if (target not in profile_config['targets'] and - not target_opts.get('always', None)): - return fn(*args, **kw) - elapsed, load_stats, result = _profile( filename, fn, *args, **kw) @@ -59,8 +60,9 @@ def profiled(target=None, **target_opts): if report: sort_ = target_opts.get('sort', profile_config['sort']) limit = target_opts.get('limit', profile_config['limit']) - print "Profile report for target '%s' (%s)" % ( + print ("Profile report for target '%s' (%s)" % ( target, filename) + ) stats = load_stats() stats.sort_stats(*sort_) @@ -83,134 +85,201 @@ def profiled(target=None, **target_opts): return result return decorate -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. +class ProfileStatsFile(object): + """"Store per-platform/fn profiling results in a file. - versions - Optional, a dictionary of Python version strings to counts, - for example:: + We're still targeting Py2.5, 2.4 on 0.7 with no dependencies, + so no json lib :( need to roll something silly - { '2.5.1': 110, - '2.5': 100, - '2.4': 150 } + """ + def __init__(self): + from test.bootstrap.config import options + self.write = options.write_profiles + dirname, fname = os.path.split(__file__) + self.short_fname = "profiles.txt" + self.fname = os.path.join(dirname, self.short_fname) + self.data = collections.defaultdict(lambda: collections.defaultdict(dict)) + self._read() + if self.write: + # rewrite for the case where features changed, + # etc. + self._write() + + @util.memoized_property + def platform_key(self): + + dbapi_key = testing.db.name + "_" + testing.db.driver + + # keep it at 2.7, 3.1, 3.2, etc. for now. + py_version = '.'.join([str(v) for v in sys.version_info[0:2]]) + + platform_tokens = [py_version] + platform_tokens.append(dbapi_key) + if jython: + platform_tokens.append("jython") + if pypy: + platform_tokens.append("pypy") + if win32: + platform_tokens.append("win") + platform_tokens.append(_has_cext and "cextensions" or "nocextensions") + return "_".join(platform_tokens) + + def has_stats(self): + test_key = testing.current_test + return test_key in self.data and self.platform_key in self.data[test_key] + + def result(self, callcount): + test_key = testing.current_test + per_fn = self.data[test_key] + per_platform = per_fn[self.platform_key] + + if 'counts' not in per_platform: + per_platform['counts'] = counts = [] + else: + counts = per_platform['counts'] - The best match for the current running python will be used. - If none match, 'count' will be used as the fallback. + if 'current_count' not in per_platform: + per_platform['current_count'] = current_count = 0 + else: + current_count = per_platform['current_count'] - variance - An +/- deviation percentage, defaults to 5%. - """ + has_count = len(counts) > current_count - # this could easily dump the profile report if --verbose is in effect + if not has_count: + counts.append(callcount) + if self.write: + self._write() + result = None + else: + result = per_platform['lineno'], counts[current_count] + per_platform['current_count'] += 1 + return result - version_info = list(sys.version_info) - py_version = '.'.join([str(v) for v in sys.version_info]) - try: - from sqlalchemy.cprocessors import to_float - cextension = True - except ImportError: - cextension = False - while version_info: - version = '.'.join([str(v) for v in version_info]) - if cextension and (version + "+cextension") in versions: - version += "+cextension" - count = versions[version] - break - elif version in versions: - count = versions[version] - break + def _header(self): + return \ + "# %s\n"\ + "# This file is written out on a per-environment basis.\n"\ + "# For each test in aaa_profiling, the corresponding function and \n"\ + "# environment is located within this file. If it doesn't exist,\n"\ + "# the test is skipped.\n"\ + "# If a callcount does exist, it is compared to what we received. \n"\ + "# assertions are raised if the counts do not match.\n"\ + "# \n"\ + "# To add a new callcount test, apply the function_call_count \n"\ + "# decorator and re-run the tests using the --write-profiles option - \n"\ + "# this file will be rewritten including the new count.\n"\ + "# \n"\ + "" % (self.fname) + + def _read(self): + profile_f = open(self.fname) + for lineno, line in enumerate(profile_f): + line = line.strip() + if not line or line.startswith("#"): + continue + + test_key, platform_key, counts = line.split() + per_fn = self.data[test_key] + per_platform = per_fn[platform_key] + per_platform['counts'] = [int(count) for count in counts.split(",")] + per_platform['lineno'] = lineno + 1 + per_platform['current_count'] = 0 + profile_f.close() + + def _write(self): + print("Writing profile file %s" % self.fname) + profile_f = open(self.fname, "w") + profile_f.write(self._header()) + for test_key in sorted(self.data): + + per_fn = self.data[test_key] + profile_f.write("\n# TEST: %s\n\n" % test_key) + for platform_key in sorted(per_fn): + per_platform = per_fn[platform_key] + profile_f.write( + "%s %s %s\n" % ( + test_key, + platform_key, ",".join(str(count) for count in per_platform['counts']) + ) + ) + profile_f.close() + +_profile_stats = ProfileStatsFile() + +from sqlalchemy.util.compat import update_wrapper + +def function_call_count(variance=0.05): + """Assert a target for a test case's function call count. - version_info.pop() + The main purpose of this assertion is to detect changes in + callcounts for various functions - the actual number is not as important. + Callcounts are stored in a file keyed to Python version and OS platform + information. This file is generated automatically for new tests, + and versioned so that unexpected changes in callcounts will be detected. - if count is None: - print "Warning: no function call count specified for version: '%s'" % py_version - return lambda fn: fn + """ - @decorator - def decorate(fn, *args, **kw): - try: - filename = "%s.prof" % fn.__name__ + def decorate(fn): + def wrap(*args, **kw): - elapsed, stat_loader, result = _profile( - filename, fn, *args, **kw) - stats = stat_loader() - calls = stats.total_calls + if cProfile is None: + raise SkipTest("cProfile is not installed") - stats.sort_stats('calls', 'cumulative') - stats.print_stats() - #stats.print_callers() - 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 * 100), count, py_version)) - - return result - finally: - if os.path.exists(filename): - os.unlink(filename) - return decorate + if not _profile_stats.has_stats() and not _profile_stats.write: + raise SkipTest("No profiling stats available on this " + "platform for this function. Run tests with " + "--write-profiles to add statistics to %s for " + "this platform." % _profile_stats.short_fname) -def conditional_call_count(discriminator, categories): - """Apply a function call count conditionally at runtime. + gc_collect() - Takes two arguments, a callable that returns a key value, and a dict - mapping key values to a tuple of arguments to function_call_count. - The callable is not evaluated until the decorated function is actually - invoked. If the `discriminator` returns a key not present in the - `categories` dictionary, no call count assertion is applied. + timespent, load_stats, fn_result = _profile( + fn, *args, **kw + ) + stats = load_stats() + callcount = stats.total_calls - Useful for integration tests, where running a named test in isolation may - have a function count penalty not seen in the full suite, due to lazy - initialization in the DB-API, SA, etc. - """ - @decorator - def decorate(fn, *args, **kw): - criteria = categories.get(discriminator(), None) - if criteria is None: - return fn(*args, **kw) + expected = _profile_stats.result(callcount) + if expected is None: + expected_count = None + else: + line_no, expected_count = expected + + print("Pstats calls: %d Expected %s" % ( + callcount, + expected_count + ) + ) + stats.print_stats() + #stats.print_callers() - rewrapped = function_call_count(*criteria)(fn) - return rewrapped(*args, **kw) + if expected_count: + deviance = int(callcount * variance) + if abs(callcount - expected_count) > deviance: + raise AssertionError( + "Adjusted function call count %s not within %s%% " + "of expected %s. (Delete line %d of file %s to regenerate " + "this callcount, when tests are run with --write-profiles.)" + % ( + callcount, (variance * 100), + expected_count, line_no, + _profile_stats.fname)) + return fn_result + return update_wrapper(wrap, fn) return decorate -def _profile(filename, fn, *args, **kw): - global profiler - if not profiler: - if sys.version_info > (2, 5): - try: - import cProfile - profiler = 'cProfile' - except ImportError: - pass - if not profiler: - try: - import hotshot - profiler = 'hotshot' - except ImportError: - profiler = 'skip' - - if profiler == 'skip': - raise SkipTest('Profiling not supported on this platform') - elif profiler == 'cProfile': - return _profile_cProfile(filename, fn, *args, **kw) - else: - return _profile_hotshot(filename, fn, *args, **kw) - -def _profile_cProfile(filename, fn, *args, **kw): - import cProfile, pstats, time - - load_stats = lambda: pstats.Stats(filename) - gc_collect() +def _profile(fn, *args, **kw): + filename = "%s.prof" % fn.__name__ + + def load_stats(): + st = pstats.Stats(filename) + os.unlink(filename) + return st began = time.time() cProfile.runctx('result = fn(*args, **kw)', globals(), locals(), @@ -219,20 +288,3 @@ def _profile_cProfile(filename, fn, *args, **kw): return ended - began, load_stats, locals()['result'] -def _profile_hotshot(filename, fn, *args, **kw): - import hotshot, hotshot.stats, time - load_stats = lambda: hotshot.stats.load(filename) - - gc_collect() - prof = hotshot.Profile(filename) - began = time.time() - prof.start() - try: - result = fn(*args, **kw) - finally: - prof.stop() - ended = time.time() - prof.close() - - return ended - began, load_stats, result - diff --git a/test/lib/requires.py b/test/lib/requires.py index 9c0526514a..1fe55df39e 100644 --- a/test/lib/requires.py +++ b/test/lib/requires.py @@ -301,6 +301,7 @@ def cextensions(fn): skip_if(lambda: not _has_cextensions(), "C extensions not installed") ) + def dbapi_lastrowid(fn): if util.pypy: return _chain_decorators_on( -- 2.47.2