Merge lp:~sinzui/launchpad/auto-yui-tests into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: not available
Proposed branch: lp:~sinzui/launchpad/auto-yui-tests
Merge into: lp:launchpad
Diff against target: 238 lines (+126/-16)
6 files modified
configs/testrunner-appserver/yui-unittest.zcml (+16/-0)
lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js (+6/-0)
lib/canonical/launchpad/javascript/registry/tests/timeline.js (+12/-0)
lib/lp/registry/windmill/tests/test_yuitests.py (+21/-0)
lib/lp/testing/__init__.py (+60/-16)
lib/lp/testing/views.py (+11/-0)
To merge this branch: bzr merge lp:~sinzui/launchpad/auto-yui-tests
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+22485@code.launchpad.net

Description of the change

This is my branch to automatically test YUI unit tests in windmill.
The scope of this branch is only for the registry. This solution is not
comprehensive for launchpad, nor does it solve hard problems like
testing in iframes (a requirement for testing timelines and help)

    lp:~sinzui/launchpad/auto-yui-tests
    Diff size: 186
    Launchpad bug: None, this is to unblock the product branch.js feature.
    Test command: ./bin/test -vv -t test_yuitests
    Pre-implementation: gary, sidnei
    Target release: 10.04

Automatically add YUI unit tests to the test runner
---------------------------------------------------

The automated js test runner needs a harness to run the YUI unit test in
canonical/launchpad/javascript/*/test.

The following conditions must be met for a test to be automatically played
by windmill.
    * It must be lib/canonical/launchpad/javascript/registry/tests
    * The test js inject a node with id="complete" when complete.
    * The html file must match test_*.html
    * The html cannot test iframes
      * To accommodate iframes, the YUI test would need to inject the results
        in the parent page. This is work that does not solve the immediate
        issue.

Rules
-----

    The harness needs to:
    * Locate each html file like */javascript/test/test_*.html
    * Open it in the browser, wait for the log to populate
    * Verify there are no fail nodes in the log
    * Report failing notes as tests.

QA
--

None for the apps. This is successful if it correctly reports failures.

Lint
----

Linting changed files:
  configs/testrunner-appserver/yui-unittest.zcml
  lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js
  lib/lp/registry/windmill/tests/test_yuitests.py
  lib/lp/testing/__init__.py
  lib/lp/testing/views.py

Test
----

    * lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js
      * Added a method that provides a clear indication to windmill that the
        test is complete.
    * lib/lp/registry/windmill/tests/test_yuitests.py
      * Added a test harness to locate test_*.html in the registry YUI
        directory. Note that I renamed test_milestone_table.html too.
        The test waits for the signal that the YUI test runner is complete,
        then gets the page content, extracts the messages, and calls fail
        if there was a YUI failure.

Implementation
--------------

    * configs/testrunner-appserver/yui-unittest.zcml
      * Register a view that can serve the html pages through the webapp.
        This is a requirement because windmill cannot load a file from the
        filesystem--it is a js security prohibition. This view is only
        available to the test runner.
    * lib/lp/testing/__init__.py
      * Removed a duplicate class. This looks like a paste error.
    * lib/lp/testing/views.py
      * Create a view to retrieve the yui files that need to play in the
        windmill test.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Curtis this branch looks good. As we discussed on IRC it will need to be modified to allow at least lp.code in addition to lp.registry to satisfy the immediate use case.

I look forward to trying it out.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'configs/testrunner-appserver/yui-unittest.zcml'
2--- configs/testrunner-appserver/yui-unittest.zcml 1970-01-01 00:00:00 +0000
3+++ configs/testrunner-appserver/yui-unittest.zcml 2010-04-02 14:27:39 +0000
4@@ -0,0 +1,16 @@
5+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
6+ GNU Affero General Public License version 3 (see the file LICENSE).
7+-->
8+
9+<configure
10+ xmlns="http://namespaces.zope.org/zope"
11+ xmlns:browser="http://namespaces.zope.org/browser">
12+
13+ <browser:page
14+ name="+yui-unittest"
15+ for="canonical.launchpad.webapp.interfaces.ILaunchpadRoot"
16+ class="lp.testing.views.YUITestFileView"
17+ attribute="__call__"
18+ permission="zope.Public"/>
19+
20+</configure>
21
22=== renamed file 'lib/canonical/launchpad/javascript/registry/tests/milestone_table.html' => 'lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.html'
23=== modified file 'lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js'
24--- lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js 2010-02-11 20:10:01 +0000
25+++ lib/canonical/launchpad/javascript/registry/tests/test_milestone_table.js 2010-04-02 14:27:39 +0000
26@@ -244,6 +244,12 @@
27 }));
28
29 // Lock, stock, and two smoking barrels.
30+ var handle_complete = function(data) {
31+ status_node = Y.Node.create(
32+ '<p id="complete">Test status: complete</p>');
33+ Y.get('body').appendChild(status_node);
34+ };
35+ Y.Test.Runner.on('complete', handle_complete);
36 Y.Test.Runner.add(suite);
37
38 var console = new Y.Console({newestOnTop: false});
39
40=== renamed file 'lib/canonical/launchpad/javascript/registry/tests/timeline.html' => 'lib/canonical/launchpad/javascript/registry/tests/test_timeline.html'
41=== modified file 'lib/canonical/launchpad/javascript/registry/tests/timeline.js'
42--- lib/canonical/launchpad/javascript/registry/tests/timeline.js 2010-02-11 20:10:01 +0000
43+++ lib/canonical/launchpad/javascript/registry/tests/timeline.js 2010-04-02 14:27:39 +0000
44@@ -263,6 +263,18 @@
45
46
47 Y.Test.Runner.add(suite);
48+var handle_complete = function(data) {
49+ var parent_log = Y.Node.create(
50+ '<div id="log" style="display: none">' +
51+ Y.get('#log').get('innerHTML') + '</div>');
52+ var parent_body = Y.one(parent.document).get('body');
53+ parent_body.appendChild(parent_log);
54+ var status_node = Y.Node.create(
55+ '<p id="complete">Test status: complete</p>');
56+ parent_body.appendChild(status_node);
57+ };
58+Y.Test.Runner.on('complete', handle_complete);
59+
60
61 var yconsole = new Y.Console({
62 newestOnTop: false
63
64=== added file 'lib/lp/registry/windmill/tests/test_yuitests.py'
65--- lib/lp/registry/windmill/tests/test_yuitests.py 1970-01-01 00:00:00 +0000
66+++ lib/lp/registry/windmill/tests/test_yuitests.py 2010-04-02 14:27:39 +0000
67@@ -0,0 +1,21 @@
68+# Copyright 2010 Canonical Ltd. This software is licensed under the
69+# GNU Affero General Public License version 3 (see the file LICENSE).
70+
71+"""Run YUI.test tests."""
72+
73+__metaclass__ = type
74+__all__ = []
75+
76+from lp.testing import build_yui_unittest_suite, YUIUnitTestCase
77+from lp.registry.windmill.testing import RegistryWindmillLayer
78+
79+
80+class RegistryYUIUnitTestCase(YUIUnitTestCase):
81+
82+ layer = RegistryWindmillLayer
83+ suite_name = 'RegistryYUIUnitTests'
84+
85+
86+def test_suite():
87+ app_testing_path = 'canonical/launchpad/javascript/registry/tests'
88+ return build_yui_unittest_suite(app_testing_path, RegistryYUIUnitTestCase)
89
90=== modified file 'lib/lp/testing/__init__.py'
91--- lib/lp/testing/__init__.py 2010-03-18 10:05:01 +0000
92+++ lib/lp/testing/__init__.py 2010-04-02 14:27:39 +0000
93@@ -6,6 +6,7 @@
94 __metaclass__ = type
95 __all__ = [
96 'ANONYMOUS',
97+ 'build_yui_unittest_suite',
98 'capture_events',
99 'FakeTime',
100 'get_lsb_information',
101@@ -29,6 +30,7 @@
102 'validate_mock_class',
103 'WindmillTestCase',
104 'with_anonymous_login',
105+ 'YUIUnitTestCase',
106 'ZopeTestInSubProcess',
107 ]
108
109@@ -43,6 +45,7 @@
110 import sys
111 import tempfile
112 import time
113+import unittest
114
115 from bzrlib.branch import Branch as BzrBranch
116 from bzrlib.bzrdir import BzrDir, format_registry
117@@ -70,6 +73,7 @@
118 from canonical.launchpad.webapp import errorlog
119 from canonical.config import config
120 from canonical.launchpad.webapp.interfaces import ILaunchBag
121+from canonical.launchpad.windmill.testing import constants
122 from lp.codehosting.vfs import branch_id_to_path, get_multi_server
123 # Import the login and logout functions here as it is a much better
124 # place to import them from in tests.
125@@ -568,25 +572,65 @@
126 self.client.open(url=u'http://launchpad.dev:8085')
127
128
129-class WindmillTestCase(TestCaseWithFactory):
130- """A TestCase class for Windmill tests.
131-
132- It provides a WindmillTestClient (self.client) with Launchpad's front
133- page loaded.
134- """
135-
136+class YUIUnitTestCase(WindmillTestCase):
137+
138+ layer = None
139 suite_name = ''
140
141+ _yui_results = None
142+ _view_name = u'http://launchpad.dev:8085/+yui-unittest/'
143+
144+ def initialize(self, test_path):
145+ self.test_path = test_path
146+ self.yui_runner_url = self._view_name + test_path
147+
148 def setUp(self):
149- TestCaseWithFactory.setUp(self)
150- self.client = WindmillTestClient(self.suite_name)
151- # Load the front page to make sure we don't get fooled by stale pages
152- # left by the previous test. (For some reason, when you create a new
153- # WindmillTestClient you get a new session and everything, but if you
154- # do anything before you open() something you'd be operating on the
155- # page that was last accessed by the previous test, which is the cause
156- # of things like https://launchpad.net/bugs/515494)
157- self.client.open(url=u'http://launchpad.dev:8085')
158+ super(YUIUnitTestCase, self).setUp()
159+ client = self.client
160+ client.open(url=self.yui_runner_url)
161+ client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
162+ client.waits.forElement(id='complete')
163+ response = client.commands.getPageText()
164+ self._yui_results = {}
165+ # Maybe testing.pages should move to lp to avoid circular imports.
166+ from canonical.launchpad.testing.pages import find_tags_by_class
167+ entries = find_tags_by_class(
168+ response['result'], 'yui-console-entry-TestRunner')
169+ for entry in entries:
170+ category = entry.find(
171+ attrs={'class': 'yui-console-entry-cat'})
172+ if category is None:
173+ continue
174+ result = category.string
175+ if result not in ('pass', 'fail'):
176+ continue
177+ message = entry.pre.string
178+ test_name, ignore = message.split(':', 1)
179+ self._yui_results[test_name] = dict(
180+ result=result, message=message)
181+
182+ def runTest(self):
183+ if self._yui_results is None or len(self._yui_results) == 0:
184+ self.fail("Test harness or js failed.")
185+ for test_name in self._yui_results:
186+ result = self._yui_results[test_name]
187+ self.assertTrue('pass' == result['result'],
188+ 'Failure in %s.%s: %s' % (
189+ self.test_path, test_name, result['message']))
190+
191+
192+def build_yui_unittest_suite(app_testing_path, yui_test_class):
193+ suite = unittest.TestSuite()
194+ testing_path = os.path.join(config.root, 'lib', app_testing_path)
195+ unit_test_names = [
196+ file_name for file_name in os.listdir(testing_path)
197+ if file_name.startswith('test_') and file_name.endswith('.html')]
198+ for unit_test_name in unit_test_names:
199+ test_path = os.path.join(app_testing_path, unit_test_name)
200+ test_case = yui_test_class()
201+ test_case.initialize(test_path)
202+ suite.addTest(test_case)
203+ return suite
204
205
206 class ZopeTestInSubProcess:
207
208=== modified file 'lib/lp/testing/views.py'
209--- lib/lp/testing/views.py 2009-12-14 22:00:41 +0000
210+++ lib/lp/testing/views.py 2010-04-02 14:27:39 +0000
211@@ -7,12 +7,16 @@
212 __all__ = [
213 'create_view',
214 'create_initialized_view',
215+ 'YUITestFileView',
216 ]
217
218+import os
219
220 from zope.component import getUtility, getMultiAdapter
221 from zope.security.management import endInteraction, newInteraction
222
223+from canonical.config import config
224+from canonical.lazr import ExportedFolder
225 from canonical.launchpad.layers import setFirstLayer
226 from canonical.launchpad.webapp.interfaces import IPlacelessAuthUtility
227 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
228@@ -72,3 +76,10 @@
229 query_string, cookie, request, path_info)
230 view.initialize()
231 return view
232+
233+
234+class YUITestFileView(ExportedFolder):
235+ """Export the lib directory where the test assets reside."""
236+
237+ folder = os.path.join(config.root, 'lib/')
238+ export_subdirectories = True