Merge lp:~frankban/juju-quickstart/support-old-series into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 117
Proposed branch: lp:~frankban/juju-quickstart/support-old-series
Merge into: lp:juju-quickstart
Diff against target: 446 lines (+265/-35)
10 files modified
HACKING.rst (+41/-0)
Makefile (+10/-0)
quickstart/juju.py (+2/-2)
quickstart/manage.py (+0/-1)
quickstart/platform_support.py (+1/-4)
quickstart/tests/functional/__init__.py (+41/-0)
quickstart/tests/functional/test_functional.py (+139/-0)
quickstart/tests/test_juju.py (+30/-22)
setup.cfg (+0/-1)
tox.ini (+1/-5)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/support-old-series
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+248150@code.launchpad.net

Description of the change

Enable tests for trusty and utopic.

Fix the unit test suite so that tests pass
when run in trusty and utopic scenarios.

Also add some simple functional tests. In those
tests, Quickstart is run against a real Juju env.

See the changes in the HACKING file for instructions
about running functional tests.

To QA this, run `make check` and `make fcheck`: the
latter can take some time.

https://codereview.appspot.com/197240043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+248150_code.launchpad.net,

Message:
Please take a look.

Description:
Enable tests for trusty and utopic.

Fix the unit test suite so that tests pass
when run in trusty and utopic scenarios.

Also add some simple functional tests. In those
tests, Quickstart is run against a real Juju env.

See the changes in the HACKING file for instructions
about running functional tests.

To QA this, run `make check` and `make fcheck`: the
latter can take some time.

https://code.launchpad.net/~frankban/juju-quickstart/support-old-series/+merge/248150

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/197240043/

Affected files (+265, -35 lines):
   M HACKING.rst
   M Makefile
   A [revision details]
   M quickstart/juju.py
   M quickstart/manage.py
   M quickstart/platform_support.py
   A quickstart/tests/functional/__init__.py
   A quickstart/tests/functional/test_functional.py
   M quickstart/tests/test_juju.py
   M setup.cfg
   M tox.ini

Revision history for this message
Brad Crittenden (bac) wrote :

LGTM. No QA.

https://codereview.appspot.com/197240043/diff/1/quickstart/tests/functional/test_functional.py
File quickstart/tests/functional/test_functional.py (right):

https://codereview.appspot.com/197240043/diff/1/quickstart/tests/functional/test_functional.py#newcode87
quickstart/tests/functional/test_functional.py:87: cls.retcode,
cls.output, cls.error = run_quickstart(cls.env_name)
Thanks for the thoughtful test implementation.

It may be worth noting that run_quickstart only happens once per suite
not once per test as it isn't super obvious since we don't use
setUpClass all that much.

https://codereview.appspot.com/197240043/

138. By Francesco Banconi

Changes as per review.

Revision history for this message
Francesco Banconi (frankban) wrote :
Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM Thanks for getting this working! I'll QA once we chat about my
comment.

https://codereview.appspot.com/197240043/diff/20001/quickstart/tests/functional/test_functional.py
File quickstart/tests/functional/test_functional.py (right):

https://codereview.appspot.com/197240043/diff/20001/quickstart/tests/functional/test_functional.py#newcode46
quickstart/tests/functional/test_functional.py:46: env_name =
envs.get_default_env_name()
As a precautionary measure I would prefer if there was a default
environment name for these tests. That way there would be no possible
way someone would accidentally run these tests on a live environment.

https://codereview.appspot.com/197240043/

Revision history for this message
Jeff Pihach (hatch) wrote :

QA OK Thanks for this branch!

https://codereview.appspot.com/197240043/

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Enable tests for trusty and utopic.

Fix the unit test suite so that tests pass
when run in trusty and utopic scenarios.

Also add some simple functional tests. In those
tests, Quickstart is run against a real Juju env.

See the changes in the HACKING file for instructions
about running functional tests.

To QA this, run `make check` and `make fcheck`: the
latter can take some time.

R=bac, jeff.pihach
CC=
https://codereview.appspot.com/197240043

https://codereview.appspot.com/197240043/

Revision history for this message
Francesco Banconi (frankban) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING.rst'
2--- HACKING.rst 2015-01-30 09:28:38 +0000
3+++ HACKING.rst 2015-02-03 14:13:23 +0000
4@@ -67,6 +67,43 @@
5
6 Run ``make help`` for more information about all the available make targets.
7
8+Running functional tests
9+~~~~~~~~~~~~~~~~~~~~~~~~
10+
11+The Juju Quickstart test suite includes functional tests exercising the
12+application execution against a real Juju environment. Functional tests are
13+not run by default (i.e. when using ``make test`` or ``make check``), but they
14+can be activated by setting the ``JUJU_QUICKSTART_FTESTS`` environment variable
15+to ``1``, or, in a simpler way, by running ``make ftest`` or ``make fcheck``.
16+
17+Note that functional tests are not intended to be run in the day-by-day
18+development process. Their goal is to automate part of the release QA.
19+
20+Functional tests require:
21+
22+- some time to complete their execution: a real Juju environment is
23+ bootstrapped in the process;
24+- a Juju home correctly set up with at least one environment defined in the
25+ ``environments.yaml`` file;
26+- SSH keys already generated for the user running the tests;
27+- a working Internet connection.
28+
29+By default, the current default environment is used to run those tests.
30+To change the environment, use the JUJU_ENV environment variable, e.g.::
31+
32+ make fcheck JUJU_ENV=myenv
33+
34+The environment used by the functional suite is destroyed after the tests
35+complete. For this reason, **ensure the selected environment is not in use**.
36+
37+To run the test using a customized build of Juju, pass the ``JUJU`` environment
38+variable to ``make``, e.g.::
39+
40+ make fcheck JUJU=$GOPATH/bin/juju
41+
42+Also note that when running functional tests against a local environment, the
43+password for sudo privileges may be asked while the suite is run.
44+
45 Requirements
46 ~~~~~~~~~~~~
47
48@@ -157,6 +194,10 @@
49 Pre-release QA
50 ~~~~~~~~~~~~~~
51
52+Run the Quickstart functional tests on all supported platforms::
53+
54+ make fcheck
55+
56 The general steps for manual QA (until we get a continuous integration set up
57 with functional tests) should be run on trusty, utopic and vivid.
58
59
60=== modified file 'Makefile'
61--- Makefile 2015-01-30 16:34:50 +0000
62+++ Makefile 2015-02-03 14:13:23 +0000
63@@ -66,6 +66,14 @@
64 # Remove the virtualenv used in previous configurations.
65 rm -rfv .venv/
66
67+.PHONY: fcheck
68+fcheck: setup
69+ $(MAKE) check JUJU_QUICKSTART_FTESTS=1
70+
71+.PHONY: ftest
72+ftest: setup
73+ $(MAKE) test JUJU_QUICKSTART_FTESTS=1
74+
75 .PHONY: help
76 help:
77 @echo -e 'Juju Quickstart - list of make targets:\n'
78@@ -73,6 +81,8 @@
79 @echo 'make test - Run tests.'
80 @echo 'make lint - Run linter and pep8.'
81 @echo 'make check - Run all the tests and lint in all supported scenarios.'
82+ @echo 'make ftest - Run tests (including functional tests).'
83+ @echo 'make fcheck - Run functional tests and lint in all scenarios.'
84 @echo 'make source - Create source package.'
85 @echo 'make install - Install on local system.'
86 @echo 'make clean - Get rid of bytecode files, build and dist dirs, venvs.'
87
88=== modified file 'quickstart/juju.py'
89--- quickstart/juju.py 2014-12-16 11:10:10 +0000
90+++ quickstart/juju.py 2015-02-03 14:13:23 +0000
91@@ -148,7 +148,7 @@
92 Overridden to add logging in the case the payload is text.
93 """
94 if opcode == OPCODE_TEXT:
95- message = payload.decode('utf-8')
96+ message = payload.decode('utf-8', 'ignore')
97 logging.debug('API message: --> {}'.format(message))
98 return super(WebSocketConnection, self).send(payload, opcode=opcode)
99
100@@ -159,6 +159,6 @@
101 """
102 data = super(WebSocketConnection, self).recv()
103 if isinstance(data, bytes):
104- message = data.decode('utf-8')
105+ message = data.decode('utf-8', 'ignore')
106 logging.debug('API message: <-- {}'.format(message))
107 return data
108
109=== modified file 'quickstart/manage.py'
110--- quickstart/manage.py 2015-01-12 12:10:38 +0000
111+++ quickstart/manage.py 2015-02-03 14:13:23 +0000
112@@ -386,7 +386,6 @@
113
114 Exit with an error if the provided arguments are not valid.
115 """
116-
117 # Determine the host platform. This needs to be done early as it affects
118 # the options we present.
119 platform = platform_support.get_platform()
120
121=== modified file 'quickstart/platform_support.py'
122--- quickstart/platform_support.py 2014-08-25 16:57:41 +0000
123+++ quickstart/platform_support.py 2015-02-03 14:13:23 +0000
124@@ -55,10 +55,7 @@
125
126
127 def get_platform():
128- """Return the platform of the host.
129-
130- Raises UnsupportedOS if a platform we don't support is detected.
131- """
132+ """Return the platform of the host."""
133 system = platform.system()
134 if system == 'Darwin':
135 return settings.OSX
136
137=== added directory 'quickstart/tests/functional'
138=== added file 'quickstart/tests/functional/__init__.py'
139--- quickstart/tests/functional/__init__.py 1970-01-01 00:00:00 +0000
140+++ quickstart/tests/functional/__init__.py 2015-02-03 14:13:23 +0000
141@@ -0,0 +1,41 @@
142+# This file is part of the Juju Quickstart Plugin, which lets users set up a
143+# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
144+# Copyright (C) 2015 Canonical Ltd.
145+#
146+# This program is free software: you can redistribute it and/or modify it under
147+# the terms of the GNU Affero General Public License version 3, as published by
148+# the Free Software Foundation.
149+#
150+# This program is distributed in the hope that it will be useful, but WITHOUT
151+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
152+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
153+# Affero General Public License for more details.
154+#
155+# You should have received a copy of the GNU Affero General Public License
156+# along with this program. If not, see <http://www.gnu.org/licenses/>.
157+
158+"""Juju Quickstart functional tests package.
159+
160+This package includes functional tests for Juju Quickstart.
161+In those tests, Juju Quickstart is run to bootstrap and deploy the Juju GUI
162+in a real Juju environment. For this reason, completing the suite takes a
163+while. Therefore tests are disabled by default, and are intended to be only run
164+before releasing a new version of the application.
165+
166+To run the tests, set the JUJU_QUICKSTART_FTESTS environment variable to "1",
167+or use "make ftest/fcheck".
168+
169+Functional tests require:
170+- a Juju home correctly set up with at least one environment defined in the
171+ environments.yaml file;
172+- SSH keys already generated for the user running the tests;
173+- a working Internet connection.
174+
175+By default, the current default environment is used to run the application.
176+To change the environment, set the JUJU_ENV environment variable, e.g.:
177+
178+ make fcheck JUJU_ENV=myenv
179+
180+Also note that when running functional tests against a local environment, the
181+password for sudo privileges may be asked while the suite is run.
182+"""
183
184=== added file 'quickstart/tests/functional/test_functional.py'
185--- quickstart/tests/functional/test_functional.py 1970-01-01 00:00:00 +0000
186+++ quickstart/tests/functional/test_functional.py 2015-02-03 14:13:23 +0000
187@@ -0,0 +1,139 @@
188+# This file is part of the Juju Quickstart Plugin, which lets users set up a
189+# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
190+# Copyright (C) 2015 Canonical Ltd.
191+#
192+# This program is free software: you can redistribute it and/or modify it under
193+# the terms of the GNU Affero General Public License version 3, as published by
194+# the Free Software Foundation.
195+#
196+# This program is distributed in the hope that it will be useful, but WITHOUT
197+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
198+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
199+# Affero General Public License for more details.
200+#
201+# You should have received a copy of the GNU Affero General Public License
202+# along with this program. If not, see <http://www.gnu.org/licenses/>.
203+
204+"""Functional tests for Juju Quickstart."""
205+
206+from __future__ import unicode_literals
207+
208+import functools
209+import json
210+import os
211+import unittest
212+
213+import quickstart
214+from quickstart import (
215+ platform_support,
216+ settings,
217+ utils,
218+)
219+from quickstart.models import envs
220+
221+
222+# Define the name of the environment variable used to run the functional tests.
223+FTEST_ENV_VAR = 'JUJU_QUICKSTART_FTESTS'
224+
225+
226+def skip_if_disbled(func):
227+ """Decorate a test method so that it is only run when required/possible."""
228+ # Check that functional tests are enabled.
229+ if os.getenv(FTEST_ENV_VAR) != '1':
230+ return unittest.skip(
231+ 'to run functional tests, set {} to "1"'.format(FTEST_ENV_VAR))
232+ # Check that a Juju environment can be found.
233+ env_name = envs.get_default_env_name()
234+ if env_name is None:
235+ return unittest.skip('cannot find a configured Juju environment')
236+
237+ @functools.wraps(func)
238+ def decorated(cls_or_self):
239+ # Decorate the function to update the received class or instance with
240+ # the Juju env name.
241+ cls_or_self.env_name = env_name
242+ return func(cls_or_self)
243+
244+ return decorated
245+
246+
247+def run_juju(*args):
248+ """Run the juju command with the given args.
249+
250+ Return a tuple including the command exit code, its output and error.
251+ """
252+ platform = platform_support.get_platform()
253+ cmd, _ = platform_support.get_juju_command(platform)
254+ return utils.call(cmd, *args)
255+
256+
257+def run_quickstart(env_name, *args):
258+ """Run the Juju Quickstart command.
259+
260+ Return a tuple including the command exit code, its output and error.
261+ """
262+ package_dir = os.path.dirname(quickstart.__file__)
263+ cmd = os.path.abspath(os.path.join(package_dir, '..', 'juju-quickstart'))
264+ return utils.call(
265+ cmd, '-e', env_name, '--distro-only', '--no-browser', *args)
266+
267+
268+class TestFunctional(unittest.TestCase):
269+
270+ @classmethod
271+ @skip_if_disbled
272+ def setUpClass(cls):
273+ # Run Juju Quickstart to bootstrap Juju and deploy the Juju GUI.
274+ # Note that this is done once per suite. The resulting environment is
275+ # then destroyed when the suite completes.
276+ cls.retcode, cls.output, cls.error = run_quickstart(cls.env_name)
277+
278+ @classmethod
279+ @skip_if_disbled
280+ def tearDownClass(cls):
281+ # Destroy the environment.
282+ run_juju('destroy-environment', cls.env_name, '-y', '--force')
283+
284+ def test_executed(self):
285+ # The application successfully completed its execution.
286+ self.assertEqual(0, self.retcode)
287+ self.assertIn('done!', self.output)
288+ self.assertEqual('', self.error)
289+
290+ def test_gui_started(self):
291+ # At the end of the process, the Juju GUI unit is started.
292+ retcode, output, _ = run_juju(
293+ 'status', '-e', self.env_name, '--format', 'json')
294+ self.assertEqual(0, retcode)
295+ status = json.loads(output)
296+ # The Juju GUI service is exposed.
297+ service = status['services'][settings.JUJU_GUI_SERVICE_NAME]
298+ self.assertTrue(service['exposed'])
299+ # The Juju GUI unit is started.
300+ unit = service['units']['{}/0'.format(settings.JUJU_GUI_SERVICE_NAME)]
301+ self.assertEqual('started', unit['agent-state'])
302+
303+ def test_idempotent(self):
304+ # The application can be run again on an already bootstrapped
305+ # environment.
306+ retcode, output, error = run_quickstart(self.env_name)
307+ self.assertEqual(0, retcode)
308+ msg = 'reusing the already bootstrapped {} environment'
309+ self.assertIn(msg.format(self.env_name), output)
310+ self.assertEqual('', error)
311+
312+ def test_version(self):
313+ # The application correctly prints out its own version.
314+ retcode, output, error = run_quickstart(self.env_name, '--version')
315+ self.assertEqual(0, retcode)
316+ self.assertEqual('', output)
317+ self.assertEqual(
318+ 'juju-quickstart {}\n'.format(quickstart.get_version()), error)
319+
320+ def test_bundle_deployment(self):
321+ # The application can be used to deploy bundles.
322+ retcode, output, error = run_quickstart(
323+ self.env_name, 'bundle:mediawiki/single')
324+ self.assertEqual(0, retcode)
325+ self.assertIn('bundle deployment request accepted', output)
326+ self.assertEqual('', error)
327
328=== modified file 'quickstart/tests/test_juju.py'
329--- quickstart/tests/test_juju.py 2014-12-16 15:53:25 +0000
330+++ quickstart/tests/test_juju.py 2015-02-03 14:13:23 +0000
331@@ -63,8 +63,13 @@
332 api_url = self.api_url
333 with mock.patch('websocket.create_connection') as mock_connect:
334 self.env = juju.Environment(api_url)
335- mock_connect.assert_called_once_with(
336- api_url, origin=api_url, sslopt=juju.SSLOPT)
337+ # In old versions of jujuclient the SSL options are not passed as
338+ # kwargs to create_connection.
339+ if len(mock_connect.call_args[1]) == 1:
340+ mock_connect.assert_called_once_with(api_url, origin=api_url)
341+ else:
342+ mock_connect.assert_called_once_with(
343+ api_url, origin=api_url, sslopt=juju.SSLOPT)
344 # Keep track of watcher changes in the changesets list.
345 self.changesets = []
346
347@@ -345,49 +350,52 @@
348
349 def setUp(self):
350 self.conn = juju.WebSocketConnection()
351- # The send method calls the send_frame one.
352- self.conn.send_frame = self.mock_send = mock.Mock()
353- # The recv method calls the recv_data one.
354- self.conn.recv_data = self.mock_recv = mock.Mock()
355
356 def test_send(self):
357 # Outgoing messages are properly logged.
358 with helpers.assert_logs(['API message: --> my message'], 'debug'):
359- self.conn.send('my message')
360- self.assertTrue(self.mock_send.called)
361+ with mock.patch('websocket.WebSocket.send') as mock_send:
362+ self.conn.send('my message')
363+ mock_send.assert_called_once_with(
364+ 'my message', opcode=juju.OPCODE_TEXT)
365
366 def test_send_unicode(self):
367 # Outgoing unicode messages are properly logged.
368 expected = 'API message: --> {}'.format(self.snowman)
369 with helpers.assert_logs([expected], 'debug'):
370- self.conn.send(self.snowman.encode('utf-8'))
371- self.assertTrue(self.mock_send.called)
372+ with mock.patch('websocket.WebSocket.send') as mock_send:
373+ self.conn.send(self.snowman.encode('utf-8'))
374+ mock_send.assert_called_once_with(
375+ self.snowman.encode('utf-8'), opcode=juju.OPCODE_TEXT)
376
377 def test_send_not_text(self):
378 # Outgoing non-textual messages are not logged.
379 with helpers.assert_logs([], 'debug'):
380- self.conn.send(0x0, opcode=websocket.ABNF.OPCODE_BINARY)
381- self.assertTrue(self.mock_send.called)
382+ with mock.patch('websocket.WebSocket.send') as mock_send:
383+ self.conn.send(0x0, opcode=websocket.ABNF.OPCODE_BINARY)
384+ mock_send.assert_called_once_with(
385+ 0x0, opcode=websocket.ABNF.OPCODE_BINARY)
386
387 def test_recv(self):
388 # Incoming messages are properly logged.
389- self.mock_recv.return_value = (juju.OPCODE_TEXT, b'my message')
390 with helpers.assert_logs(['API message: <-- my message'], 'debug'):
391- self.conn.recv()
392- self.mock_recv.assert_called_once_with()
393+ with mock.patch('websocket.WebSocket.recv') as mock_recv:
394+ mock_recv.return_value = (b'my message')
395+ message = self.conn.recv()
396+ self.assertEqual('my message', message)
397
398 def test_recv_unicode(self):
399 # Incoming unicode messages are properly logged.
400- self.mock_recv.return_value = (
401- juju.OPCODE_TEXT, self.snowman.encode('utf-8'))
402 expected = 'API message: <-- {}'.format(self.snowman)
403 with helpers.assert_logs([expected], 'debug'):
404- self.conn.recv()
405- self.mock_recv.assert_called_once_with()
406+ with mock.patch('websocket.WebSocket.recv') as mock_recv:
407+ mock_recv.return_value = self.snowman.encode('utf-8')
408+ message = self.conn.recv()
409+ self.assertEqual(self.snowman.encode('utf-8'), message)
410
411 def test_recv_not_text(self):
412 # Incoming non-textual messages are not logged.
413- self.mock_recv.return_value = (websocket.ABNF.OPCODE_BINARY, 0x0)
414 with helpers.assert_logs([], 'debug'):
415- self.conn.recv()
416- self.mock_recv.assert_called_once_with()
417+ with mock.patch('websocket.WebSocket.recv') as mock_recv:
418+ mock_recv.return_value = 0x0
419+ self.conn.recv()
420
421=== modified file 'setup.cfg'
422--- setup.cfg 2015-01-16 12:32:38 +0000
423+++ setup.cfg 2015-02-03 14:13:23 +0000
424@@ -15,6 +15,5 @@
425 # along with this program. If not, see <http://www.gnu.org/licenses/>.
426
427 [nosetests]
428-detailed-errors=1
429 nocapture=1
430 verbosity=2
431
432=== modified file 'tox.ini'
433--- tox.ini 2015-01-30 09:28:38 +0000
434+++ tox.ini 2015-02-03 14:13:23 +0000
435@@ -18,11 +18,7 @@
436 # The envlist option must only include platform scenarios. In essence, those
437 # are used to test Juju Quickstart with the specific dependencies present in
438 # each supported OS platform.
439-envlist = pypi,ppa,vivid
440-# XXX frankban: currently tests do not pass on trusty and utopic scenarios.
441-# Fix the tests, then remove the line above and uncomment the line below.
442-# envlist = pypi,ppa,trusty,utopic,vivid
443-
444+envlist = pypi,ppa,trusty,utopic,vivid
445
446 # Define the base requirements and commands to use when testing the program.
447

Subscribers

People subscribed via source and target branches