mirror of
https://github.com/django/django.git
synced 2025-07-24 05:36:15 +00:00
Reorganized backends tests.
This commit is contained in:
parent
0f91ba1adc
commit
8cb1b1fd8e
21 changed files with 731 additions and 710 deletions
0
tests/backends/postgresql/__init__.py
Normal file
0
tests/backends/postgresql/__init__.py
Normal file
95
tests/backends/postgresql/test_creation.py
Normal file
95
tests/backends/postgresql/test_creation.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from django.db import connection
|
||||
from django.db.backends.base.creation import BaseDatabaseCreation
|
||||
from django.db.utils import DatabaseError
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
try:
|
||||
import psycopg2 # NOQA
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from psycopg2 import errorcodes
|
||||
from django.db.backends.postgresql.creation import DatabaseCreation
|
||||
|
||||
|
||||
@unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL tests')
|
||||
class DatabaseCreationTests(SimpleTestCase):
|
||||
|
||||
@contextmanager
|
||||
def changed_test_settings(self, **kwargs):
|
||||
settings = connection.settings_dict['TEST']
|
||||
saved_values = {}
|
||||
for name in kwargs:
|
||||
if name in settings:
|
||||
saved_values[name] = settings[name]
|
||||
|
||||
for name, value in kwargs.items():
|
||||
settings[name] = value
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for name, value in kwargs.items():
|
||||
if name in saved_values:
|
||||
settings[name] = saved_values[name]
|
||||
else:
|
||||
del settings[name]
|
||||
|
||||
def check_sql_table_creation_suffix(self, settings, expected):
|
||||
with self.changed_test_settings(**settings):
|
||||
creation = DatabaseCreation(connection)
|
||||
suffix = creation.sql_table_creation_suffix()
|
||||
self.assertEqual(suffix, expected)
|
||||
|
||||
def test_sql_table_creation_suffix_with_none_settings(self):
|
||||
settings = {'CHARSET': None, 'TEMPLATE': None}
|
||||
self.check_sql_table_creation_suffix(settings, "")
|
||||
|
||||
def test_sql_table_creation_suffix_with_encoding(self):
|
||||
settings = {'CHARSET': 'UTF8'}
|
||||
self.check_sql_table_creation_suffix(settings, "WITH ENCODING 'UTF8'")
|
||||
|
||||
def test_sql_table_creation_suffix_with_template(self):
|
||||
settings = {'TEMPLATE': 'template0'}
|
||||
self.check_sql_table_creation_suffix(settings, 'WITH TEMPLATE "template0"')
|
||||
|
||||
def test_sql_table_creation_suffix_with_encoding_and_template(self):
|
||||
settings = {'CHARSET': 'UTF8', 'TEMPLATE': 'template0'}
|
||||
self.check_sql_table_creation_suffix(settings, '''WITH ENCODING 'UTF8' TEMPLATE "template0"''')
|
||||
|
||||
def _execute_raise_database_already_exists(self, cursor, parameters, keepdb=False):
|
||||
error = DatabaseError('database %s already exists' % parameters['dbname'])
|
||||
error.pgcode = errorcodes.DUPLICATE_DATABASE
|
||||
raise DatabaseError() from error
|
||||
|
||||
def _execute_raise_permission_denied(self, cursor, parameters, keepdb=False):
|
||||
error = DatabaseError('permission denied to create database')
|
||||
error.pgcode = errorcodes.INSUFFICIENT_PRIVILEGE
|
||||
raise DatabaseError() from error
|
||||
|
||||
def patch_test_db_creation(self, execute_create_test_db):
|
||||
return mock.patch.object(BaseDatabaseCreation, '_execute_create_test_db', execute_create_test_db)
|
||||
|
||||
@mock.patch('sys.stdout', new_callable=StringIO)
|
||||
@mock.patch('sys.stderr', new_callable=StringIO)
|
||||
def test_create_test_db(self, *mocked_objects):
|
||||
creation = DatabaseCreation(connection)
|
||||
# Simulate test database creation raising "database already exists"
|
||||
with self.patch_test_db_creation(self._execute_raise_database_already_exists):
|
||||
with mock.patch('builtins.input', return_value='no'):
|
||||
with self.assertRaises(SystemExit):
|
||||
# SystemExit is raised if the user answers "no" to the
|
||||
# prompt asking if it's okay to delete the test database.
|
||||
creation._create_test_db(verbosity=0, autoclobber=False, keepdb=False)
|
||||
# "Database already exists" error is ignored when keepdb is on
|
||||
creation._create_test_db(verbosity=0, autoclobber=False, keepdb=True)
|
||||
# Simulate test database creation raising unexpected error
|
||||
with self.patch_test_db_creation(self._execute_raise_permission_denied):
|
||||
with self.assertRaises(SystemExit):
|
||||
creation._create_test_db(verbosity=0, autoclobber=False, keepdb=False)
|
||||
with self.assertRaises(SystemExit):
|
||||
creation._create_test_db(verbosity=0, autoclobber=False, keepdb=True)
|
82
tests/backends/postgresql/test_server_side_cursors.py
Normal file
82
tests/backends/postgresql/test_server_side_cursors.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
import operator
|
||||
import unittest
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.db import connection
|
||||
from django.test import TestCase
|
||||
|
||||
from ..models import Person
|
||||
|
||||
|
||||
@unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL tests')
|
||||
class ServerSideCursorsPostgres(TestCase):
|
||||
cursor_fields = 'name, statement, is_holdable, is_binary, is_scrollable, creation_time'
|
||||
PostgresCursor = namedtuple('PostgresCursor', cursor_fields)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
Person.objects.create(first_name='a', last_name='a')
|
||||
Person.objects.create(first_name='b', last_name='b')
|
||||
|
||||
def inspect_cursors(self):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT {fields} FROM pg_cursors;'.format(fields=self.cursor_fields))
|
||||
cursors = cursor.fetchall()
|
||||
return [self.PostgresCursor._make(cursor) for cursor in cursors]
|
||||
|
||||
@contextmanager
|
||||
def override_db_setting(self, **kwargs):
|
||||
for setting, value in kwargs.items():
|
||||
original_value = connection.settings_dict.get(setting)
|
||||
if setting in connection.settings_dict:
|
||||
self.addCleanup(operator.setitem, connection.settings_dict, setting, original_value)
|
||||
else:
|
||||
self.addCleanup(operator.delitem, connection.settings_dict, setting)
|
||||
|
||||
connection.settings_dict[setting] = kwargs[setting]
|
||||
yield
|
||||
|
||||
def test_server_side_cursor(self):
|
||||
persons = Person.objects.iterator()
|
||||
next(persons) # Open a server-side cursor
|
||||
cursors = self.inspect_cursors()
|
||||
self.assertEqual(len(cursors), 1)
|
||||
self.assertIn('_django_curs_', cursors[0].name)
|
||||
self.assertFalse(cursors[0].is_scrollable)
|
||||
self.assertFalse(cursors[0].is_holdable)
|
||||
self.assertFalse(cursors[0].is_binary)
|
||||
|
||||
def test_server_side_cursor_many_cursors(self):
|
||||
persons = Person.objects.iterator()
|
||||
persons2 = Person.objects.iterator()
|
||||
next(persons) # Open a server-side cursor
|
||||
next(persons2) # Open a second server-side cursor
|
||||
cursors = self.inspect_cursors()
|
||||
self.assertEqual(len(cursors), 2)
|
||||
for cursor in cursors:
|
||||
self.assertIn('_django_curs_', cursor.name)
|
||||
self.assertFalse(cursor.is_scrollable)
|
||||
self.assertFalse(cursor.is_holdable)
|
||||
self.assertFalse(cursor.is_binary)
|
||||
|
||||
def test_closed_server_side_cursor(self):
|
||||
persons = Person.objects.iterator()
|
||||
next(persons) # Open a server-side cursor
|
||||
del persons
|
||||
cursors = self.inspect_cursors()
|
||||
self.assertEqual(len(cursors), 0)
|
||||
|
||||
def test_server_side_cursors_setting(self):
|
||||
with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=False):
|
||||
persons = Person.objects.iterator()
|
||||
next(persons) # Open a server-side cursor
|
||||
cursors = self.inspect_cursors()
|
||||
self.assertEqual(len(cursors), 1)
|
||||
del persons # Close server-side cursor
|
||||
|
||||
with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=True):
|
||||
persons = Person.objects.iterator()
|
||||
next(persons) # Should not open a server-side cursor
|
||||
cursors = self.inspect_cursors()
|
||||
self.assertEqual(len(cursors), 0)
|
147
tests/backends/postgresql/tests.py
Normal file
147
tests/backends/postgresql/tests.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
import unittest
|
||||
import warnings
|
||||
from unittest import mock
|
||||
|
||||
from django.db import DatabaseError, connection
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
@unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL tests')
|
||||
class Tests(TestCase):
|
||||
|
||||
def test_nodb_connection(self):
|
||||
"""
|
||||
The _nodb_connection property fallbacks to the default connection
|
||||
database when access to the 'postgres' database is not granted.
|
||||
"""
|
||||
def mocked_connect(self):
|
||||
if self.settings_dict['NAME'] is None:
|
||||
raise DatabaseError()
|
||||
return ''
|
||||
|
||||
nodb_conn = connection._nodb_connection
|
||||
self.assertIsNone(nodb_conn.settings_dict['NAME'])
|
||||
|
||||
# Now assume the 'postgres' db isn't available
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.connect',
|
||||
side_effect=mocked_connect, autospec=True):
|
||||
warnings.simplefilter('always', RuntimeWarning)
|
||||
nodb_conn = connection._nodb_connection
|
||||
self.assertIsNotNone(nodb_conn.settings_dict['NAME'])
|
||||
self.assertEqual(nodb_conn.settings_dict['NAME'], connection.settings_dict['NAME'])
|
||||
# Check a RuntimeWarning has been emitted
|
||||
self.assertEqual(len(w), 1)
|
||||
self.assertEqual(w[0].message.__class__, RuntimeWarning)
|
||||
|
||||
def test_connect_and_rollback(self):
|
||||
"""
|
||||
PostgreSQL shouldn't roll back SET TIME ZONE, even if the first
|
||||
transaction is rolled back (#17062).
|
||||
"""
|
||||
new_connection = connection.copy()
|
||||
try:
|
||||
# Ensure the database default time zone is different than
|
||||
# the time zone in new_connection.settings_dict. We can
|
||||
# get the default time zone by reset & show.
|
||||
cursor = new_connection.cursor()
|
||||
cursor.execute("RESET TIMEZONE")
|
||||
cursor.execute("SHOW TIMEZONE")
|
||||
db_default_tz = cursor.fetchone()[0]
|
||||
new_tz = 'Europe/Paris' if db_default_tz == 'UTC' else 'UTC'
|
||||
new_connection.close()
|
||||
|
||||
# Invalidate timezone name cache, because the setting_changed
|
||||
# handler cannot know about new_connection.
|
||||
del new_connection.timezone_name
|
||||
|
||||
# Fetch a new connection with the new_tz as default
|
||||
# time zone, run a query and rollback.
|
||||
with self.settings(TIME_ZONE=new_tz):
|
||||
new_connection.set_autocommit(False)
|
||||
cursor = new_connection.cursor()
|
||||
new_connection.rollback()
|
||||
|
||||
# Now let's see if the rollback rolled back the SET TIME ZONE.
|
||||
cursor.execute("SHOW TIMEZONE")
|
||||
tz = cursor.fetchone()[0]
|
||||
self.assertEqual(new_tz, tz)
|
||||
|
||||
finally:
|
||||
new_connection.close()
|
||||
|
||||
def test_connect_non_autocommit(self):
|
||||
"""
|
||||
The connection wrapper shouldn't believe that autocommit is enabled
|
||||
after setting the time zone when AUTOCOMMIT is False (#21452).
|
||||
"""
|
||||
new_connection = connection.copy()
|
||||
new_connection.settings_dict['AUTOCOMMIT'] = False
|
||||
|
||||
try:
|
||||
# Open a database connection.
|
||||
new_connection.cursor()
|
||||
self.assertFalse(new_connection.get_autocommit())
|
||||
finally:
|
||||
new_connection.close()
|
||||
|
||||
def test_connect_isolation_level(self):
|
||||
"""
|
||||
The transaction level can be configured with
|
||||
DATABASES ['OPTIONS']['isolation_level'].
|
||||
"""
|
||||
import psycopg2
|
||||
from psycopg2.extensions import (
|
||||
ISOLATION_LEVEL_READ_COMMITTED as read_committed,
|
||||
ISOLATION_LEVEL_SERIALIZABLE as serializable,
|
||||
)
|
||||
# Since this is a django.test.TestCase, a transaction is in progress
|
||||
# and the isolation level isn't reported as 0. This test assumes that
|
||||
# PostgreSQL is configured with the default isolation level.
|
||||
|
||||
# Check the level on the psycopg2 connection, not the Django wrapper.
|
||||
default_level = read_committed if psycopg2.__version__ < '2.7' else None
|
||||
self.assertEqual(connection.connection.isolation_level, default_level)
|
||||
|
||||
new_connection = connection.copy()
|
||||
new_connection.settings_dict['OPTIONS']['isolation_level'] = serializable
|
||||
try:
|
||||
# Start a transaction so the isolation level isn't reported as 0.
|
||||
new_connection.set_autocommit(False)
|
||||
# Check the level on the psycopg2 connection, not the Django wrapper.
|
||||
self.assertEqual(new_connection.connection.isolation_level, serializable)
|
||||
finally:
|
||||
new_connection.close()
|
||||
|
||||
def _select(self, val):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT %s', (val,))
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
def test_select_ascii_array(self):
|
||||
a = ['awef']
|
||||
b = self._select(a)
|
||||
self.assertEqual(a[0], b[0])
|
||||
|
||||
def test_select_unicode_array(self):
|
||||
a = ['ᄲawef']
|
||||
b = self._select(a)
|
||||
self.assertEqual(a[0], b[0])
|
||||
|
||||
def test_lookup_cast(self):
|
||||
from django.db.backends.postgresql.operations import DatabaseOperations
|
||||
do = DatabaseOperations(connection=None)
|
||||
lookups = (
|
||||
'iexact', 'contains', 'icontains', 'startswith', 'istartswith',
|
||||
'endswith', 'iendswith', 'regex', 'iregex',
|
||||
)
|
||||
for lookup in lookups:
|
||||
with self.subTest(lookup=lookup):
|
||||
self.assertIn('::text', do.lookup_cast(lookup))
|
||||
|
||||
def test_correct_extraction_psycopg2_version(self):
|
||||
from django.db.backends.postgresql.base import psycopg2_version
|
||||
with mock.patch('psycopg2.__version__', '4.2.1 (dt dec pq3 ext lo64)'):
|
||||
self.assertEqual(psycopg2_version(), (4, 2, 1))
|
||||
with mock.patch('psycopg2.__version__', '4.2b0.dev1 (dt dec pq3 ext lo64)'):
|
||||
self.assertEqual(psycopg2_version(), (4, 2))
|
Loading…
Add table
Add a link
Reference in a new issue