diff --git a/.travis.yml b/.travis.yml index de36ce3..602ec9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,15 @@ sudo: false language: python dist: xenial +services: + - mysql + - postgresql matrix: include: - python: 2.7 env: TOXENV=py27-django111 - - python: 3.4 - env: TOXENV=py34-django111 - - python: 3.4 - env: TOXENV=py34-django20 - - python: 3.5 env: TOXENV=py35-django111 - python: 3.5 diff --git a/tests/fixtures.py b/tests/fixtures.py index 81fb577..2cc0f63 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -13,70 +13,18 @@ # limitations under the License. import os -import shutil -_BASE_DIR = os.path.join( +_PROJ_TEST_DIR = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - 'tests/') -_FIXTURES_FOLDER = os.path.join(_BASE_DIR, 'test_project_fixtures/') - -ADD_NOT_NULL_COLUMN_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_add_not_null_column/') -CREATE_TABLE_WITH_NOT_NULL_COLUMN_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_create_table_with_not_null_column/') -DROP_COLUMN_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_drop_column/') -RENAME_COLUMN_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_rename_column/') -ALTER_COLUMN_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_alter_column') -RENAME_TABLE_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_rename_table/') -ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_add_not_null_column_followed_by_default/') -MULTI_COMMIT_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_multi_commit/') -CORRECT_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_correct_project/') -DELETED_MIGRATION_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_deleted_migration_project/') -IGNORE_MIGRATION_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_project_ignore_migration/') - -NOT_DJANGO_GIT_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_git_project/') -NOT_GIT_DJANGO_PROJECT = os.path.join( - _FIXTURES_FOLDER, 'test_django_without_git_project/') - -NON_GIT_ROOT_GIT_FOLDER = os.path.join( - _FIXTURES_FOLDER, 'test_non_root_git_project/' -) -NON_GIT_ROOT_DJANGO_PROJECT = os.path.join( - NON_GIT_ROOT_GIT_FOLDER, 'django_project/' + "tests/", + "test_project", ) - -ALL_GIT_PROJECTS = ( - NOT_DJANGO_GIT_PROJECT, - MULTI_COMMIT_PROJECT, - DELETED_MIGRATION_PROJECT, - NON_GIT_ROOT_GIT_FOLDER, -) - - -def prepare_git_project(path): - """Copy the git/ folder to .git/ so it is - actually versioned""" - src_git = os.path.join(path, 'git/') - dest_git = os.path.join(path, '.git/') - try: - shutil.copytree(src_git, dest_git) - except OSError: - pass # .git/ might already exist - - -def clear_all_git_projects(): - """Delete all .git/ folders in test projects""" - for project_path in ALL_GIT_PROJECTS: - git_path = os.path.join(project_path, '.git/') - shutil.rmtree(git_path, ignore_errors=True) +CREATE_TABLE_WITH_NOT_NULL_COLUMN = "app_create_table_with_not_null_column" +ADD_NOT_NULL_COLUMN = "app_add_not_null_column" +DROP_COLUMN = "app_drop_column" +RENAME_COLUMN = "app_rename_column" +RENAME_TABLE = "app_rename_table" +IGNORE_MIGRATION = "app_ignore_migration" +ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT = "app_add_not_null_column_followed_by_default" +ALTER_COLUMN = "app_alter_column" diff --git a/tests/functional/test_cache.py b/tests/functional/test_cache.py index 7853642..4fc5643 100644 --- a/tests/functional/test_cache.py +++ b/tests/functional/test_cache.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -import shutil -import unittest import sys +import unittest -from tests import fixtures +from django.conf import settings +from django.db.migrations import Migration -from django_migration_linter import MigrationLinter, Cache, DEFAULT_CACHE_PATH +from django_migration_linter import ( + MigrationLinter, + analyse_sql_statements, + get_migration_abspath, +) if sys.version_info >= (3, 3): import unittest.mock as mock @@ -26,131 +30,208 @@ else: import mock -class CacheTest(unittest.TestCase): - MIGRATION_FILE = os.path.join(fixtures.ALTER_COLUMN_PROJECT, 'test_app', 'migrations', '0001_initial.py') +class CacheTestCase(unittest.TestCase): + def setUp(self): + self.test_project_path = os.path.dirname(settings.BASE_DIR) - def test_cache_normal(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_project_add_not_null_column.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - linter = MigrationLinter(fixtures.ADD_NOT_NULL_COLUMN_PROJECT) + @mock.patch( + "django_migration_linter.MigrationLinter._gather_all_migrations", + return_value=[ + Migration("0001_create_table", "app_add_not_null_column"), + Migration("0002_add_new_not_null_field", "app_add_not_null_column"), + ], + ) + def test_cache_normal(self, *args): + linter = MigrationLinter(self.test_project_path) + linter.old_cache.clear() + linter.old_cache.save() - with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql)as sql_mock: + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: linter.lint_all_migrations() - self.assertEqual(sql_mock.call_count, 2) + self.assertEqual(2, analyse_sql_statements_mock.call_count) - cache = Cache( - fixtures.ADD_NOT_NULL_COLUMN_PROJECT, - DEFAULT_CACHE_PATH - ) + cache = linter.new_cache cache.load() - self.assertEqual(cache['3ef74e7f3e53e273e2fc95379248d58d']['result'], 'OK') - self.assertEqual(cache['e1e312b6d08ecbe017c25c58fc2be257']['result'], 'ERR') + self.assertEqual("OK", cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"]) + self.assertEqual("ERR", cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"]) self.assertListEqual( - cache['e1e312b6d08ecbe017c25c58fc2be257']['errors'], - [{'err_msg': 'RENAMING tables', 'code': 'RENAME_TABLE', 'table': None, 'column': None}] + cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"], + [ + { + "err_msg": "RENAMING tables", + "code": "RENAME_TABLE", + "table": None, + "column": None, + } + ], ) # Start the Linter again -> should use cache now. - linter = MigrationLinter(fixtures.ADD_NOT_NULL_COLUMN_PROJECT) + linter = MigrationLinter(self.test_project_path) - with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql) as sql_mock: + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: linter.lint_all_migrations() - self.assertEqual(sql_mock.call_count, 0) + analyse_sql_statements_mock.assert_not_called() self.assertTrue(linter.has_errors) - def test_cache_ignored(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_project_ignore_migration.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - linter = MigrationLinter(fixtures.IGNORE_MIGRATION_PROJECT) + @mock.patch( + "django_migration_linter.MigrationLinter._gather_all_migrations", + return_value=[ + Migration("0001_create_table", "app_add_not_null_column"), + Migration("0002_add_new_not_null_field", "app_add_not_null_column"), + ], + ) + def test_cache_different_databases(self, *args): + linter = MigrationLinter(self.test_project_path, database="mysql") + linter.old_cache.clear() + linter.old_cache.save() - with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql) as sql_mock: + linter = MigrationLinter(self.test_project_path, database="sqlite") + linter.old_cache.clear() + linter.old_cache.save() + + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: linter.lint_all_migrations() - self.assertEqual(sql_mock.call_count, 2) + self.assertEqual(2, analyse_sql_statements_mock.call_count) - cache = Cache( - fixtures.IGNORE_MIGRATION_PROJECT, - DEFAULT_CACHE_PATH - ) + cache = linter.new_cache cache.load() - self.assertEqual(cache['63230606af0eccaef7f1f78c537c624c']['result'], 'OK') - self.assertEqual(cache['5c5ca1780a9f28439c1defc1f32af894']['result'], 'IGNORE') + self.assertEqual("OK", cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"]) + self.assertEqual("ERR", cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"]) + self.assertListEqual( + cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"], + [ + { + "err_msg": "RENAMING tables", + "code": "RENAME_TABLE", + "table": None, + "column": None, + } + ], + ) + + # Start the Linter again but with different database, should not be the same cache + linter = MigrationLinter(self.test_project_path, database="mysql") + + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: + linter.lint_all_migrations() + self.assertEqual(2, analyse_sql_statements_mock.call_count) + + cache = linter.new_cache + cache.load() + + self.assertEqual("OK", cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"]) + self.assertEqual("ERR", cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"]) + self.assertListEqual( + cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"], + [ + { + "err_msg": "NOT NULL constraint on columns", + "code": "NOT_NULL", + "table": "app_add_not_null_column_a", + "column": "new_not_null_field", + } + ], + ) + + self.assertTrue(linter.has_errors) + + @mock.patch( + "django_migration_linter.MigrationLinter._gather_all_migrations", + return_value=[ + Migration("0001_initial", "app_ignore_migration"), + Migration("0002_ignore_migration", "app_ignore_migration"), + ], + ) + def test_cache_ignored(self, *args): + linter = MigrationLinter(self.test_project_path, ignore_name_contains="0001") + linter.old_cache.clear() + linter.old_cache.save() + + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: + linter.lint_all_migrations() + self.assertEqual(1, analyse_sql_statements_mock.call_count) + + cache = linter.new_cache + cache.load() + + self.assertEqual("IGNORE", cache["0fab48322ba76570da1a3c193abb77b5"]["result"]) # Start the Linter again -> should use cache now. - linter = MigrationLinter(fixtures.IGNORE_MIGRATION_PROJECT) + linter = MigrationLinter(self.test_project_path) - with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql) as sql_mock: + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: linter.lint_all_migrations() - self.assertEqual(sql_mock.call_count, 0) + self.assertEqual(1, analyse_sql_statements_mock.call_count) - def test_cache_ignored_command_line(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_project_ignore_migration.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - linter = MigrationLinter(fixtures.IGNORE_MIGRATION_PROJECT, - ignore_name_contains='0001') + @mock.patch( + "django_migration_linter.MigrationLinter._gather_all_migrations", + return_value=[ + Migration("0002_add_new_not_null_field", "app_add_not_null_column") + ], + ) + def test_cache_modified(self, *args): + linter = MigrationLinter(self.test_project_path) + linter.old_cache.clear() + linter.old_cache.save() - with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql) as sql_mock: + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: linter.lint_all_migrations() - self.assertEqual(sql_mock.call_count, 1) + self.assertEqual(1, analyse_sql_statements_mock.call_count) - cache = Cache( - fixtures.IGNORE_MIGRATION_PROJECT, - DEFAULT_CACHE_PATH - ) + cache = linter.new_cache cache.load() - self.assertNotIn('63230606af0eccaef7f1f78c537c624c', cache) - self.assertEqual(cache['5c5ca1780a9f28439c1defc1f32af894']['result'], 'IGNORE') + self.assertEqual("ERR", cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"]) - def test_cache_modified(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_project_alter_column.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - linter = MigrationLinter(fixtures.ALTER_COLUMN_PROJECT) - linter.lint_all_migrations() - cache = Cache( - fixtures.ALTER_COLUMN_PROJECT, - DEFAULT_CACHE_PATH + # Get the content of the migration file and mock the open call to append + # some content to change the hash + migration_path = get_migration_abspath( + "app_add_not_null_column", "0002_add_new_not_null_field" ) + with open(migration_path, "rb") as f: + file_content = f.read() + file_content += b"# test comment" + + linter = MigrationLinter(self.test_project_path) + with mock.patch( + "django_migration_linter.migration_linter.open", + mock.mock_open(read_data=file_content), + ): + with mock.patch( + "django_migration_linter.migration_linter.analyse_sql_statements", + wraps=analyse_sql_statements, + ) as analyse_sql_statements_mock: + linter.lint_all_migrations() + self.assertEqual(1, analyse_sql_statements_mock.call_count) + + cache = linter.new_cache cache.load() - self.assertEqual(cache['8589aa107b6da296c4b49cd2681d2230']['result'], 'OK', - 'If this fails, tearDown might have failed to remove ' - 'the modification from tests/test_project_fixtures/' - 'test_project_alter_column/test_app/migrations/0001_initial.py' - ) - self.assertEqual(cache['8f54c4a434cfaa9838e8ca12eb988255']['result'], 'ERR') - self.assertListEqual( - cache['8f54c4a434cfaa9838e8ca12eb988255']['errors'], - [{u'err_msg': u'ALTERING columns (Could be backward compatible. ' - u'You may ignore this migration.)', - u'code': u'ALTER_COLUMN', - u'table': u'test_app_a', - u'column': None} - ] - ) - - # Modify migration - backup_migration_file = self.MIGRATION_FILE + "_backup" - shutil.copy2(self.MIGRATION_FILE, backup_migration_file) - with open(self.MIGRATION_FILE, "a") as f: - f.write("# modification at the end of the file") - - # Start the Linter again -> Cache should look different now - linter = MigrationLinter(fixtures.ALTER_COLUMN_PROJECT) - linter.lint_all_migrations() - cache = Cache( - fixtures.ALTER_COLUMN_PROJECT, - DEFAULT_CACHE_PATH - ) - cache.load() - shutil.copy2(backup_migration_file, self.MIGRATION_FILE) - os.remove(backup_migration_file) - - self.assertNotIn('8589aa107b6da296c4b49cd2681d2230', cache) - self.assertEqual(cache['fbee628b1ab4bd1c14f8a4b41123e7cf']['result'], 'OK') + self.assertNotIn("19fd3ea688fc05e2cc2a6e67c0b7aa17", cache) + self.assertEqual(1, len(cache)) + self.assertEqual("ERR", cache["a25768641a0ad526fad199f97c303784"]["result"]) diff --git a/tests/functional/test_cmd_line_call.py b/tests/functional/test_cmd_line_call.py deleted file mode 100644 index 47d7944..0000000 --- a/tests/functional/test_cmd_line_call.py +++ /dev/null @@ -1,298 +0,0 @@ -# Copyright 2019 3YOURMIND GmbH - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import shutil -import unittest -from subprocess import Popen, PIPE -from django_migration_linter import utils, DEFAULT_CACHE_PATH, constants -from tests import fixtures -import sys - - -class CallLinterFromCommandLineTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - super(CallLinterFromCommandLineTest, cls).setUpClass() - linter_name = 'django-migration-linter' - cls.linter_exec = '{0}/bin/{1}'.format(sys.prefix, linter_name) if hasattr(sys, 'real_prefix') else linter_name - - def test_call_linter_cmd_line_working(self): - cmd = '{0} --no-cache {1}'.format(self.linter_exec, fixtures.CORRECT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... OK", - "(test_app1, 0002_a_new_null_field)... OK", - "(test_app2, 0001_foo)... OK", - "(test_app3, 0001_initial)... OK", - ]) - ) - - def test_call_linter_cmd_line_errors(self): - cmd = '{0} --no-cache {1}'.format( - self.linter_exec, - fixtures.ADD_NOT_NULL_COLUMN_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertNotEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 5) - self.assertTrue(lines[0].endswith('OK')) - self.assertTrue(lines[1].endswith('ERR')) - self.assertTrue('RENAMING tables' in lines[2]) - - def test_call_linter_cmd_line_exclude_apps(self): - cmd = '{0} --no-cache {1} --exclude-apps test_app2'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... OK", - "(test_app1, 0002_a_new_null_field)... OK", - "(test_app2, 0001_foo)... IGNORE", - "(test_app3, 0001_initial)... OK", - ]) - ) - - def test_call_linter_cmd_line_include_apps(self): - cmd = '{0} --no-cache {1} --include-apps test_app2'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... IGNORE", - "(test_app1, 0002_a_new_null_field)... IGNORE", - "(test_app2, 0001_foo)... OK", - "(test_app3, 0001_initial)... IGNORE", - ]) - ) - - def test_call_linter_cmd_line_ignore_name(self): - cmd = '{0} --no-cache {1} --ignore-name 0001_initial'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... IGNORE", - "(test_app1, 0002_a_new_null_field)... OK", - "(test_app2, 0001_foo)... OK", - "(test_app3, 0001_initial)... IGNORE", - ]) - ) - - def test_call_linter_cmd_line_ignore_name_contains(self): - cmd = '{0} --no-cache {1} --ignore-name-contains 0001'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... IGNORE", - "(test_app1, 0002_a_new_null_field)... OK", - "(test_app2, 0001_foo)... IGNORE", - "(test_app3, 0001_initial)... IGNORE", - ]) - ) - - def test_call_linter_cmd_line_cache(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_correct_project.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - cmd = '{0} {1}'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT - ) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertTrue(os.path.exists(DEFAULT_CACHE_PATH)) - - def test_call_linter_cmd_line_cache_path(self): - if os.path.exists('/tmp/migration-linter-cache-tests/'): - shutil.rmtree('/tmp/migration-linter-cache-tests/') - cmd = '{0} {1} --cache-path={2}'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT, - '/tmp/migration-linter-cache-tests/' - ) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - self.assertTrue(os.path.exists('/tmp/migration-linter-cache-tests')) - - def test_call_linter_cmd_line_no_cache(self): - cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_correct_project.pickle') - if os.path.exists(cache_file): - os.remove(cache_file) - - cmd = '{0} --no-cache {1}'.format( - self.linter_exec, - fixtures.CORRECT_PROJECT - ) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - self.assertFalse(os.path.exists(cache_file)) - - def test_call_linter_cmd_line_git_id(self): - cmd = '{0} --no-cache {1} d7125d5f4f0cc9623f670a66c54f131acc50032d'.format( - self.linter_exec, - fixtures.MULTI_COMMIT_PROJECT) - fixtures.prepare_git_project(fixtures.MULTI_COMMIT_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 3) - self.assertTrue(lines[0].endswith('OK')) - self.assertTrue(lines[1].startswith('*** Summary')) - - def test_call_linter_with_deleted_migrations(self): - cmd = '{0} --no-cache {1} 154ecf6119325cc3b1f3f5a4e709bfbd61a4a4ba'.format( - self.linter_exec, - fixtures.DELETED_MIGRATION_PROJECT) - fixtures.prepare_git_project(fixtures.DELETED_MIGRATION_PROJECT) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 3) - self.assertTrue(lines[0].endswith('OK')) - self.assertTrue(lines[1].startswith('*** Summary')) - - def test_call_from_within_project(self): - cmd = 'cd {0} && {1} --no-cache .'.format( - fixtures.CORRECT_PROJECT, - self.linter_exec) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 6) - self.assertEqual( - sorted(lines[:4]), - sorted([ - "(test_app1, 0001_initial)... OK", - "(test_app1, 0002_a_new_null_field)... OK", - "(test_app2, 0001_foo)... OK", - "(test_app3, 0001_initial)... OK", - ]) - ) - - def test_call_project_non_git_root(self): - # could use tag: version_ok (but doesn't work in Travis) - cmd = '{0} --no-cache {1} 2c46f438a89972f9ce2d5151a825a5bfb7f7db4b'.format( - self.linter_exec, - fixtures.NON_GIT_ROOT_DJANGO_PROJECT) - fixtures.prepare_git_project(fixtures.NON_GIT_ROOT_GIT_FOLDER) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 3) - self.assertTrue(lines[0].endswith('OK')) - self.assertTrue(lines[1].startswith('*** Summary')) - - def test_call_project_non_git_root_ko(self): - # could use tag: version_ko (but doesn't work in Travis) - cmd = '{0} --no-cache {1} 0140e142724fc58944797f9ddc2ebf964146339a'.format( - self.linter_exec, - fixtures.NON_GIT_ROOT_DJANGO_PROJECT) - fixtures.prepare_git_project(fixtures.NON_GIT_ROOT_GIT_FOLDER) - - process = Popen( - cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertNotEqual(process.returncode, 0) - lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) - self.assertEqual(len(lines), 5) - self.assertTrue(lines[0].endswith('ERR')) - self.assertTrue(lines[2].endswith('OK')) - self.assertTrue(lines[3].startswith('*** Summary')) - - -class VersionOptionLinterFromCommandLineTest(CallLinterFromCommandLineTest): - def test_call_with_version_option(self): - cmd = "{} --version".format(self.linter_exec) - process = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - process_read_stream = process.stderr if sys.version_info.major == 2 else process.stdout - lines = list(map(utils.clean_bytes_to_str, process_read_stream.readlines())) - self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], "django-migration-linter {}".format(constants.__version__)) - - def test_call_with_short_version_option(self): - cmd = "{} -V".format(self.linter_exec) - process = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - process.wait() - self.assertEqual(process.returncode, 0) - process_read_stream = process.stderr if sys.version_info.major == 2 else process.stdout - lines = list(map(utils.clean_bytes_to_str, process_read_stream.readlines())) - self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], "django-migration-linter {}".format(constants.__version__)) diff --git a/tests/functional/test_migration_linter.py b/tests/functional/test_migration_linter.py index ae2ba24..4ecf1aa 100644 --- a/tests/functional/test_migration_linter.py +++ b/tests/functional/test_migration_linter.py @@ -11,100 +11,97 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import os import unittest +from django.conf import settings + from django_migration_linter import MigrationLinter from tests import fixtures class BaseBackwardCompatibilityDetection(object): - def tearDown(self, *args, **kwargs): - fixtures.clear_all_git_projects() - super( - BaseBackwardCompatibilityDetection, - self).tearDown(*args, **kwargs) + def setUp(self, *args, **kwargs): + self.test_project_path = os.path.dirname(settings.BASE_DIR) + return super(BaseBackwardCompatibilityDetection, self).setUp(*args, **kwargs) - def _test_linter_finds_errors(self, path, commit_id=None): - linter = MigrationLinter(path, database=self.DATABASE, no_cache=True) - linter.lint_all_migrations(git_commit_id=commit_id) + def _test_linter_finds_errors(self, app=None, commit_id=None): + linter = self._launch_linter(app, commit_id) self.assertTrue(linter.has_errors) + self.assertNotEqual(linter.nb_valid + linter.nb_erroneous, 0) - def _test_linter_finds_no_errors(self, path, commit_id=None): - linter = MigrationLinter(path, database=self.DATABASE, no_cache=True) - linter.lint_all_migrations(git_commit_id=commit_id) + def _test_linter_finds_no_errors(self, app=None, commit_id=None): + linter = self._launch_linter(app, commit_id) self.assertFalse(linter.has_errors) + self.assertNotEqual(linter.nb_valid + linter.nb_erroneous, 0) + + def _launch_linter(self, app=None, commit_id=None): + if app is not None: + app = [app] + + linter = MigrationLinter( + self.test_project_path, + include_apps=app, + database=next(iter(self.databases)), + no_cache=True, + ) + linter.lint_all_migrations(git_commit_id=commit_id) + return linter # *** Tests *** def test_create_table_with_not_null_column(self): - test_project_path = fixtures.CREATE_TABLE_WITH_NOT_NULL_COLUMN_PROJECT - self._test_linter_finds_no_errors(test_project_path) + app = fixtures.CREATE_TABLE_WITH_NOT_NULL_COLUMN + self._test_linter_finds_no_errors(app) def test_detect_adding_not_null_column(self): - test_project_path = fixtures.ADD_NOT_NULL_COLUMN_PROJECT - self._test_linter_finds_errors(test_project_path) + app = fixtures.ADD_NOT_NULL_COLUMN + self._test_linter_finds_errors(app) def test_detect_drop_column(self): - test_project_path = fixtures.DROP_COLUMN_PROJECT - self._test_linter_finds_errors(test_project_path) + app = fixtures.DROP_COLUMN + self._test_linter_finds_errors(app) def test_detect_rename_column(self): - test_project_path = fixtures.RENAME_COLUMN_PROJECT - self._test_linter_finds_errors(test_project_path) + app = fixtures.RENAME_COLUMN + self._test_linter_finds_errors(app) def test_detect_rename_table(self): - test_project_path = fixtures.RENAME_TABLE_PROJECT - self._test_linter_finds_errors(test_project_path) + app = fixtures.RENAME_TABLE + self._test_linter_finds_errors(app) def test_ignore_migration(self): - test_project_path = fixtures.IGNORE_MIGRATION_PROJECT - self._test_linter_finds_no_errors(test_project_path) + app = fixtures.IGNORE_MIGRATION + self._test_linter_finds_no_errors(app) def test_accept_not_null_column_followed_by_adding_default(self): - test_project_path = \ - fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT_PROJECT - self._test_linter_finds_no_errors(test_project_path) + app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT + self._test_linter_finds_no_errors(app) def test_detect_alter_column(self): - test_project_path = fixtures.ALTER_COLUMN_PROJECT - self._test_linter_finds_errors(test_project_path) + app = fixtures.ALTER_COLUMN + self._test_linter_finds_errors(app) - def test_no_specify_git_hash(self): - test_project_path = fixtures.MULTI_COMMIT_PROJECT - fixtures.prepare_git_project(test_project_path) - self._test_linter_finds_errors(test_project_path, commit_id=None) - - def test_specify_git_hash_by_commit_hash(self): - test_project_path = fixtures.MULTI_COMMIT_PROJECT - fixtures.prepare_git_project(test_project_path) - self._test_linter_finds_no_errors( - test_project_path, - commit_id='d7125d5f4f0cc9623f670a66c54f131acc50032d') - - #def test_specify_git_hash_by_tag(self): - # test_project_path = fixtures.MULTI_COMMIT_PROJECT - # fixtures.prepare_git_project(test_project_path) - # self._test_linter_finds_no_errors(test_project_path, commit_id='tag1') + def test_with_git_ref(self): + self._test_linter_finds_errors(commit_id="v0.1.4") -class MySqlBackwardCompatibilityDetectionTest(unittest.TestCase, BaseBackwardCompatibilityDetection): - DATABASE = "mysql" - -class SqliteBackwardCompatibilityDetectionTest(unittest.TestCase, BaseBackwardCompatibilityDetection): - DATABASE = "sqlite" +class SqliteBackwardCompatibilityDetectionTestCase( + BaseBackwardCompatibilityDetection, unittest.TestCase +): + databases = ["sqlite"] def test_accept_not_null_column_followed_by_adding_default(self): - test_project_path = \ - fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT_PROJECT - self._test_linter_finds_errors(test_project_path) - - def test_specify_git_hash_by_commit_hash(self): - test_project_path = fixtures.MULTI_COMMIT_PROJECT - fixtures.prepare_git_project(test_project_path) - self._test_linter_finds_errors( - test_project_path, - commit_id='d7125d5f4f0cc9623f670a66c54f131acc50032d') + app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT + self._test_linter_finds_errors(app) -class PostgresqlBackwardCompatibilityDetectionTest(unittest.TestCase, BaseBackwardCompatibilityDetection): - DATABASE = "postgresql" +class MySqlBackwardCompatibilityDetectionTestCase( + BaseBackwardCompatibilityDetection, unittest.TestCase +): + databases = ["mysql"] + + +class PostgresqlBackwardCompatibilityDetectionTestCase( + BaseBackwardCompatibilityDetection, unittest.TestCase +): + databases = ["postgresql"] diff --git a/tests/functional/test_multiple_calls.py b/tests/functional/test_multiple_calls.py deleted file mode 100644 index e58a473..0000000 --- a/tests/functional/test_multiple_calls.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2019 3YOURMIND GmbH - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest -import os - -from django_migration_linter import MigrationLinter -from django_migration_linter.migration import Migration -from tests import fixtures - - -class TestMultipleLinters(unittest.TestCase): - def test_multiple_linters(self): - l1 = MigrationLinter(fixtures.ADD_NOT_NULL_COLUMN_PROJECT) - l2 = MigrationLinter(fixtures.RENAME_COLUMN_PROJECT) - l3 = MigrationLinter(fixtures.CORRECT_PROJECT) - - l1.lint_migration(Migration(os.path.join(fixtures.ADD_NOT_NULL_COLUMN_PROJECT, 'test_app/migrations/0002_add_new_not_null_field.py'))) - l2.lint_migration(Migration(os.path.join(fixtures.RENAME_COLUMN_PROJECT, 'test_app/migrations/0002_rename_column.py'))) - l3.lint_migration(Migration(os.path.join(fixtures.CORRECT_PROJECT, 'test_app1/migrations/0001_initial.py'))) - l3.lint_migration(Migration(os.path.join(fixtures.CORRECT_PROJECT, 'test_app1/migrations/0002_a_new_null_field.py'))) - - self.assertTrue(l1.has_errors) - self.assertTrue(l2.has_errors) - self.assertFalse(l3.has_errors) - - self.assertEqual(l1.nb_total, 1) - self.assertEqual(l2.nb_total, 1) - self.assertEqual(l3.nb_total, 2) diff --git a/tests/unit/test_linter.py b/tests/unit/test_linter.py index 25d1a65..a3f644b 100644 --- a/tests/unit/test_linter.py +++ b/tests/unit/test_linter.py @@ -13,85 +13,63 @@ # limitations under the License. import unittest -import os + +from django.db.migrations import Migration from django_migration_linter import MigrationLinter -from django_migration_linter.migration import Migration -from tests import fixtures -class TestLinterFunctions(unittest.TestCase): +class LinterFunctionsTestCase(unittest.TestCase): def test_get_sql(self): - project_path = fixtures.ADD_NOT_NULL_COLUMN_PROJECT - linter = MigrationLinter(project_path) - sql_statements = linter.get_sql('test_app', '0001') + linter = MigrationLinter() + sql_statements = linter.get_sql("app_add_not_null_column", "0001") self.assertEqual(len(sql_statements), 6) - self.assertEqual(sql_statements[0], 'BEGIN;') - self.assertEqual(sql_statements[-1], 'COMMIT;') + self.assertEqual(sql_statements[0], "BEGIN;") + self.assertEqual(sql_statements[-1], "COMMIT;") def test_has_errors(self): - project_path = fixtures.MULTI_COMMIT_PROJECT - linter = MigrationLinter(project_path) + linter = MigrationLinter() self.assertFalse(linter.has_errors) - m = Migration(os.path.join(project_path, 'test_app/migrations/0001_create_table.py')) + m = Migration("0001_create_table", "app_add_not_null_column") linter.lint_migration(m) self.assertFalse(linter.has_errors) - m = Migration(os.path.join(project_path, 'test_app/migrations/0002_add_new_not_null_field.py')) + m = Migration("0002_add_new_not_null_field", "app_add_not_null_column") linter.lint_migration(m) self.assertTrue(linter.has_errors) - m = Migration(os.path.join(project_path, 'test_app/migrations/0001_create_table.py')) + m = Migration("0001_create_table", "app_add_not_null_column") linter.lint_migration(m) self.assertTrue(linter.has_errors) - def test_linter_creation(self): - with self.assertRaises(ValueError): - MigrationLinter('foo/') - with self.assertRaises(ValueError): - MigrationLinter('/dev/null') - with self.assertRaises(ValueError): - MigrationLinter(fixtures.NOT_DJANGO_GIT_PROJECT) - def test_ignore_migration_include_apps(self): - linter = MigrationLinter( - fixtures.ADD_NOT_NULL_COLUMN_PROJECT, - include_apps=('test_app2',)) - self.assertTrue(linter.should_ignore_migration('test_app1', '0001')) - self.assertFalse(linter.should_ignore_migration('test_app2', '0001')) + linter = MigrationLinter(include_apps=("app_add_not_null_column",)) + self.assertTrue(linter.should_ignore_migration("app_correct", "0001")) + self.assertTrue(linter.should_ignore_migration("app_correct", "0002")) + self.assertFalse( + linter.should_ignore_migration("app_add_not_null_column", "0001") + ) def test_ignore_migration_exclude_apps(self): - linter = MigrationLinter( - fixtures.ADD_NOT_NULL_COLUMN_PROJECT, - exclude_apps=('test_app1',)) - self.assertTrue(linter.should_ignore_migration('test_app1', '0001')) - self.assertFalse(linter.should_ignore_migration('test_app2', '0001')) + linter = MigrationLinter(exclude_apps=("app_add_not_null_column",)) + self.assertFalse(linter.should_ignore_migration("app_correct", "0001")) + self.assertFalse(linter.should_ignore_migration("app_correct", "0002")) + self.assertTrue( + linter.should_ignore_migration("app_add_not_null_column", "0001") + ) def test_ignore_migration_name_contains(self): - linter = MigrationLinter( - fixtures.ADD_NOT_NULL_COLUMN_PROJECT, - ignore_name_contains='foo') - self.assertTrue(linter.should_ignore_migration('test_app', '0001_foo')) - self.assertFalse(linter.should_ignore_migration('test_app', '0002_bar')) + linter = MigrationLinter(ignore_name_contains="foo") + self.assertFalse(linter.should_ignore_migration("app_correct", "0001_initial")) + self.assertTrue(linter.should_ignore_migration("app_correct", "0002_foo")) def test_ignore_migration_full_name(self): - linter = MigrationLinter( - fixtures.ADD_NOT_NULL_COLUMN_PROJECT, - ignore_name=('0001_foo',)) - self.assertTrue(linter.should_ignore_migration('test_app', '0001_foo')) - self.assertFalse(linter.should_ignore_migration('test_app', '0002_bar')) + linter = MigrationLinter(ignore_name=("0002_foo",)) + self.assertFalse(linter.should_ignore_migration("app_correct", "0001_initial")) + self.assertTrue(linter.should_ignore_migration("app_correct", "0002_foo")) def test_gather_all_migrations(self): - linter = MigrationLinter(fixtures.CORRECT_PROJECT) + linter = MigrationLinter() migrations = linter._gather_all_migrations() - self.assertEqual(len(migrations), 4) - self.assertEqual( - sorted([(m.app_name, m.name) for m in migrations]), - sorted([ - ("test_app1", "0001_initial"), - ("test_app1", "0002_a_new_null_field"), - ("test_app2", "0001_foo"), - ("test_app3", "0001_initial"), - ]) - ) + self.assertGreater(len(list(migrations)), 1) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 62202f7..1be4aa9 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -12,74 +12,46 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import unittest -from tests import fixtures -from django_migration_linter.utils import is_django_project, is_directory, find_project_settings_module, \ - split_path, split_migration_path +from django_migration_linter.utils import split_path, split_migration_path -class UtilityFunctionTest(unittest.TestCase): - def test_detect_django_project(self): - self.assertTrue(is_django_project(fixtures.ADD_NOT_NULL_COLUMN_PROJECT)) - - def test_detect_not_django_project(self): - self.assertFalse(is_django_project(fixtures._BASE_DIR)) - - def test_detect_directory(self): - self.assertTrue(is_directory(fixtures.ADD_NOT_NULL_COLUMN_PROJECT)) - - def test_detect_not_directory(self): - self.assertFalse(is_directory( - os.path.join(fixtures.ADD_NOT_NULL_COLUMN_PROJECT, 'manage.py'))) - - def test_find_project_settings_module_same_project_name(self): - expected = 'test_project_add_not_null_column.settings' - actual = find_project_settings_module(fixtures.ADD_NOT_NULL_COLUMN_PROJECT) - self.assertEqual(actual, expected) - - def test_find_project_settings_module_different_project_name(self): - expected = 'test_project.settings' - actual = find_project_settings_module(fixtures.CORRECT_PROJECT) - self.assertEqual(actual, expected) - - -class SplitPathTest(unittest.TestCase): +class SplitPathTestCase(unittest.TestCase): def test_split_path(self): - split = split_path('foo/bar/fuz.py') + split = split_path("foo/bar/fuz.py") self.assertEqual(split, ["foo", "bar", "fuz.py"]) def test_split_full_path(self): - split = split_path('/foo/bar/fuz.py') + split = split_path("/foo/bar/fuz.py") self.assertEqual(split, ["/", "foo", "bar", "fuz.py"]) def test_split_folder_path(self): - split = split_path('/foo/bar') + split = split_path("/foo/bar") self.assertEqual(split, ["/", "foo", "bar"]) def test_split_folder_path_trailing_slash(self): - split = split_path('/foo/bar/') + split = split_path("/foo/bar/") self.assertEqual(split, ["/", "foo", "bar"]) def test_split_folder_path_trailing_slashes(self): - split = split_path('/foo/bar///') + split = split_path("/foo/bar///") self.assertEqual(split, ["/", "foo", "bar"]) def test_split_migration_long_path(self): - input_path = 'apps/the_app/migrations/0001_stuff.py' + input_path = "apps/the_app/migrations/0001_stuff.py" app, mig = split_migration_path(input_path) - self.assertEqual(app, 'the_app') - self.assertEqual(mig, '0001_stuff') + self.assertEqual(app, "the_app") + self.assertEqual(mig, "0001_stuff") def test_split_migration_path(self): - input_path = 'the_app/migrations/0001_stuff.py' + input_path = "the_app/migrations/0001_stuff.py" app, mig = split_migration_path(input_path) - self.assertEqual(app, 'the_app') - self.assertEqual(mig, '0001_stuff') + self.assertEqual(app, "the_app") + self.assertEqual(mig, "0001_stuff") def test_split_migration_full_path(self): - input_path = '/home/user/djangostuff/apps/the_app/migrations/0001_stuff.py' + input_path = "/home/user/djangostuff/apps/the_app/migrations/0001_stuff.py" app, mig = split_migration_path(input_path) - self.assertEqual(app, 'the_app') - self.assertEqual(mig, '0001_stuff') + self.assertEqual(app, "the_app") + self.assertEqual(mig, "0001_stuff") diff --git a/tox.ini b/tox.ini index 81a15c9..f644d37 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,11 @@ [tox] envlist = - py{27,34,35,36,37}-django111 - py{34,35,36,37}-django20 - py{35,36,37}-django21 + py27-django111 + py{35,36,37}-django{111,20,21} lint [testenv] -commands = py.test {posargs} +commands = ./manage.py test --no-input {posargs} extras = test pip_pre = true deps = @@ -23,4 +22,4 @@ deps = black commands = flake8 --max-line-length=88 django_migration_linter - black --check django_migration_linter + black --check django_migration_linter/ tests/ manage.py setup.py