diff -r 5ff1fc726848 -r c6bca38c1cbf eggs/djangorecipe-0.20-py2.6.egg/djangorecipe/tests.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eggs/djangorecipe-0.20-py2.6.egg/djangorecipe/tests.py Sat Jan 08 11:20:57 2011 +0530 @@ -0,0 +1,790 @@ +import unittest +import tempfile +import os +import sys +import shutil + +import mock +from zc.buildout import UserError +from zc.recipe.egg.egg import Scripts as ZCRecipeEggScripts + +from djangorecipe.recipe import Recipe + +# Add the testing dir to the Python path so we can use a fake Django +# install. This needs to be done so that we can use this as a base for +# mock's with some of the tests. +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'testing')) + +# Now that we have a fake Django on the path we can import the +# scripts. These are depenent on a Django install, hence the fake one. +from djangorecipe import test +from djangorecipe import manage + + +class TestRecipe(unittest.TestCase): + + def setUp(self): + # Create a directory for our buildout files created by the recipe + self.buildout_dir = tempfile.mkdtemp('djangorecipe') + + self.bin_dir = os.path.join(self.buildout_dir, 'bin') + self.develop_eggs_dir = os.path.join(self.buildout_dir, + 'develop-eggs') + self.eggs_dir = os.path.join(self.buildout_dir, 'eggs') + self.parts_dir = os.path.join(self.buildout_dir, 'parts') + + # We need to create the bin dir since the recipe should be able to expect it exists + os.mkdir(self.bin_dir) + + self.recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir, + 'develop-eggs-directory': self.develop_eggs_dir, + 'python': 'python-version', + 'bin-directory': self.bin_dir, + 'parts-directory': self.parts_dir, + 'directory': self.buildout_dir, + }, + 'python-version': {'executable': sys.executable}}, + 'django', + {'recipe': 'djangorecipe', + 'version': 'trunk'}) + + def tearDown(self): + # Remove our test dir + shutil.rmtree(self.buildout_dir) + + def test_consistent_options(self): + # Buildout is pretty clever in detecting changing options. If + # the recipe modifies it's options during initialisation it + # will store this to determine wheter it needs to update or do + # a uninstall & install. We need to make sure that we normally + # do not trigger this. That means running the recipe with the + # same options should give us the same results. + self.assertEqual(*[ + Recipe({'buildout': {'eggs-directory': self.eggs_dir, + 'develop-eggs-directory': self.develop_eggs_dir, + 'python': 'python-version', + 'bin-directory': self.bin_dir, + 'parts-directory': self.parts_dir, + 'directory': self.buildout_dir, + }, + 'python-version': {'executable': sys.executable}}, + 'django', + {'recipe': 'djangorecipe', + 'version': 'trunk'}).options.copy() for i in range(2)]) + + def test_svn_url(self): + # Make sure that only a few specific type of url's are + # considered svn url's + + # This is a plain release version so it should indicate it is + # not a svn url + self.failIf(self.recipe.is_svn_url('0.96.2')) + # The next line specifies a proper link with the trunk + self.assert_(self.recipe.is_svn_url('trunk')) + # A url looking like trunk should also fail + self.failIf(self.recipe.is_svn_url('trunka')) + # A full svn url including version should work + self.assert_(self.recipe.is_svn_url( + 'http://code.djangoproject.com/svn/django/branches/newforms-admin@7833')) + # HTTPS should work too + self.assert_(self.recipe.is_svn_url( + 'https://code.djangoproject.com/svn/django/branches/newforms-admin@7833')) + # Svn+ssh should work + self.assert_(self.recipe.is_svn_url( + 'svn+ssh://myserver/newforms-admin@7833')) + # Svn protocol through any custom tunnel defined in ~/.subversion/config should work + self.assert_(self.recipe.is_svn_url( + 'svn+MY_Custom-tunnel://myserver/newforms-admin@7833')) + # Using a non existent protocol should not be a svn url? + self.failIf(self.recipe.is_svn_url( + 'unknown://myserver/newforms-admin@7833')) + + def test_command(self): + # The command method is a wrapper for subprocess which excutes + # a command and return's it's status code. We will demonstrate + # this with a simple test of running `dir`. + self.failIf(self.recipe.command('echo')) + # Executing a non existing command should return an error code + self.assert_(self.recipe.command('spamspamspameggs')) + + @mock.patch('subprocess', 'Popen') + def test_command_verbose_mode(self, popen): + # When buildout is put into verbose mode the command methode + # should stop capturing the ouput of it's commands. + popen.return_value = mock.Mock() + self.recipe.buildout['buildout']['verbosity'] = 'verbose' + self.recipe.command('silly-command') + self.assertEqual( + popen.call_args, + (('silly-command',), {'shell': True, 'stdout': None})) + + def test_create_file(self): + # The create file helper should create a file at a certain + # location unless it already exists. We will need a + # non-existing file first. + f, name = tempfile.mkstemp() + # To show the function in action we need to delete the file + # before testing. + os.remove(name) + # The method accepts a template argument which it will use + # with the options argument for string substitution. + self.recipe.create_file(name, 'Spam %s', 'eggs') + # Let's check the contents of the file + self.assertEqual(open(name).read(), 'Spam eggs') + # If we try to write it again it will just ignore our request + self.recipe.create_file(name, 'Spam spam spam %s', 'eggs') + # The content of the file should therefore be the same + self.assertEqual(open(name).read(), 'Spam eggs') + # Now remove our temp file + os.remove(name) + + def test_generate_secret(self): + # To create a basic skeleton the recipe also generates a + # random secret for the settings file. Since it should very + # unlikely that it will generate the same key a few times in a + # row we will test it with letting it generate a few keys. + self.assert_(len(set( + [self.recipe.generate_secret() for i in xrange(10)])) > 1) + + def test_version_to_svn(self): + # Version specification that lead to a svn repository can be + # specified in different ways. Just specifying `trunk` should + # be enough to get the full url to the Django trunk. + self.assertEqual(self.recipe.version_to_svn('trunk'), + 'http://code.djangoproject.com/svn/django/trunk/') + # Any other specification should lead to the url it is given + self.assertEqual(self.recipe.version_to_svn('svn://somehost/trunk'), + 'svn://somehost/trunk') + + def test_version_to_download_suffic(self): + # To create standard names for the download directory a method + # is provided which converts a version to a dir suffix. A + # simple pointer to trunk should return svn. + self.assertEqual(self.recipe.version_to_download_suffix('trunk'), + 'svn') + # Any other url should return the last path component. This + # works out nicely for branches or version pinned url's. + self.assertEqual(self.recipe.version_to_download_suffix( + 'http://monty/branches/python'), 'python') + + def test_make_protocol_scripts(self): + # To ease deployment a WSGI script can be generated. The + # script adds any paths from the `extra_paths` option to the + # Python path. + self.recipe.options['wsgi'] = 'true' + self.recipe.options['fcgi'] = 'true' + self.recipe.make_scripts([], []) + # This should have created a script in the bin dir + wsgi_script = os.path.join(self.bin_dir, 'django.wsgi') + self.assert_(os.path.exists(wsgi_script)) + # The contents should list our paths + contents = open(wsgi_script).read() + # It should also have a reference to our settings module + self.assert_('project.development' in contents) + # and a line which set's up the WSGI app + self.assert_("application = " + "djangorecipe.wsgi.main('project.development', logfile='')" + in contents) + self.assert_("class logger(object)" not in contents) + + # Another deployment options is FCGI. The recipe supports an option to + # automatically create the required script. + fcgi_script = os.path.join(self.bin_dir, 'django.fcgi') + self.assert_(os.path.exists(fcgi_script)) + # The contents should list our paths + contents = open(fcgi_script).read() + # It should also have a reference to our settings module + self.assert_('project.development' in contents) + # and a line which set's up the WSGI app + self.assert_("djangorecipe.fcgi.main('project.development', logfile='')" + in contents) + self.assert_("class logger(object)" not in contents) + + self.recipe.options['logfile'] = '/foo' + self.recipe.make_scripts([], []) + wsgi_script = os.path.join(self.bin_dir, 'django.wsgi') + contents = open(wsgi_script).read() + self.assert_("logfile='/foo'" in contents) + + self.recipe.options['logfile'] = '/foo' + self.recipe.make_scripts([], []) + fcgi_script = os.path.join(self.bin_dir, 'django.fcgi') + contents = open(fcgi_script).read() + self.assert_("logfile='/foo'" in contents) + + @mock.patch('zc.buildout.easy_install', 'scripts') + def test_make_protocol_scripts_return_value(self, scripts): + # The return value of make scripts lists the generated scripts. + self.recipe.options['wsgi'] = 'true' + self.recipe.options['fcgi'] = 'true' + scripts.return_value = ['some-path'] + self.assertEqual(self.recipe.make_scripts([], []), + ['some-path', 'some-path']) + + + + def test_create_project(self): + # If a project does not exist already the recipe will create + # one. + project_dir = os.path.join(self.buildout_dir, 'project') + self.recipe.create_project(project_dir) + # This should have create a project directory + self.assert_(os.path.exists(project_dir)) + # With this directory we should have __init__.py to make it a + # package + self.assert_( + os.path.exists(os.path.join(project_dir, '__init__.py'))) + # There should also be a urls.py + self.assert_( + os.path.exists(os.path.join(project_dir, 'urls.py'))) + # To make it easier to start using this project both a media + # and a templates folder are created + self.assert_( + os.path.exists(os.path.join(project_dir, 'media'))) + self.assert_( + os.path.exists(os.path.join(project_dir, 'templates'))) + # The project is ready to go since the recipe has generated a + # base settings, development and production file + for f in ('settings.py', 'development.py', 'production.py'): + self.assert_( + os.path.exists(os.path.join(project_dir, f))) + + def test_create_test_runner(self): + # An executable script can be generated which will make it + # possible to execute the Django test runner. This options + # only works if we specify one or apps to test. + testrunner = os.path.join(self.bin_dir, 'test') + + # This first argument sets extra_paths, we will use this to + # make sure the script can find this recipe + recipe_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..')) + + # First we will show it does nothing by default + self.recipe.create_test_runner([recipe_dir], []) + self.failIf(os.path.exists(testrunner)) + + # When we specify an app to test it should create the the + # testrunner + self.recipe.options['test'] = 'knight' + self.recipe.create_test_runner([recipe_dir], []) + self.assert_(os.path.exists(testrunner)) + + def test_create_manage_script(self): + # This buildout recipe creates a alternative for the standard + # manage.py script. It has all the same functionality as the + # original one but it sits in the bin dir instead of within + # the project. + manage = os.path.join(self.bin_dir, 'django') + self.recipe.create_manage_script([], []) + self.assert_(os.path.exists(manage)) + + def test_create_manage_script_projectegg(self): + # When a projectegg is specified, then the egg specified + # should get used as the project file. + manage = os.path.join(self.bin_dir, 'django') + self.recipe.options['projectegg'] = 'spameggs' + self.recipe.create_manage_script([], []) + self.assert_(os.path.exists(manage)) + # Check that we have 'spameggs' as the project + self.assert_("djangorecipe.manage.main('spameggs.development')" + in open(manage).read()) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'create_manage_script') + @mock.patch(Recipe, 'create_test_runner') + @mock.patch('zc.recipe.egg', 'Develop') + def test_fulfills_django_dependency(self, rmtree, path_exists, + urlretrieve, copytree, working_set, scripts, install_release, + manage, testrunner, develop): + # Test for https://bugs.launchpad.net/djangorecipe/+bug/397864 + # djangorecipe should always fulfil the 'Django' requirement. + self.recipe.options['version'] = '1.0' + path_exists.return_value = True + working_set.return_value = (None, []) + manage.return_value = [] + scripts.return_value = [] + testrunner.return_value = [] + develop_install = mock.Mock() + develop.return_value = develop_install + self.recipe.install() + + # We should see that Django was added as a develop egg. + options = develop.call_args[0][2] + self.assertEqual(options['location'], os.path.join(self.parts_dir, 'django')) + + # Check that the install() method for the develop egg was called with no args + first_method_name, args, kwargs = develop_install.method_calls[0] + self.assertEqual('install', first_method_name) + self.assertEqual(0, len(args)) + self.assertEqual(0, len(kwargs)) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'create_manage_script') + @mock.patch(Recipe, 'create_test_runner') + @mock.patch('zc.recipe.egg', 'Develop') + def test_extra_paths(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, manage, testrunner, + develop): + # The recipe allows extra-paths to be specified. It uses these to + # extend the Python path within it's generated scripts. + self.recipe.options['version'] = '1.0' + self.recipe.options['extra-paths'] = 'somepackage\nanotherpackage' + path_exists.return_value = True + working_set.return_value = (None, []) + manage.return_value = [] + scripts.return_value = [] + testrunner.return_value = [] + develop.return_value = mock.Mock() + self.recipe.install() + self.assertEqual(manage.call_args[0][0][-2:], + ['somepackage', 'anotherpackage']) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'create_manage_script') + @mock.patch(Recipe, 'create_test_runner') + @mock.patch('site', 'addsitedir') + @mock.patch('zc.recipe.egg', 'Develop') + def test_pth_files(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, manage, testrunner, addsitedir, + develop): + # When a pth-files option is set the recipe will use that to add more + # paths to extra-paths. + self.recipe.options['version'] = '1.0' + path_exists.return_value = True + working_set.return_value = (None, []) + scripts.return_value = [] + manage.return_value = [] + testrunner.return_value = [] + develop.return_value = mock.Mock() + + # The mock values needed to demonstrate the pth-files option. + addsitedir.return_value = ['extra', 'dirs'] + self.recipe.options['pth-files'] = 'somedir' + + self.recipe.install() + self.assertEqual(addsitedir.call_args, (('somedir', set([])), {})) + # The extra-paths option has been extended. + self.assertEqual(self.recipe.options['extra-paths'], '\nextra\ndirs') + + def test_create_wsgi_script_projectegg(self): + # When a projectegg is specified, then the egg specified + # should get used as the project in the wsgi script. + wsgi = os.path.join(self.bin_dir, 'django.wsgi') + recipe_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..')) + self.recipe.options['projectegg'] = 'spameggs' + self.recipe.options['wsgi'] = 'true' + self.recipe.make_scripts([recipe_dir], []) + self.assert_(os.path.exists(wsgi)) + # Check that we have 'spameggs' as the project + self.assert_('spameggs.development' in open(wsgi).read()) + + def test_settings_option(self): + # The settings option can be used to specify the settings file + # for Django to use. By default it uses `development`. + self.assertEqual(self.recipe.options['settings'], 'development') + # When we change it an generate a manage script it will use + # this var. + self.recipe.options['settings'] = 'spameggs' + self.recipe.create_manage_script([], []) + manage = os.path.join(self.bin_dir, 'django') + self.assert_("djangorecipe.manage.main('project.spameggs')" + in open(manage).read()) + + @mock.patch('urllib2', 'urlopen') + def test_get_release(self, mock): + # The get_release method fecthes a release tarball and + # extracts it. We have setup a mock so that it won't actually + # download the release. Let's call the code. + class FakeFile(object): + def read(self): + return 'Django tarball' + def close(self): + self.closed = True + + tmp = tempfile.mkdtemp() + filename = os.path.join(tmp, 'django-0.96.2.tar.gz') + mock.return_value = FakeFile() + try: + self.assertEqual( + self.recipe.get_release('0.96.2', tmp), + filename) + # It tried to download the release through our mock + mock.assert_called_with( + 'http://www.djangoproject.com/download/0.96.2/tarball/') + # The file should have been filled with the contents from the + # handle it got. + self.assertEqual(open(filename).read(), 'Django tarball') + finally: + shutil.rmtree(tmp) + + @mock.patch('setuptools.archive_util', 'unpack_archive') + @mock.patch('shutil', 'move') + @mock.patch('shutil', 'rmtree') + @mock.patch('os', 'listdir') + def test_install_release(self, unpack, move, rmtree, listdir): + # To install a release the recipe uses a specific method. We + # have have mocked all the calls which interact with the + # filesystem. + listdir.return_value = ('Django-0.96-2',) + self.recipe.install_release('0.96.2', 'downloads', + 'downloads/django-0.96.2.tar.gz', + 'parts/django') + # Let's see what the mock's have been called with + self.assertEqual(listdir.call_args, + (('downloads/django-archive',), {})) + self.assertEqual(unpack.call_args, + (('downloads/django-0.96.2.tar.gz', + 'downloads/django-archive'), {})) + self.assertEqual(move.call_args, + (('downloads/django-archive/Django-0.96-2', + 'parts/django'), {})) + self.assertEqual(rmtree.call_args, + (('downloads/django-archive',), {})) + + @mock.patch('shutil', 'copytree') + @mock.patch(Recipe, 'command') + def test_install_svn_version(self, copytree, command): + # Installation from svn is handled by a method. We have mocked + # the command method to avoid actual checkouts of Django. + self.recipe.install_svn_version('trunk', 'downloads', + 'parts/django', False) + # This should have tried to do a checkout of the Django trunk + self.assertEqual(command.call_args, + (('svn co http://code.djangoproject.com/svn/django/trunk/ downloads/django-svn -q',), {})) + # A copy command to the parts directory should also have been + # issued + self.assertEqual(copytree.call_args, + (('downloads/django-svn', 'parts/django'), {})) + + @mock.patch('shutil', 'copytree') + @mock.patch('os.path', 'exists') + @mock.patch(Recipe, 'command') + def test_install_and_update_svn_version(self, copytree, exists, command): + # When an checkout has been done of a svn based installation + # is already done the recipe should just update it. + exists.return_value = True + + self.recipe.install_svn_version('trunk', 'downloads', + 'parts/django', False) + self.assertEqual(exists.call_args, (('downloads/django-svn',), {})) + self.assertEqual(command.call_args, + (('svn up -q',), {'cwd': 'downloads/django-svn'})) + + @mock.patch(Recipe, 'command') + def test_install_broken_svn(self, command): + # When the checkout from svn fails during a svn build the + # installation method raises an error. We will simulate this + # failure by telling our mock what to do. + command.return_value = 1 + # The line above should indicate a failure (non-zero exit + # code) + self.assertRaises(UserError, self.recipe.install_svn_version, + 'trunk', 'downloads', 'parts/django', False) + + @mock.patch('shutil', 'copytree') + @mock.patch(Recipe, 'command') + def test_svn_install_from_cache(self, copytree, command): + # If the buildout is told to install from cache it will not do + # a checkout but instead an existing checkout + self.recipe.buildout['buildout']['install-from-cache'] = 'true' + # Now we can run the installation method + self.recipe.install_svn_version('trunk', 'downloads', + 'parts/django', True) + # This should not have called the recipe's command method + self.failIf(command.called) + # A copy from the cache to the destination should have been + # made + self.assertEqual(copytree.call_args, + (('downloads/django-svn', 'parts/django'), {})) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'command') + def test_update_svn(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, command): + path_exists.return_value = True + working_set.return_value = (None, []) + # When the recipe is asked to do an update and the version is + # a svn version it just does an update on the parts folder. + self.recipe.update() + self.assertEqual('svn up -q', command.call_args[0][0]) + # It changes the working directory so that the simple svn up + # command will work. + self.assertEqual(command.call_args[1].keys(), ['cwd']) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch('subprocess', 'call') + def test_update_with_cache(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, call_process): + path_exists.return_value = True + working_set.return_value = (None, []) + # When the recipe is asked to do an update whilst in install + # from cache mode it just ignores it + self.recipe.install_from_cache = True + self.recipe.update() + self.failIf(call_process.called) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch('subprocess', 'call') + def test_update_with_newest_false(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, call_process): + path_exists.return_value = True + working_set.return_value = (None, []) + # When the recipe is asked to do an update whilst in install + # from cache mode it just ignores it + self.recipe.buildout['buildout']['newest'] = 'false' + self.recipe.update() + self.assertFalse(call_process.called) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch('zc.recipe.egg', 'Develop') + def test_clear_existing_django(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, develop): + # When the recipe is executed and Django is already installed + # within parts it should remove it. We will mock the exists + # check to make it let the recipe think it has an existing + # Django install. + self.recipe.options['version'] = '1.0' + path_exists.return_value = True + working_set.return_value = (None, []) + scripts.return_value = [] + develop.return_value = mock.Mock() + self.recipe.install() + # This should have called remove tree + self.assert_(rmtree.called) + # We will assert that the last two compontents of the path + # passed to rmtree are the ones we wanted to delete. + self.assertEqual(rmtree.call_args[0][0].split('/')[-2:], + ['parts', 'django']) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'command') + def test_update_pinned_svn_url(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, command): + path_exists.return_value = True + working_set.return_value = (None, []) + # Make sure that updating a pinned version is updated + # accordingly. It must not switch to updating beyond it's + # requested revision. + # The recipe does this by checking for an @ sign in the url / + # version. + self.recipe.is_svn_url = lambda version: True + + self.recipe.options['version'] = 'http://testing/trunk@2531' + self.recipe.update() + self.assertEqual(command.call_args[0], ('svn up -r 2531 -q',)) + + @mock.patch('shutil', 'rmtree') + @mock.patch('os.path', 'exists') + @mock.patch('urllib', 'urlretrieve') + @mock.patch('shutil', 'copytree') + @mock.patch(ZCRecipeEggScripts, 'working_set') + @mock.patch('zc.buildout.easy_install', 'scripts') + @mock.patch(Recipe, 'install_release') + @mock.patch(Recipe, 'command') + def test_update_username_in_svn_url(self, rmtree, path_exists, urlretrieve, + copytree, working_set, scripts, + install_release, command): + path_exists.return_value = True + working_set.return_value = (None, []) + # Make sure that updating a version with a username + # in the URL works + self.recipe.is_svn_url = lambda version: True + + # First test with both a revision and a username in the url + self.recipe.options['version'] = 'http://user@testing/trunk@2531' + self.recipe.update() + self.assertEqual(command.call_args[0], ('svn up -r 2531 -q',)) + + # Now test with only the username + self.recipe.options['version'] = 'http://user@testing/trunk' + self.recipe.update() + self.assertEqual(command.call_args[0], ('svn up -q',)) + + def test_python_option(self): + # The python option makes it possible to specify a specific Python + # executable which is to be used for the generated scripts. + recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir, + 'develop-eggs-directory': self.develop_eggs_dir, + 'python': 'python-version', + 'bin-directory': self.bin_dir, + 'parts-directory': self.parts_dir, + 'directory': self.buildout_dir, + }, + 'python-version': {'executable': '/python4k'}}, + 'django', + {'recipe': 'djangorecipe', 'version': 'trunk', + 'wsgi': 'true'}) + recipe.make_scripts([], []) + # This should have created a script in the bin dir + wsgi_script = os.path.join(self.bin_dir, 'django.wsgi') + self.assertEqual(open(wsgi_script).readlines()[0], '#!/python4k\n') + # Changeing the option for only the part will change the used Python + # version. + recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir, + 'develop-eggs-directory': self.develop_eggs_dir, + 'python': 'python-version', + 'bin-directory': self.bin_dir, + 'parts-directory': self.parts_dir, + 'directory': self.buildout_dir, + }, + 'python-version': {'executable': '/python4k'}, + 'py5k': {'executable': '/python5k'}}, + 'django', + {'recipe': 'djangorecipe', 'version': 'trunk', + 'python': 'py5k', 'wsgi': 'true'}) + recipe.make_scripts([], []) + self.assertEqual(open(wsgi_script).readlines()[0], '#!/python5k\n') + +class ScriptTestCase(unittest.TestCase): + + def setUp(self): + # We will also need to fake the settings file's module + self.settings = mock.sentinel.Settings + sys.modules['cheeseshop'] = mock.sentinel.CheeseShop + sys.modules['cheeseshop.development'] = self.settings + sys.modules['cheeseshop'].development = self.settings + + def tearDown(self): + # We will clear out sys.modules again to clean up + for m in ['cheeseshop', 'cheeseshop.development']: + del sys.modules[m] + + +class TestTestScript(ScriptTestCase): + + @mock.patch('django.core.management', 'execute_manager') + def test_script(self, execute_manager): + # The test script should execute the standard Django test + # command with any apps given as its arguments. + test.main('cheeseshop.development', 'spamm', 'eggs') + # We only care about the arguments given to execute_manager + self.assertEqual(execute_manager.call_args[1], + {'argv': ['test', 'test', 'spamm', 'eggs']}) + + @mock.patch('django.core.management', 'execute_manager') + def test_deeply_nested_settings(self, execute_manager): + # Settings files can be more than two levels deep. We need to + # make sure the test script can properly import those. To + # demonstrate this we need to add another level to our + # sys.modules entries. + settings = mock.sentinel.SettingsModule + nce = mock.sentinel.NCE + nce.development = settings + sys.modules['cheeseshop'].nce = nce + sys.modules['cheeseshop.nce'] = nce + sys.modules['cheeseshop.nce.development'] = settings + + test.main('cheeseshop.nce.development', 'tilsit', 'stilton') + self.assertEqual(execute_manager.call_args[0], (settings,)) + + @mock.patch('sys', 'exit') + def test_settings_error(self, sys_exit): + # When the settings file cannot be imported the test runner + # wil exit with a message and a specific exit code. + test.main('cheeseshop.tilsit', 'stilton') + self.assertEqual(sys_exit.call_args, ((1,), {})) + +class TestManageScript(ScriptTestCase): + + @mock.patch('django.core.management', 'execute_manager') + def test_script(self, execute_manager): + # The manage script is a replacement for the default manage.py + # script. It has all the same bells and whistles since all it + # does is call the normal Django stuff. + manage.main('cheeseshop.development') + self.assertEqual(execute_manager.call_args, + ((self.settings,), {})) + + @mock.patch('sys', 'exit') + def test_settings_error(self, sys_exit): + # When the settings file cannot be imported the management + # script it wil exit with a message and a specific exit code. + manage.main('cheeseshop.tilsit') + self.assertEqual(sys_exit.call_args, ((1,), {})) + +def setUp(test): + zc.buildout.testing.buildoutSetUp(test) + + # Make a semi permanent download cache to speed up the test + tmp = tempfile.gettempdir() + cache_dir = os.path.join(tmp, 'djangorecipe-test-cache') + if not os.path.exists(cache_dir): + os.mkdir(cache_dir) + + # Create the default.cfg which sets the download cache + home = test.globs['tmpdir']('home') + test.globs['mkdir'](home, '.buildout') + test.globs['write'](home, '.buildout', 'default.cfg', + """ +[buildout] +download-cache = %(cache_dir)s + """ % dict(cache_dir=cache_dir)) + os.environ['HOME'] = home + + zc.buildout.testing.install('zc.recipe.egg', test) + zc.buildout.testing.install_develop('djangorecipe', test) + + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(TestRecipe), + unittest.makeSuite(TestTestScript), + unittest.makeSuite(TestManageScript), + ))