def temporary_migration_module(self, app_label="migrations", module=None): """ Shamelessly copied from Django. See django.tests.migrations.test_base.MigrationTestBase.temporary_migration_module Allows testing management commands in a temporary migrations module. Wrap all invocations to makemigrations and squashmigrations with this context manager in order to avoid creating migration files in your source tree inadvertently. """ with tempfile.TemporaryDirectory() as temp_dir: target_dir = tempfile.mkdtemp(dir=temp_dir) with open(os.path.join(target_dir, "__init__.py"), "w"): pass target_migrations_dir = os.path.join(target_dir, "migrations") if module is None: module = apps.get_app_config(app_label).name + ".migrations" try: source_migrations_dir = module_dir(import_module(module)) except (ImportError, ValueError): pass else: shutil.copytree(source_migrations_dir, target_migrations_dir) with extend_sys_path(temp_dir): new_module = os.path.basename(target_dir) + ".migrations" with self.settings(MIGRATION_MODULES={app_label: new_module}): yield target_migrations_dir
def setUp(self): self.temp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.temp_dir) # get modification and access times for no_label/static/file2.txt self.orig_path = os.path.join(TEST_ROOT, 'apps', 'no_label', 'static', 'file2.txt') self.orig_mtime = os.path.getmtime(self.orig_path) self.orig_atime = os.path.getatime(self.orig_path) # prepare duplicate of file2.txt from a temporary app # this file will have modification time older than no_label/static/file2.txt # anyway it should be taken to STATIC_ROOT because the temporary app is before # 'no_label' app in installed apps self.temp_app_path = os.path.join(self.temp_dir, 'staticfiles_test_app') self.testfile_path = os.path.join(self.temp_app_path, 'static', 'file2.txt') os.makedirs(self.temp_app_path) with open(os.path.join(self.temp_app_path, '__init__.py'), 'w+'): pass os.makedirs(os.path.dirname(self.testfile_path)) with open(self.testfile_path, 'w+') as f: f.write('duplicate of file2.txt') os.utime(self.testfile_path, (self.orig_atime - 1, self.orig_mtime - 1)) self.settings_with_test_app = self.modify_settings( INSTALLED_APPS={'prepend': 'staticfiles_test_app'}) with extend_sys_path(self.temp_dir): self.settings_with_test_app.enable() super(TestCollectionFilesOverride, self).setUp()
def setUp(self): self.temp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.temp_dir) # get modification and access times for no_label/static/file2.txt self.orig_path = os.path.join(TEST_ROOT, 'apps', 'no_label', 'static', 'file2.txt') self.orig_mtime = os.path.getmtime(self.orig_path) self.orig_atime = os.path.getatime(self.orig_path) # prepare duplicate of file2.txt from a temporary app # this file will have modification time older than no_label/static/file2.txt # anyway it should be taken to STATIC_ROOT because the temporary app is before # 'no_label' app in installed apps self.temp_app_path = os.path.join(self.temp_dir, 'staticfiles_test_app') self.testfile_path = os.path.join(self.temp_app_path, 'static', 'file2.txt') os.makedirs(self.temp_app_path) with open(os.path.join(self.temp_app_path, '__init__.py'), 'w+'): pass os.makedirs(os.path.dirname(self.testfile_path)) with open(self.testfile_path, 'w+') as f: f.write('duplicate of file2.txt') os.utime(self.testfile_path, (self.orig_atime - 1, self.orig_mtime - 1)) self.settings_with_test_app = self.modify_settings( INSTALLED_APPS={'prepend': 'staticfiles_test_app'}, ) with extend_sys_path(self.temp_dir): self.settings_with_test_app.enable() super().setUp()
def test_egg5(self): """Loading an app from an egg that has an import error in its models module raises that error""" egg_name = '%s/brokenapp.egg' % self.egg_dir with extend_sys_path(egg_name): with six.assertRaisesRegex(self, ImportError, 'modelz'): with self.settings(INSTALLED_APPS=['broken_app']): pass
def test_middleware_headers(self): """ Ensure headers sent by the default MIDDLEWARE don't inadvertently change. For example, we never want "Vary: Cookie" to appear in the list since it prevents the caching of responses. """ with extend_sys_path(self.temp_dir.name): from test_settings import MIDDLEWARE with self.settings( MIDDLEWARE=MIDDLEWARE, ROOT_URLCONF="project_template.urls", ): response = self.client.get("/empty/") headers = sorted(response.serialize_headers().split(b"\r\n")) self.assertEqual( headers, [ b"Content-Length: 0", b"Content-Type: text/html; charset=utf-8", b"Cross-Origin-Opener-Policy: same-origin", b"Referrer-Policy: same-origin", b"X-Content-Type-Options: nosniff", b"X-Frame-Options: DENY", ], )
def temporary_migration_module(self, app_label='test_app', module=None): """ Allows testing management commands in a temporary migrations module. The migrations module is used as a template for creating the temporary migrations module. If it isn't provided, the application's migrations module is used, if it exists. Returns the filesystem path to the temporary migrations module. """ temp_dir = tempfile.mkdtemp() try: target_dir = tempfile.mkdtemp(dir=temp_dir) with open(os.path.join(target_dir, '__init__.py'), 'w'): pass target_migrations_dir = os.path.join(target_dir, 'migrations') if module is None: module = apps.get_app_config(app_label).name + '.migrations' try: source_migrations_dir = module_dir(import_module(module)) except (ImportError, ValueError): pass else: shutil.copytree(source_migrations_dir, target_migrations_dir) with extend_sys_path(temp_dir): new_module = os.path.basename(target_dir) + '.migrations' new_setting = settings.MIGRATION_MODULES.copy() new_setting[app_label] = new_module with self.settings(MIGRATION_MODULES=new_setting): yield target_migrations_dir finally: shutil.rmtree(temp_dir)
def test_only_new_files(self): """ When calling a second time gen_filenames with only_new = True, only files from newly loaded modules should be given. """ dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_only_new_module.py') self.addCleanup(shutil.rmtree, dirname) with open(filename, 'w'): pass # Test uncached access self.clear_autoreload_caches() filenames = set(autoreload.gen_filenames(only_new=True)) filenames_reference = set(autoreload.gen_filenames()) self.assertEqual(filenames, filenames_reference) # Test cached access: no changes filenames = set(autoreload.gen_filenames(only_new=True)) self.assertEqual(filenames, set()) # Test cached access: add a module with extend_sys_path(dirname): import_module('test_only_new_module') filenames = set(autoreload.gen_filenames(only_new=True)) self.assertEqual(filenames, {npath(filename)})
def test_only_new_files(self): """ When calling a second time gen_filenames with only_new = True, only files from newly loaded modules should be given. """ dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_only_new_module.py') self.addCleanup(shutil.rmtree, dirname) with open(filename, 'w'): pass # Test uncached access self.clear_autoreload_caches() filenames = set(autoreload.gen_filenames(only_new=True)) filenames_reference = set(autoreload.gen_filenames()) self.assertEqual(filenames, filenames_reference) # Test cached access: no changes filenames = set(autoreload.gen_filenames(only_new=True)) self.assertEqual(filenames, set()) # Test cached access: add a module with extend_sys_path(dirname): import_module('test_only_new_module') filenames = set(autoreload.gen_filenames(only_new=True)) self.assertEqual(filenames, {filename})
def test_egg5(self): """Loading an app from an egg that has an import error in its models module raises that error""" egg_name = "%s/brokenapp.egg" % self.egg_dir with extend_sys_path(egg_name): with self.assertRaisesMessage(ImportError, "modelz"): with self.settings(INSTALLED_APPS=["broken_app"]): pass
def test_egg3(self): """Models module can be loaded from an app located under an egg's top-level package""" egg_name = '%s/omelet.egg' % self.egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['omelet.app_with_models']): models_module = apps.get_app_config('app_with_models').models_module self.assertIsNotNone(models_module) del apps.all_models['app_with_models']
def test_load_working_egg(self): ttext = "{% load working_egg %}" egg_name = '%s/tagsegg.egg' % self.egg_dir with extend_sys_path(egg_name): engine = Engine(libraries={ 'working_egg': 'tagsegg.templatetags.working_egg', }) engine.from_string(ttext)
def test_single_path(self): """ A Py3.3+ namespace package can be an app if it has only one path. """ with extend_sys_path(self.base_location): with self.settings(INSTALLED_APPS=['nsapp']): app_config = apps.get_app_config('nsapp') self.assertEqual(app_config.path, upath(self.app_path))
def test_egg1(self): """Models module can be loaded from an app in an egg""" egg_name = '%s/modelapp.egg' % self.egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['app_with_models']): models_module = apps.get_app_config('app_with_models').models_module self.assertIsNotNone(models_module) del apps.all_models['app_with_models']
def test_egg2(self): """Loading an app from an egg that has no models returns no models (and no error)""" egg_name = '%s/nomodelapp.egg' % self.egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['app_no_models']): models_module = apps.get_app_config('app_no_models').models_module self.assertIsNone(models_module) del apps.all_models['app_no_models']
def test_single_path(self): """ A Py3.3+ namespace package can be an app if it has only one path. """ with extend_sys_path(self.base_location): with self.settings(INSTALLED_APPS=['nsapp']): app_config = apps.get_app_config('nsapp') self.assertEqual(app_config.path, self.app_path)
def test_egg4(self): """Loading an app with no models from under the top-level egg package generates no error""" egg_name = '%s/omelet.egg' % self.egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['omelet.app_no_models']): models_module = apps.get_app_config('app_no_models').models_module self.assertIsNone(models_module) del apps.all_models['app_no_models']
def test_table_exists(self): with extend_sys_path(os.path.dirname(os.path.abspath(upath(__file__)))): with self.modify_settings(INSTALLED_APPS={'append': ['app1', 'app2']}): call_command('migrate', verbosity=0, run_syncdb=True) from app1.models import ProxyModel from app2.models import NiceModel self.assertEqual(NiceModel.objects.all().count(), 0) self.assertEqual(ProxyModel.objects.all().count(), 0)
def test_load_working_egg(self): ttext = "{% load working_egg %}" egg_name = "%s/tagsegg.egg" % self.egg_dir with extend_sys_path(egg_name): engine = Engine(libraries={"working_egg": "tagsegg.templatetags.working_egg"}) engine.from_string(ttext) with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg): Engine(libraries={"broken_tag": "template_tests.broken_tag"})
def test_bytecode_conversion_to_source(self): """.pyc and .pyo files are included in the files list.""" filename = self.temporary_file('test_compiled.py') filename.touch() compiled_file = Path(py_compile.compile(str(filename), str(filename.with_suffix('.pyc')))) filename.unlink() with extend_sys_path(str(compiled_file.parent)): self.import_and_cleanup('test_compiled') self.assertFileFound(compiled_file)
def test_discover_commands_in_eggs(self): """ Management commands can also be loaded from Python eggs. """ egg_dir = '%s/eggs' % os.path.dirname(__file__) egg_name = '%s/basic.egg' % egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['commandegg']): cmds = find_commands(os.path.join(apps.get_app_config('commandegg').path, 'management')) self.assertEqual(cmds, ['eggcommand'])
def test_load_error_egg(self): egg_name = '%s/tagsegg.egg' % self.egg_dir msg = ( "Invalid template library specified. ImportError raised when " "trying to load 'tagsegg.templatetags.broken_egg': cannot " "import name 'Xtemplate'" ) with extend_sys_path(egg_name): with self.assertRaisesMessage(InvalidTemplateLibrary, msg): Engine(libraries={'broken_egg': 'tagsegg.templatetags.broken_egg'})
def test_deleted_removed(self): dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_deleted_removed_module.py') with open(filename, 'w'): pass with extend_sys_path(dirname): import_module('test_deleted_removed_module') self.assertIn(npath(filename), gen_filenames()) os.unlink(filename) self.assertNotIn(filename, gen_filenames())
def test_multiple_paths_explicit_path(self): """ Multiple locations are ok only if app-config has explicit path. """ # Temporarily add two directories to sys.path that both contain # components of the "nsapp" package. with extend_sys_path(self.base_location, self.other_location): with self.settings(INSTALLED_APPS=['nsapp.apps.NSAppConfig']): app_config = apps.get_app_config('nsapp') self.assertEqual(app_config.path, self.app_path)
def test_discover_commands_in_eggs(self): """ Test that management commands can also be loaded from Python eggs. """ egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) egg_name = '%s/basic.egg' % egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['commandegg']): cmds = find_commands(os.path.join(apps.get_app_config('commandegg').path, 'management')) self.assertEqual(cmds, ['eggcommand'])
def test_load_error_egg(self): egg_name = "%s/tagsegg.egg" % self.egg_dir msg = ( "Invalid template library specified. ImportError raised when " "trying to load 'tagsegg.templatetags.broken_egg': cannot " "import name '?Xtemplate'?" ) with extend_sys_path(egg_name): with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg): Engine(libraries={"broken_egg": "tagsegg.templatetags.broken_egg"})
def test_load_error_egg(self): egg_name = '%s/tagsegg.egg' % self.egg_dir msg = ("Invalid template library specified. ImportError raised when " "trying to load 'tagsegg.templatetags.broken_egg': cannot " "import name 'Xtemplate'") with extend_sys_path(egg_name): with self.assertRaisesMessage(InvalidTemplateLibrary, msg): Engine(libraries={ 'broken_egg': 'tagsegg.templatetags.broken_egg' })
def test_multiple_paths_explicit_path(self): """ Multiple locations are ok only if app-config has explicit path. """ # Temporarily add two directories to sys.path that both contain # components of the "nsapp" package. with extend_sys_path(self.base_location, self.other_location): with self.settings(INSTALLED_APPS=['nsapp.apps.NSAppConfig']): app_config = apps.get_app_config('nsapp') self.assertEqual(app_config.path, upath(self.app_path))
def test_file_added(self): """ When a file is added, it's returned by iter_all_python_module_files(). """ filename = self.temporary_file('test_deleted_removed_module.py') filename.touch() with extend_sys_path(str(filename.parent)): self.import_and_cleanup('test_deleted_removed_module') self.assertFileFound(filename.absolute())
def test_check_errors_catches_all_exceptions(self): """ Since Python may raise arbitrary exceptions when importing code, check_errors() must catch Exception, not just some subclasses. """ filename = self.temporary_file('test_exception.py') filename.write_text('raise Exception') with extend_sys_path(str(filename.parent)): with self.assertRaises(Exception): autoreload.check_errors(import_module)('test_exception') self.assertFileFound(filename)
def test_zip_reload(self): """ Modules imported from zipped files have their archive location included in the result. """ zip_file = self.temporary_file('zip_import.zip') with zipfile.ZipFile(str(zip_file), 'w', zipfile.ZIP_DEFLATED) as zipf: zipf.writestr('test_zipped_file.py', '') with extend_sys_path(str(zip_file)): self.import_and_cleanup('test_zipped_file') self.assertFileFound(zip_file)
def test_check_errors(self): """ When a file containing an error is imported in a function wrapped by check_errors(), gen_filenames() returns it. """ filename = self.temporary_file('test_syntax_error.py') filename.write_text("Ceci n'est pas du Python.") with extend_sys_path(str(filename.parent)): with self.assertRaises(SyntaxError): autoreload.check_errors(import_module)('test_syntax_error') self.assertFileFound(filename)
def test_load_error_egg(self): ttext = "{% load broken_egg %}" egg_name = '%s/tagsegg.egg' % self.egg_dir with extend_sys_path(egg_name): with self.assertRaises(template.TemplateSyntaxError): with self.settings(INSTALLED_APPS=['tagsegg']): template.Template(ttext) try: with self.settings(INSTALLED_APPS=['tagsegg']): template.Template(ttext) except template.TemplateSyntaxError as e: self.assertIn('ImportError', e.args[0]) self.assertIn('Xtemplate', e.args[0])
def test_multiple_paths(self): """ A Py3.3+ namespace package with multiple locations cannot be an app. (Because then we wouldn't know where to load its templates, static assets, etc. from.) """ # Temporarily add two directories to sys.path that both contain # components of the "nsapp" package. with extend_sys_path(self.base_location, self.other_location): with self.assertRaises(ImproperlyConfigured): with self.settings(INSTALLED_APPS=['nsapp']): pass
def test_multiple_paths(self): """ A Py3.3+ namespace package with multiple locations cannot be an app. (Because then we wouldn't know where to load its templates, static assets, etc from.) """ # Temporarily add two directories to sys.path that both contain # components of the "nsapp" package. with extend_sys_path(self.base_location, self.other_location): with self.assertRaises(ImproperlyConfigured): with self.settings(INSTALLED_APPS=['nsapp']): pass
def test_check_errors_only_new(self): """ When a file containing an error is imported in a function wrapped by check_errors(), gen_filenames(only_new=True) returns it. """ dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_syntax_error.py') self.addCleanup(shutil.rmtree, dirname) with open(filename, 'w') as f: f.write("Ceci n'est pas du Python.") with extend_sys_path(dirname): with self.assertRaises(SyntaxError): autoreload.check_errors(import_module)('test_syntax_error') self.assertFileFoundOnlyNew(filename)
def test_check_errors_catches_all_exceptions(self): """ Since Python may raise arbitrary exceptions when importing code, check_errors() must catch Exception, not just some subclasses. """ dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_exception.py') self.addCleanup(shutil.rmtree, dirname) with open(filename, 'w') as f: f.write("raise Exception") with extend_sys_path(dirname): with self.assertRaises(Exception): autoreload.check_errors(import_module)('test_exception') self.assertFileFound(filename)
def test_deleted_removed(self): """ When a file is deleted, gen_filenames() no longer returns it. """ dirname = tempfile.mkdtemp() filename = os.path.join(dirname, 'test_deleted_removed_module.py') self.addCleanup(shutil.rmtree, dirname) with open(filename, 'w'): pass with extend_sys_path(dirname): import_module('test_deleted_removed_module') self.assertFileFound(filename) os.unlink(filename) self.assertFileNotFound(filename)
def test_deep_loader(self): "Modules deep inside an egg can still be tested for existence" egg_name = '%s/test_egg.egg' % self.egg_dir with extend_sys_path(egg_name): egg_module = import_module('egg_module.sub1.sub2') # An importable child self.assertTrue(module_has_submodule(egg_module, 'good_module')) mod = import_module('egg_module.sub1.sub2.good_module') self.assertEqual(mod.content, 'Deep Good Module') # A child that exists, but will generate an import error if loaded self.assertTrue(module_has_submodule(egg_module, 'bad_module')) self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module') # A child that doesn't exist self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
def test_shallow_loader(self): "Module existence can be tested inside eggs" egg_name = '%s/test_egg.egg' % self.egg_dir with extend_sys_path(egg_name): egg_module = import_module('egg_module') # An importable child self.assertTrue(module_has_submodule(egg_module, 'good_module')) mod = import_module('egg_module.good_module') self.assertEqual(mod.content, 'Good Module') # A child that exists, but will generate an import error if loaded self.assertTrue(module_has_submodule(egg_module, 'bad_module')) self.assertRaises(ImportError, import_module, 'egg_module.bad_module') # A child that doesn't exist self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')
def test_middleware_headers(self): """ Ensure headers sent by the default MIDDLEWARE don't inadvertently change. For example, we never want "Vary: Cookie" to appear in the list since it prevents the caching of responses. """ with extend_sys_path(self.temp_dir.name): from test_settings import MIDDLEWARE with self.settings( MIDDLEWARE=MIDDLEWARE, ROOT_URLCONF='project_template.urls', ): response = self.client.get('/empty/') headers = sorted(response.serialize_headers().split(b'\r\n')) self.assertEqual(headers, [ b'Content-Length: 0', b'Content-Type: text/html; charset=utf-8', b'X-Frame-Options: SAMEORIGIN', ])