|
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 )) |