diff --git a/django_migration_linter/cache.py b/django_migration_linter/cache.py new file mode 100644 index 0000000..0d3f524 --- /dev/null +++ b/django_migration_linter/cache.py @@ -0,0 +1,53 @@ +# Copyright 2018 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 hashlib +import os +import pickle + +from django_migration_linter.utils import split_path, compose_migration_path + + +class Cache(dict): + def __init__(self, django_folder, cache_path): + self.django_folder = django_folder + project_name = split_path(django_folder)[-2] + self.filename = os.path.join(cache_path, '{0}.pickle'.format(project_name)) + + if not os.path.exists(os.path.dirname(self.filename)): + os.makedirs(os.path.dirname(self.filename)) + + super(Cache, self).__init__() + + def load(self): + try: + f = open(self.filename, 'rb') + tmp_dict = pickle.load(f) + self.update(tmp_dict) + f.close() + except IOError: + pass + + def save(self): + f = open(self.filename, 'wb') + pickle.dump(self, f, 2) + f.close() + + def md5(self, app_name, migration): + path = compose_migration_path(self.django_folder, app_name, migration) + hash_md5 = hashlib.md5() + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() diff --git a/django_migration_linter/constants.py b/django_migration_linter/constants.py index a667d9a..1bdfc9b 100644 --- a/django_migration_linter/constants.py +++ b/django_migration_linter/constants.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pathlib import Path - from appdirs import user_cache_dir -__VERSION__ = '0.0.7' +__version__ = '0.0.7' -DEFAULT_CACHE_PATH = Path( - user_cache_dir("django-migration-linter", version=__VERSION__) +MIGRATION_FOLDER_NAME = 'migrations' + +DEFAULT_CACHE_PATH = user_cache_dir( + "django-migration-linter", version=__version__ ) diff --git a/django_migration_linter/migration_linter.py b/django_migration_linter/migration_linter.py index b2cd6aa..66fa34c 100644 --- a/django_migration_linter/migration_linter.py +++ b/django_migration_linter/migration_linter.py @@ -18,6 +18,10 @@ import os import re from subprocess import Popen, PIPE import sys + +from django_migration_linter.cache import Cache +from django_migration_linter.constants import DEFAULT_CACHE_PATH, MIGRATION_FOLDER_NAME +from django_migration_linter.utils import split_migration_path from . import utils from .sql_analyser import analyse_sql_statements @@ -25,7 +29,6 @@ logger = logging.getLogger(__name__) class MigrationLinter(object): - MIGRATION_FOLDER_NAME = 'migrations' def __init__(self, project_path, **kwargs): # Verify correctness @@ -45,6 +48,9 @@ class MigrationLinter(object): self.include_apps = kwargs.get('include_apps', None) self.exclude_apps = kwargs.get('exclude_apps', None) self.database = kwargs.get('database', None) or 'default' + self.cache_path = kwargs.get('cache_path', None) or DEFAULT_CACHE_PATH + self.no_cache = kwargs.get('no_cache', None) or False + self.python_exe = '{0}/bin/{1}'.format(sys.prefix, 'python') if \ hasattr(sys, 'real_prefix') else 'python' @@ -54,12 +60,37 @@ class MigrationLinter(object): self.nb_erroneous = 0 self.nb_total = 0 + # Initialise cache. Read from old, write to new to prune old entries. + self.old_cache = Cache(self.django_path, self.cache_path) + self.new_cache = Cache(self.django_path, self.cache_path) + if not self.no_cache: + self.old_cache.load() + def lint_migration(self, app_name, migration_name): print('({0}, {1})... '.format(app_name, migration_name), end='') self.nb_total += 1 + md5hash = self.old_cache.md5(app_name, migration_name) + if md5hash in self.old_cache: + if self.old_cache[md5hash]['result'] == 'IGNORE': + print('IGNORE (cached)') + self.nb_ignored += 1 + elif self.old_cache[md5hash]['result'] == 'OK': + print('OK (cached)') + self.nb_valid += 1 + else: + print('ERR (cached)') + self.nb_erroneous += 1 + + if 'errors' in self.old_cache[md5hash]: + self.print_errors(self.old_cache[md5hash]['errors']) + + self.new_cache[md5hash] = self.old_cache[md5hash] + return + if self.should_ignore_migration(app_name, migration_name): print('IGNORE') + self.new_cache[md5hash] = {'result': 'IGNORE'} self.nb_ignored += 1 return @@ -70,12 +101,17 @@ class MigrationLinter(object): errors = analysis_result['errors'] if not errors: print('OK') + self.new_cache[md5hash] = {'result': 'OK'} self.nb_valid += 1 return - # Print errors print('ERR') + self.new_cache[md5hash] = {'result': 'ERR', 'errors': errors} self.nb_erroneous += 1 + self.print_errors(errors) + + @staticmethod + def print_errors(errors): for err in errors: error_str = '\t{0}'.format(err['err_msg']) if err['table']: @@ -101,6 +137,9 @@ class MigrationLinter(object): for m in migrations: self.lint_migration(*m) + if not self.no_cache: + self.new_cache.save() + def print_summary(self): print('*** Summary:') print(('Valid migrations: {1}/{0} - ' @@ -166,10 +205,10 @@ class MigrationLinter(object): utils.clean_bytes_to_str, diff_process.stdout.readlines()): # Only gather lines that include added migrations if re.search( - '\/{0}\/.*\.py'.format(self.MIGRATION_FOLDER_NAME), + '\/{0}\/.*\.py'.format(MIGRATION_FOLDER_NAME), line) and \ '__init__' not in line: - app_name, migration_name = self._split_migration_path(line) + app_name, migration_name = split_migration_path(line) migrations.append((app_name, migration_name)) diff_process.wait() @@ -187,11 +226,11 @@ class MigrationLinter(object): migrations = [] for root, dirs, files in os.walk(self.django_path): for file_name in files: - if os.path.basename(root) == self.MIGRATION_FOLDER_NAME and \ + if os.path.basename(root) == MIGRATION_FOLDER_NAME and \ file_name.endswith('.py') and \ file_name != '__init__.py': full_migration_path = os.path.join(root, file_name) - app_name, migration_name = self._split_migration_path( + app_name, migration_name = split_migration_path( full_migration_path) migrations.append((app_name, migration_name)) return migrations @@ -205,14 +244,6 @@ class MigrationLinter(object): self.ignore_name_contains in migration_name)\ or (migration_name in self.ignore_name) - @classmethod - def _split_migration_path(cls, migration_path): - decomposed_path = utils.split_path(migration_path) - for i, p in enumerate(decomposed_path): - if p == cls.MIGRATION_FOLDER_NAME: - return (decomposed_path[i-1], - os.path.splitext(decomposed_path[i+1])[0]) - def _main(): import argparse @@ -253,6 +284,17 @@ def _main(): help=('specify the database for which to generate the SQL. ' 'Defaults to default')) + cache_group = parser.add_mutually_exclusive_group( + required=False) + cache_group.add_argument( + '--cache-path', + type=str, + help='specify a directory that should be used to store cache-files in.') + cache_group.add_argument( + '--no-cache', + action='store_true', + help='don\'t use a cache') + incl_excl_group = parser.add_mutually_exclusive_group( required=False) incl_excl_group.add_argument( @@ -280,7 +322,10 @@ def _main(): ignore_name=args.ignore_name, include_apps=args.include_apps, exclude_apps=args.exclude_apps, - database=args.database) + database=args.database, + cache_path=args.cache_path, + no_cache=args.no_cache + ) linter.lint_all_migrations( git_commit_id=args.commit_id) linter.print_summary() diff --git a/django_migration_linter/utils.py b/django_migration_linter/utils.py index 0f83ad5..8e26011 100644 --- a/django_migration_linter/utils.py +++ b/django_migration_linter/utils.py @@ -13,10 +13,11 @@ # limitations under the License. from __future__ import print_function + import os import sys -from django_migration_linter.constants import DEFAULT_CACHE_PATH +from django_migration_linter.constants import MIGRATION_FOLDER_NAME def is_django_project(path): @@ -54,9 +55,25 @@ def split_path(path): return (split_path(a) if (len(a) > 0 and a != '/') else []) + [b] +def split_migration_path(migration_path): + decomposed_path = split_path(migration_path) + for i, p in enumerate(decomposed_path): + if p == MIGRATION_FOLDER_NAME: + return (decomposed_path[i-1], + os.path.splitext(decomposed_path[i+1])[0]) + + +def compose_migration_path(django_folder, app_name, migration): + return os.path.join( + django_folder, + app_name, + MIGRATION_FOLDER_NAME, + '{0}.py'.format(migration) + ) + + def clean_bytes_to_str(byte_input): return byte_input.decode('utf-8').strip() -def get_default_cache_file(project_name): - return os.path.join(DEFAULT_CACHE_PATH, '{0}.pickle'.format(project_name)) + diff --git a/setup.py b/setup.py index 7337984..dd6bee3 100644 --- a/setup.py +++ b/setup.py @@ -22,14 +22,13 @@ PROJECT_DIR = path.abspath(path.dirname(__file__)) def get_version(): constants = path.join(PROJECT_DIR, 'django_migration_linter', 'constants.py') - _version_re = re.compile(r"__VERSION__\s+=\s+(?P.*)") + _version_re = re.compile(r"__version__\s+=\s+(?P.*)") with open(constants, "r") as f: match = _version_re.search(f.read()) version = match.group("version") if match is not None else '"unknown"' return str(ast.literal_eval(version)) - with open(path.join(PROJECT_DIR, 'README.rst')) as f: long_description = f.read() diff --git a/tests/functional/test_cache.py b/tests/functional/test_cache.py index 85a94ef..4ea631f 100644 --- a/tests/functional/test_cache.py +++ b/tests/functional/test_cache.py @@ -12,20 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. import os +import pprint import shutil import tempfile import unittest -from unittest import mock +import sys -from django_migration_linter.utils import get_default_cache_file from tests import fixtures -from django_migration_linter import MigrationLinter -from tests.fixtures import ADD_NOT_NULL_COLUMN_PROJECT +from django_migration_linter import MigrationLinter, Cache, DEFAULT_CACHE_PATH + +if sys.version_info >= (3, 3): + import unittest.mock as mock +else: + import mock + class CacheTest(unittest.TestCase): - MIGRATION_FILE = os.path.join(ADD_NOT_NULL_COLUMN_PROJECT, 'test_app', 'migrations', '0001_create_table.py') + MIGRATION_FILE = os.path.join(fixtures.ALTER_COLUMN_PROJECT, 'test_app', 'migrations', '0001_initial.py') fd, temp_path = tempfile.mkstemp() def setUp(self): @@ -36,50 +41,83 @@ class CacheTest(unittest.TestCase): os.remove(self.temp_path) def test_cache_normal(self): - if os.path.exists(get_default_cache_file('test_project_add_not_null_column')): - os.remove(get_default_cache_file('test_project_add_not_null_column')) + 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) - linter.lint_all_migrations() - cache = linter.get_cache() - self.assertEqual(cache['hash']['result'], 'OK') - self.assertEqual(cache['hashERR']['result'], 'ERR') - self.assertDictEqual(cache['hashERR']['errors'], ...) - with mock.patch( - 'subprocess.Popen' - ) as popen_mock: + with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql)as sql_mock: linter.lint_all_migrations() - self.assertEqual(popen_mock.call_count, 2) + sql_mock.assert_called() - with mock.patch( - 'subprocess.Popen' - ) as popen_mock: + cache = Cache( + fixtures.ADD_NOT_NULL_COLUMN_PROJECT, + DEFAULT_CACHE_PATH + ) + cache.load() + + self.assertEqual(cache['3ef74e7f3e53e273e2fc95379248d58d']['result'], 'OK') + self.assertEqual(cache['e1e312b6d08ecbe017c25c58fc2be257']['result'], 'ERR') + self.assertListEqual( + cache['e1e312b6d08ecbe017c25c58fc2be257']['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) + + with mock.patch.object(MigrationLinter, 'get_sql', wraps=linter.get_sql)as sql_mock: linter.lint_all_migrations() + sql_mock.assert_not_called() - self.assertEqual(popen_mock.call_count, 0) - self.assertFalse(linter.has_errors()) + self.assertTrue(linter.has_errors) def test_cache_ignored(self): pass def test_cache_modified(self): - if os.path.exists(get_default_cache_file('test_project_add_not_null_column')): - os.remove(get_default_cache_file('test_project_add_not_null_column')) - linter = MigrationLinter(fixtures.ADD_NOT_NULL_COLUMN_PROJECT) + 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 = linter.get_cache() - self.assertEqual(cache['hash']['result'], 'OK') - self.assertEqual(cache['hashERR']['result'], 'ERR') - self.assertDictEqual(cache['hashERR']['errors'], ...) + cache = Cache( + fixtures.ALTER_COLUMN_PROJECT, + DEFAULT_CACHE_PATH + ) + cache.load() - with open( self.MIGRATION_FILE, "a") as f: + 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 + with open(self.MIGRATION_FILE, "a") as f: f.write("# modification at the end of the file\n") + # Start the Linter again -> Cache should look different now + linter = MigrationLinter(fixtures.ALTER_COLUMN_PROJECT) linter.lint_all_migrations() - cache = linter.get_cache() - - self.assertNotIn('hash', cache) - self.assertEqual(cache['newhash']['result'], 'OK') - + cache = Cache( + fixtures.ALTER_COLUMN_PROJECT, + DEFAULT_CACHE_PATH + ) + cache.load() + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(cache) + self.assertNotIn('8589aa107b6da296c4b49cd2681d2230', cache) + self.assertEqual(cache['44f9d6d019d6f158946c2819b2bd8bf9']['result'], 'OK') diff --git a/tests/functional/test_cmd_line_call.py b/tests/functional/test_cmd_line_call.py index 4f1c4b5..e651ea2 100644 --- a/tests/functional/test_cmd_line_call.py +++ b/tests/functional/test_cmd_line_call.py @@ -13,10 +13,10 @@ # limitations under the License. import os +import shutil import unittest from subprocess import Popen, PIPE -from django_migration_linter import utils -from django_migration_linter.utils import get_default_cache_file +from django_migration_linter import utils, DEFAULT_CACHE_PATH from tests import fixtures import sys @@ -29,7 +29,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): 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} {1}'.format(self.linter_exec, fixtures.CORRECT_PROJECT) + cmd = '{0} --no-cache {1}'.format(self.linter_exec, fixtures.CORRECT_PROJECT) process = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE) @@ -42,7 +42,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[2].endswith('OK')) def test_call_linter_cmd_line_errors(self): - cmd = '{0} {1}'.format( + cmd = '{0} --no-cache {1}'.format( self.linter_exec, fixtures.ADD_NOT_NULL_COLUMN_PROJECT) @@ -58,12 +58,12 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue('RENAMING tables' in lines[2]) def test_call_linter_cmd_line_exclude_apps(self): - cmd = '{0} {1} --exclude-apps test_app2'.format( + 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) + cmd, shell=True, stdout=PIPE, stderr=sys.stderr) process.wait() self.assertEqual(process.returncode, 0) lines = list(map(utils.clean_bytes_to_str, process.stdout.readlines())) @@ -73,7 +73,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[2].endswith('IGNORE')) def test_call_linter_cmd_line_include_apps(self): - cmd = '{0} {1} --include-apps test_app2'.format( + cmd = '{0} --no-cache {1} --include-apps test_app2'.format( self.linter_exec, fixtures.CORRECT_PROJECT) @@ -88,7 +88,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[2].endswith('OK')) def test_call_linter_cmd_line_ignore_name(self): - cmd = '{0} {1} --ignore-name 0001_initial'.format( + cmd = '{0} --no-cache {1} --ignore-name 0001_initial'.format( self.linter_exec, fixtures.CORRECT_PROJECT) @@ -103,7 +103,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[2].endswith('OK')) def test_call_linter_cmd_line_ignore_name_contains(self): - cmd = '{0} {1} --ignore-name-contains 0001'.format( + cmd = '{0} --no-cache {1} --ignore-name-contains 0001'.format( self.linter_exec, fixtures.CORRECT_PROJECT) @@ -118,8 +118,9 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[2].endswith('IGNORE')) def test_call_linter_cmd_line_cache(self): - if os.path.exists(get_default_cache_file('test_correct_project')): - os.remove(get_default_cache_file('test_correct_project')) + 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 @@ -128,15 +129,15 @@ class CallLinterFromCommandLineTest(unittest.TestCase): process = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE) process.wait() - self.assertTrue(os.path.exists(get_default_cache_file('test_correct_project'))) + 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'): - os.remove('/tmp/migration-linter-cache-tests') + 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' + '/tmp/migration-linter-cache-tests/' ) process = Popen( @@ -146,10 +147,12 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(os.path.exists('/tmp/migration-linter-cache-tests')) def test_call_linter_cmd_line_no_cache(self): - if os.path.exists(get_default_cache_file('test_correct_project')): - os.remove(get_default_cache_file('test_correct_project')) + cache_file = os.path.join(DEFAULT_CACHE_PATH, 'test_correct_project.pickle') + print(cache_file) + if os.path.exists(cache_file): + os.remove(cache_file) - cmd = '{0} {1} --no-cache'.format( + cmd = '{0} --no-cache {1}'.format( self.linter_exec, fixtures.CORRECT_PROJECT ) @@ -158,10 +161,10 @@ class CallLinterFromCommandLineTest(unittest.TestCase): cmd, shell=True, stdout=PIPE, stderr=PIPE) process.wait() self.assertEqual(process.returncode, 0) - self.assertFalse(os.path.exists(get_default_cache_file('test_correct_project'))) + self.assertFalse(os.path.exists(cache_file)) def test_call_linter_cmd_line_git_id(self): - cmd = '{0} {1} d7125d5f4f0cc9623f670a66c54f131acc50032d'.format( + cmd = '{0} --no-cache {1} d7125d5f4f0cc9623f670a66c54f131acc50032d'.format( self.linter_exec, fixtures.MULTI_COMMIT_PROJECT) fixtures.prepare_git_project(fixtures.MULTI_COMMIT_PROJECT) @@ -176,7 +179,7 @@ class CallLinterFromCommandLineTest(unittest.TestCase): self.assertTrue(lines[1].startswith('*** Summary')) def test_call_linter_with_deleted_migrations(self): - cmd = '{0} {1} 154ecf6119325cc3b1f3f5a4e709bfbd61a4a4ba'.format( + cmd = '{0} --no-cache {1} 154ecf6119325cc3b1f3f5a4e709bfbd61a4a4ba'.format( self.linter_exec, fixtures.DELETED_MIGRATION_PROJECT) fixtures.prepare_git_project(fixtures.DELETED_MIGRATION_PROJECT) diff --git a/tests/functional/test_multiple_calls.py b/tests/functional/test_multiple_calls.py index bea9262..0920c1b 100644 --- a/tests/functional/test_multiple_calls.py +++ b/tests/functional/test_multiple_calls.py @@ -24,10 +24,10 @@ class TestMultipleLinters(unittest.TestCase): l2 = MigrationLinter(fixtures.RENAME_COLUMN_PROJECT) l3 = MigrationLinter(fixtures.CORRECT_PROJECT) - l1.lint_migration('test_app', '0002') - l2.lint_migration('test_app', '0002') - l3.lint_migration('test_app1', '0001') - l3.lint_migration('test_app1', '0002') + l1.lint_migration('test_app', '0002_add_new_not_null_field') + l2.lint_migration('test_app', '0002_rename_column') + l3.lint_migration('test_app1', '0001_initial') + l3.lint_migration('test_app1', '0002_a_new_null_field') self.assertTrue(l1.has_errors) self.assertTrue(l2.has_errors) diff --git a/tests/unit/test_linter.py b/tests/unit/test_linter.py index 0a4e47e..37fc754 100644 --- a/tests/unit/test_linter.py +++ b/tests/unit/test_linter.py @@ -32,13 +32,13 @@ class TestLinterFunctions(unittest.TestCase): linter = MigrationLinter(project_path) self.assertFalse(linter.has_errors) - linter.lint_migration('test_app', '0001') + linter.lint_migration('test_app', '0001_create_table') self.assertFalse(linter.has_errors) - linter.lint_migration('test_app', '0002') + linter.lint_migration('test_app', '0002_add_new_not_null_field') self.assertTrue(linter.has_errors) - linter.lint_migration('test_app', '0001') + linter.lint_migration('test_app', '0001_create_table') self.assertTrue(linter.has_errors) def test_linter_creation(self): @@ -49,24 +49,6 @@ class TestLinterFunctions(unittest.TestCase): with self.assertRaises(ValueError): MigrationLinter(fixtures.NOT_DJANGO_GIT_PROJECT) - def test_split_migration_long_path(self): - input_path = 'apps/the_app/migrations/0001_stuff.py' - app, mig = MigrationLinter._split_migration_path(input_path) - self.assertEqual(app, 'the_app') - self.assertEqual(mig, '0001_stuff') - - def test_split_migration_path(self): - input_path = 'the_app/migrations/0001_stuff.py' - app, mig = MigrationLinter._split_migration_path(input_path) - 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' - app, mig = MigrationLinter._split_migration_path(input_path) - self.assertEqual(app, 'the_app') - self.assertEqual(mig, '0001_stuff') - def test_ignore_migration_include_apps(self): linter = MigrationLinter( fixtures.ADD_NOT_NULL_COLUMN_PROJECT, diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 88d20af..7ac89d5 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -16,7 +16,8 @@ import os import unittest from tests import fixtures -from django_migration_linter.utils import is_django_project, is_git_project, is_directory, find_project_settings_module, split_path +from django_migration_linter.utils import is_django_project, is_git_project, is_directory, find_project_settings_module, \ + split_path, split_migration_path class UtilityFunctionTest(unittest.TestCase): @@ -70,3 +71,21 @@ class UtilityFunctionTest(unittest.TestCase): self.assertEqual(splitted[0], 'foo') self.assertEqual(splitted[1], 'bar') self.assertEqual(splitted[2], 'fuz.py') + + def test_split_migration_long_path(self): + 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') + + def test_split_migration_path(self): + 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') + + def test_split_migration_full_path(self): + 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') diff --git a/tox.ini b/tox.ini index a1e4ccc..a420feb 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ deps = django111: django>=1.11,<2.0 django20: django>=2.0 appdirs==1.4.3 + py27: mock==2.0.0 [testenv:lint] basepython = python3.6