~gary/charms/precise/juju-gui/bug1218888

« back to all changes in this revision

Viewing changes to tests/test_utils.py

  • Committer: Francesco Banconi
  • Date: 2013-10-09 09:25:26 UTC
  • mfrom: (113.2.7 local-releases)
  • Revision ID: francesco.banconi@canonical.com-20131009092526-jyqnh2ldx6d9qzxg
Introduce local GUI releases.

Implemented the new "local" juju-gui-source option.
By default the GUI release is now retrieved from
the charm itself, so that in the deployment process
the charm can avoid connecting to Launchpad.

This should result in the charm being able to be
deployed behind a firewall without the traditional
workarounds. Note that this is not yet demonstrated
and must be QAed in a real firewalled environment.

The process tries is like the following:
- if juju-gui-source is "local", the last tarball
  in the releases dir is installed;
- if juju-gui-source is a stable or development version,
  the charm checks if that version is present in the
  local repository before downloading it from Launchpad.
- the rest of the options should still work as usual
  (but some QA could help, e.g. deploying from a branch).

Also updated the charm documentation and added some
missing tests.

Tests: `make unittest` from the branch root
(I am currently running the functional tests).

QA (assuming 0.10.1 is the latest GUI release):
- `juju bootstrap --debug`;
- `make deploy`;
- check the logs: no PPAs are used, the local release
  is installed;
- `juju set juju-gui juju-gui-source=0.10.0`;
- check the logs: the release is downloaded from
  Launchpad;
- `juju set juju-gui juju-gui-source=0.10.1`;
- check the logs: the charm successfully find
  the 0.10.1 release in the local repository and
  avoids downloading it from Launchpad.
- keep trying to break the charm in all the ways
  you can imagine...

Thank you.

R=matthew.scott, gary.poster
CC=
https://codereview.appspot.com/14545044

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
    _get_by_attr,
40
40
    cmd_log,
41
41
    compute_build_dir,
 
42
    download_release,
 
43
    fetch_gui_release,
42
44
    first_path_in_dir,
43
45
    get_api_address,
44
 
    get_release_file_url,
 
46
    get_launchpad_release,
 
47
    get_npm_cache_archive_url,
 
48
    get_release_file_path,
45
49
    get_zookeeper_address,
 
50
    install_builtin_server,
 
51
    install_missing_packages,
46
52
    legacy_juju,
47
53
    log_hook,
48
54
    parse_source,
49
 
    get_npm_cache_archive_url,
50
 
    install_builtin_server,
51
 
    install_missing_packages,
52
55
    remove_apache_setup,
53
56
    remove_haproxy_setup,
54
57
    render_to_file,
94
97
            AttrDict().myattr
95
98
 
96
99
 
 
100
@mock.patch('utils.run')
 
101
@mock.patch('utils.log')
 
102
@mock.patch('utils.cmd_log', mock.Mock())
 
103
class TestDownloadRelease(unittest.TestCase):
 
104
 
 
105
    def test_download(self, mock_log, mock_run):
 
106
        # A release is properly downloaded using curl.
 
107
        url = 'http://download.example.com/release.tgz'
 
108
        filename = 'local-release.tgz'
 
109
        destination = download_release(url, filename)
 
110
        expected_destination = os.path.join(os.getcwd(), 'releases', filename)
 
111
        self.assertEqual(expected_destination, destination)
 
112
        expected_log = 'Downloading release file: {} --> {}.'.format(
 
113
            url, expected_destination)
 
114
        mock_log.assert_called_once_with(expected_log)
 
115
        mock_run.assert_called_once_with(
 
116
            'curl', '-L', '-o', expected_destination, url)
 
117
 
 
118
 
 
119
@mock.patch('utils.log', mock.Mock())
 
120
class TestFetchGuiRelease(unittest.TestCase):
 
121
 
 
122
    release_path = '/my/release.tgz'
 
123
 
 
124
    @contextmanager
 
125
    def patch_launchpad(self, origin, version):
 
126
        """Mock the functions used to download a release from Launchpad.
 
127
 
 
128
        Ensure all the functions are called correctly.
 
129
        """
 
130
        url = 'http://launchpad.example.com/release.tgz/file'
 
131
        filename = 'release.tgz'
 
132
        patch_launchpad = mock.patch('utils.Launchpad')
 
133
        patch_get_launchpad_release = mock.patch(
 
134
            'utils.get_launchpad_release',
 
135
            mock.Mock(return_value=(url, filename)),
 
136
        )
 
137
        patch_download_release = mock.patch(
 
138
            'utils.download_release',
 
139
            mock.Mock(return_value=self.release_path),
 
140
        )
 
141
        with patch_launchpad as mock_launchpad:
 
142
            with patch_get_launchpad_release as mock_get_launchpad_release:
 
143
                with patch_download_release as mock_download_release:
 
144
                    yield
 
145
        login = mock_launchpad.login_anonymously
 
146
        login.assert_called_once_with('Juju GUI charm', 'production')
 
147
        mock_get_launchpad_release.assert_called_once_with(
 
148
            login().projects['juju-gui'], origin, version)
 
149
        mock_download_release.assert_called_once_with(url, filename)
 
150
 
 
151
    @mock.patch('utils.download_release')
 
152
    def test_url(self, mock_download_release):
 
153
        # The release is retrieved from an URL.
 
154
        mock_download_release.return_value = self.release_path
 
155
        url = 'http://download.example.com/release.tgz'
 
156
        path = fetch_gui_release('url', url)
 
157
        self.assertEqual(self.release_path, path)
 
158
        mock_download_release.assert_called_once_with(url, 'url-release.tgz')
 
159
 
 
160
    @mock.patch('utils.get_release_file_path')
 
161
    def test_local(self, mock_get_release_file_path):
 
162
        # The last local release is requested.
 
163
        mock_get_release_file_path.return_value = self.release_path
 
164
        path = fetch_gui_release('local', None)
 
165
        self.assertEqual(self.release_path, path)
 
166
        mock_get_release_file_path.assert_called_once_with()
 
167
 
 
168
    @mock.patch('utils.get_release_file_path')
 
169
    def test_version_found(self, mock_get_release_file_path):
 
170
        # A release version is specified and found locally.
 
171
        mock_get_release_file_path.return_value = self.release_path
 
172
        path = fetch_gui_release('stable', '0.1.42')
 
173
        self.assertEqual(self.release_path, path)
 
174
        mock_get_release_file_path.assert_called_once_with('0.1.42')
 
175
 
 
176
    @mock.patch('utils.get_release_file_path')
 
177
    def test_version_not_found(self, mock_get_release_file_path):
 
178
         # A release version is specified but not found locally.
 
179
        mock_get_release_file_path.return_value = None
 
180
        with self.patch_launchpad('stable', '0.1.42'):
 
181
            path = fetch_gui_release('stable', '0.1.42')
 
182
        self.assertEqual(self.release_path, path)
 
183
        mock_get_release_file_path.assert_called_once_with('0.1.42')
 
184
 
 
185
    @mock.patch('utils.get_release_file_path')
 
186
    def test_stable(self, mock_get_release_file_path):
 
187
        # The last stable release is requested.
 
188
        with self.patch_launchpad('stable', None):
 
189
            path = fetch_gui_release('stable', None)
 
190
        self.assertEqual(self.release_path, path)
 
191
        self.assertFalse(mock_get_release_file_path.called)
 
192
 
 
193
    @mock.patch('utils.get_release_file_path')
 
194
    def test_trunk(self, mock_get_release_file_path):
 
195
        # The last development release is requested.
 
196
        with self.patch_launchpad('trunk', None):
 
197
            path = fetch_gui_release('trunk', None)
 
198
        self.assertEqual(self.release_path, path)
 
199
        self.assertFalse(mock_get_release_file_path.called)
 
200
 
 
201
 
97
202
class TestFirstPathInDir(unittest.TestCase):
98
203
 
99
204
    def setUp(self):
184
289
            self.assertRaises(IOError, get_api_address, unit_dir)
185
290
 
186
291
 
 
292
class TestGetReleaseFilePath(unittest.TestCase):
 
293
 
 
294
    def setUp(self):
 
295
        self.playground = tempfile.mkdtemp()
 
296
        self.addCleanup(shutil.rmtree, self.playground)
 
297
 
 
298
    def mock_releases_dir(self):
 
299
        """Mock the releases directory."""
 
300
        return mock.patch('utils.RELEASES_DIR', self.playground)
 
301
 
 
302
    def assert_path(self, filename, path):
 
303
        """Ensure the absolute path of filename equals the given path."""
 
304
        expected = os.path.join(self.playground, filename)
 
305
        self.assertEqual(expected, path)
 
306
 
 
307
    @contextmanager
 
308
    def assert_error(self):
 
309
        """Ensure the code executed in the context block raises a ValueError.
 
310
 
 
311
        Also check the error message.
 
312
        """
 
313
        with self.assertRaises(ValueError) as context_manager:
 
314
            yield
 
315
        error = str(context_manager.exception)
 
316
        self.assertEqual('Error: no releases found in the charm.', error)
 
317
 
 
318
    def add(self, filename):
 
319
        """Create a release file in the playground directory."""
 
320
        path = os.path.join(self.playground, filename)
 
321
        open(path, 'w').close()
 
322
 
 
323
    def test_last_release(self):
 
324
        # The last release is correctly retrieved.
 
325
        self.add('juju-gui-0.12.1.tgz')
 
326
        self.add('juju-gui-1.2.3.tgz')
 
327
        self.add('juju-gui-2.0.0+build.42.tgz')
 
328
        self.add('juju-gui-2.0.1.tgz')
 
329
        with self.mock_releases_dir():
 
330
            path = get_release_file_path()
 
331
        self.assert_path('juju-gui-2.0.1.tgz', path)
 
332
 
 
333
    def test_ordering(self):
 
334
        # Release versions are correctly ordered.
 
335
        self.add('juju-gui-0.12.1.tgz')
 
336
        self.add('juju-gui-0.9.1.tgz')
 
337
        with self.mock_releases_dir():
 
338
            path = get_release_file_path()
 
339
        self.assert_path('juju-gui-0.12.1.tgz', path)
 
340
 
 
341
    def test_no_releases(self):
 
342
        # A ValueError is raised if no releases are found.
 
343
        with self.mock_releases_dir():
 
344
            with self.assert_error():
 
345
                get_release_file_path()
 
346
 
 
347
    def test_no_releases_with_files(self):
 
348
        # A ValueError is raised if no releases are found.
 
349
        # Extraneous files are ignored while looking for releases.
 
350
        self.add('jujugui-1.2.3.tgz')  # Wrong prefix.
 
351
        self.add('juju-gui-1.2.tgz')  # Missing patch version number.
 
352
        self.add('juju-gui-1.2.3.bz2')  # Wrong file extension.
 
353
        self.add('juju-gui-1.2.3.4.tgz')  # Wrong version.
 
354
        self.add('juju-gui-1.2.3.build.42.tgz')  # Missing "+" separator.
 
355
        self.add('juju-gui-1.2.3+built.42.tgz')  # Typo.
 
356
        self.add('juju-gui-1.2.3+build.42.47.tgz')  # Invalid bzr revno.
 
357
        self.add('juju-gui-1.2.3+build.42.bz2')  # Wrong file extension again.
 
358
        with self.mock_releases_dir():
 
359
            with self.assert_error():
 
360
                print get_release_file_path()
 
361
 
 
362
    def test_stable_version(self):
 
363
        # A specific stable version is correctly retrieved.
 
364
        self.add('juju-gui-1.2.3.tgz')
 
365
        self.add('juju-gui-2.0.1+build.42.tgz')
 
366
        self.add('juju-gui-2.0.1.tgz')
 
367
        self.add('juju-gui-3.2.1.tgz')
 
368
        with self.mock_releases_dir():
 
369
            path = get_release_file_path('2.0.1')
 
370
        self.assert_path('juju-gui-2.0.1.tgz', path)
 
371
 
 
372
    def test_development_version(self):
 
373
        # A specific development version is correctly retrieved.
 
374
        self.add('juju-gui-1.2.3+build.4247.tgz')
 
375
        self.add('juju-gui-2.42.47+build.4247.tgz')
 
376
        self.add('juju-gui-2.42.47.tgz')
 
377
        self.add('juju-gui-3.42.47+build.4247.tgz')
 
378
        with self.mock_releases_dir():
 
379
            path = get_release_file_path('2.42.47+build.4247')
 
380
        self.assert_path('juju-gui-2.42.47+build.4247.tgz', path)
 
381
 
 
382
    def test_version_not_found(self):
 
383
        # None is returned if the requested version is not found.
 
384
        self.add('juju-gui-1.2.3.tgz')
 
385
        self.add('juju-GUI-1.42.47.tgz')  # This is not a valid release.
 
386
        with self.mock_releases_dir():
 
387
            path = get_release_file_path('1.42.47')
 
388
        self.assertIsNone(path)
 
389
 
 
390
 
187
391
class TestLegacyJuju(unittest.TestCase):
188
392
 
189
393
    def setUp(self):
261
465
        return self.file_link
262
466
 
263
467
 
264
 
class TestGetReleaseFileUrl(unittest.TestCase):
 
468
class TestGetLaunchpadRelease(unittest.TestCase):
265
469
 
266
470
    project = AttrDict(
267
471
        series=(
308
512
 
309
513
    def test_latest_stable_release(self):
310
514
        # Ensure the correct URL is returned for the latest stable release.
311
 
        url = get_release_file_url(self.project, 'stable', None)
 
515
        url, name = get_launchpad_release(self.project, 'stable', None)
312
516
        self.assertEqual('http://example.com/0.1.1.tgz', url)
 
517
        self.assertEqual('0.1.1.tgz', name)
313
518
 
314
519
    def test_latest_trunk_release(self):
315
520
        # Ensure the correct URL is returned for the latest trunk release.
316
 
        url = get_release_file_url(self.project, 'trunk', None)
 
521
        url, name = get_launchpad_release(self.project, 'trunk', None)
317
522
        self.assertEqual('http://example.com/0.1.1+build.1.tgz', url)
 
523
        self.assertEqual('0.1.1+build.1.tgz', name)
318
524
 
319
525
    def test_specific_stable_release(self):
320
526
        # Ensure the correct URL is returned for a specific version of the
321
527
        # stable release.
322
 
        url = get_release_file_url(self.project, 'stable', '0.1.0')
 
528
        url, name = get_launchpad_release(self.project, 'stable', '0.1.0')
323
529
        self.assertEqual('http://example.com/0.1.0.tgz', url)
 
530
        self.assertEqual('0.1.0.tgz', name)
324
531
 
325
532
    def test_specific_trunk_release(self):
326
533
        # Ensure the correct URL is returned for a specific version of the
327
534
        # trunk release.
328
 
        url = get_release_file_url(self.project, 'trunk', '0.1.0+build.1')
 
535
        url, name = get_launchpad_release(
 
536
            self.project, 'trunk', '0.1.0+build.1')
329
537
        self.assertEqual('http://example.com/0.1.0+build.1.tgz', url)
 
538
        self.assertEqual('0.1.0+build.1.tgz', name)
330
539
 
331
540
    def test_series_not_found(self):
332
541
        # A ValueError is raised if the series cannot be found.
333
542
        with self.assertRaises(ValueError) as cm:
334
 
            get_release_file_url(self.project, 'unstable', None)
 
543
            get_launchpad_release(self.project, 'unstable', None)
335
544
        self.assertIn('series not found', str(cm.exception))
336
545
 
337
546
    def test_no_releases(self):
338
547
        # A ValueError is raised if the series does not contain releases.
339
548
        project = AttrDict(series=[AttrDict(name='stable', releases=[])])
340
549
        with self.assertRaises(ValueError) as cm:
341
 
            get_release_file_url(project, 'stable', None)
 
550
            get_launchpad_release(project, 'stable', None)
342
551
        self.assertIn('series does not contain releases', str(cm.exception))
343
552
 
344
553
    def test_release_not_found(self):
345
554
        # A ValueError is raised if the release cannot be found.
346
555
        with self.assertRaises(ValueError) as cm:
347
 
            get_release_file_url(self.project, 'stable', '2.0')
 
556
            get_launchpad_release(self.project, 'stable', '2.0')
348
557
        self.assertIn('release not found', str(cm.exception))
349
558
 
350
559
    def test_file_not_found(self):
358
567
            ],
359
568
        )
360
569
        with self.assertRaises(ValueError) as cm:
361
 
            get_release_file_url(project, 'stable', None)
 
570
            get_launchpad_release(project, 'stable', None)
362
571
        self.assertIn('file not found', str(cm.exception))
363
572
 
364
573
    def test_file_not_found_in_latest_release(self):
378
587
                ),
379
588
            ],
380
589
        )
381
 
        url = get_release_file_url(project, 'stable', None)
 
590
        url, name = get_launchpad_release(project, 'stable', None)
382
591
        self.assertEqual('http://example.com/0.1.0.tgz', url)
 
592
        self.assertEqual('0.1.0.tgz', name)
383
593
 
384
594
 
385
595
class TestGetZookeeperAddress(unittest.TestCase):
452
662
        # Restore the original utils.CURRENT_DIR.
453
663
        utils.CURRENT_DIR = self.original_current_dir
454
664
 
 
665
    def test_latest_local_release(self):
 
666
        # Ensure the latest local release is correctly parsed.
 
667
        expected = ('local', None)
 
668
        self.assertTupleEqual(expected, parse_source('local'))
 
669
 
455
670
    def test_latest_stable_release(self):
456
671
        # Ensure the latest stable release is correctly parsed.
457
672
        expected = ('stable', None)