~benji/charms/precise/juju-gui/use-known-pip

« back to all changes in this revision

Viewing changes to tests/test_backends.py

  • Committer: Francesco Banconi
  • Date: 2013-10-08 07:49:30 UTC
  • mfrom: (113.1.8 support-firewall)
  • Revision ID: francesco.banconi@canonical.com-20131008074930-t0y5nuq46r08hmaz
Avoid installing from PPA if not required.

No longer add the juju-gui PPA by default:
the external repository is added only if required,
i.e. if the legacy server is used or if a branch
is passed to juju-gui-source.

The only missing bit to make the charm work well
from behind a firewall AFAICT is avoiding the release 
to be downloaded from Launchpad.

Also included the shelltoolbox file in the charm:
unfortunately the python-shelltoolbox package is
not available on precise. On the other hand, this
allows for getting rid of the bootstrap_utils.py
file, and the install hook now feels cleaner.

Refactoring + some magic removal on the backend
framework. Now it should be less surprising, and
also allows for more customizations, e.g. what
I did in the install method.

Also added missing tests for the backend framework:
those were required in order to increase our control
over what's really happening in the backend "hooks".

Switched to the builtin Tornado server by default.

This diff is very big, I am sorry, but:
- you can ignore the bootstrap_utils removal;
- you can ignore the shelltoolbox.py file: it is
  just a copy of the one present in the raring
  python-shelltoolbox package;
- a lot of code is tests, the rest of the code
  should be quite easy to follow.

QA:
    `make deploy` and watch the logs:
    - no PPA should be installed by default;
    - the deployment succeeds and the GUI works well;
    switch to builtin-server=false and watch the logs:
    - the PPA is installed (and then haproxy, apache...);
    - the config-change hook exits without errors and
      the GUI works well.

Tests:
    `make unittest`
    (I ran the functional tests myself).


Thank you!

R=gary.poster, rharding
CC=
https://codereview.appspot.com/14433049

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Backend tests."""
18
18
 
19
19
 
20
 
from collections import defaultdict
21
 
from contextlib import contextmanager
 
20
from contextlib import (
 
21
    contextmanager,
 
22
    nested,
 
23
)
22
24
import os
23
25
import shutil
24
26
import tempfile
25
27
import unittest
26
28
 
27
 
import charmhelpers
28
29
import mock
29
 
import shelltoolbox
30
30
 
31
31
import backend
32
32
import utils
33
33
 
34
34
 
35
 
def get_mixin_names(test_backend):
36
 
    return tuple(b.__class__.__name__ for b in test_backend.mixins)
37
 
 
38
 
 
39
 
class GotEmAllDict(defaultdict):
40
 
    """A dictionary that returns the same default value for all given keys."""
41
 
 
42
 
    def get(self, key, default=None):
43
 
        return self.default_factory()
 
35
EXPECTED_PYTHON_LEGACY_DEBS = ('apache2', 'curl', 'haproxy', 'openssl')
 
36
EXPECTED_GO_LEGACY_DEBS = (
 
37
    'apache2', 'curl', 'haproxy', 'openssl', 'python-yaml')
 
38
EXPECTED_PYTHON_BUILTIN_DEBS = (
 
39
    'curl', 'openssl', 'python-bzrlib', 'python-pip')
 
40
EXPECTED_GO_BUILTIN_DEBS = (
 
41
    'curl', 'openssl', 'python-bzrlib', 'python-pip', 'python-yaml')
 
42
 
 
43
simulate_pyjuju = mock.patch('utils.legacy_juju', mock.Mock(return_value=True))
 
44
simulate_juju_core = mock.patch(
 
45
    'utils.legacy_juju', mock.Mock(return_value=False))
44
46
 
45
47
 
46
48
class TestBackendProperties(unittest.TestCase):
47
49
    """Ensure the correct mixins and property values are collected."""
48
50
 
49
 
    simulate_pyjuju = mock.patch(
50
 
        'utils.legacy_juju', mock.Mock(return_value=True))
51
 
    simulate_juju_core = mock.patch(
52
 
        'utils.legacy_juju', mock.Mock(return_value=False))
 
51
    def assert_mixins(self, expected, backend):
 
52
        """Ensure the given backend includes the expected mixins."""
 
53
        obtained = tuple(mixin.__class__.__name__ for mixin in backend.mixins)
 
54
        self.assertEqual(tuple(expected), obtained)
 
55
 
 
56
    def assert_dependencies(self, expected_debs, expected_repository, backend):
 
57
        """Ensure the given backend includes the expected dependencies."""
 
58
        obtained_debs, obtained_repository = backend.get_dependencies()
 
59
        self.assertEqual(set(expected_debs), obtained_debs)
 
60
        self.assertEqual(expected_repository, obtained_repository)
53
61
 
54
62
    def check_sandbox_mode(self):
55
63
        """The backend includes the correct mixins when sandbox mode is active.
56
64
        """
57
 
        test_backend = backend.Backend(config={
58
 
            'sandbox': True, 'staging': False, 'builtin-server': False})
59
 
        mixin_names = get_mixin_names(test_backend)
60
 
        self.assertEqual(
61
 
            ('SetUpMixin', 'SandboxMixin', 'GuiMixin', 'HaproxyApacheMixin'),
62
 
            mixin_names)
63
 
        self.assertEqual(
64
 
            frozenset(('apache2', 'curl', 'haproxy', 'openssl')),
65
 
            test_backend.debs)
 
65
        expected_mixins = (
 
66
            'SetUpMixin', 'SandboxMixin', 'GuiMixin', 'HaproxyApacheMixin')
 
67
        config = {
 
68
            'builtin-server': False,
 
69
            'repository-location': 'ppa:my/location',
 
70
            'sandbox': True,
 
71
            'staging': False,
 
72
        }
 
73
        test_backend = backend.Backend(config=config)
 
74
        self.assert_mixins(expected_mixins, test_backend)
 
75
        self.assert_dependencies(
 
76
            EXPECTED_PYTHON_LEGACY_DEBS, 'ppa:my/location', test_backend)
66
77
 
67
78
    def test_python_staging_backend(self):
68
79
        expected_mixins = (
69
80
            'SetUpMixin', 'ImprovMixin', 'GuiMixin', 'HaproxyApacheMixin')
70
 
        with self.simulate_pyjuju:
71
 
            test_backend = backend.Backend(config={
72
 
                'sandbox': False, 'staging': True, 'builtin-server': False})
73
 
            mixin_names = get_mixin_names(test_backend)
74
 
            self.assertEqual(expected_mixins, mixin_names)
75
 
            debs = ('apache2', 'curl', 'haproxy', 'openssl', 'zookeeper')
76
 
            self.assertEqual(frozenset(debs), test_backend.debs)
 
81
        config = {
 
82
            'builtin-server': False,
 
83
            'repository-location': 'ppa:my/location',
 
84
            'sandbox': False,
 
85
            'staging': True,
 
86
        }
 
87
        with simulate_pyjuju:
 
88
            test_backend = backend.Backend(config=config)
 
89
            self.assert_mixins(expected_mixins, test_backend)
 
90
            self.assert_dependencies(
 
91
                EXPECTED_PYTHON_LEGACY_DEBS + ('zookeeper',),
 
92
                'ppa:my/location', test_backend)
77
93
 
78
94
    def test_go_staging_backend(self):
79
95
        config = {'sandbox': False, 'staging': True, 'builtin-server': False}
80
 
        with self.simulate_juju_core:
 
96
        with simulate_juju_core:
81
97
            with self.assertRaises(ValueError) as context_manager:
82
98
                backend.Backend(config=config)
83
99
        error = str(context_manager.exception)
84
100
        self.assertEqual('Unable to use staging with go backend', error)
85
101
 
86
102
    def test_python_sandbox_backend(self):
87
 
        with self.simulate_pyjuju:
 
103
        with simulate_pyjuju:
88
104
            self.check_sandbox_mode()
89
105
 
90
106
    def test_go_sandbox_backend(self):
91
 
        with self.simulate_juju_core:
 
107
        with simulate_juju_core:
92
108
            self.check_sandbox_mode()
93
109
 
94
110
    def test_python_backend(self):
95
111
        expected_mixins = (
96
112
            'SetUpMixin', 'PythonMixin', 'GuiMixin', 'HaproxyApacheMixin')
97
 
        with self.simulate_pyjuju:
98
 
            test_backend = backend.Backend(config={
99
 
                'sandbox': False, 'staging': False, 'builtin-server': False})
100
 
            mixin_names = get_mixin_names(test_backend)
101
 
            self.assertEqual(expected_mixins, mixin_names)
102
 
            self.assertEqual(
103
 
                frozenset(('apache2', 'curl', 'haproxy', 'openssl')),
104
 
                test_backend.debs)
 
113
        config = {
 
114
            'builtin-server': False,
 
115
            'repository-location': 'ppa:my/location',
 
116
            'sandbox': False,
 
117
            'staging': False,
 
118
        }
 
119
        with simulate_pyjuju:
 
120
            test_backend = backend.Backend(config=config)
 
121
            self.assert_mixins(expected_mixins, test_backend)
 
122
            self.assert_dependencies(
 
123
                EXPECTED_PYTHON_LEGACY_DEBS, 'ppa:my/location', test_backend)
105
124
 
106
125
    def test_go_backend(self):
107
 
        with self.simulate_juju_core:
108
 
            test_backend = backend.Backend(config={
109
 
                'sandbox': False, 'staging': False, 'builtin-server': False})
110
 
            mixin_names = get_mixin_names(test_backend)
111
 
            self.assertEqual(
112
 
                ('SetUpMixin', 'GoMixin', 'GuiMixin', 'HaproxyApacheMixin'),
113
 
                mixin_names)
114
 
            self.assertEqual(
115
 
                frozenset(
116
 
                    ('apache2', 'curl', 'haproxy', 'openssl', 'python-yaml')),
117
 
                test_backend.debs)
 
126
        expected_mixins = (
 
127
            'SetUpMixin', 'GoMixin', 'GuiMixin', 'HaproxyApacheMixin')
 
128
        config = {
 
129
            'builtin-server': False,
 
130
            'repository-location': 'ppa:my/location',
 
131
            'sandbox': False,
 
132
            'staging': False,
 
133
        }
 
134
        with simulate_juju_core:
 
135
            test_backend = backend.Backend(config=config)
 
136
            self.assert_mixins(expected_mixins, test_backend)
 
137
            self.assert_dependencies(
 
138
                EXPECTED_GO_LEGACY_DEBS, 'ppa:my/location', test_backend)
118
139
 
119
 
    def test_builtin_server(self):
 
140
    def test_go_builtin_server(self):
 
141
        config = {
 
142
            'builtin-server': True,
 
143
            'repository-location': 'ppa:my/location',
 
144
            'sandbox': False,
 
145
            'staging': False,
 
146
        }
120
147
        expected_mixins = (
121
148
            'SetUpMixin', 'GoMixin', 'GuiMixin', 'BuiltinServerMixin')
122
 
        expected_debs = set([
123
 
            'python-pip', 'python-yaml', 'curl', 'openssl', 'python-bzrlib'])
124
 
        with self.simulate_juju_core:
125
 
            test_backend = backend.Backend(config={
126
 
                'sandbox': False, 'staging': False, 'builtin-server': True})
127
 
            mixin_names = get_mixin_names(test_backend)
128
 
            self.assertEqual(expected_mixins, mixin_names)
129
 
            self.assertEqual(expected_debs, test_backend.debs)
 
149
        with simulate_juju_core:
 
150
            test_backend = backend.Backend(config)
 
151
            self.assert_mixins(expected_mixins, test_backend)
 
152
            self.assert_dependencies(
 
153
                EXPECTED_GO_BUILTIN_DEBS, None, test_backend)
 
154
 
 
155
    def test_python_builtin_server(self):
 
156
        config = {
 
157
            'builtin-server': True,
 
158
            'repository-location': 'ppa:my/location',
 
159
            'sandbox': False,
 
160
            'staging': False,
 
161
        }
 
162
        expected_mixins = (
 
163
            'SetUpMixin', 'PythonMixin', 'GuiMixin', 'BuiltinServerMixin')
 
164
        with simulate_pyjuju:
 
165
            test_backend = backend.Backend(config)
 
166
            self.assert_mixins(expected_mixins, test_backend)
 
167
            self.assert_dependencies(
 
168
                EXPECTED_PYTHON_BUILTIN_DEBS, None, test_backend)
 
169
 
 
170
    def test_sandbox_builtin_server(self):
 
171
        config = {
 
172
            'builtin-server': True,
 
173
            'repository-location': 'ppa:my/location',
 
174
            'sandbox': True,
 
175
            'staging': False,
 
176
        }
 
177
        expected_mixins = (
 
178
            'SetUpMixin', 'SandboxMixin', 'GuiMixin', 'BuiltinServerMixin')
 
179
        with simulate_juju_core:
 
180
            test_backend = backend.Backend(config)
 
181
            self.assert_mixins(expected_mixins, test_backend)
 
182
            self.assert_dependencies(
 
183
                EXPECTED_PYTHON_BUILTIN_DEBS, None, test_backend)
130
184
 
131
185
 
132
186
class TestBackendCommands(unittest.TestCase):
133
187
 
134
188
    def setUp(self):
135
 
        self.called = {}
136
 
        self.alwaysFalse = GotEmAllDict(lambda: False)
137
 
        self.alwaysTrue = GotEmAllDict(lambda: True)
138
 
 
139
 
        # Monkeypatch functions.
140
 
        self.utils_mocks = {
141
 
            'compute_build_dir': utils.compute_build_dir,
142
 
            'fetch_api': utils.fetch_api,
143
 
            'fetch_gui_from_branch': utils.fetch_gui_from_branch,
144
 
            'fetch_gui_release': utils.fetch_gui_release,
145
 
            'find_missing_packages': utils.find_missing_packages,
146
 
            'get_api_address': utils.get_api_address,
147
 
            'get_npm_cache_archive_url': utils.get_npm_cache_archive_url,
148
 
            'install_builtin_server': utils.install_builtin_server,
149
 
            'parse_source': utils.parse_source,
150
 
            'prime_npm_cache': utils.prime_npm_cache,
151
 
            'remove_apache_setup': utils.remove_apache_setup,
152
 
            'remove_haproxy_setup': utils.remove_haproxy_setup,
153
 
            'save_or_create_certificates': utils.save_or_create_certificates,
154
 
            'setup_apache_config': utils.setup_apache_config,
155
 
            'setup_gui': utils.setup_gui,
156
 
            'setup_haproxy_config': utils.setup_haproxy_config,
157
 
            'start_agent': utils.start_agent,
158
 
            'start_improv': utils.start_improv,
159
 
            'write_builtin_server_startup': utils.write_builtin_server_startup,
160
 
            'write_gui_config': utils.write_gui_config,
161
 
        }
162
 
        self.charmhelpers_mocks = {
163
 
            'log': charmhelpers.log,
164
 
            'open_port': charmhelpers.open_port,
165
 
            'service_control': charmhelpers.service_control,
166
 
        }
167
 
 
168
 
        def make_mock_function(name):
169
 
            def mock_function(*args, **kwargs):
170
 
                self.called[name] = True
171
 
                return (None, None)
172
 
            mock_function.__name__ = name
173
 
            return mock_function
174
 
 
175
 
        for name in self.utils_mocks.keys():
176
 
            setattr(utils, name, make_mock_function(name))
177
 
        for name in self.charmhelpers_mocks.keys():
178
 
            setattr(charmhelpers, name, make_mock_function(name))
179
 
 
180
 
        @contextmanager
181
 
        def mock_su(user):
182
 
            self.called['su'] = True
183
 
            yield
184
 
        self.orig_su = utils.su
185
 
        utils.su = mock_su
186
 
 
187
 
        def mock_apt_get_install(*debs):
188
 
            self.called['apt_get_install'] = True
189
 
        self.orig_apt_get_install = shelltoolbox.apt_get_install
190
 
        shelltoolbox.apt_get_install = mock_apt_get_install
191
 
 
192
 
        def mock_run(*debs):
193
 
            self.called['run'] = True
194
 
        self.orig_run = shelltoolbox.run
195
 
        shelltoolbox.run = mock_run
196
 
 
197
 
        # Monkeypatch directories.
 
189
        # Set up directories.
198
190
        self.playground = tempfile.mkdtemp()
199
 
        self.orig_juju_dir = utils.JUJU_AGENT_DIR
200
 
        utils.JUJU_AGENT_DIR = tempfile.mkdtemp(dir=self.playground)
201
 
        self.orig_base_dir = utils.BASE_DIR
202
 
        utils.BASE_DIR = os.path.join(self.playground, 'juju-gui')
203
 
 
204
 
    def tearDown(self):
205
 
        # Cleanup directories.
206
 
        shutil.rmtree(self.playground)
207
 
        utils.JUJU_AGENT_DIR = self.orig_juju_dir
208
 
        utils.BASE_DIR = self.orig_base_dir
209
 
        # Undo the monkeypatching.
210
 
        shelltoolbox.run = self.orig_run
211
 
        shelltoolbox.apt_get_install = self.orig_apt_get_install
212
 
        utils.su = self.orig_su
213
 
        for name, orig_fun in self.charmhelpers_mocks.items():
214
 
            setattr(charmhelpers, name, orig_fun)
215
 
        for name, orig_fun in self.utils_mocks.items():
216
 
            setattr(utils, name, orig_fun)
 
191
        self.addCleanup(shutil.rmtree, self.playground)
 
192
        self.base_dir = os.path.join(self.playground, 'juju-gui')
 
193
        self.command_log_file = os.path.join(self.playground, 'logs')
 
194
        self.juju_agent_dir = os.path.join(self.playground, 'juju-agent-dir')
 
195
        self.ssl_cert_path = os.path.join(self.playground, 'ssl-cert-path')
 
196
        # Set up default values.
 
197
        self.juju_api_branch = 'lp:juju-api'
 
198
        self.juju_gui_source = 'stable'
 
199
        self.repository_location = 'ppa:my/location'
 
200
        self.parse_source_return_value = ('stable', None)
 
201
 
 
202
    def make_config(self, options=None):
 
203
        """Create and return a backend configuration dict."""
 
204
        config = {
 
205
            'builtin-server': True,
 
206
            'builtin-server-logging': 'info',
 
207
            'charmworld-url': 'http://charmworld.example.com',
 
208
            'command-log-file': self.command_log_file,
 
209
            'default-viewmode': 'sidebar',
 
210
            'ga-key': 'my-key',
 
211
            'juju-api-branch': self.juju_api_branch,
 
212
            'juju-gui-debug': False,
 
213
            'juju-gui-console-enabled': False,
 
214
            'juju-gui-source': self.juju_gui_source,
 
215
            'login-help': 'login-help',
 
216
            'read-only': False,
 
217
            'repository-location': self.repository_location,
 
218
            'sandbox': False,
 
219
            'secure': True,
 
220
            'serve-tests': False,
 
221
            'show-get-juju-button': False,
 
222
            'ssl-cert-path': self.ssl_cert_path,
 
223
            'staging': False,
 
224
        }
 
225
        if options is not None:
 
226
            config.update(options)
 
227
        return config
 
228
 
 
229
    @contextmanager
 
230
    def mock_all(self):
 
231
        """Mock all the extrenal functions used by the backend framework."""
 
232
        mock_parse_source = mock.Mock(
 
233
            return_value=self.parse_source_return_value)
 
234
        mocks = {
 
235
            'base_dir': mock.patch('backend.utils.BASE_DIR', self.base_dir),
 
236
            'compute_build_dir': mock.patch('backend.utils.compute_build_dir'),
 
237
            'fetch_api': mock.patch('backend.utils.fetch_api'),
 
238
            'fetch_gui_from_branch': mock.patch(
 
239
                'backend.utils.fetch_gui_from_branch'),
 
240
            'fetch_gui_release': mock.patch('backend.utils.fetch_gui_release'),
 
241
            'install_builtin_server': mock.patch(
 
242
                'backend.utils.install_builtin_server'),
 
243
            'install_missing_packages': mock.patch(
 
244
                'backend.utils.install_missing_packages'),
 
245
            'juju_agent_dir': mock.patch(
 
246
                'backend.utils.JUJU_AGENT_DIR', self.juju_agent_dir),
 
247
            'log': mock.patch('backend.log'),
 
248
            'open_port': mock.patch('backend.open_port'),
 
249
            'parse_source': mock.patch(
 
250
                'backend.utils.parse_source', mock_parse_source),
 
251
            'save_or_create_certificates': mock.patch(
 
252
                'backend.utils.save_or_create_certificates'),
 
253
            'setup_gui': mock.patch('backend.utils.setup_gui'),
 
254
            'start_agent': mock.patch('backend.utils.start_agent'),
 
255
            'start_builtin_server': mock.patch(
 
256
                'backend.utils.start_builtin_server'),
 
257
            'start_haproxy_apache': mock.patch(
 
258
                'backend.utils.start_haproxy_apache'),
 
259
            'stop_agent': mock.patch('backend.utils.stop_agent'),
 
260
            'stop_builtin_server': mock.patch(
 
261
                'backend.utils.stop_builtin_server'),
 
262
            'stop_haproxy_apache': mock.patch(
 
263
                'backend.utils.stop_haproxy_apache'),
 
264
            'write_gui_config': mock.patch('backend.utils.write_gui_config'),
 
265
        }
 
266
        # Note: nested is deprecated for good reasons which do not apply here.
 
267
        # Used here to easily nest a dynamically generated list of context
 
268
        # managers.
 
269
        with nested(*mocks.values()) as context_managers:
 
270
            object_dict = dict(zip(mocks.keys(), context_managers))
 
271
            yield type('Mocks', (object,), object_dict)
 
272
 
 
273
    def assert_write_gui_config_called(self, mocks, config):
 
274
        """Ensure the mocked write_gui_config has been properly called."""
 
275
        mocks.write_gui_config.assert_called_once_with(
 
276
            config['juju-gui-console-enabled'], config['login-help'],
 
277
            config['read-only'], config['staging'], config['charmworld-url'],
 
278
            mocks.compute_build_dir(), secure=config['secure'],
 
279
            sandbox=config['sandbox'], ga_key=config['ga-key'],
 
280
            default_viewmode=config['default-viewmode'],
 
281
            show_get_juju_button=config['show-get-juju-button'], password=None)
217
282
 
218
283
    def test_base_dir_created(self):
219
 
        test_backend = backend.Backend(config=self.alwaysFalse)
220
 
        test_backend.install()
221
 
        self.assertTrue(os.path.isdir(utils.BASE_DIR))
 
284
        # The base Juju GUI directory is correctly created.
 
285
        config = self.make_config()
 
286
        test_backend = backend.Backend(config=config)
 
287
        with self.mock_all():
 
288
            test_backend.install()
 
289
        self.assertTrue(os.path.isdir(self.base_dir))
222
290
 
223
291
    def test_base_dir_removed(self):
224
 
        test_backend = backend.Backend(config=self.alwaysFalse)
225
 
        test_backend.install()
226
 
        test_backend.destroy()
 
292
        # The base Juju GUI directory is correctly removed.
 
293
        config = self.make_config()
 
294
        test_backend = backend.Backend(config=config)
 
295
        with self.mock_all():
 
296
            test_backend.install()
 
297
            test_backend.destroy()
227
298
        self.assertFalse(os.path.exists(utils.BASE_DIR), utils.BASE_DIR)
228
299
 
229
 
    def test_install_python(self):
230
 
        test_backend = backend.Backend(config=self.alwaysFalse)
231
 
        test_backend.install()
232
 
        for mocked in (
233
 
            'apt_get_install', 'fetch_api', 'find_missing_packages',
234
 
        ):
235
 
            self.assertTrue(
236
 
                self.called.get(mocked), '{} was not called'.format(mocked))
237
 
 
238
 
    def test_install_improv_builtin(self):
239
 
        test_backend = backend.Backend(config=self.alwaysTrue)
240
 
        test_backend.install()
241
 
        for mocked in (
242
 
            'apt_get_install', 'fetch_api', 'find_missing_packages',
243
 
            'install_builtin_server',
244
 
        ):
245
 
            self.assertTrue(
246
 
                self.called.get(mocked), '{} was not called'.format(mocked))
247
 
 
248
 
    def test_start_agent(self):
249
 
        test_backend = backend.Backend(config=self.alwaysFalse)
250
 
        test_backend.start()
251
 
        for mocked in (
252
 
            'compute_build_dir', 'open_port', 'setup_apache_config',
253
 
            'setup_haproxy_config', 'start_agent', 'su', 'write_gui_config',
254
 
        ):
255
 
            self.assertTrue(
256
 
                self.called.get(mocked), '{} was not called'.format(mocked))
257
 
 
258
 
    def test_start_improv_builtin(self):
259
 
        test_backend = backend.Backend(config=self.alwaysTrue)
260
 
        test_backend.start()
261
 
        for mocked in (
262
 
            'compute_build_dir', 'open_port', 'start_improv', 'su',
263
 
            'write_builtin_server_startup', 'write_gui_config',
264
 
        ):
265
 
            self.assertTrue(
266
 
                self.called.get(mocked), '{} was not called'.format(mocked))
267
 
 
268
 
    def test_stop(self):
269
 
        test_backend = backend.Backend(config=self.alwaysFalse)
270
 
        test_backend.stop()
271
 
        self.assertTrue(self.called.get('su'), 'su was not called')
 
300
    def test_install_python_legacy_stable(self):
 
301
        # Install a pyJuju backend with legacy server and stable release.
 
302
        config = self.make_config({'builtin-server': False})
 
303
        with simulate_pyjuju:
 
304
            test_backend = backend.Backend(config=config)
 
305
            with self.mock_all() as mocks:
 
306
                test_backend.install()
 
307
        mocks.install_missing_packages.assert_called_once_with(
 
308
            set(EXPECTED_PYTHON_LEGACY_DEBS),
 
309
            repository=self.repository_location)
 
310
        mocks.fetch_api.assert_called_once_with(self.juju_api_branch)
 
311
        mocks.parse_source.assert_called_once_with(self.juju_gui_source)
 
312
        mocks.fetch_gui_release.assert_called_once_with(
 
313
            *self.parse_source_return_value)
 
314
        self.assertFalse(mocks.fetch_gui_from_branch.called)
 
315
        mocks.setup_gui.assert_called_once_with(mocks.fetch_gui_release())
 
316
        self.assertFalse(mocks.install_builtin_server.called)
 
317
 
 
318
    def test_install_go_legacy_stable(self):
 
319
        # Install a juju-core backend with legacy server and stable release.
 
320
        config = self.make_config({'builtin-server': False})
 
321
        with simulate_juju_core:
 
322
            test_backend = backend.Backend(config=config)
 
323
            with self.mock_all() as mocks:
 
324
                test_backend.install()
 
325
        mocks.install_missing_packages.assert_called_once_with(
 
326
            set(EXPECTED_GO_LEGACY_DEBS), repository=self.repository_location)
 
327
        self.assertFalse(mocks.fetch_api.called)
 
328
        mocks.parse_source.assert_called_once_with(self.juju_gui_source)
 
329
        mocks.fetch_gui_release.assert_called_once_with(
 
330
            *self.parse_source_return_value)
 
331
        self.assertFalse(mocks.fetch_gui_from_branch.called)
 
332
        mocks.setup_gui.assert_called_once_with(mocks.fetch_gui_release())
 
333
        self.assertFalse(mocks.install_builtin_server.called)
 
334
 
 
335
    def test_install_python_builtin_stable(self):
 
336
        # Install a pyJuju backend with builtin server and stable release.
 
337
        config = self.make_config({'builtin-server': True})
 
338
        with simulate_pyjuju:
 
339
            test_backend = backend.Backend(config=config)
 
340
            with self.mock_all() as mocks:
 
341
                test_backend.install()
 
342
        mocks.install_missing_packages.assert_called_once_with(
 
343
            set(EXPECTED_PYTHON_BUILTIN_DEBS), repository=None)
 
344
        mocks.fetch_api.assert_called_once_with(self.juju_api_branch)
 
345
        mocks.parse_source.assert_called_once_with(self.juju_gui_source)
 
346
        mocks.fetch_gui_release.assert_called_once_with(
 
347
            *self.parse_source_return_value)
 
348
        self.assertFalse(mocks.fetch_gui_from_branch.called)
 
349
        mocks.setup_gui.assert_called_once_with(mocks.fetch_gui_release())
 
350
        mocks.install_builtin_server.assert_called_once_with()
 
351
 
 
352
    def test_install_go_builtin_stable(self):
 
353
        # Install a juju-core backend with builtin server and stable release.
 
354
        config = self.make_config({'builtin-server': True})
 
355
        with simulate_juju_core:
 
356
            test_backend = backend.Backend(config=config)
 
357
            with self.mock_all() as mocks:
 
358
                test_backend.install()
 
359
        mocks.install_missing_packages.assert_called_once_with(
 
360
            set(EXPECTED_GO_BUILTIN_DEBS), repository=None)
 
361
        self.assertFalse(mocks.fetch_api.called)
 
362
        mocks.parse_source.assert_called_once_with(self.juju_gui_source)
 
363
        mocks.fetch_gui_release.assert_called_once_with(
 
364
            *self.parse_source_return_value)
 
365
        self.assertFalse(mocks.fetch_gui_from_branch.called)
 
366
        mocks.setup_gui.assert_called_once_with(mocks.fetch_gui_release())
 
367
        mocks.install_builtin_server.assert_called_once_with()
 
368
 
 
369
    def test_install_go_builtin_branch(self):
 
370
        # Install a juju-core backend with builtin server and branch release.
 
371
        self.parse_source_return_value = ('branch', ('lp:juju-gui', 42))
 
372
        expected_calls = [
 
373
            mock.call(set(EXPECTED_GO_BUILTIN_DEBS), repository=None),
 
374
            mock.call(
 
375
                utils.DEB_BUILD_DEPENDENCIES,
 
376
                repository=self.repository_location,
 
377
            ),
 
378
        ]
 
379
        config = self.make_config({'builtin-server': True})
 
380
        with simulate_juju_core:
 
381
            test_backend = backend.Backend(config=config)
 
382
            with self.mock_all() as mocks:
 
383
                test_backend.install()
 
384
        mocks.install_missing_packages.assert_has_calls(expected_calls)
 
385
        self.assertFalse(mocks.fetch_api.called)
 
386
        mocks.parse_source.assert_called_once_with(self.juju_gui_source)
 
387
        mocks.fetch_gui_from_branch.assert_called_once_with(
 
388
            'lp:juju-gui', 42, self.command_log_file)
 
389
        self.assertFalse(mocks.fetch_gui_release.called)
 
390
        mocks.setup_gui.assert_called_once_with(mocks.fetch_gui_from_branch())
 
391
        mocks.install_builtin_server.assert_called_once_with()
 
392
 
 
393
    def test_start_python_legacy(self):
 
394
        # Start a pyJuju backend with legacy server.
 
395
        config = self.make_config({'builtin-server': False})
 
396
        with simulate_pyjuju:
 
397
            test_backend = backend.Backend(config=config)
 
398
            with self.mock_all() as mocks:
 
399
                test_backend.start()
 
400
        mocks.start_agent.assert_called_once_with(self.ssl_cert_path)
 
401
        mocks.compute_build_dir.assert_called_with(
 
402
            config['juju-gui-debug'], config['serve-tests'])
 
403
        self.assert_write_gui_config_called(mocks, config)
 
404
        mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
 
405
        mocks.start_haproxy_apache.assert_called_once_with(
 
406
            mocks.compute_build_dir(), config['serve-tests'],
 
407
            self.ssl_cert_path, config['secure'])
 
408
        self.assertFalse(mocks.start_builtin_server.called)
 
409
 
 
410
    def test_start_go_legacy(self):
 
411
        # Start a juju-core backend with legacy server.
 
412
        config = self.make_config({'builtin-server': False})
 
413
        with simulate_juju_core:
 
414
            test_backend = backend.Backend(config=config)
 
415
            with self.mock_all() as mocks:
 
416
                test_backend.start()
 
417
        self.assertFalse(mocks.start_agent.called)
 
418
        mocks.compute_build_dir.assert_called_with(
 
419
            config['juju-gui-debug'], config['serve-tests'])
 
420
        self.assert_write_gui_config_called(mocks, config)
 
421
        mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
 
422
        mocks.start_haproxy_apache.assert_called_once_with(
 
423
            mocks.compute_build_dir(), config['serve-tests'],
 
424
            self.ssl_cert_path, config['secure'])
 
425
        self.assertFalse(mocks.start_builtin_server.called)
 
426
 
 
427
    def test_start_python_builtin(self):
 
428
        # Start a pyJuju backend with builtin server.
 
429
        config = self.make_config({'builtin-server': True})
 
430
        with simulate_pyjuju:
 
431
            test_backend = backend.Backend(config=config)
 
432
            with self.mock_all() as mocks:
 
433
                test_backend.start()
 
434
        mocks.start_agent.assert_called_once_with(self.ssl_cert_path)
 
435
        mocks.compute_build_dir.assert_called_with(
 
436
            config['juju-gui-debug'], config['serve-tests'])
 
437
        self.assert_write_gui_config_called(mocks, config)
 
438
        mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
 
439
        mocks.start_builtin_server.assert_called_once_with(
 
440
            mocks.compute_build_dir(), self.ssl_cert_path,
 
441
            config['serve-tests'], config['sandbox'],
 
442
            config['builtin-server-logging'], not config['secure'])
 
443
        self.assertFalse(mocks.start_haproxy_apache.called)
 
444
 
 
445
    def test_start_go_builtin(self):
 
446
        # Start a juju-core backend with builtin server.
 
447
        config = self.make_config({'builtin-server': True})
 
448
        with simulate_juju_core:
 
449
            test_backend = backend.Backend(config=config)
 
450
            with self.mock_all() as mocks:
 
451
                test_backend.start()
 
452
        self.assertFalse(mocks.start_agent.called)
 
453
        mocks.compute_build_dir.assert_called_with(
 
454
            config['juju-gui-debug'], config['serve-tests'])
 
455
        self.assert_write_gui_config_called(mocks, config)
 
456
        mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
 
457
        mocks.start_builtin_server.assert_called_once_with(
 
458
            mocks.compute_build_dir(), self.ssl_cert_path,
 
459
            config['serve-tests'], config['sandbox'],
 
460
            config['builtin-server-logging'], not config['secure'])
 
461
        self.assertFalse(mocks.start_haproxy_apache.called)
 
462
 
 
463
    def test_stop_python_legacy(self):
 
464
        # Stop a pyJuju backend with legacy server.
 
465
        config = self.make_config({'builtin-server': False})
 
466
        with simulate_pyjuju:
 
467
            test_backend = backend.Backend(config=config)
 
468
            with self.mock_all() as mocks:
 
469
                test_backend.stop()
 
470
        mocks.stop_agent.assert_called_once_with()
 
471
        mocks.stop_haproxy_apache.assert_called_once_with()
 
472
        self.assertFalse(mocks.stop_builtin_server.called)
 
473
 
 
474
    def test_stop_go_legacy(self):
 
475
        # Stop a juju-core backend with legacy server.
 
476
        config = self.make_config({'builtin-server': False})
 
477
        with simulate_juju_core:
 
478
            test_backend = backend.Backend(config=config)
 
479
            with self.mock_all() as mocks:
 
480
                test_backend.stop()
 
481
        self.assertFalse(mocks.stop_agent.called)
 
482
        mocks.stop_haproxy_apache.assert_called_once_with()
 
483
        self.assertFalse(mocks.stop_builtin_server.called)
 
484
 
 
485
    def test_stop_python_builtin(self):
 
486
        # Stop a pyJuju backend with builtin server.
 
487
        config = self.make_config({'builtin-server': True})
 
488
        with simulate_pyjuju:
 
489
            test_backend = backend.Backend(config=config)
 
490
            with self.mock_all() as mocks:
 
491
                test_backend.stop()
 
492
        mocks.stop_agent.assert_called_once_with()
 
493
        mocks.stop_builtin_server.assert_called_once_with()
 
494
        self.assertFalse(mocks.stop_haproxy_apache.called)
 
495
 
 
496
    def test_stop_go_builtin(self):
 
497
        # Stop a juju-core backend with builtin server.
 
498
        config = self.make_config({'builtin-server': True})
 
499
        with simulate_juju_core:
 
500
            test_backend = backend.Backend(config=config)
 
501
            with self.mock_all() as mocks:
 
502
                test_backend.stop()
 
503
        self.assertFalse(mocks.stop_agent.called)
 
504
        mocks.stop_builtin_server.assert_called_once_with()
 
505
        self.assertFalse(mocks.stop_haproxy_apache.called)
272
506
 
273
507
 
274
508
class TestBackendUtils(unittest.TestCase):
294
528
        self.assertFalse(test_backend.different('staging'))
295
529
 
296
530
 
297
 
class TestChainMethods(unittest.TestCase):
 
531
class TestCallMethods(unittest.TestCase):
298
532
 
299
533
    def setUp(self):
300
534
        self.called = []
301
 
 
302
 
        def method(mixin, backend):
303
 
            self.called.append(mixin.__class__.__name__)
304
 
        mixin1 = type('Mixin1', (object,), {'method': method})()
305
 
        mixin2 = type('Mixin2', (object,), {'method': method})()
306
 
        self.backend = type('Backend', (), {'mixins': (mixin1, mixin2)})()
307
 
 
308
 
    def test_chain(self):
309
 
        method = backend.chain_methods('method')
310
 
        method(self.backend)
311
 
        self.assertEqual(['Mixin1', 'Mixin2'], self.called)
312
 
 
313
 
    def test_reversed(self):
314
 
        method = backend.chain_methods('method', reverse=True)
315
 
        method(self.backend)
316
 
        self.assertEqual(['Mixin2', 'Mixin1'], self.called)
 
535
        self.objects = [self.make_object('Obj1'), self.make_object('Obj2')]
 
536
 
 
537
    def make_object(self, name, has_method=True):
 
538
        """Create and return an test object with the given name."""
 
539
        def method(obj, *args):
 
540
            self.called.append([obj.__class__.__name__, args])
 
541
        object_dict = {'method': method} if has_method else {}
 
542
        return type(name, (object,), object_dict)()
 
543
 
 
544
    def test_call(self):
 
545
        # The methods are correctly called.
 
546
        backend.call_methods(self.objects, 'method', 'arg1', 'arg2')
 
547
        expected = [['Obj1', ('arg1', 'arg2')], ['Obj2', ('arg1', 'arg2')]]
 
548
        self.assertEqual(expected, self.called)
 
549
 
 
550
    def test_no_method(self):
 
551
        # An object without the method is ignored.
 
552
        self.objects.append(self.make_object('Obj3', has_method=False))
 
553
        backend.call_methods(self.objects, 'method')
 
554
        expected = [['Obj1', ()], ['Obj2', ()]]
 
555
        self.assertEqual(expected, self.called)