eggs/djangorecipe-0.20-py2.6.egg/djangorecipe/tests.py
changeset 307 c6bca38c1cbf
equal deleted inserted replaced
306:5ff1fc726848 307:c6bca38c1cbf
       
     1 import unittest
       
     2 import tempfile
       
     3 import os
       
     4 import sys
       
     5 import shutil
       
     6 
       
     7 import mock
       
     8 from zc.buildout import UserError
       
     9 from zc.recipe.egg.egg import Scripts as ZCRecipeEggScripts
       
    10 
       
    11 from djangorecipe.recipe import Recipe
       
    12 
       
    13 # Add the testing dir to the Python path so we can use a fake Django
       
    14 # install. This needs to be done so that we can use this as a base for
       
    15 # mock's with some of the tests.
       
    16 sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'testing'))
       
    17 
       
    18 # Now that we have a fake Django on the path we can import the
       
    19 # scripts. These are depenent on a Django install, hence the fake one.
       
    20 from djangorecipe import test
       
    21 from djangorecipe import manage
       
    22 
       
    23 
       
    24 class TestRecipe(unittest.TestCase):
       
    25 
       
    26     def setUp(self):
       
    27         # Create a directory for our buildout files created by the recipe
       
    28         self.buildout_dir = tempfile.mkdtemp('djangorecipe')
       
    29 
       
    30         self.bin_dir = os.path.join(self.buildout_dir, 'bin')
       
    31         self.develop_eggs_dir = os.path.join(self.buildout_dir,
       
    32                                              'develop-eggs')
       
    33         self.eggs_dir = os.path.join(self.buildout_dir, 'eggs')
       
    34         self.parts_dir = os.path.join(self.buildout_dir, 'parts')
       
    35 
       
    36         # We need to create the bin dir since the recipe should be able to expect it exists
       
    37         os.mkdir(self.bin_dir)
       
    38 
       
    39         self.recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir,
       
    40                                            'develop-eggs-directory': self.develop_eggs_dir,
       
    41                                            'python': 'python-version',
       
    42                                            'bin-directory': self.bin_dir,
       
    43                                            'parts-directory': self.parts_dir,
       
    44                                            'directory': self.buildout_dir,
       
    45                                            },
       
    46                               'python-version': {'executable': sys.executable}},
       
    47                              'django',
       
    48                              {'recipe': 'djangorecipe',
       
    49                               'version': 'trunk'})
       
    50 
       
    51     def tearDown(self):
       
    52         # Remove our test dir
       
    53         shutil.rmtree(self.buildout_dir)
       
    54 
       
    55     def test_consistent_options(self):
       
    56         # Buildout is pretty clever in detecting changing options. If
       
    57         # the recipe modifies it's options during initialisation it
       
    58         # will store this to determine wheter it needs to update or do
       
    59         # a uninstall & install. We need to make sure that we normally
       
    60         # do not trigger this. That means running the recipe with the
       
    61         # same options should give us the same results.
       
    62         self.assertEqual(*[
       
    63                 Recipe({'buildout': {'eggs-directory': self.eggs_dir,
       
    64                                      'develop-eggs-directory': self.develop_eggs_dir,
       
    65                                      'python': 'python-version',
       
    66                                      'bin-directory': self.bin_dir,
       
    67                                      'parts-directory': self.parts_dir,
       
    68                                      'directory': self.buildout_dir,
       
    69                                      },
       
    70                         'python-version': {'executable': sys.executable}},
       
    71                        'django',
       
    72                        {'recipe': 'djangorecipe',
       
    73                         'version': 'trunk'}).options.copy() for i in range(2)])
       
    74 
       
    75     def test_svn_url(self):
       
    76         # Make sure that only a few specific type of url's are
       
    77         # considered svn url's
       
    78 
       
    79         # This is a plain release version so it should indicate it is
       
    80         # not a svn url
       
    81         self.failIf(self.recipe.is_svn_url('0.96.2'))
       
    82         # The next line specifies a proper link with the trunk
       
    83         self.assert_(self.recipe.is_svn_url('trunk'))
       
    84         # A url looking like trunk should also fail
       
    85         self.failIf(self.recipe.is_svn_url('trunka'))
       
    86         # A full svn url including version should work
       
    87         self.assert_(self.recipe.is_svn_url(
       
    88             'http://code.djangoproject.com/svn/django/branches/newforms-admin@7833'))
       
    89         # HTTPS should work too
       
    90         self.assert_(self.recipe.is_svn_url(
       
    91             'https://code.djangoproject.com/svn/django/branches/newforms-admin@7833'))
       
    92         # Svn+ssh should work
       
    93         self.assert_(self.recipe.is_svn_url(
       
    94             'svn+ssh://myserver/newforms-admin@7833'))
       
    95         # Svn protocol through any custom tunnel defined in ~/.subversion/config should work
       
    96         self.assert_(self.recipe.is_svn_url(
       
    97             'svn+MY_Custom-tunnel://myserver/newforms-admin@7833'))
       
    98         # Using a non existent protocol should not be a svn url?
       
    99         self.failIf(self.recipe.is_svn_url(
       
   100             'unknown://myserver/newforms-admin@7833'))
       
   101 
       
   102     def test_command(self):
       
   103         # The command method is a wrapper for subprocess which excutes
       
   104         # a command and return's it's status code. We will demonstrate
       
   105         # this with a simple test of running `dir`.
       
   106         self.failIf(self.recipe.command('echo'))
       
   107         # Executing a non existing command should return an error code
       
   108         self.assert_(self.recipe.command('spamspamspameggs'))
       
   109 
       
   110     @mock.patch('subprocess', 'Popen')
       
   111     def test_command_verbose_mode(self, popen):
       
   112         # When buildout is put into verbose mode the command methode
       
   113         # should stop capturing the ouput of it's commands.
       
   114         popen.return_value = mock.Mock()
       
   115         self.recipe.buildout['buildout']['verbosity'] = 'verbose'
       
   116         self.recipe.command('silly-command')
       
   117         self.assertEqual(
       
   118             popen.call_args,
       
   119             (('silly-command',), {'shell': True, 'stdout': None}))
       
   120 
       
   121     def test_create_file(self):
       
   122         # The create file helper should create a file at a certain
       
   123         # location unless it already exists. We will need a
       
   124         # non-existing file first.
       
   125         f, name = tempfile.mkstemp()
       
   126         # To show the function in action we need to delete the file
       
   127         # before testing.
       
   128         os.remove(name)
       
   129         # The method accepts a template argument which it will use
       
   130         # with the options argument for string substitution.
       
   131         self.recipe.create_file(name, 'Spam %s', 'eggs')
       
   132         # Let's check the contents of the file
       
   133         self.assertEqual(open(name).read(), 'Spam eggs')
       
   134         # If we try to write it again it will just ignore our request
       
   135         self.recipe.create_file(name, 'Spam spam spam %s', 'eggs')
       
   136         # The content of the file should therefore be the same
       
   137         self.assertEqual(open(name).read(), 'Spam eggs')
       
   138         # Now remove our temp file
       
   139         os.remove(name)
       
   140 
       
   141     def test_generate_secret(self):
       
   142         # To create a basic skeleton the recipe also generates a
       
   143         # random secret for the settings file. Since it should very
       
   144         # unlikely that it will generate the same key a few times in a
       
   145         # row we will test it with letting it generate a few keys.
       
   146         self.assert_(len(set(
       
   147                     [self.recipe.generate_secret() for i in xrange(10)])) > 1)
       
   148 
       
   149     def test_version_to_svn(self):
       
   150         # Version specification that lead to a svn repository can be
       
   151         # specified in different ways. Just specifying `trunk` should
       
   152         # be enough to get the full url to the Django trunk.
       
   153         self.assertEqual(self.recipe.version_to_svn('trunk'),
       
   154                          'http://code.djangoproject.com/svn/django/trunk/')
       
   155         # Any other specification should lead to the url it is given
       
   156         self.assertEqual(self.recipe.version_to_svn('svn://somehost/trunk'),
       
   157                          'svn://somehost/trunk')
       
   158 
       
   159     def test_version_to_download_suffic(self):
       
   160         # To create standard names for the download directory a method
       
   161         # is provided which converts a version to a dir suffix. A
       
   162         # simple pointer to trunk should return svn.
       
   163         self.assertEqual(self.recipe.version_to_download_suffix('trunk'),
       
   164                          'svn')
       
   165         # Any other url should return the last path component. This
       
   166         # works out nicely for branches or version pinned url's.
       
   167         self.assertEqual(self.recipe.version_to_download_suffix(
       
   168                 'http://monty/branches/python'), 'python')
       
   169 
       
   170     def test_make_protocol_scripts(self):
       
   171         # To ease deployment a WSGI script can be generated. The
       
   172         # script adds any paths from the `extra_paths` option to the
       
   173         # Python path.
       
   174         self.recipe.options['wsgi'] = 'true'
       
   175         self.recipe.options['fcgi'] = 'true'
       
   176         self.recipe.make_scripts([], [])
       
   177         # This should have created a script in the bin dir
       
   178         wsgi_script = os.path.join(self.bin_dir, 'django.wsgi')
       
   179         self.assert_(os.path.exists(wsgi_script))
       
   180         # The contents should list our paths
       
   181         contents = open(wsgi_script).read()
       
   182         # It should also have a reference to our settings module
       
   183         self.assert_('project.development' in contents)
       
   184         # and a line which set's up the WSGI app
       
   185         self.assert_("application = "
       
   186                      "djangorecipe.wsgi.main('project.development', logfile='')"
       
   187                      in contents)
       
   188         self.assert_("class logger(object)" not in contents)
       
   189 
       
   190         # Another deployment options is FCGI. The recipe supports an option to
       
   191         # automatically create the required script.
       
   192         fcgi_script = os.path.join(self.bin_dir, 'django.fcgi')
       
   193         self.assert_(os.path.exists(fcgi_script))
       
   194         # The contents should list our paths
       
   195         contents = open(fcgi_script).read()
       
   196         # It should also have a reference to our settings module
       
   197         self.assert_('project.development' in contents)
       
   198         # and a line which set's up the WSGI app
       
   199         self.assert_("djangorecipe.fcgi.main('project.development', logfile='')"
       
   200                      in contents)
       
   201         self.assert_("class logger(object)" not in contents)
       
   202 
       
   203         self.recipe.options['logfile'] = '/foo'
       
   204         self.recipe.make_scripts([], [])
       
   205         wsgi_script = os.path.join(self.bin_dir, 'django.wsgi')
       
   206         contents = open(wsgi_script).read()
       
   207         self.assert_("logfile='/foo'" in contents)
       
   208 
       
   209         self.recipe.options['logfile'] = '/foo'
       
   210         self.recipe.make_scripts([], [])
       
   211         fcgi_script = os.path.join(self.bin_dir, 'django.fcgi')
       
   212         contents = open(fcgi_script).read()
       
   213         self.assert_("logfile='/foo'" in contents)
       
   214 
       
   215     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   216     def test_make_protocol_scripts_return_value(self, scripts):
       
   217         # The return value of make scripts lists the generated scripts.
       
   218         self.recipe.options['wsgi'] = 'true'
       
   219         self.recipe.options['fcgi'] = 'true'
       
   220         scripts.return_value = ['some-path']
       
   221         self.assertEqual(self.recipe.make_scripts([], []),
       
   222                          ['some-path', 'some-path'])
       
   223 
       
   224 
       
   225 
       
   226     def test_create_project(self):
       
   227         # If a project does not exist already the recipe will create
       
   228         # one.
       
   229         project_dir = os.path.join(self.buildout_dir, 'project')
       
   230         self.recipe.create_project(project_dir)
       
   231         # This should have create a project directory
       
   232         self.assert_(os.path.exists(project_dir))
       
   233         # With this directory we should have __init__.py to make it a
       
   234         # package
       
   235         self.assert_(
       
   236             os.path.exists(os.path.join(project_dir, '__init__.py')))
       
   237         # There should also be a urls.py
       
   238         self.assert_(
       
   239             os.path.exists(os.path.join(project_dir, 'urls.py')))
       
   240         # To make it easier to start using this project both a media
       
   241         # and a templates folder are created
       
   242         self.assert_(
       
   243             os.path.exists(os.path.join(project_dir, 'media')))
       
   244         self.assert_(
       
   245             os.path.exists(os.path.join(project_dir, 'templates')))
       
   246         # The project is ready to go since the recipe has generated a
       
   247         # base settings, development and production file
       
   248         for f in ('settings.py', 'development.py', 'production.py'):
       
   249             self.assert_(
       
   250                 os.path.exists(os.path.join(project_dir, f)))
       
   251 
       
   252     def test_create_test_runner(self):
       
   253         # An executable script can be generated which will make it
       
   254         # possible to execute the Django test runner. This options
       
   255         # only works if we specify one or apps to test.
       
   256         testrunner = os.path.join(self.bin_dir, 'test')
       
   257 
       
   258         # This first argument sets extra_paths, we will use this to
       
   259         # make sure the script can find this recipe
       
   260         recipe_dir = os.path.abspath(
       
   261             os.path.join(os.path.dirname(__file__), '..'))
       
   262 
       
   263         # First we will show it does nothing by default
       
   264         self.recipe.create_test_runner([recipe_dir], [])
       
   265         self.failIf(os.path.exists(testrunner))
       
   266 
       
   267         # When we specify an app to test it should create the the
       
   268         # testrunner
       
   269         self.recipe.options['test'] = 'knight'
       
   270         self.recipe.create_test_runner([recipe_dir], [])
       
   271         self.assert_(os.path.exists(testrunner))
       
   272 
       
   273     def test_create_manage_script(self):
       
   274         # This buildout recipe creates a alternative for the standard
       
   275         # manage.py script. It has all the same functionality as the
       
   276         # original one but it sits in the bin dir instead of within
       
   277         # the project.
       
   278         manage = os.path.join(self.bin_dir, 'django')
       
   279         self.recipe.create_manage_script([], [])
       
   280         self.assert_(os.path.exists(manage))
       
   281 
       
   282     def test_create_manage_script_projectegg(self):
       
   283         # When a projectegg is specified, then the egg specified
       
   284         # should get used as the project file.
       
   285         manage = os.path.join(self.bin_dir, 'django')
       
   286         self.recipe.options['projectegg'] = 'spameggs'
       
   287         self.recipe.create_manage_script([], [])
       
   288         self.assert_(os.path.exists(manage))
       
   289         # Check that we have 'spameggs' as the project
       
   290         self.assert_("djangorecipe.manage.main('spameggs.development')"
       
   291                      in open(manage).read())
       
   292                      
       
   293     @mock.patch('shutil', 'rmtree')
       
   294     @mock.patch('os.path', 'exists')
       
   295     @mock.patch('urllib', 'urlretrieve')
       
   296     @mock.patch('shutil', 'copytree')
       
   297     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   298     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   299     @mock.patch(Recipe, 'install_release')
       
   300     @mock.patch(Recipe, 'create_manage_script')
       
   301     @mock.patch(Recipe, 'create_test_runner')
       
   302     @mock.patch('zc.recipe.egg', 'Develop')
       
   303     def test_fulfills_django_dependency(self, rmtree, path_exists, 
       
   304         urlretrieve, copytree, working_set, scripts, install_release, 
       
   305         manage, testrunner, develop):
       
   306         # Test for https://bugs.launchpad.net/djangorecipe/+bug/397864
       
   307         # djangorecipe should always fulfil the 'Django' requirement.        
       
   308         self.recipe.options['version'] = '1.0'
       
   309         path_exists.return_value = True
       
   310         working_set.return_value = (None, [])
       
   311         manage.return_value = []
       
   312         scripts.return_value = []
       
   313         testrunner.return_value = []
       
   314         develop_install = mock.Mock()
       
   315         develop.return_value = develop_install
       
   316         self.recipe.install()
       
   317 
       
   318         # We should see that Django was added as a develop egg.
       
   319         options = develop.call_args[0][2]
       
   320         self.assertEqual(options['location'], os.path.join(self.parts_dir, 'django'))
       
   321         
       
   322         # Check that the install() method for the develop egg was called with no args
       
   323         first_method_name, args, kwargs = develop_install.method_calls[0]
       
   324         self.assertEqual('install', first_method_name)
       
   325         self.assertEqual(0, len(args))
       
   326         self.assertEqual(0, len(kwargs))
       
   327 
       
   328     @mock.patch('shutil', 'rmtree')
       
   329     @mock.patch('os.path', 'exists')
       
   330     @mock.patch('urllib', 'urlretrieve')
       
   331     @mock.patch('shutil', 'copytree')
       
   332     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   333     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   334     @mock.patch(Recipe, 'install_release')
       
   335     @mock.patch(Recipe, 'create_manage_script')
       
   336     @mock.patch(Recipe, 'create_test_runner')
       
   337     @mock.patch('zc.recipe.egg', 'Develop')    
       
   338     def test_extra_paths(self, rmtree, path_exists, urlretrieve,
       
   339                                    copytree, working_set, scripts,
       
   340                                    install_release, manage, testrunner,
       
   341                                    develop):
       
   342         # The recipe allows extra-paths to be specified. It uses these to
       
   343         # extend the Python path within it's generated scripts.
       
   344         self.recipe.options['version'] = '1.0'
       
   345         self.recipe.options['extra-paths'] = 'somepackage\nanotherpackage'
       
   346         path_exists.return_value = True
       
   347         working_set.return_value = (None, [])
       
   348         manage.return_value = []
       
   349         scripts.return_value = []
       
   350         testrunner.return_value = []
       
   351         develop.return_value = mock.Mock()        
       
   352         self.recipe.install()
       
   353         self.assertEqual(manage.call_args[0][0][-2:],
       
   354                          ['somepackage', 'anotherpackage'])
       
   355 
       
   356     @mock.patch('shutil', 'rmtree')
       
   357     @mock.patch('os.path', 'exists')
       
   358     @mock.patch('urllib', 'urlretrieve')
       
   359     @mock.patch('shutil', 'copytree')
       
   360     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   361     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   362     @mock.patch(Recipe, 'install_release')
       
   363     @mock.patch(Recipe, 'create_manage_script')
       
   364     @mock.patch(Recipe, 'create_test_runner')
       
   365     @mock.patch('site', 'addsitedir')
       
   366     @mock.patch('zc.recipe.egg', 'Develop')    
       
   367     def test_pth_files(self, rmtree, path_exists, urlretrieve,
       
   368                        copytree, working_set, scripts,
       
   369                        install_release, manage, testrunner, addsitedir,
       
   370                        develop):
       
   371         # When a pth-files option is set the recipe will use that to add more
       
   372         # paths to extra-paths.
       
   373         self.recipe.options['version'] = '1.0'
       
   374         path_exists.return_value = True
       
   375         working_set.return_value = (None, [])
       
   376         scripts.return_value = []
       
   377         manage.return_value = []
       
   378         testrunner.return_value = []
       
   379         develop.return_value = mock.Mock()
       
   380         
       
   381         # The mock values needed to demonstrate the pth-files option.
       
   382         addsitedir.return_value = ['extra', 'dirs']
       
   383         self.recipe.options['pth-files'] = 'somedir'
       
   384 
       
   385         self.recipe.install()
       
   386         self.assertEqual(addsitedir.call_args, (('somedir', set([])), {}))
       
   387         # The extra-paths option has been extended.
       
   388         self.assertEqual(self.recipe.options['extra-paths'], '\nextra\ndirs')
       
   389 
       
   390     def test_create_wsgi_script_projectegg(self):
       
   391         # When a projectegg is specified, then the egg specified
       
   392         # should get used as the project in the wsgi script.
       
   393         wsgi = os.path.join(self.bin_dir, 'django.wsgi')
       
   394         recipe_dir = os.path.abspath(
       
   395             os.path.join(os.path.dirname(__file__), '..'))
       
   396         self.recipe.options['projectegg'] = 'spameggs'
       
   397         self.recipe.options['wsgi'] = 'true'
       
   398         self.recipe.make_scripts([recipe_dir], [])
       
   399         self.assert_(os.path.exists(wsgi))
       
   400         # Check that we have 'spameggs' as the project
       
   401         self.assert_('spameggs.development' in open(wsgi).read())
       
   402 
       
   403     def test_settings_option(self):
       
   404         # The settings option can be used to specify the settings file
       
   405         # for Django to use. By default it uses `development`.
       
   406         self.assertEqual(self.recipe.options['settings'], 'development')
       
   407         # When we change it an generate a manage script it will use
       
   408         # this var.
       
   409         self.recipe.options['settings'] = 'spameggs'
       
   410         self.recipe.create_manage_script([], [])
       
   411         manage = os.path.join(self.bin_dir, 'django')
       
   412         self.assert_("djangorecipe.manage.main('project.spameggs')"
       
   413                      in open(manage).read())
       
   414 
       
   415     @mock.patch('urllib2', 'urlopen')
       
   416     def test_get_release(self, mock):
       
   417         # The get_release method fecthes a release tarball and
       
   418         # extracts it. We have setup a mock so that it won't actually
       
   419         # download the release. Let's call the code.
       
   420         class FakeFile(object):
       
   421             def read(self):
       
   422                 return 'Django tarball'
       
   423             def close(self):
       
   424                 self.closed = True
       
   425 
       
   426         tmp = tempfile.mkdtemp()
       
   427         filename = os.path.join(tmp, 'django-0.96.2.tar.gz')
       
   428         mock.return_value = FakeFile()
       
   429         try:
       
   430             self.assertEqual(
       
   431                 self.recipe.get_release('0.96.2', tmp),
       
   432                 filename)
       
   433             # It tried to download the release through our mock
       
   434             mock.assert_called_with(
       
   435                 'http://www.djangoproject.com/download/0.96.2/tarball/')
       
   436             # The file should have been filled with the contents from the
       
   437             # handle it got.
       
   438             self.assertEqual(open(filename).read(), 'Django tarball')
       
   439         finally:
       
   440             shutil.rmtree(tmp)
       
   441 
       
   442     @mock.patch('setuptools.archive_util', 'unpack_archive')
       
   443     @mock.patch('shutil', 'move')
       
   444     @mock.patch('shutil', 'rmtree')
       
   445     @mock.patch('os', 'listdir')
       
   446     def test_install_release(self, unpack, move, rmtree, listdir):
       
   447         # To install a release the recipe uses a specific method. We
       
   448         # have have mocked all the calls which interact with the
       
   449         # filesystem.
       
   450         listdir.return_value = ('Django-0.96-2',)
       
   451         self.recipe.install_release('0.96.2', 'downloads',
       
   452                                     'downloads/django-0.96.2.tar.gz',
       
   453                                     'parts/django')
       
   454         # Let's see what the mock's have been called with
       
   455         self.assertEqual(listdir.call_args,
       
   456                          (('downloads/django-archive',), {}))
       
   457         self.assertEqual(unpack.call_args,
       
   458                          (('downloads/django-0.96.2.tar.gz',
       
   459                            'downloads/django-archive'), {}))
       
   460         self.assertEqual(move.call_args,
       
   461                          (('downloads/django-archive/Django-0.96-2',
       
   462                            'parts/django'), {}))
       
   463         self.assertEqual(rmtree.call_args,
       
   464                          (('downloads/django-archive',), {}))
       
   465 
       
   466     @mock.patch('shutil', 'copytree')
       
   467     @mock.patch(Recipe, 'command')
       
   468     def test_install_svn_version(self, copytree, command):
       
   469         # Installation from svn is handled by a method. We have mocked
       
   470         # the command method to avoid actual checkouts of Django.
       
   471         self.recipe.install_svn_version('trunk', 'downloads',
       
   472                                         'parts/django', False)
       
   473         # This should have tried to do a checkout of the Django trunk
       
   474         self.assertEqual(command.call_args,
       
   475                          (('svn co http://code.djangoproject.com/svn/django/trunk/ downloads/django-svn -q',), {}))
       
   476         # A copy command to the parts directory should also have been
       
   477         # issued
       
   478         self.assertEqual(copytree.call_args,
       
   479                          (('downloads/django-svn', 'parts/django'), {}))
       
   480 
       
   481     @mock.patch('shutil', 'copytree')
       
   482     @mock.patch('os.path', 'exists')
       
   483     @mock.patch(Recipe, 'command')
       
   484     def test_install_and_update_svn_version(self, copytree, exists, command):
       
   485         # When an checkout has been done of a svn based installation
       
   486         # is already done the recipe should just update it.
       
   487         exists.return_value = True
       
   488 
       
   489         self.recipe.install_svn_version('trunk', 'downloads',
       
   490                                         'parts/django', False)
       
   491         self.assertEqual(exists.call_args, (('downloads/django-svn',), {}))
       
   492         self.assertEqual(command.call_args,
       
   493                          (('svn up -q',), {'cwd': 'downloads/django-svn'}))
       
   494 
       
   495     @mock.patch(Recipe, 'command')
       
   496     def test_install_broken_svn(self, command):
       
   497         # When the checkout from svn fails during a svn build the
       
   498         # installation method raises an error. We will simulate this
       
   499         # failure by telling our mock what to do.
       
   500         command.return_value = 1
       
   501         # The line above should indicate a failure (non-zero exit
       
   502         # code)
       
   503         self.assertRaises(UserError, self.recipe.install_svn_version,
       
   504                           'trunk', 'downloads', 'parts/django', False)
       
   505 
       
   506     @mock.patch('shutil', 'copytree')
       
   507     @mock.patch(Recipe, 'command')
       
   508     def test_svn_install_from_cache(self, copytree, command):
       
   509         # If the buildout is told to install from cache it will not do
       
   510         # a checkout but instead an existing checkout
       
   511         self.recipe.buildout['buildout']['install-from-cache'] = 'true'
       
   512         # Now we can run the installation method
       
   513         self.recipe.install_svn_version('trunk', 'downloads',
       
   514                                         'parts/django', True)
       
   515         # This should not have called the recipe's command method
       
   516         self.failIf(command.called)
       
   517         # A copy from the cache to the destination should have been
       
   518         # made
       
   519         self.assertEqual(copytree.call_args,
       
   520                          (('downloads/django-svn', 'parts/django'), {}))
       
   521 
       
   522     @mock.patch('shutil', 'rmtree')
       
   523     @mock.patch('os.path', 'exists')
       
   524     @mock.patch('urllib', 'urlretrieve')
       
   525     @mock.patch('shutil', 'copytree')
       
   526     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   527     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   528     @mock.patch(Recipe, 'install_release')
       
   529     @mock.patch(Recipe, 'command')
       
   530     def test_update_svn(self, rmtree, path_exists, urlretrieve,
       
   531                         copytree, working_set, scripts,
       
   532                         install_release, command):
       
   533         path_exists.return_value = True
       
   534         working_set.return_value = (None, [])
       
   535         # When the recipe is asked to do an update and the version is
       
   536         # a svn version it just does an update on the parts folder.
       
   537         self.recipe.update()
       
   538         self.assertEqual('svn up -q', command.call_args[0][0])
       
   539         # It changes the working directory so that the simple svn up
       
   540         # command will work.
       
   541         self.assertEqual(command.call_args[1].keys(), ['cwd'])
       
   542 
       
   543     @mock.patch('shutil', 'rmtree')
       
   544     @mock.patch('os.path', 'exists')
       
   545     @mock.patch('urllib', 'urlretrieve')
       
   546     @mock.patch('shutil', 'copytree')
       
   547     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   548     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   549     @mock.patch(Recipe, 'install_release')
       
   550     @mock.patch('subprocess', 'call')
       
   551     def test_update_with_cache(self, rmtree, path_exists, urlretrieve,
       
   552                                copytree, working_set, scripts,
       
   553                                install_release, call_process):
       
   554         path_exists.return_value = True
       
   555         working_set.return_value = (None, [])
       
   556         # When the recipe is asked to do an update whilst in install
       
   557         # from cache mode it just ignores it
       
   558         self.recipe.install_from_cache = True
       
   559         self.recipe.update()
       
   560         self.failIf(call_process.called)
       
   561 
       
   562     @mock.patch('shutil', 'rmtree')
       
   563     @mock.patch('os.path', 'exists')
       
   564     @mock.patch('urllib', 'urlretrieve')
       
   565     @mock.patch('shutil', 'copytree')
       
   566     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   567     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   568     @mock.patch(Recipe, 'install_release')
       
   569     @mock.patch('subprocess', 'call')
       
   570     def test_update_with_newest_false(self, rmtree, path_exists, urlretrieve,
       
   571                                       copytree, working_set, scripts,
       
   572                                       install_release, call_process):
       
   573         path_exists.return_value = True
       
   574         working_set.return_value = (None, [])
       
   575         # When the recipe is asked to do an update whilst in install
       
   576         # from cache mode it just ignores it
       
   577         self.recipe.buildout['buildout']['newest'] = 'false'
       
   578         self.recipe.update()
       
   579         self.assertFalse(call_process.called)
       
   580 
       
   581     @mock.patch('shutil', 'rmtree')
       
   582     @mock.patch('os.path', 'exists')
       
   583     @mock.patch('urllib', 'urlretrieve')
       
   584     @mock.patch('shutil', 'copytree')
       
   585     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   586     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   587     @mock.patch(Recipe, 'install_release')
       
   588     @mock.patch('zc.recipe.egg', 'Develop')        
       
   589     def test_clear_existing_django(self, rmtree, path_exists, urlretrieve,
       
   590                                    copytree, working_set, scripts,
       
   591                                    install_release, develop):
       
   592         # When the recipe is executed and Django is already installed
       
   593         # within parts it should remove it. We will mock the exists
       
   594         # check to make it let the recipe think it has an existing
       
   595         # Django install.
       
   596         self.recipe.options['version'] = '1.0'
       
   597         path_exists.return_value = True
       
   598         working_set.return_value = (None, [])
       
   599         scripts.return_value = []
       
   600         develop.return_value = mock.Mock()
       
   601         self.recipe.install()
       
   602         # This should have called remove tree
       
   603         self.assert_(rmtree.called)
       
   604         # We will assert that the last two compontents of the path
       
   605         # passed to rmtree are the ones we wanted to delete.
       
   606         self.assertEqual(rmtree.call_args[0][0].split('/')[-2:],
       
   607                          ['parts', 'django'])
       
   608 
       
   609     @mock.patch('shutil', 'rmtree')
       
   610     @mock.patch('os.path', 'exists')
       
   611     @mock.patch('urllib', 'urlretrieve')
       
   612     @mock.patch('shutil', 'copytree')
       
   613     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   614     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   615     @mock.patch(Recipe, 'install_release')
       
   616     @mock.patch(Recipe, 'command')
       
   617     def test_update_pinned_svn_url(self, rmtree, path_exists, urlretrieve,
       
   618                                    copytree, working_set, scripts,
       
   619                                    install_release, command):
       
   620         path_exists.return_value = True
       
   621         working_set.return_value = (None, [])
       
   622         # Make sure that updating a pinned version is updated
       
   623         # accordingly. It must not switch to updating beyond it's
       
   624         # requested revision.
       
   625         # The recipe does this by checking for an @ sign in the url /
       
   626         # version.
       
   627         self.recipe.is_svn_url = lambda version: True
       
   628 
       
   629         self.recipe.options['version'] = 'http://testing/trunk@2531'
       
   630         self.recipe.update()
       
   631         self.assertEqual(command.call_args[0], ('svn up -r 2531 -q',))
       
   632 
       
   633     @mock.patch('shutil', 'rmtree')
       
   634     @mock.patch('os.path', 'exists')
       
   635     @mock.patch('urllib', 'urlretrieve')
       
   636     @mock.patch('shutil', 'copytree')
       
   637     @mock.patch(ZCRecipeEggScripts, 'working_set')
       
   638     @mock.patch('zc.buildout.easy_install', 'scripts')
       
   639     @mock.patch(Recipe, 'install_release')
       
   640     @mock.patch(Recipe, 'command')
       
   641     def test_update_username_in_svn_url(self, rmtree, path_exists, urlretrieve,
       
   642                                         copytree, working_set, scripts,
       
   643                                         install_release, command):
       
   644         path_exists.return_value = True
       
   645         working_set.return_value = (None, [])
       
   646         # Make sure that updating a version with a username
       
   647         # in the URL works
       
   648         self.recipe.is_svn_url = lambda version: True
       
   649 
       
   650         # First test with both a revision and a username in the url
       
   651         self.recipe.options['version'] = 'http://user@testing/trunk@2531'
       
   652         self.recipe.update()
       
   653         self.assertEqual(command.call_args[0], ('svn up -r 2531 -q',))
       
   654 
       
   655         # Now test with only the username
       
   656         self.recipe.options['version'] = 'http://user@testing/trunk'
       
   657         self.recipe.update()
       
   658         self.assertEqual(command.call_args[0], ('svn up -q',))
       
   659 
       
   660     def test_python_option(self):
       
   661         # The python option makes it possible to specify a specific Python
       
   662         # executable which is to be used for the generated scripts.
       
   663         recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir,
       
   664                                       'develop-eggs-directory': self.develop_eggs_dir,
       
   665                                       'python': 'python-version',
       
   666                                       'bin-directory': self.bin_dir,
       
   667                                       'parts-directory': self.parts_dir,
       
   668                                       'directory': self.buildout_dir,
       
   669                                      },
       
   670                          'python-version': {'executable': '/python4k'}},
       
   671                         'django',
       
   672                         {'recipe': 'djangorecipe', 'version': 'trunk',
       
   673                          'wsgi': 'true'})
       
   674         recipe.make_scripts([], [])
       
   675         # This should have created a script in the bin dir
       
   676         wsgi_script = os.path.join(self.bin_dir, 'django.wsgi')
       
   677         self.assertEqual(open(wsgi_script).readlines()[0], '#!/python4k\n')
       
   678         # Changeing the option for only the part will change the used Python
       
   679         # version.
       
   680         recipe = Recipe({'buildout': {'eggs-directory': self.eggs_dir,
       
   681                                       'develop-eggs-directory': self.develop_eggs_dir,
       
   682                                       'python': 'python-version',
       
   683                                       'bin-directory': self.bin_dir,
       
   684                                       'parts-directory': self.parts_dir,
       
   685                                       'directory': self.buildout_dir,
       
   686                                      },
       
   687                          'python-version': {'executable': '/python4k'},
       
   688                          'py5k': {'executable': '/python5k'}},
       
   689                         'django',
       
   690                         {'recipe': 'djangorecipe', 'version': 'trunk',
       
   691                          'python': 'py5k', 'wsgi': 'true'})
       
   692         recipe.make_scripts([], [])
       
   693         self.assertEqual(open(wsgi_script).readlines()[0], '#!/python5k\n')
       
   694 
       
   695 class ScriptTestCase(unittest.TestCase):
       
   696 
       
   697     def setUp(self):
       
   698         # We will also need to fake the settings file's module
       
   699         self.settings = mock.sentinel.Settings
       
   700         sys.modules['cheeseshop'] = mock.sentinel.CheeseShop
       
   701         sys.modules['cheeseshop.development'] = self.settings
       
   702         sys.modules['cheeseshop'].development = self.settings
       
   703 
       
   704     def tearDown(self):
       
   705         # We will clear out sys.modules again to clean up
       
   706         for m in ['cheeseshop', 'cheeseshop.development']:
       
   707             del sys.modules[m]
       
   708 
       
   709 
       
   710 class TestTestScript(ScriptTestCase):
       
   711 
       
   712     @mock.patch('django.core.management', 'execute_manager')
       
   713     def test_script(self, execute_manager):
       
   714         # The test script should execute the standard Django test
       
   715         # command with any apps given as its arguments.
       
   716         test.main('cheeseshop.development',  'spamm', 'eggs')
       
   717         # We only care about the arguments given to execute_manager
       
   718         self.assertEqual(execute_manager.call_args[1],
       
   719                          {'argv': ['test', 'test', 'spamm', 'eggs']})
       
   720 
       
   721     @mock.patch('django.core.management', 'execute_manager')
       
   722     def test_deeply_nested_settings(self, execute_manager):
       
   723         # Settings files can be more than two levels deep. We need to
       
   724         # make sure the test script can properly import those. To
       
   725         # demonstrate this we need to add another level to our
       
   726         # sys.modules entries.
       
   727         settings = mock.sentinel.SettingsModule
       
   728         nce = mock.sentinel.NCE
       
   729         nce.development = settings
       
   730         sys.modules['cheeseshop'].nce = nce
       
   731         sys.modules['cheeseshop.nce'] = nce
       
   732         sys.modules['cheeseshop.nce.development'] = settings
       
   733 
       
   734         test.main('cheeseshop.nce.development',  'tilsit', 'stilton')
       
   735         self.assertEqual(execute_manager.call_args[0], (settings,))
       
   736 
       
   737     @mock.patch('sys', 'exit')
       
   738     def test_settings_error(self, sys_exit):
       
   739         # When the settings file cannot be imported the test runner
       
   740         # wil exit with a message and a specific exit code.
       
   741         test.main('cheeseshop.tilsit', 'stilton')
       
   742         self.assertEqual(sys_exit.call_args, ((1,), {}))
       
   743 
       
   744 class TestManageScript(ScriptTestCase):
       
   745 
       
   746     @mock.patch('django.core.management', 'execute_manager')
       
   747     def test_script(self, execute_manager):
       
   748         # The manage script is a replacement for the default manage.py
       
   749         # script. It has all the same bells and whistles since all it
       
   750         # does is call the normal Django stuff.
       
   751         manage.main('cheeseshop.development')
       
   752         self.assertEqual(execute_manager.call_args,
       
   753                          ((self.settings,), {}))
       
   754 
       
   755     @mock.patch('sys', 'exit')
       
   756     def test_settings_error(self, sys_exit):
       
   757         # When the settings file cannot be imported the management
       
   758         # script it wil exit with a message and a specific exit code.
       
   759         manage.main('cheeseshop.tilsit')
       
   760         self.assertEqual(sys_exit.call_args, ((1,), {}))
       
   761 
       
   762 def setUp(test):
       
   763     zc.buildout.testing.buildoutSetUp(test)
       
   764 
       
   765     # Make a semi permanent download cache to speed up the test
       
   766     tmp = tempfile.gettempdir()
       
   767     cache_dir = os.path.join(tmp, 'djangorecipe-test-cache')
       
   768     if not os.path.exists(cache_dir):
       
   769         os.mkdir(cache_dir)
       
   770 
       
   771     # Create the default.cfg which sets the download cache
       
   772     home = test.globs['tmpdir']('home')
       
   773     test.globs['mkdir'](home, '.buildout')
       
   774     test.globs['write'](home, '.buildout', 'default.cfg',
       
   775     """
       
   776 [buildout]
       
   777 download-cache = %(cache_dir)s
       
   778     """ % dict(cache_dir=cache_dir))
       
   779     os.environ['HOME'] = home
       
   780 
       
   781     zc.buildout.testing.install('zc.recipe.egg', test)
       
   782     zc.buildout.testing.install_develop('djangorecipe', test)
       
   783 
       
   784 
       
   785 def test_suite():
       
   786     return unittest.TestSuite((
       
   787             unittest.makeSuite(TestRecipe),
       
   788             unittest.makeSuite(TestTestScript),
       
   789             unittest.makeSuite(TestManageScript),
       
   790             ))