summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/testing
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-01-21 20:10:23 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-01-21 20:10:23 -0500
commit07fb90c6cc14de6d02cf4be592c57d56831f59f7 (patch)
tree050ef65db988559c60f7aa40f2d0bfe24947e548 /lib/sqlalchemy/testing
parent560fd1d5ed643a1b0f95296f3b840c1963bbe67f (diff)
parentee1f4d21037690ad996c5eacf7e1200e92f2fbaa (diff)
downloadsqlalchemy-ticket_2501.tar.gz
Merge branch 'master' into ticket_2501ticket_2501
Conflicts: lib/sqlalchemy/orm/mapper.py
Diffstat (limited to 'lib/sqlalchemy/testing')
-rw-r--r--lib/sqlalchemy/testing/__init__.py7
-rw-r--r--lib/sqlalchemy/testing/assertions.py73
-rw-r--r--lib/sqlalchemy/testing/assertsql.py5
-rw-r--r--lib/sqlalchemy/testing/config.py6
-rw-r--r--lib/sqlalchemy/testing/engines.py18
-rw-r--r--lib/sqlalchemy/testing/entities.py6
-rw-r--r--lib/sqlalchemy/testing/exclusions.py19
-rw-r--r--lib/sqlalchemy/testing/fixtures.py6
-rw-r--r--lib/sqlalchemy/testing/mock.py10
-rw-r--r--lib/sqlalchemy/testing/pickleable.py6
-rw-r--r--lib/sqlalchemy/testing/plugin/noseplugin.py23
-rw-r--r--lib/sqlalchemy/testing/profiling.py47
-rw-r--r--lib/sqlalchemy/testing/requirements.py47
-rw-r--r--lib/sqlalchemy/testing/runner.py5
-rw-r--r--lib/sqlalchemy/testing/schema.py5
-rw-r--r--lib/sqlalchemy/testing/suite/test_insert.py33
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py8
-rw-r--r--lib/sqlalchemy/testing/suite/test_types.py139
-rw-r--r--lib/sqlalchemy/testing/util.py6
-rw-r--r--lib/sqlalchemy/testing/warnings.py6
20 files changed, 402 insertions, 73 deletions
diff --git a/lib/sqlalchemy/testing/__init__.py b/lib/sqlalchemy/testing/__init__.py
index a87829499..8ad856e2b 100644
--- a/lib/sqlalchemy/testing/__init__.py
+++ b/lib/sqlalchemy/testing/__init__.py
@@ -1,3 +1,8 @@
+# testing/__init__.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
from .warnings import testing_warn, assert_warnings, resetwarnings
@@ -11,7 +16,7 @@ from .exclusions import db_spec, _is_excluded, fails_if, skip_if, future,\
from .assertions import emits_warning, emits_warning_on, uses_deprecated, \
eq_, ne_, is_, is_not_, startswith_, assert_raises, \
assert_raises_message, AssertsCompiledSQL, ComparesTables, \
- AssertsExecutionResults
+ AssertsExecutionResults, expect_deprecated
from .util import run_as_contextmanager, rowset, fail, provide_metadata, adict
diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py
index 96a8bc023..61649e5e3 100644
--- a/lib/sqlalchemy/testing/assertions.py
+++ b/lib/sqlalchemy/testing/assertions.py
@@ -1,8 +1,14 @@
+# testing/assertions.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
from __future__ import absolute_import
from . import util as testutil
from sqlalchemy import pool, orm, util
-from sqlalchemy.engine import default, create_engine
+from sqlalchemy.engine import default, create_engine, url
from sqlalchemy import exc as sa_exc
from sqlalchemy.util import decorator
from sqlalchemy import types as sqltypes, schema
@@ -92,30 +98,36 @@ def uses_deprecated(*messages):
@decorator
def decorate(fn, *args, **kw):
- # todo: should probably be strict about this, too
- filters = [dict(action='ignore',
- category=sa_exc.SAPendingDeprecationWarning)]
- if not messages:
- filters.append(dict(action='ignore',
- category=sa_exc.SADeprecationWarning))
- else:
- filters.extend(
- [dict(action='ignore',
- message=message,
- category=sa_exc.SADeprecationWarning)
- for message in
- [(m.startswith('//') and
- ('Call to deprecated function ' + m[2:]) or m)
- for m in messages]])
-
- for f in filters:
- warnings.filterwarnings(**f)
- try:
+ with expect_deprecated(*messages):
return fn(*args, **kw)
- finally:
- resetwarnings()
return decorate
+@contextlib.contextmanager
+def expect_deprecated(*messages):
+ # todo: should probably be strict about this, too
+ filters = [dict(action='ignore',
+ category=sa_exc.SAPendingDeprecationWarning)]
+ if not messages:
+ filters.append(dict(action='ignore',
+ category=sa_exc.SADeprecationWarning))
+ else:
+ filters.extend(
+ [dict(action='ignore',
+ message=message,
+ category=sa_exc.SADeprecationWarning)
+ for message in
+ [(m.startswith('//') and
+ ('Call to deprecated function ' + m[2:]) or m)
+ for m in messages]])
+
+ for f in filters:
+ warnings.filterwarnings(**f)
+ try:
+ yield
+ finally:
+ resetwarnings()
+
+
def global_cleanup_assertions():
"""Check things that have to be finalized at the end of a test suite.
@@ -181,7 +193,8 @@ class AssertsCompiledSQL(object):
checkparams=None, dialect=None,
checkpositional=None,
use_default_dialect=False,
- allow_dialect_select=False):
+ allow_dialect_select=False,
+ literal_binds=False):
if use_default_dialect:
dialect = default.DefaultDialect()
elif allow_dialect_select:
@@ -195,26 +208,36 @@ class AssertsCompiledSQL(object):
elif dialect == 'default':
dialect = default.DefaultDialect()
elif isinstance(dialect, util.string_types):
- dialect = create_engine("%s://" % dialect).dialect
+ dialect = url.URL(dialect).get_dialect()()
kw = {}
+ compile_kwargs = {}
+
if params is not None:
kw['column_keys'] = list(params)
+ if literal_binds:
+ compile_kwargs['literal_binds'] = True
+
if isinstance(clause, orm.Query):
context = clause._compile_context()
context.statement.use_labels = True
clause = context.statement
+ if compile_kwargs:
+ kw['compile_kwargs'] = compile_kwargs
+
c = clause.compile(dialect=dialect, **kw)
param_str = repr(getattr(c, 'params', {}))
if util.py3k:
param_str = param_str.encode('utf-8').decode('ascii', 'ignore')
+ print(("\nSQL String:\n" + util.text_type(c) + param_str).encode('utf-8'))
+ else:
+ print("\nSQL String:\n" + util.text_type(c).encode('utf-8') + param_str)
- print("\nSQL String:\n" + util.text_type(c) + param_str)
cc = re.sub(r'[\n\t]', '', util.text_type(c))
diff --git a/lib/sqlalchemy/testing/assertsql.py b/lib/sqlalchemy/testing/assertsql.py
index a6b63b2c3..3e0d4c9d3 100644
--- a/lib/sqlalchemy/testing/assertsql.py
+++ b/lib/sqlalchemy/testing/assertsql.py
@@ -1,3 +1,8 @@
+# testing/assertsql.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
from ..engine.default import DefaultDialect
from .. import util
diff --git a/lib/sqlalchemy/testing/config.py b/lib/sqlalchemy/testing/config.py
index ae4f585e1..64f578dab 100644
--- a/lib/sqlalchemy/testing/config.py
+++ b/lib/sqlalchemy/testing/config.py
@@ -1,2 +1,8 @@
+# testing/config.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
requirements = None
db = None
diff --git a/lib/sqlalchemy/testing/engines.py b/lib/sqlalchemy/testing/engines.py
index 29c8b6a03..d85771f8a 100644
--- a/lib/sqlalchemy/testing/engines.py
+++ b/lib/sqlalchemy/testing/engines.py
@@ -1,3 +1,9 @@
+# testing/engines.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
from __future__ import absolute_import
import types
@@ -26,6 +32,9 @@ class ConnectionKiller(object):
def checkout(self, dbapi_con, con_record, con_proxy):
self.proxy_refs[con_proxy] = True
+ def invalidate(self, dbapi_con, con_record, exception):
+ self.conns.discard((dbapi_con, con_record))
+
def _safe(self, fn):
try:
fn()
@@ -43,7 +52,7 @@ class ConnectionKiller(object):
def close_all(self):
for rec in list(self.proxy_refs):
- if rec is not None:
+ if rec is not None and rec.is_valid:
self._safe(rec._close)
def _after_test_ctx(self):
@@ -52,7 +61,7 @@ class ConnectionKiller(object):
# is collecting in finalize_fairy, deadlock.
# not sure if this should be if pypy/jython only.
# note that firebird/fdb definitely needs this though
- for conn, rec in self.conns:
+ for conn, rec in list(self.conns):
self._safe(conn.rollback)
def _stop_test_ctx(self):
@@ -72,10 +81,10 @@ class ConnectionKiller(object):
def _stop_test_ctx_aggressive(self):
self.close_all()
- for conn, rec in self.conns:
+ for conn, rec in list(self.conns):
self._safe(conn.close)
rec.connection = None
-
+
self.conns = set()
for rec in list(self.testing_engines):
rec.dispose()
@@ -220,6 +229,7 @@ def testing_engine(url=None, options=None):
if use_reaper:
event.listen(engine.pool, 'connect', testing_reaper.connect)
event.listen(engine.pool, 'checkout', testing_reaper.checkout)
+ event.listen(engine.pool, 'invalidate', testing_reaper.invalidate)
testing_reaper.add_engine(engine)
return engine
diff --git a/lib/sqlalchemy/testing/entities.py b/lib/sqlalchemy/testing/entities.py
index c0dd58650..0553e0e22 100644
--- a/lib/sqlalchemy/testing/entities.py
+++ b/lib/sqlalchemy/testing/entities.py
@@ -1,3 +1,9 @@
+# testing/entities.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
import sqlalchemy as sa
from sqlalchemy import exc as sa_exc
diff --git a/lib/sqlalchemy/testing/exclusions.py b/lib/sqlalchemy/testing/exclusions.py
index f580f3fde..f868f6396 100644
--- a/lib/sqlalchemy/testing/exclusions.py
+++ b/lib/sqlalchemy/testing/exclusions.py
@@ -1,3 +1,8 @@
+# testing/exclusions.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
import operator
@@ -19,6 +24,11 @@ class skip_if(object):
def enabled(self):
return not self.predicate()
+ def __add__(self, other):
+ def decorate(fn):
+ return other(self(fn))
+ return decorate
+
@contextlib.contextmanager
def fail_if(self, name='block'):
try:
@@ -93,7 +103,14 @@ class Predicate(object):
elif isinstance(predicate, tuple):
return SpecPredicate(*predicate)
elif isinstance(predicate, util.string_types):
- return SpecPredicate(predicate, None, None)
+ tokens = predicate.split(" ", 2)
+ op = spec = None
+ db = tokens.pop(0)
+ if tokens:
+ op = tokens.pop(0)
+ if tokens:
+ spec = tuple(int(d) for d in tokens.pop(0).split("."))
+ return SpecPredicate(db, op, spec)
elif util.callable(predicate):
return LambdaPredicate(predicate)
else:
diff --git a/lib/sqlalchemy/testing/fixtures.py b/lib/sqlalchemy/testing/fixtures.py
index daa779ae3..464a723d2 100644
--- a/lib/sqlalchemy/testing/fixtures.py
+++ b/lib/sqlalchemy/testing/fixtures.py
@@ -1,3 +1,9 @@
+# testing/fixtures.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
from . import config
from . import assertions, schema
from .util import adict
diff --git a/lib/sqlalchemy/testing/mock.py b/lib/sqlalchemy/testing/mock.py
index 650962384..18ba053ea 100644
--- a/lib/sqlalchemy/testing/mock.py
+++ b/lib/sqlalchemy/testing/mock.py
@@ -1,13 +1,19 @@
+# testing/mock.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
"""Import stub for mock library.
"""
from __future__ import absolute_import
from ..util import py33
if py33:
- from unittest.mock import MagicMock, Mock, call
+ from unittest.mock import MagicMock, Mock, call, patch
else:
try:
- from mock import MagicMock, Mock, call
+ from mock import MagicMock, Mock, call, patch
except ImportError:
raise ImportError(
"SQLAlchemy's test suite requires the "
diff --git a/lib/sqlalchemy/testing/pickleable.py b/lib/sqlalchemy/testing/pickleable.py
index 09d51b5fa..9a41034bf 100644
--- a/lib/sqlalchemy/testing/pickleable.py
+++ b/lib/sqlalchemy/testing/pickleable.py
@@ -1,3 +1,9 @@
+# testing/pickleable.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
"""Classes used in pickling tests, need to be at the module level for
unpickling.
"""
diff --git a/lib/sqlalchemy/testing/plugin/noseplugin.py b/lib/sqlalchemy/testing/plugin/noseplugin.py
index b3cd3a4e3..27a028cd4 100644
--- a/lib/sqlalchemy/testing/plugin/noseplugin.py
+++ b/lib/sqlalchemy/testing/plugin/noseplugin.py
@@ -1,3 +1,9 @@
+# plugin/noseplugin.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
"""Enhance nose with extra options and behaviors for running SQLAlchemy tests.
When running ./sqla_nose.py, this module is imported relative to the
@@ -351,6 +357,8 @@ class NoseSQLAlchemy(Plugin):
return ""
def wantFunction(self, fn):
+ if fn.__module__ is None:
+ return False
if fn.__module__.startswith('sqlalchemy.testing'):
return False
@@ -385,8 +393,9 @@ class NoseSQLAlchemy(Plugin):
check.reason if check.reason
else
(
- "'%s' unsupported on DB implementation '%s'" % (
- cls.__name__, config.db.name
+ "'%s' unsupported on DB implementation '%s' == %s" % (
+ cls.__name__, config.db.name,
+ config.db.dialect.server_version_info
)
)
)
@@ -395,16 +404,18 @@ class NoseSQLAlchemy(Plugin):
spec = exclusions.db_spec(*cls.__unsupported_on__)
if spec(config.db):
raise SkipTest(
- "'%s' unsupported on DB implementation '%s'" % (
- cls.__name__, config.db.name)
+ "'%s' unsupported on DB implementation '%s' == %s" % (
+ cls.__name__, config.db.name,
+ config.db.dialect.server_version_info)
)
if getattr(cls, '__only_on__', None):
spec = exclusions.db_spec(*util.to_list(cls.__only_on__))
if not spec(config.db):
raise SkipTest(
- "'%s' unsupported on DB implementation '%s'" % (
- cls.__name__, config.db.name)
+ "'%s' unsupported on DB implementation '%s' == %s" % (
+ cls.__name__, config.db.name,
+ config.db.dialect.server_version_info)
)
if getattr(cls, '__skip_if__', False):
diff --git a/lib/sqlalchemy/testing/profiling.py b/lib/sqlalchemy/testing/profiling.py
index bda44d80c..fa2490649 100644
--- a/lib/sqlalchemy/testing/profiling.py
+++ b/lib/sqlalchemy/testing/profiling.py
@@ -1,3 +1,9 @@
+# testing/profiling.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
"""Profiling support for unit and performance tests.
These are special purpose profiling methods which operate
@@ -45,12 +51,10 @@ def profiled(target=None, **target_opts):
if target is None:
target = 'anonymous_target'
- filename = "%s.prof" % target
-
@decorator
def decorate(fn, *args, **kw):
elapsed, load_stats, result = _profile(
- filename, fn, *args, **kw)
+ fn, *args, **kw)
graphic = target_opts.get('graphic', profile_config['graphic'])
if graphic:
@@ -60,8 +64,8 @@ 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)" % (
- target, filename)
+ print(("Profile report for target '%s'" % (
+ target, )
))
stats = load_stats()
@@ -81,7 +85,6 @@ def profiled(target=None, **target_opts):
if print_callees:
stats.print_callees()
- os.unlink(filename)
return result
return decorate
@@ -162,6 +165,15 @@ class ProfileStatsFile(object):
per_platform['current_count'] += 1
return result
+ def replace(self, callcount):
+ test_key = _current_test
+ per_fn = self.data[test_key]
+ per_platform = per_fn[self.platform_key]
+ counts = per_platform['counts']
+ counts[-1] = callcount
+ if self.write:
+ self._write()
+
def _header(self):
return \
"# %s\n"\
@@ -263,16 +275,19 @@ def function_call_count(variance=0.05):
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))
+ failed = abs(callcount - expected_count) > deviance
+
+ if failed:
+ if _profile_stats.write:
+ _profile_stats.replace(callcount)
+ else:
+ raise AssertionError(
+ "Adjusted function call count %s not within %s%% "
+ "of expected %s. Rerun with --write-profiles to "
+ "regenerate this callcount."
+ % (
+ callcount, (variance * 100),
+ expected_count))
return fn_result
return update_wrapper(wrap, fn)
return decorate
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index d301dc69f..706d6d060 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1,3 +1,9 @@
+# testing/requirements.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
"""Global database feature support policy.
Provides decorators to mark tests requiring specific feature support from the
@@ -133,6 +139,20 @@ class SuiteRequirements(Requirements):
return exclusions.open()
@property
+ def fetch_rows_post_commit(self):
+ """target platform will allow cursor.fetchone() to proceed after a
+ COMMIT.
+
+ Typically this refers to an INSERT statement with RETURNING which
+ is invoked within "autocommit". If the row can be returned
+ after the autocommit, then this rule can be open.
+
+ """
+
+ return exclusions.open()
+
+
+ @property
def empty_inserts(self):
"""target platform supports INSERT with no values, i.e.
INSERT DEFAULT VALUES or equivalent."""
@@ -296,6 +316,15 @@ class SuiteRequirements(Requirements):
return exclusions.closed()
@property
+ def datetime_literals(self):
+ """target dialect supports rendering of a date, time, or datetime as a
+ literal string, e.g. via the TypeEngine.literal_processor() method.
+
+ """
+
+ return exclusions.closed()
+
+ @property
def datetime(self):
"""target dialect supports representation of Python
datetime.datetime() objects."""
@@ -379,6 +408,14 @@ class SuiteRequirements(Requirements):
return exclusions.closed()
@property
+ def precision_generic_float_type(self):
+ """target backend will return native floating point numbers with at
+ least seven decimal places when using the generic Float type.
+
+ """
+ return exclusions.open()
+
+ @property
def floats_to_four_decimals(self):
"""target backend can return a floating-point number with four
significant digits (such as 15.7563) accurately
@@ -388,6 +425,16 @@ class SuiteRequirements(Requirements):
return exclusions.open()
@property
+ def fetch_null_from_numeric(self):
+ """target backend doesn't crash when you try to select a NUMERIC
+ value that has a value of NULL.
+
+ Added to support Pyodbc bug #351.
+ """
+
+ return exclusions.open()
+
+ @property
def text_type(self):
"""Target database must support an unbounded Text() "
"type such as TEXT or CLOB"""
diff --git a/lib/sqlalchemy/testing/runner.py b/lib/sqlalchemy/testing/runner.py
index 2bdbaebd1..19aba53df 100644
--- a/lib/sqlalchemy/testing/runner.py
+++ b/lib/sqlalchemy/testing/runner.py
@@ -1,4 +1,9 @@
#!/usr/bin/env python
+# testing/runner.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
Nose test runner module.
diff --git a/lib/sqlalchemy/testing/schema.py b/lib/sqlalchemy/testing/schema.py
index 025bbaabe..ec0085219 100644
--- a/lib/sqlalchemy/testing/schema.py
+++ b/lib/sqlalchemy/testing/schema.py
@@ -1,3 +1,8 @@
+# testing/schema.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
from . import exclusions
from .. import schema, event
diff --git a/lib/sqlalchemy/testing/suite/test_insert.py b/lib/sqlalchemy/testing/suite/test_insert.py
index e671eeb7a..5732e37ec 100644
--- a/lib/sqlalchemy/testing/suite/test_insert.py
+++ b/lib/sqlalchemy/testing/suite/test_insert.py
@@ -56,8 +56,9 @@ class LastrowidTest(fixtures.TablesTest):
[pk]
)
- @exclusions.fails_if(lambda: util.pypy, "lastrowid not maintained after "
- "connection close")
+ # failed on pypy1.9 but seems to be OK on pypy 2.1
+ #@exclusions.fails_if(lambda: util.pypy, "lastrowid not maintained after "
+ # "connection close")
@requirements.dbapi_lastrowid
def test_native_lastrowid_autoinc(self):
r = config.db.execute(
@@ -81,6 +82,10 @@ class InsertBehaviorTest(fixtures.TablesTest):
test_needs_autoincrement=True),
Column('data', String(50))
)
+ Table('manual_pk', metadata,
+ Column('id', Integer, primary_key=True, autoincrement=False),
+ Column('data', String(50))
+ )
def test_autoclose_on_insert(self):
if requirements.returning.enabled:
@@ -123,13 +128,13 @@ class InsertBehaviorTest(fixtures.TablesTest):
@requirements.insert_from_select
def test_insert_from_select(self):
- table = self.tables.autoinc_pk
+ table = self.tables.manual_pk
config.db.execute(
table.insert(),
[
- dict(data="data1"),
- dict(data="data2"),
- dict(data="data3"),
+ dict(id=1, data="data1"),
+ dict(id=2, data="data2"),
+ dict(id=3, data="data3"),
]
)
@@ -171,7 +176,8 @@ class ReturningTest(fixtures.TablesTest):
Column('data', String(50))
)
- def test_explicit_returning_pk(self):
+ @requirements.fetch_rows_post_commit
+ def test_explicit_returning_pk_autocommit(self):
engine = config.db
table = self.tables.autoinc_pk
r = engine.execute(
@@ -183,6 +189,19 @@ class ReturningTest(fixtures.TablesTest):
fetched_pk = config.db.scalar(select([table.c.id]))
eq_(fetched_pk, pk)
+ def test_explicit_returning_pk_no_autocommit(self):
+ engine = config.db
+ table = self.tables.autoinc_pk
+ with engine.begin() as conn:
+ r = conn.execute(
+ table.insert().returning(
+ table.c.id),
+ data="some data"
+ )
+ pk = r.first()[0]
+ fetched_pk = config.db.scalar(select([table.c.id]))
+ eq_(fetched_pk, pk)
+
def test_autoincrement_on_insert_implcit_returning(self):
config.db.execute(
diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py
index 5a8a54c46..9f737bc64 100644
--- a/lib/sqlalchemy/testing/suite/test_reflection.py
+++ b/lib/sqlalchemy/testing/suite/test_reflection.py
@@ -147,6 +147,7 @@ class ComponentReflectionTest(fixtures.TablesTest):
table_names = insp.get_view_names(schema)
table_names.sort()
answer = ['email_addresses_v', 'users_v']
+ eq_(sorted(table_names), answer)
else:
table_names = insp.get_table_names(schema,
order_by=order_by)
@@ -180,6 +181,12 @@ class ComponentReflectionTest(fixtures.TablesTest):
def test_get_view_names_with_schema(self):
self._test_get_table_names('test_schema', table_type='view')
+ @testing.requires.table_reflection
+ @testing.requires.view_reflection
+ def test_get_tables_and_views(self):
+ self._test_get_table_names()
+ self._test_get_table_names(table_type='view')
+
def _test_get_columns(self, schema=None, table_type='table'):
meta = MetaData(testing.db)
users, addresses, dingalings = self.tables.users, \
@@ -448,6 +455,7 @@ class ComponentReflectionTest(fixtures.TablesTest):
def test_get_table_oid_with_schema(self):
self._test_get_table_oid('users', schema='test_schema')
+ @testing.requires.table_reflection
@testing.provide_metadata
def test_autoincrement_col(self):
"""test that 'autoincrement' is reflected according to sqla's policy.
diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py
index 0de462eb7..a6e937e8e 100644
--- a/lib/sqlalchemy/testing/suite/test_types.py
+++ b/lib/sqlalchemy/testing/suite/test_types.py
@@ -5,7 +5,7 @@ from ..assertions import eq_
from ..config import requirements
from sqlalchemy import Integer, Unicode, UnicodeText, select
from sqlalchemy import Date, DateTime, Time, MetaData, String, \
- Text, Numeric, Float
+ Text, Numeric, Float, literal
from ..schema import Table, Column
from ... import testing
import decimal
@@ -13,7 +13,34 @@ import datetime
from ...util import u
from ... import util
-class _UnicodeFixture(object):
+
+class _LiteralRoundTripFixture(object):
+ @testing.provide_metadata
+ def _literal_round_trip(self, type_, input_, output, filter_=None):
+ """test literal rendering """
+
+ # for literal, we test the literal render in an INSERT
+ # into a typed column. we can then SELECT it back as it's
+ # official type; ideally we'd be able to use CAST here
+ # but MySQL in particular can't CAST fully
+ t = Table('t', self.metadata, Column('x', type_))
+ t.create()
+
+ for value in input_:
+ ins = t.insert().values(x=literal(value)).compile(
+ dialect=testing.db.dialect,
+ compile_kwargs=dict(literal_binds=True)
+ )
+ testing.db.execute(ins)
+
+ for row in t.select().execute():
+ value = row[0]
+ if filter_ is not None:
+ value = filter_(value)
+ assert value in output
+
+
+class _UnicodeFixture(_LiteralRoundTripFixture):
__requires__ = 'unicode_data',
data = u("Alors vous imaginez ma surprise, au lever du jour, "\
@@ -87,6 +114,9 @@ class _UnicodeFixture(object):
).first()
eq_(row, (u(''),))
+ def test_literal(self):
+ self._literal_round_trip(self.datatype, [self.data], [self.data])
+
class UnicodeVarcharTest(_UnicodeFixture, fixtures.TablesTest):
__requires__ = 'unicode_data',
@@ -107,7 +137,7 @@ class UnicodeTextTest(_UnicodeFixture, fixtures.TablesTest):
def test_empty_strings_text(self):
self._test_empty_strings()
-class TextTest(fixtures.TablesTest):
+class TextTest(_LiteralRoundTripFixture, fixtures.TablesTest):
@classmethod
def define_tables(cls, metadata):
Table('text_table', metadata,
@@ -140,8 +170,18 @@ class TextTest(fixtures.TablesTest):
).first()
eq_(row, ('',))
+ def test_literal(self):
+ self._literal_round_trip(Text, ["some text"], ["some text"])
-class StringTest(fixtures.TestBase):
+ def test_literal_quoting(self):
+ data = '''some 'text' hey "hi there" that's text'''
+ self._literal_round_trip(Text, [data], [data])
+
+ def test_literal_backslashes(self):
+ data = r'backslash one \ backslash two \\ end'
+ self._literal_round_trip(Text, [data], [data])
+
+class StringTest(_LiteralRoundTripFixture, fixtures.TestBase):
@requirements.unbounded_varchar
def test_nolength_string(self):
metadata = MetaData()
@@ -152,8 +192,19 @@ class StringTest(fixtures.TestBase):
foo.create(config.db)
foo.drop(config.db)
+ def test_literal(self):
+ self._literal_round_trip(String(40), ["some text"], ["some text"])
+
+ def test_literal_quoting(self):
+ data = '''some 'text' hey "hi there" that's text'''
+ self._literal_round_trip(String(40), [data], [data])
+
+ def test_literal_backslashes(self):
+ data = r'backslash one \ backslash two \\ end'
+ self._literal_round_trip(Text, [data], [data])
-class _DateFixture(object):
+
+class _DateFixture(_LiteralRoundTripFixture):
compare = None
@classmethod
@@ -198,6 +249,12 @@ class _DateFixture(object):
).first()
eq_(row, (None,))
+ @testing.requires.datetime_literals
+ def test_literal(self):
+ compare = self.compare or self.data
+ self._literal_round_trip(self.datatype, [self.data], [compare])
+
+
class DateTimeTest(_DateFixture, fixtures.TablesTest):
__requires__ = 'datetime',
@@ -247,7 +304,12 @@ class DateHistoricTest(_DateFixture, fixtures.TablesTest):
datatype = Date
data = datetime.date(1727, 4, 1)
-class NumericTest(fixtures.TestBase):
+
+class IntegerTest(_LiteralRoundTripFixture, fixtures.TestBase):
+ def test_literal(self):
+ self._literal_round_trip(Integer, [5], [5])
+
+class NumericTest(_LiteralRoundTripFixture, fixtures.TestBase):
@testing.emits_warning(r".*does \*not\* support Decimal objects natively")
@testing.provide_metadata
@@ -269,18 +331,69 @@ class NumericTest(fixtures.TestBase):
[str(x) for x in output],
)
+
+ @testing.emits_warning(r".*does \*not\* support Decimal objects natively")
+ def test_render_literal_numeric(self):
+ self._literal_round_trip(
+ Numeric(precision=8, scale=4),
+ [15.7563, decimal.Decimal("15.7563")],
+ [decimal.Decimal("15.7563")],
+ )
+
+ @testing.emits_warning(r".*does \*not\* support Decimal objects natively")
+ def test_render_literal_numeric_asfloat(self):
+ self._literal_round_trip(
+ Numeric(precision=8, scale=4, asdecimal=False),
+ [15.7563, decimal.Decimal("15.7563")],
+ [15.7563],
+ )
+
+ def test_render_literal_float(self):
+ self._literal_round_trip(
+ Float(4),
+ [15.7563, decimal.Decimal("15.7563")],
+ [15.7563,],
+ filter_=lambda n: n is not None and round(n, 5) or None
+ )
+
+
+ @testing.requires.precision_generic_float_type
+ def test_float_custom_scale(self):
+ self._do_test(
+ Float(None, decimal_return_scale=7, asdecimal=True),
+ [15.7563827, decimal.Decimal("15.7563827")],
+ [decimal.Decimal("15.7563827"),],
+ check_scale=True
+ )
+
def test_numeric_as_decimal(self):
self._do_test(
Numeric(precision=8, scale=4),
- [15.7563, decimal.Decimal("15.7563"), None],
- [decimal.Decimal("15.7563"), None],
+ [15.7563, decimal.Decimal("15.7563")],
+ [decimal.Decimal("15.7563")],
)
def test_numeric_as_float(self):
self._do_test(
Numeric(precision=8, scale=4, asdecimal=False),
- [15.7563, decimal.Decimal("15.7563"), None],
- [15.7563, None],
+ [15.7563, decimal.Decimal("15.7563")],
+ [15.7563],
+ )
+
+ @testing.requires.fetch_null_from_numeric
+ def test_numeric_null_as_decimal(self):
+ self._do_test(
+ Numeric(precision=8, scale=4),
+ [None],
+ [None],
+ )
+
+ @testing.requires.fetch_null_from_numeric
+ def test_numeric_null_as_float(self):
+ self._do_test(
+ Numeric(precision=8, scale=4, asdecimal=False),
+ [None],
+ [None],
)
@testing.requires.floats_to_four_decimals
@@ -291,6 +404,7 @@ class NumericTest(fixtures.TestBase):
[decimal.Decimal("15.7563"), None],
)
+
def test_float_as_float(self):
self._do_test(
Float(precision=8),
@@ -299,6 +413,7 @@ class NumericTest(fixtures.TestBase):
filter_=lambda n: n is not None and round(n, 5) or None
)
+
@testing.requires.precision_numerics_general
def test_precision_decimal(self):
numbers = set([
@@ -313,6 +428,7 @@ class NumericTest(fixtures.TestBase):
numbers,
)
+
@testing.requires.precision_numerics_enotation_large
def test_enotation_decimal(self):
"""test exceedingly small decimals.
@@ -342,6 +458,7 @@ class NumericTest(fixtures.TestBase):
numbers
)
+
@testing.requires.precision_numerics_enotation_large
def test_enotation_decimal_large(self):
"""test exceedingly large decimals.
@@ -389,7 +506,7 @@ class NumericTest(fixtures.TestBase):
__all__ = ('UnicodeVarcharTest', 'UnicodeTextTest',
'DateTest', 'DateTimeTest', 'TextTest',
- 'NumericTest',
+ 'NumericTest', 'IntegerTest',
'DateTimeHistoricTest', 'DateTimeCoercedToDateTimeTest',
'TimeMicrosecondsTest', 'TimeTest', 'DateTimeMicrosecondsTest',
'DateHistoricTest', 'StringTest')
diff --git a/lib/sqlalchemy/testing/util.py b/lib/sqlalchemy/testing/util.py
index 1288902f2..bde11a356 100644
--- a/lib/sqlalchemy/testing/util.py
+++ b/lib/sqlalchemy/testing/util.py
@@ -1,3 +1,9 @@
+# testing/util.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
from ..util import jython, pypy, defaultdict, decorator, py2k
import decimal
import gc
diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py
index 6193acd88..74a8933a6 100644
--- a/lib/sqlalchemy/testing/warnings.py
+++ b/lib/sqlalchemy/testing/warnings.py
@@ -1,3 +1,9 @@
+# testing/warnings.py
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
from __future__ import absolute_import
import warnings