1# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions
5# are met:
6#
7# 1. Redistributions of source code must retain the above
8#    copyright notice, this list of conditions and the following
9#    disclaimer.
10# 2. Redistributions in binary form must reproduce the above
11#    copyright notice, this list of conditions and the following
12#    disclaimer in the documentation and/or other materials
13#    provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
16# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
20# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
24# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
25# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27
28import os
29import re
30import unittest
31
32from webkitpy.common.host import Host
33from webkitpy.common.system.outputcapture import OutputCapture
34from webkitpy.common.webkit_finder import WebKitFinder
35from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
36from webkitpy.w3c.test_converter import _W3CTestConverter
37
38DUMMY_FILENAME = 'dummy.html'
39DUMMY_PATH = 'dummy/testharness/path'
40
41class W3CTestConverterTest(unittest.TestCase):
42
43    # FIXME: When we move to using a MockHost, this method should be removed, since
44    #        then we can just pass in a dummy dir path
45    def fake_dir_path(self, dirname):
46        filesystem = Host().filesystem
47        webkit_root = WebKitFinder(filesystem).webkit_base()
48        return filesystem.abspath(filesystem.join(webkit_root, "LayoutTests", "css", dirname))
49
50    def test_read_prefixed_property_list(self):
51        """ Tests that the current list of properties requiring the -webkit- prefix load correctly """
52
53        # FIXME: We should be passing in a MockHost here ...
54        converter = _W3CTestConverter(DUMMY_PATH, DUMMY_FILENAME)
55        prop_list = converter.prefixed_properties
56        self.assertTrue(prop_list, 'No prefixed properties found')
57
58    def test_convert_for_webkit_nothing_to_convert(self):
59        """ Tests convert_for_webkit() using a basic test that has nothing to convert """
60
61        test_html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
62"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
63<html xmlns="http://www.w3.org/1999/xhtml">
64<head>
65<title>CSS Test: DESCRIPTION OF TEST</title>
66<link rel="author" title="NAME_OF_AUTHOR"
67href="mailto:EMAIL OR http://CONTACT_PAGE"/>
68<link rel="help" href="RELEVANT_SPEC_SECTION"/>
69<meta name="assert" content="TEST ASSERTION"/>
70<style type="text/css"><![CDATA[
71CSS FOR TEST
72]]></style>
73</head>
74<body>
75CONTENT OF TEST
76</body>
77</html>
78"""
79        converter = _W3CTestConverter(DUMMY_PATH, DUMMY_FILENAME)
80
81        oc = OutputCapture()
82        oc.capture_output()
83        try:
84            converter.feed(test_html)
85            converter.close()
86            converted = converter.output()
87        finally:
88            oc.restore_output()
89
90        self.verify_no_conversion_happened(converted, test_html)
91
92    def test_convert_for_webkit_harness_only(self):
93        """ Tests convert_for_webkit() using a basic JS test that uses testharness.js only and has no prefixed properties """
94
95        test_html = """<head>
96<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
97<script src="/resources/testharness.js"></script>
98</head>
99"""
100        fake_dir_path = self.fake_dir_path("harnessonly")
101        converter = _W3CTestConverter(fake_dir_path, DUMMY_FILENAME)
102        converter.feed(test_html)
103        converter.close()
104        converted = converter.output()
105
106        self.verify_conversion_happened(converted)
107        self.verify_test_harness_paths(converter, converted[1], fake_dir_path, 1, 1)
108        self.verify_prefixed_properties(converted, [])
109
110    def test_convert_for_webkit_properties_only(self):
111        """ Tests convert_for_webkit() using a test that has 2 prefixed properties: 1 in a style block + 1 inline style """
112
113        test_html = """<html>
114<head>
115<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
116<script src="/resources/testharness.js"></script>
117<style type="text/css">
118
119#block1 { @test0@: propvalue; }
120
121</style>
122</head>
123<body>
124<div id="elem1" style="@test1@: propvalue;"></div>
125</body>
126</html>
127"""
128        fake_dir_path = self.fake_dir_path('harnessandprops')
129        converter = _W3CTestConverter(fake_dir_path, DUMMY_FILENAME)
130        test_content = self.generate_test_content(converter.prefixed_properties, 1, test_html)
131
132        oc = OutputCapture()
133        oc.capture_output()
134        try:
135            converter.feed(test_content[1])
136            converter.close()
137            converted = converter.output()
138        finally:
139            oc.restore_output()
140
141        self.verify_conversion_happened(converted)
142        self.verify_test_harness_paths(converter, converted[1], fake_dir_path, 1, 1)
143        self.verify_prefixed_properties(converted, test_content[0])
144
145    def test_convert_for_webkit_harness_and_properties(self):
146        """ Tests convert_for_webkit() using a basic JS test that uses testharness.js and testharness.css and has 4 prefixed properties: 3 in a style block + 1 inline style """
147
148        test_html = """<html>
149<head>
150<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
151<script src="/resources/testharness.js"></script>
152<style type="text/css">
153
154#block1 { @test0@: propvalue; }
155#block2 { @test1@: propvalue; }
156#block3 { @test2@: propvalue; }
157
158</style>
159</head>
160<body>
161<div id="elem1" style="@test3@: propvalue;"></div>
162</body>
163</html>
164"""
165        fake_dir_path = self.fake_dir_path('harnessandprops')
166        converter = _W3CTestConverter(fake_dir_path, DUMMY_FILENAME)
167
168        oc = OutputCapture()
169        oc.capture_output()
170        try:
171            test_content = self.generate_test_content(converter.prefixed_properties, 2, test_html)
172            converter.feed(test_content[1])
173            converter.close()
174            converted = converter.output()
175        finally:
176            oc.restore_output()
177
178        self.verify_conversion_happened(converted)
179        self.verify_test_harness_paths(converter, converted[1], fake_dir_path, 1, 1)
180        self.verify_prefixed_properties(converted, test_content[0])
181
182    def test_convert_test_harness_paths(self):
183        """ Tests convert_testharness_paths() with a test that uses all three testharness files """
184
185        test_html = """<head>
186<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
187<script src="/resources/testharness.js"></script>
188<script src="/resources/testharnessreport.js"></script>
189</head>
190"""
191        fake_dir_path = self.fake_dir_path('testharnesspaths')
192        converter = _W3CTestConverter(fake_dir_path, DUMMY_FILENAME)
193
194        oc = OutputCapture()
195        oc.capture_output()
196        try:
197            converter.feed(test_html)
198            converter.close()
199            converted = converter.output()
200        finally:
201            oc.restore_output()
202
203        self.verify_conversion_happened(converted)
204        self.verify_test_harness_paths(converter, converted[1], fake_dir_path, 2, 1)
205
206    def test_convert_vendor_prefix_js_paths(self):
207        test_html = """<head>
208<script src="/common/vendor-prefix.js">
209</head>
210"""
211        fake_dir_path = self.fake_dir_path('adapterjspaths')
212        converter = _W3CTestConverter(fake_dir_path, DUMMY_FILENAME)
213
214        oc = OutputCapture()
215        oc.capture_output()
216        try:
217            converter.feed(test_html)
218            converter.close()
219            converted = converter.output()
220        finally:
221            oc.restore_output()
222
223        new_html = BeautifulSoup(converted[1])
224
225        # Verify the original paths are gone, and the new paths are present.
226        orig_path_pattern = re.compile('\"/common/vendor-prefix.js')
227        self.assertEquals(len(new_html.findAll(src=orig_path_pattern)), 0, 'vendor-prefix.js path was not converted')
228
229        resources_dir = converter.path_from_webkit_root("LayoutTests", "resources")
230        new_relpath = os.path.relpath(resources_dir, fake_dir_path)
231        relpath_pattern = re.compile(new_relpath)
232        self.assertEquals(len(new_html.findAll(src=relpath_pattern)), 1, 'vendor-prefix.js relative path not correct')
233
234    def test_convert_prefixed_properties(self):
235        """ Tests convert_prefixed_properties() file that has 20 properties requiring the -webkit- prefix:
236        10 in one style block + 5 in another style
237        block + 5 inline styles, including one with multiple prefixed properties.
238        The properties in the test content are in all sorts of wack formatting.
239        """
240
241        test_html = """<html>
242<style type="text/css"><![CDATA[
243
244.block1 {
245    width: 300px;
246    height: 300px
247}
248
249.block2 {
250    @test0@: propvalue;
251}
252
253.block3{@test1@: propvalue;}
254
255.block4 { @test2@:propvalue; }
256
257.block5{ @test3@ :propvalue; }
258
259#block6 {    @test4@   :   propvalue;  }
260
261#block7
262{
263    @test5@: propvalue;
264}
265
266#block8 { @test6@: propvalue; }
267
268#block9:pseudo
269{
270
271    @test7@: propvalue;
272    @test8@:  propvalue propvalue propvalue;;
273}
274
275]]></style>
276</head>
277<body>
278    <div id="elem1" style="@test9@: propvalue;"></div>
279    <div id="elem2" style="propname: propvalue; @test10@ : propvalue; propname:propvalue;"></div>
280    <div id="elem2" style="@test11@: propvalue; @test12@ : propvalue; @test13@   :propvalue;"></div>
281    <div id="elem3" style="@test14@:propvalue"></div>
282</body>
283<style type="text/css"><![CDATA[
284
285.block10{ @test15@: propvalue; }
286.block11{ @test16@: propvalue; }
287.block12{ @test17@: propvalue; }
288#block13:pseudo
289{
290    @test18@: propvalue;
291    @test19@: propvalue;
292}
293
294]]></style>
295</html>
296"""
297        converter = _W3CTestConverter(DUMMY_PATH, DUMMY_FILENAME)
298        test_content = self.generate_test_content(converter.prefixed_properties, 20, test_html)
299
300        oc = OutputCapture()
301        oc.capture_output()
302        try:
303            converter.feed(test_content[1])
304            converter.close()
305            converted = converter.output()
306        finally:
307            oc.restore_output()
308
309        self.verify_conversion_happened(converted)
310        self.verify_prefixed_properties(converted, test_content[0])
311
312    def test_hides_all_instructions_for_manual_testers(self):
313        test_html = """<body>
314<h1 class="instructions">Hello manual tester!</h1>
315<p class="instructions some_other_class">This is how you run this test.</p>
316<p style="willbeoverwritten" class="instructions">...</p>
317<doesntmatterwhichtagitis class="some_other_class instructions">...</p>
318<p>Legit content may contain the instructions string</p>
319</body>
320"""
321        expected_test_html = """<body>
322<h1 class="instructions" style="display:none">Hello manual tester!</h1>
323<p class="instructions some_other_class" style="display:none">This is how you run this test.</p>
324<p class="instructions" style="display:none">...</p>
325<doesntmatterwhichtagitis class="some_other_class instructions" style="display:none">...</p>
326<p>Legit content may contain the instructions string</p>
327</body>
328"""
329        converter = _W3CTestConverter(DUMMY_PATH, DUMMY_FILENAME)
330
331        oc = OutputCapture()
332        oc.capture_output()
333        try:
334            converter.feed(test_html)
335            converter.close()
336            converted = converter.output()
337        finally:
338            oc.restore_output()
339
340        self.assertEqual(converted[1], expected_test_html)
341
342    def verify_conversion_happened(self, converted):
343        self.assertTrue(converted, "conversion didn't happen")
344
345    def verify_no_conversion_happened(self, converted, original):
346        self.assertEqual(converted[1], original, 'test should not have been converted')
347
348    def verify_test_harness_paths(self, converter, converted, test_path, num_src_paths, num_href_paths):
349        if isinstance(converted, basestring):
350            converted = BeautifulSoup(converted)
351
352        resources_dir = converter.path_from_webkit_root("LayoutTests", "resources")
353
354        # Verify the original paths are gone, and the new paths are present.
355        orig_path_pattern = re.compile('\"/resources/testharness')
356        self.assertEquals(len(converted.findAll(src=orig_path_pattern)), 0, 'testharness src path was not converted')
357        self.assertEquals(len(converted.findAll(href=orig_path_pattern)), 0, 'testharness href path was not converted')
358
359        new_relpath = os.path.relpath(resources_dir, test_path)
360        relpath_pattern = re.compile(new_relpath)
361        self.assertEquals(len(converted.findAll(src=relpath_pattern)), num_src_paths, 'testharness src relative path not correct')
362        self.assertEquals(len(converted.findAll(href=relpath_pattern)), num_href_paths, 'testharness href relative path not correct')
363
364    def verify_prefixed_properties(self, converted, test_properties):
365        self.assertEqual(len(set(converted[0])), len(set(test_properties)), 'Incorrect number of properties converted')
366        for test_prop in test_properties:
367            self.assertTrue((test_prop in converted[1]), 'Property ' + test_prop + ' not found in converted doc')
368
369    def generate_test_content(self, full_property_list, num_test_properties, html):
370        """Inserts properties requiring a -webkit- prefix into the content, replacing \'@testXX@\' with a property."""
371        test_properties = []
372        count = 0
373        while count < num_test_properties:
374            test_properties.append(full_property_list[count])
375            count += 1
376
377        # Replace the tokens in the testhtml with the test properties. Walk backward
378        # through the list to replace the double-digit tokens first
379        index = len(test_properties) - 1
380        while index >= 0:
381            # Use the unprefixed version
382            test_prop = test_properties[index].replace('-webkit-', '')
383            # Replace the token
384            html = html.replace('@test' + str(index) + '@', test_prop)
385            index -= 1
386
387        return (test_properties, html)
388