Merge lp:~bjornt/launchpad/windmill-test-layer into lp:launchpad

Proposed by Björn Tillenius
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~bjornt/launchpad/windmill-test-layer
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~bjornt/launchpad/windmill-test-layer
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+11771@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Björn Tillenius (bjornt) wrote :

Make it possible to run Windmill tests using our normal test runner.

This branch adds test layers for Windmill tests, so ideally you could be
able to run all Windmill tests using: bin/test --layer=WindmillLayer.
This doesn't quite work yet. Windmill isn't torned down properly, so you
can only run the tests for one domain at a time:

    bin/test --layer=BugsWindmillLayer

Note the hack in test.in. This is because of bug #429375, which need to
get fixed before landing this branch.

All the old tests need to be converted to use a TestCase. I'm intending
to do that in a separate branch, in order to keep down the diff size.
I've converted one Bugs and one Code test as a proof-of-concept.

The reason for integrating the Windmill test into our test suite is to
make it easier to run the tests. I always forget how to run the tests
using bin/lp-windmill and bin/windmill. Using bin/test also means that
it's easier to see which tests pass and fail.

As a bonus, it's now possible to use LaunchpadObjectFactory in our
Windmill tests to do setup. In the future, I'd also like to reset the db
between tests.

--
Björn Tillenius | https://launchpad.net/~bjornt

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

Hi Bjorn,

Thanks for taking on this testing infrastructure change. It'll make
it a lot easier to run the windmill tests.

> === modified file 'Makefile'
> --- Makefile 2009-08-21 17:50:58 +0000
+++ Makefile 2009-09-15 08:58:30 +0000
> @@ -90,6 +90,8 @@
> @echo
> @echo "Running the JavaScript integration test suite"
> @echo
> + bin/test $(VERBOSITY) --layer=BugsWindmillLayer
> + bin/test $(VERBOSITY) --layer=CodeWindmillLayer
> bin/jstest
>
> check_mailman: build

> === modified file 'lib/canonical/testing/layers.py'
> --- lib/canonical/testing/layers.py 2009-08-21 19:38:21 +0000
> +++ lib/canonical/testing/layers.py 2009-09-15 09:12:47 +0000
> @@ -1638,3 +1643,53 @@
> @profiled
> def testTearDown(cls):
> LayerProcessController.postTestInvariants()
> +
> +
> +class BaseWindmillLayer(AppServerLayer):
> + """Layer for Windmill tests.
> +
> + This layer shouldn't be used directly. A subclass needs to be
> + created specifying which base URL to use (e.g.
> + http://bugs.launchpad.dev:8085/).
> + """
> +
> + base_url = None
> + shell_objects = None
> + config_file = None
> +
> + @classmethod
> + @profiled
> + def setUp(cls):
> + if cls.base_url is None:
> + # Only do the setup if we're in a subclass that defines
> + # base_url. With no base_url, we can't create the config
> + # file windmill needs.
> + return

Is the silent return sufficient or should there be an asssert?

> + # Kill the SMTP server. The current Windmill test don't need it,
> + # and if it's running, uuid will somehow end up listen to its
> + # port, preventing the next test run from starting.
> + LayerProcessController.stopSMTPServer()
> + # Windmill needs a config file on disk.
> + config_text = dedent("""\
> + START_FIREFOX = True
> + TEST_URL = '%s'
> + """ % cls.base_url)
> + cls.config_file = tempfile.NamedTemporaryFile(suffix='.py')
> + cls.config_file.write(config_text)
> + # Flush the file so that windmill can read it.
> + cls.config_file.flush()
> + os.environ['WINDMILL_CONFIG_FILE'] = cls.config_file.name
> + cls.shell_objects = start_windmill()
> +
> + @classmethod
> + @profiled
> + def tearDown(cls):
> + if cls.shell_objects is not None:
> + AppServerLayer.tearDown()
> + windmill_teardown(cls.shell_objects)
> + # Start the SMTP server, in case other layers need it. It
> + # will be killed by AppServerLayer later.
> + LayerProcessController.startSMTPServer()
> + if cls.config_file is not None:
> + # Close the file so that it gets deleted.
> + cls.config_file.close()

review: Approve (code)
Revision history for this message
Björn Tillenius (bjornt) wrote :

On Tue, Sep 15, 2009 at 02:29:24PM -0000, Brad Crittenden wrote:
> Review: Approve code
> Hi Bjorn,
>
> Thanks for taking on this testing infrastructure change. It'll make
> it a lot easier to run the windmill tests.
>
> > === modified file 'Makefile'
> > --- Makefile 2009-08-21 17:50:58 +0000
> +++ Makefile 2009-09-15 08:58:30 +0000
> > @@ -90,6 +90,8 @@
> > @echo
> > @echo "Running the JavaScript integration test suite"
> > @echo
> > + bin/test $(VERBOSITY) --layer=BugsWindmillLayer
> > + bin/test $(VERBOSITY) --layer=CodeWindmillLayer
> > bin/jstest
> >
> > check_mailman: build
>
> > === modified file 'lib/canonical/testing/layers.py'
> > --- lib/canonical/testing/layers.py 2009-08-21 19:38:21 +0000
> > +++ lib/canonical/testing/layers.py 2009-09-15 09:12:47 +0000
> > @@ -1638,3 +1643,53 @@
> > @profiled
> > def testTearDown(cls):
> > LayerProcessController.postTestInvariants()
> > +
> > +
> > +class BaseWindmillLayer(AppServerLayer):
> > + """Layer for Windmill tests.
> > +
> > + This layer shouldn't be used directly. A subclass needs to be
> > + created specifying which base URL to use (e.g.
> > + http://bugs.launchpad.dev:8085/).
> > + """
> > +
> > + base_url = None
> > + shell_objects = None
> > + config_file = None
> > +
> > + @classmethod
> > + @profiled
> > + def setUp(cls):
> > + if cls.base_url is None:
> > + # Only do the setup if we're in a subclass that defines
> > + # base_url. With no base_url, we can't create the config
> > + # file windmill needs.
> > + return
>
> Is the silent return sufficient or should there be an asssert?

It has to be a silent return. Due to the way layers work, this method
will be called on BaseWindmillLayer, before it's called on
BugsWindmillLayer. It felt like the easiest way of reducing code
duplicating between the different windmill layers.

--
Björn Tillenius | https://launchpad.net/~bjornt

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2009-08-21 17:50:58 +0000
3+++ Makefile 2009-09-15 08:58:30 +0000
4@@ -90,6 +90,8 @@
5 @echo
6 @echo "Running the JavaScript integration test suite"
7 @echo
8+ bin/test $(VERBOSITY) --layer=BugsWindmillLayer
9+ bin/test $(VERBOSITY) --layer=CodeWindmillLayer
10 bin/jstest
11
12 check_mailman: build
13
14=== modified file 'buildout-templates/bin/test.in'
15--- buildout-templates/bin/test.in 2009-08-10 22:08:05 +0000
16+++ buildout-templates/bin/test.in 2009-09-15 08:55:41 +0000
17@@ -140,7 +140,8 @@
18 '--test-path=${buildout:directory}/lib',
19 '--package=canonical',
20 '--package=lp',
21- '--layer=!MailmanLayer',
22+ # XXX: Add this back before landing this branch.
23+ #'--layer=!(MailmanLayer|WindmillLayer)',
24 ]
25
26 # Monkey-patch os.listdir to randomise the results
27
28=== modified file 'lib/canonical/testing/layers.py'
29--- lib/canonical/testing/layers.py 2009-08-21 19:38:21 +0000
30+++ lib/canonical/testing/layers.py 2009-09-15 09:12:47 +0000
31@@ -25,6 +25,7 @@
32 __all__ = [
33 'AppServerLayer',
34 'BaseLayer',
35+ 'BaseWindmillLayer',
36 'DatabaseFunctionalLayer',
37 'DatabaseLayer',
38 'ExperimentalLaunchpadZopelessLayer',
39@@ -58,6 +59,7 @@
40 import socket
41 import subprocess
42 import sys
43+import tempfile
44 import threading
45 import time
46
47@@ -70,6 +72,9 @@
48 from storm.zope.interfaces import IZStorm
49 import transaction
50
51+from windmill.bin.admin_lib import (
52+ start_windmill, teardown as windmill_teardown)
53+
54 import zope.app.testing.functional
55 from zope.app.testing.functional import FunctionalTestSetup, ZopePublication
56 from zope.component import getUtility, provideUtility
57@@ -1638,3 +1643,53 @@
58 @profiled
59 def testTearDown(cls):
60 LayerProcessController.postTestInvariants()
61+
62+
63+class BaseWindmillLayer(AppServerLayer):
64+ """Layer for Windmill tests.
65+
66+ This layer shouldn't be used directly. A subclass needs to be
67+ created specifying which base URL to use (e.g.
68+ http://bugs.launchpad.dev:8085/).
69+ """
70+
71+ base_url = None
72+ shell_objects = None
73+ config_file = None
74+
75+ @classmethod
76+ @profiled
77+ def setUp(cls):
78+ if cls.base_url is None:
79+ # Only do the setup if we're in a subclass that defines
80+ # base_url. With no base_url, we can't create the config
81+ # file windmill needs.
82+ return
83+ # Kill the SMTP server. The current Windmill test don't need it,
84+ # and if it's running, uuid will somehow end up listen to its
85+ # port, preventing the next test run from starting.
86+ LayerProcessController.stopSMTPServer()
87+ # Windmill needs a config file on disk.
88+ config_text = dedent("""\
89+ START_FIREFOX = True
90+ TEST_URL = '%s'
91+ """ % cls.base_url)
92+ cls.config_file = tempfile.NamedTemporaryFile(suffix='.py')
93+ cls.config_file.write(config_text)
94+ # Flush the file so that windmill can read it.
95+ cls.config_file.flush()
96+ os.environ['WINDMILL_CONFIG_FILE'] = cls.config_file.name
97+ cls.shell_objects = start_windmill()
98+
99+ @classmethod
100+ @profiled
101+ def tearDown(cls):
102+ if cls.shell_objects is not None:
103+ AppServerLayer.tearDown()
104+ windmill_teardown(cls.shell_objects)
105+ # Start the SMTP server, in case other layers need it. It
106+ # will be killed by AppServerLayer later.
107+ LayerProcessController.startSMTPServer()
108+ if cls.config_file is not None:
109+ # Close the file so that it gets deleted.
110+ cls.config_file.close()
111
112=== added file 'lib/lp/bugs/windmill/testing.py'
113--- lib/lp/bugs/windmill/testing.py 1970-01-01 00:00:00 +0000
114+++ lib/lp/bugs/windmill/testing.py 2009-09-15 08:58:30 +0000
115@@ -0,0 +1,18 @@
116+# Copyright 2009 Canonical Ltd. This software is licensed under the
117+# GNU Affero General Public License version 3 (see the file LICENSE).
118+
119+"""Bugs-specific testing infrastructure for Windmill."""
120+
121+__metaclass__ = type
122+__all__ = [
123+ 'BugsWindmillLayer',
124+ ]
125+
126+
127+from canonical.testing.layers import BaseWindmillLayer
128+
129+
130+class BugsWindmillLayer(BaseWindmillLayer):
131+ """Layer for Bugs Windmill tests."""
132+
133+ base_url = 'http://bugs.launchpad.dev:8085/'
134
135=== renamed file 'lib/lp/bugs/windmill/tests/test_bugs/test_bug_commenting.py' => 'lib/lp/bugs/windmill/tests/test_bug_commenting.py'
136--- lib/lp/bugs/windmill/tests/test_bugs/test_bug_commenting.py 2009-08-18 14:33:08 +0000
137+++ lib/lp/bugs/windmill/tests/test_bug_commenting.py 2009-09-15 08:58:30 +0000
138@@ -6,10 +6,14 @@
139 __metaclass__ = type
140 __all__ = []
141
142+import unittest
143+
144 from windmill.authoring import WindmillTestClient
145
146 from canonical.launchpad.windmill.testing import lpuser
147 from canonical.uuid import generate_uuid
148+from lp.bugs.windmill.testing import BugsWindmillLayer
149+from lp.testing import TestCaseWithFactory
150
151 WAIT_PAGELOAD = u'30000'
152 WAIT_ELEMENT_COMPLETE = u'30000'
153@@ -18,20 +22,28 @@
154 u'//input[@id="field.actions.save" and @class="button js-action"]')
155
156
157-def test_bug_commenting():
158- """Test commenting on bugs."""
159- client = WindmillTestClient('Bug commenting')
160- lpuser.NO_PRIV.ensure_login(client)
161-
162- client.open(url='http://bugs.launchpad.dev:8085/bugs/1')
163- client.waits.forPageLoad(timeout=WAIT_PAGELOAD)
164- client.waits.forElement(xpath=ADD_COMMENT_BUTTON)
165-
166- # Generate a unique piece of text, so we can run the test multiple
167- # times, without resetting the db.
168- new_comment_text = generate_uuid()
169- client.type(text=new_comment_text, id="field.comment")
170- client.click(xpath=ADD_COMMENT_BUTTON)
171- client.waits.forElement(
172- xpath=u'//div[@class="bug-comment"]/p[contains(., "%s")]' % (
173- new_comment_text))
174+class TestBugCommenting(TestCaseWithFactory):
175+
176+ layer = BugsWindmillLayer
177+
178+ def test_bug_commenting(self):
179+ """Test commenting on bugs."""
180+ client = WindmillTestClient('Bug commenting')
181+ lpuser.NO_PRIV.ensure_login(client)
182+
183+ client.open(url='http://bugs.launchpad.dev:8085/bugs/1')
184+ client.waits.forPageLoad(timeout=WAIT_PAGELOAD)
185+ client.waits.forElement(xpath=ADD_COMMENT_BUTTON)
186+
187+ # Generate a unique piece of text, so we can run the test multiple
188+ # times, without resetting the db.
189+ new_comment_text = generate_uuid()
190+ client.type(text=new_comment_text, id="field.comment")
191+ client.click(xpath=ADD_COMMENT_BUTTON)
192+ client.waits.forElement(
193+ xpath=u'//div[@class="bug-comment"]/p[contains(., "%s")]' % (
194+ new_comment_text))
195+
196+
197+def test_suite():
198+ return unittest.TestLoader().loadTestsFromName(__name__)
199
200=== added file 'lib/lp/code/windmill/testing.py'
201--- lib/lp/code/windmill/testing.py 1970-01-01 00:00:00 +0000
202+++ lib/lp/code/windmill/testing.py 2009-09-15 08:58:30 +0000
203@@ -0,0 +1,18 @@
204+# Copyright 2009 Canonical Ltd. This software is licensed under the
205+# GNU Affero General Public License version 3 (see the file LICENSE).
206+
207+"""Code-specific testing infrastructure for Windmill."""
208+
209+__metaclass__ = type
210+__all__ = [
211+ 'CodeWindmillLayer',
212+ ]
213+
214+
215+from canonical.testing.layers import BaseWindmillLayer
216+
217+
218+class CodeWindmillLayer(BaseWindmillLayer):
219+ """Layer for Code Windmill tests."""
220+
221+ base_url = 'http://code.launchpad.dev:8085/'
222
223=== added directory 'lib/lp/code/windmill/tests'
224=== added file 'lib/lp/code/windmill/tests/__init__.py'
225=== renamed file 'lib/lp/code/windmill/test_branch_links.py' => 'lib/lp/code/windmill/tests/test_branch_links.py'
226--- lib/lp/code/windmill/test_branch_links.py 2009-08-13 15:12:16 +0000
227+++ lib/lp/code/windmill/tests/test_branch_links.py 2009-09-15 09:14:25 +0000
228@@ -6,47 +6,59 @@
229 __metaclass__ = type
230 __all__ = []
231
232+import unittest
233+
234 import windmill
235 from windmill.authoring import WindmillTestClient
236
237 from canonical.launchpad.windmill.testing import lpuser
238-
239-
240-def test_inline_branch_bug_link_unlink():
241- """Test branch bug links."""
242- client = WindmillTestClient("Branch bug links")
243-
244- lpuser.FOO_BAR.ensure_login(client)
245-
246- client.open(
247- url=windmill.settings['TEST_URL'] + '/~mark/firefox/release--0.9.1')
248- client.waits.forElement(id=u'linkbug', timeout=u'10000')
249- client.click(id=u'linkbug')
250-
251- client.waits.forElement(id=u'field.bug')
252- client.type(text=u'1', id=u'field.bug')
253- client.click(xpath=u'//button[@name="buglink.actions.change"]')
254-
255- client.waits.forElement(id=u'buglink-1', timeout=u'10000')
256- client.asserts.assertText(id=u'linkbug',
257- validator=u'Link to another bug')
258-
259- client.click(id=u'linkbug')
260- client.waits.forElement(id=u'field.bug')
261- client.type(text=u'2', id=u'field.bug')
262- client.click(xpath=u'//button[@name="buglink.actions.change"]')
263-
264- client.waits.forElement(id=u'buglink-1', timeout=u'10000')
265- client.asserts.assertText(id=u'linkbug',
266- validator=u'Link to another bug')
267-
268- # And now to unlink.
269- client.click(id=u'delete-buglink-1')
270- client.waits.sleep(milliseconds=3000)
271- client.asserts.assertNotNode(id=u'buglink-1')
272- client.click(id=u'delete-buglink-2')
273- client.waits.sleep(milliseconds=3000)
274- client.asserts.assertNotNode(id=u'buglink-2')
275- client.asserts.assertText(id=u'linkbug',
276- validator=u'Link to a bug report')
277-
278+from lp.code.windmill.testing import CodeWindmillLayer
279+from lp.testing import TestCaseWithFactory
280+
281+
282+class TestBranchLinks(TestCaseWithFactory):
283+
284+ layer = CodeWindmillLayer
285+
286+ def test_inline_branch_bug_link_unlink(self):
287+ """Test branch bug links."""
288+ client = WindmillTestClient("Branch bug links")
289+
290+ lpuser.FOO_BAR.ensure_login(client)
291+
292+ start_url = (
293+ windmill.settings['TEST_URL'] + '/~mark/firefox/release--0.9.1')
294+ client.open(url=start_url)
295+ client.waits.forElement(id=u'linkbug', timeout=u'10000')
296+ client.click(id=u'linkbug')
297+
298+ client.waits.forElement(id=u'field.bug')
299+ client.type(text=u'1', id=u'field.bug')
300+ client.click(xpath=u'//button[@name="buglink.actions.change"]')
301+
302+ client.waits.forElement(id=u'buglink-1', timeout=u'10000')
303+ client.asserts.assertText(id=u'linkbug',
304+ validator=u'Link to another bug')
305+
306+ client.click(id=u'linkbug')
307+ client.waits.forElement(id=u'field.bug')
308+ client.type(text=u'2', id=u'field.bug')
309+ client.click(xpath=u'//button[@name="buglink.actions.change"]')
310+
311+ client.waits.forElement(id=u'buglink-1', timeout=u'10000')
312+ client.asserts.assertText(id=u'linkbug',
313+ validator=u'Link to another bug')
314+
315+ # And now to unlink.
316+ client.click(id=u'delete-buglink-1')
317+ client.waits.sleep(milliseconds=3000)
318+ client.asserts.assertNotNode(id=u'buglink-1')
319+ client.click(id=u'delete-buglink-2')
320+ client.waits.sleep(milliseconds=3000)
321+ client.asserts.assertNotNode(id=u'buglink-2')
322+ client.asserts.assertText(id=u'linkbug',
323+ validator=u'Link to a bug report')
324+
325+
326+def test_suite():
327+ return unittest.TestLoader().loadTestsFromName(__name__)