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
=== modified file 'Makefile'
--- Makefile 2009-08-21 17:50:58 +0000
+++ Makefile 2009-09-15 08:58:30 +0000
@@ -90,6 +90,8 @@
90 @echo90 @echo
91 @echo "Running the JavaScript integration test suite"91 @echo "Running the JavaScript integration test suite"
92 @echo92 @echo
93 bin/test $(VERBOSITY) --layer=BugsWindmillLayer
94 bin/test $(VERBOSITY) --layer=CodeWindmillLayer
93 bin/jstest95 bin/jstest
9496
95check_mailman: build97check_mailman: build
9698
=== modified file 'buildout-templates/bin/test.in'
--- buildout-templates/bin/test.in 2009-08-10 22:08:05 +0000
+++ buildout-templates/bin/test.in 2009-09-15 08:55:41 +0000
@@ -140,7 +140,8 @@
140 '--test-path=${buildout:directory}/lib',140 '--test-path=${buildout:directory}/lib',
141 '--package=canonical',141 '--package=canonical',
142 '--package=lp',142 '--package=lp',
143 '--layer=!MailmanLayer',143 # XXX: Add this back before landing this branch.
144 #'--layer=!(MailmanLayer|WindmillLayer)',
144 ]145 ]
145146
146# Monkey-patch os.listdir to randomise the results147# Monkey-patch os.listdir to randomise the results
147148
=== 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
@@ -25,6 +25,7 @@
25__all__ = [25__all__ = [
26 'AppServerLayer',26 'AppServerLayer',
27 'BaseLayer',27 'BaseLayer',
28 'BaseWindmillLayer',
28 'DatabaseFunctionalLayer',29 'DatabaseFunctionalLayer',
29 'DatabaseLayer',30 'DatabaseLayer',
30 'ExperimentalLaunchpadZopelessLayer',31 'ExperimentalLaunchpadZopelessLayer',
@@ -58,6 +59,7 @@
58import socket59import socket
59import subprocess60import subprocess
60import sys61import sys
62import tempfile
61import threading63import threading
62import time64import time
6365
@@ -70,6 +72,9 @@
70from storm.zope.interfaces import IZStorm72from storm.zope.interfaces import IZStorm
71import transaction73import transaction
7274
75from windmill.bin.admin_lib import (
76 start_windmill, teardown as windmill_teardown)
77
73import zope.app.testing.functional78import zope.app.testing.functional
74from zope.app.testing.functional import FunctionalTestSetup, ZopePublication79from zope.app.testing.functional import FunctionalTestSetup, ZopePublication
75from zope.component import getUtility, provideUtility80from zope.component import getUtility, provideUtility
@@ -1638,3 +1643,53 @@
1638 @profiled1643 @profiled
1639 def testTearDown(cls):1644 def testTearDown(cls):
1640 LayerProcessController.postTestInvariants()1645 LayerProcessController.postTestInvariants()
1646
1647
1648class BaseWindmillLayer(AppServerLayer):
1649 """Layer for Windmill tests.
1650
1651 This layer shouldn't be used directly. A subclass needs to be
1652 created specifying which base URL to use (e.g.
1653 http://bugs.launchpad.dev:8085/).
1654 """
1655
1656 base_url = None
1657 shell_objects = None
1658 config_file = None
1659
1660 @classmethod
1661 @profiled
1662 def setUp(cls):
1663 if cls.base_url is None:
1664 # Only do the setup if we're in a subclass that defines
1665 # base_url. With no base_url, we can't create the config
1666 # file windmill needs.
1667 return
1668 # Kill the SMTP server. The current Windmill test don't need it,
1669 # and if it's running, uuid will somehow end up listen to its
1670 # port, preventing the next test run from starting.
1671 LayerProcessController.stopSMTPServer()
1672 # Windmill needs a config file on disk.
1673 config_text = dedent("""\
1674 START_FIREFOX = True
1675 TEST_URL = '%s'
1676 """ % cls.base_url)
1677 cls.config_file = tempfile.NamedTemporaryFile(suffix='.py')
1678 cls.config_file.write(config_text)
1679 # Flush the file so that windmill can read it.
1680 cls.config_file.flush()
1681 os.environ['WINDMILL_CONFIG_FILE'] = cls.config_file.name
1682 cls.shell_objects = start_windmill()
1683
1684 @classmethod
1685 @profiled
1686 def tearDown(cls):
1687 if cls.shell_objects is not None:
1688 AppServerLayer.tearDown()
1689 windmill_teardown(cls.shell_objects)
1690 # Start the SMTP server, in case other layers need it. It
1691 # will be killed by AppServerLayer later.
1692 LayerProcessController.startSMTPServer()
1693 if cls.config_file is not None:
1694 # Close the file so that it gets deleted.
1695 cls.config_file.close()
16411696
=== added file 'lib/lp/bugs/windmill/testing.py'
--- lib/lp/bugs/windmill/testing.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/windmill/testing.py 2009-09-15 08:58:30 +0000
@@ -0,0 +1,18 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Bugs-specific testing infrastructure for Windmill."""
5
6__metaclass__ = type
7__all__ = [
8 'BugsWindmillLayer',
9 ]
10
11
12from canonical.testing.layers import BaseWindmillLayer
13
14
15class BugsWindmillLayer(BaseWindmillLayer):
16 """Layer for Bugs Windmill tests."""
17
18 base_url = 'http://bugs.launchpad.dev:8085/'
019
=== renamed file 'lib/lp/bugs/windmill/tests/test_bugs/test_bug_commenting.py' => 'lib/lp/bugs/windmill/tests/test_bug_commenting.py'
--- lib/lp/bugs/windmill/tests/test_bugs/test_bug_commenting.py 2009-08-18 14:33:08 +0000
+++ lib/lp/bugs/windmill/tests/test_bug_commenting.py 2009-09-15 08:58:30 +0000
@@ -6,10 +6,14 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = []7__all__ = []
88
9import unittest
10
9from windmill.authoring import WindmillTestClient11from windmill.authoring import WindmillTestClient
1012
11from canonical.launchpad.windmill.testing import lpuser13from canonical.launchpad.windmill.testing import lpuser
12from canonical.uuid import generate_uuid14from canonical.uuid import generate_uuid
15from lp.bugs.windmill.testing import BugsWindmillLayer
16from lp.testing import TestCaseWithFactory
1317
14WAIT_PAGELOAD = u'30000'18WAIT_PAGELOAD = u'30000'
15WAIT_ELEMENT_COMPLETE = u'30000'19WAIT_ELEMENT_COMPLETE = u'30000'
@@ -18,20 +22,28 @@
18 u'//input[@id="field.actions.save" and @class="button js-action"]')22 u'//input[@id="field.actions.save" and @class="button js-action"]')
1923
2024
21def test_bug_commenting():25class TestBugCommenting(TestCaseWithFactory):
22 """Test commenting on bugs."""26
23 client = WindmillTestClient('Bug commenting')27 layer = BugsWindmillLayer
24 lpuser.NO_PRIV.ensure_login(client)28
2529 def test_bug_commenting(self):
26 client.open(url='http://bugs.launchpad.dev:8085/bugs/1')30 """Test commenting on bugs."""
27 client.waits.forPageLoad(timeout=WAIT_PAGELOAD)31 client = WindmillTestClient('Bug commenting')
28 client.waits.forElement(xpath=ADD_COMMENT_BUTTON)32 lpuser.NO_PRIV.ensure_login(client)
2933
30 # Generate a unique piece of text, so we can run the test multiple34 client.open(url='http://bugs.launchpad.dev:8085/bugs/1')
31 # times, without resetting the db.35 client.waits.forPageLoad(timeout=WAIT_PAGELOAD)
32 new_comment_text = generate_uuid()36 client.waits.forElement(xpath=ADD_COMMENT_BUTTON)
33 client.type(text=new_comment_text, id="field.comment")37
34 client.click(xpath=ADD_COMMENT_BUTTON)38 # Generate a unique piece of text, so we can run the test multiple
35 client.waits.forElement(39 # times, without resetting the db.
36 xpath=u'//div[@class="bug-comment"]/p[contains(., "%s")]' % (40 new_comment_text = generate_uuid()
37 new_comment_text))41 client.type(text=new_comment_text, id="field.comment")
42 client.click(xpath=ADD_COMMENT_BUTTON)
43 client.waits.forElement(
44 xpath=u'//div[@class="bug-comment"]/p[contains(., "%s")]' % (
45 new_comment_text))
46
47
48def test_suite():
49 return unittest.TestLoader().loadTestsFromName(__name__)
3850
=== added file 'lib/lp/code/windmill/testing.py'
--- lib/lp/code/windmill/testing.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/windmill/testing.py 2009-09-15 08:58:30 +0000
@@ -0,0 +1,18 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Code-specific testing infrastructure for Windmill."""
5
6__metaclass__ = type
7__all__ = [
8 'CodeWindmillLayer',
9 ]
10
11
12from canonical.testing.layers import BaseWindmillLayer
13
14
15class CodeWindmillLayer(BaseWindmillLayer):
16 """Layer for Code Windmill tests."""
17
18 base_url = 'http://code.launchpad.dev:8085/'
019
=== added directory 'lib/lp/code/windmill/tests'
=== added file 'lib/lp/code/windmill/tests/__init__.py'
=== renamed file 'lib/lp/code/windmill/test_branch_links.py' => 'lib/lp/code/windmill/tests/test_branch_links.py'
--- lib/lp/code/windmill/test_branch_links.py 2009-08-13 15:12:16 +0000
+++ lib/lp/code/windmill/tests/test_branch_links.py 2009-09-15 09:14:25 +0000
@@ -6,47 +6,59 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = []7__all__ = []
88
9import unittest
10
9import windmill11import windmill
10from windmill.authoring import WindmillTestClient12from windmill.authoring import WindmillTestClient
1113
12from canonical.launchpad.windmill.testing import lpuser14from canonical.launchpad.windmill.testing import lpuser
1315from lp.code.windmill.testing import CodeWindmillLayer
1416from lp.testing import TestCaseWithFactory
15def test_inline_branch_bug_link_unlink():17
16 """Test branch bug links."""18
17 client = WindmillTestClient("Branch bug links")19class TestBranchLinks(TestCaseWithFactory):
1820
19 lpuser.FOO_BAR.ensure_login(client)21 layer = CodeWindmillLayer
2022
21 client.open(23 def test_inline_branch_bug_link_unlink(self):
22 url=windmill.settings['TEST_URL'] + '/~mark/firefox/release--0.9.1')24 """Test branch bug links."""
23 client.waits.forElement(id=u'linkbug', timeout=u'10000')25 client = WindmillTestClient("Branch bug links")
24 client.click(id=u'linkbug')26
2527 lpuser.FOO_BAR.ensure_login(client)
26 client.waits.forElement(id=u'field.bug')28
27 client.type(text=u'1', id=u'field.bug')29 start_url = (
28 client.click(xpath=u'//button[@name="buglink.actions.change"]')30 windmill.settings['TEST_URL'] + '/~mark/firefox/release--0.9.1')
2931 client.open(url=start_url)
30 client.waits.forElement(id=u'buglink-1', timeout=u'10000')32 client.waits.forElement(id=u'linkbug', timeout=u'10000')
31 client.asserts.assertText(id=u'linkbug',33 client.click(id=u'linkbug')
32 validator=u'Link to another bug')34
3335 client.waits.forElement(id=u'field.bug')
34 client.click(id=u'linkbug')36 client.type(text=u'1', id=u'field.bug')
35 client.waits.forElement(id=u'field.bug')37 client.click(xpath=u'//button[@name="buglink.actions.change"]')
36 client.type(text=u'2', id=u'field.bug')38
37 client.click(xpath=u'//button[@name="buglink.actions.change"]')39 client.waits.forElement(id=u'buglink-1', timeout=u'10000')
3840 client.asserts.assertText(id=u'linkbug',
39 client.waits.forElement(id=u'buglink-1', timeout=u'10000')41 validator=u'Link to another bug')
40 client.asserts.assertText(id=u'linkbug',42
41 validator=u'Link to another bug')43 client.click(id=u'linkbug')
4244 client.waits.forElement(id=u'field.bug')
43 # And now to unlink.45 client.type(text=u'2', id=u'field.bug')
44 client.click(id=u'delete-buglink-1')46 client.click(xpath=u'//button[@name="buglink.actions.change"]')
45 client.waits.sleep(milliseconds=3000)47
46 client.asserts.assertNotNode(id=u'buglink-1')48 client.waits.forElement(id=u'buglink-1', timeout=u'10000')
47 client.click(id=u'delete-buglink-2')49 client.asserts.assertText(id=u'linkbug',
48 client.waits.sleep(milliseconds=3000)50 validator=u'Link to another bug')
49 client.asserts.assertNotNode(id=u'buglink-2')51
50 client.asserts.assertText(id=u'linkbug',52 # And now to unlink.
51 validator=u'Link to a bug report')53 client.click(id=u'delete-buglink-1')
5254 client.waits.sleep(milliseconds=3000)
55 client.asserts.assertNotNode(id=u'buglink-1')
56 client.click(id=u'delete-buglink-2')
57 client.waits.sleep(milliseconds=3000)
58 client.asserts.assertNotNode(id=u'buglink-2')
59 client.asserts.assertText(id=u'linkbug',
60 validator=u'Link to a bug report')
61
62
63def test_suite():
64 return unittest.TestLoader().loadTestsFromName(__name__)